diff --git a/core/libs/tests/GuestJsonLegacyHandlers.cpp b/core/libs/tests/GuestJsonLegacyHandlers.cpp index 4572b2dba9..01835095e8 100644 --- a/core/libs/tests/GuestJsonLegacyHandlers.cpp +++ b/core/libs/tests/GuestJsonLegacyHandlers.cpp @@ -400,11 +400,17 @@ struct MapReadContext { unsigned mode; unsigned reads = 0; int registration = -1; + Pair* pair = nullptr; + const String* key = nullptr; + unsigned alias = 0; }; static void UpdateInheritedMap(void* data) { auto& context = *static_cast(data); context.registration = _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(context.target, RecoverLegacy, context.replacement); - _ZN3sce4Json5ValueC1ERKS1_(context.copy, context.target); + if (context.pair) { + if (context.alias == 1) _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(context.pair, context.key, context.target); + else _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(context.pair, context.key, context.target); + } else _ZN3sce4Json5ValueC1ERKS1_(context.copy, context.target); } static int APS5_VABI ReadInheritedMap(char& character, void* data) { auto& context = *static_cast(data); @@ -422,8 +428,11 @@ static int APS5_VABI ReadInheritedMap(char& character, void* data) { } static void CheckInheritedMap(LegacyTrace& trace, Context& first, Context& second) { const auto guestBaseline = trace.guestGenerations, heapBaseline = trace.heapGenerations; - for (bool existing : {false, true}) for (unsigned mode = 0; mode < 3; ++mode) { - Value target{}, copy{}, outer{}; ParseLegacy(target); + for (bool existing : {false, true}) for (unsigned mode = 0; mode < 3; ++mode) for (unsigned alias = 0; alias < 3; ++alias) { + Value target{}, scalarCopy{}, outer{}; Pair pair; std::memset(&pair, 0xa5, sizeof(pair)); + Value& copy = alias ? pair.value : scalarCopy; + String key{}; if (alias) _ZN3sce4Json6StringC1EPKc(&key, "key"); + ParseLegacy(target); if (existing) Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&target, RecoverLegacy, &first) == 0, "Prepare an existing legacy mismatch record before inherited failure"); std::vector targetAllocations; for (const auto& [pointer, generation] : trace.guestGenerations) { @@ -434,12 +443,17 @@ static void CheckInheritedMap(LegacyTrace& trace, Context& first, Context& secon const Value targetValue = target; _ZN3sce4Json5ValueC1Ev(&outer); MapReadContext context{&trace, &target, ©, &second, mode}; + if (alias) { context.pair = &pair; context.key = &key; context.alias = alias; } const auto requests = trace.requests.size(); const auto notices = trace.notifications; const int status = _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&outer, ReadInheritedMap, &context); const auto error = static_cast(0x80848102); Require(status == (mode ? error : 0) && context.registration == (mode == 1 ? error : 0) && context.reads == 4 && outer.type == 1 && (outer.payload & 0xff) == 1 && outer.auxiliary, "Legacy map updates must observe failure only after notifier return without clearing the outer parser error or interrupting its admitted literal"); - const auto expected = mode ? std::vector{8, 24, 32} : std::vector{24, 32}; + auto expected = mode ? std::vector{8} : std::vector{}; + if (alias) { expected.push_back(24); expected.push_back(4); } + expected.push_back(24); expected.push_back(32); + if (alias) Require(pair.reserved == UINT64_C(0xa5a5a5a5a5a5a5a5) && pair.key.storage != key.storage && std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair.key), "key") == 0, + "Inherited failure must preserve independent Pair key ownership, complete key contents and inactive padding"); Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected && trace.notifications == notices + unsigned(mode != 0) && !trace.onNotify, "Inherited map registration must preserve actual failure, root-admission and notifier ordering"); Context* targetContext = mode == 1 ? existing ? &first : nullptr : &second; @@ -468,7 +482,12 @@ static void CheckInheritedMap(LegacyTrace& trace, Context& first, Context& secon "Inherited failure, registration retry and copying must preserve the original source auxiliary and fallback generations"); Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&outer, "false", 5) == 0 && outer.type == 1 && !(outer.payload & 0xff), "The next independent parse must not inherit an already completed callback-map failure"); - _ZN3sce4Json5ValueD1Ev(©); _ZN3sce4Json5ValueD1Ev(&target); _ZN3sce4Json5ValueD1Ev(&outer); CheckMap(trace, {}); + if (alias) { + if (alias == 1) _ZN3sce4Json6Object4PairD1Ev(&pair); + else _ZN3sce4Json6Object4PairD2Ev(&pair); + _ZN3sce4Json6StringD1Ev(&key); + } else _ZN3sce4Json5ValueD1Ev(©); + _ZN3sce4Json5ValueD1Ev(&target); _ZN3sce4Json5ValueD1Ev(&outer); CheckMap(trace, {}); Require(trace.guestGenerations == guestBaseline && trace.heapGenerations == heapBaseline, "Inherited map updates and retries must reclaim every temporary generation in both allocator domains"); } diff --git a/docs/dev/debt/libSceJson2.md b/docs/dev/debt/libSceJson2.md index 8f1857dd99..8881ea6026 100644 --- a/docs/dev/debt/libSceJson2.md +++ b/docs/dev/debt/libSceJson2.md @@ -86,7 +86,7 @@ SDK 5/6 below refer to the application compatibility SDK queried by `sceKernelGe - [Legacy nested Pair copies](../../../core/libs/prx/libSceJson2/Export.cpp) - inherited failure scopes, allocator-driven reentry and libc allocation exceptions remain unverified. One hundred fifty-two SDK 5 reference-backed cases cover an array containing an object/string and an object containing an array/string, both Pair constructor/destructor aliases and persistent/one-shot failure at every allocation position through eighteen-allocation success. Checks cover partial counts, missing sentinels, independent internal/public object keys, exact request/notification fingerprints, inactive bytes, source bytes/generations and both allocator domains. Copies retain the root mismatch-map callback independently of auxiliary metadata; unbound descendants use typed defaults without inheriting that callback. Root and source recovery remain callable after copy cleanup. -- [Legacy callback map inherited failures](../../../core/libs/prx/libSceJson2/Export.cpp) - Pair/collection copies inside an inherited failure scope, allocator-driven lifecycle changes and cross-thread propagation remain unverified. Six SDK 5 reference-backed schedules exercise new/existing records through a real parser reader, with healthy controls, a published allocation error and registration inside the notifier before error publication. Published failure preserves an existing handler or retains a new empty record; scalar copying still completes but its callback record remains empty. Notifier-time registration and copying succeed before the shared error is published, while the outer parser completes its admitted literal and reports OOM. Checks cover actual allocation/notification order, callback ABI and borrowed results, exact auxiliary/fallback generations, record-reusing registration/copy retry, independent parser retry and complete cleanup in both allocator domains. +- [Legacy callback map inherited failures](../../../core/libs/prx/libSceJson2/Export.cpp) - Collection copies inside an inherited failure scope, allocator-driven lifecycle changes and cross-thread propagation remain unverified. Eighteen SDK 5 reference cases exercise new/existing records through a parser reader: healthy controls, published allocation errors and notifier-time registration. Published failure preserves an existing handler or retains a new empty record; scalar Value and both Pair constructor aliases still copy the scalar but their callback record remains empty. Pair keys retain independently allocated complete text and inactive padding. Notifier-time registration and copying succeed before the shared error is published, while the outer parser completes its admitted literal and reports OOM. Checks cover allocation/notification order, borrowed callback results, auxiliary/fallback generations, record-reusing retry, independent parser retry and both allocator domains. - [Composite-destruction termination](../../../core/libs/prx/libSceJson2/Export.cpp) - additional legacy callback combinations and thrown callbacks remain unverified. One hundred ten SDK 6 reference cases cover both Value destructor aliases, four payload/metadata shapes and every release position. Following `0x17e0`, destruction requires the published recovery mutex after clearing the payload; callback termination during payload release therefore preserves the auxiliary pointer and bytes. Checks cover exact release-generation prefixes, allocator receiver/context, module/source ownership, repeated destruction and fresh initialization. Early termination orphans the remaining caller allocations; final-release termination completes cleanup. The caller-owned fixture separately reclaims the observed orphaned epoch. The pre-fix destructor fails the auxiliary-retention regression.