diff --git a/core/libs/prx/libSceJson2/Export.cpp b/core/libs/prx/libSceJson2/Export.cpp index bccf2e24e6..f9140ca01c 100644 --- a/core/libs/prx/libSceJson2/Export.cpp +++ b/core/libs/prx/libSceJson2/Export.cpp @@ -1456,10 +1456,16 @@ void ResetDefaults(bool releaseMissing = false) { auto& defaults = SharedDefaults(); for (unsigned type = 0; type < 8; ++type) { if (defaults.ready[type]) { + if (releaseMissing && type == TypeString) defaults.values[type].string = _ZN3sce4Json5Value12s_nullstringE; + if (releaseMissing && type == TypeArray) defaults.values[type].array = _ZN3sce4Json5Value11s_nullarrayE; + if (releaseMissing && type == TypeObject) defaults.values[type].object = _ZN3sce4Json5Value12s_nullobjectE; const bool missing = type >= TypeString && !defaults.values[type].payload; Destroy(defaults.values[type]); if (releaseMissing && missing) Release(nullptr); } else if (releaseMissing && type >= TypeString) Release(nullptr); + if (releaseMissing && type == TypeString) _ZN3sce4Json5Value12s_nullstringE = nullptr; + if (releaseMissing && type == TypeArray) _ZN3sce4Json5Value11s_nullarrayE = nullptr; + if (releaseMissing && type == TypeObject) _ZN3sce4Json5Value12s_nullobjectE = nullptr; defaults.values[type] = {}; defaults.ready[type] = false; } @@ -1538,6 +1544,9 @@ void CreateDefault(ValueType type) { if constexpr (std::is_same_v) Construct(*handle, std::string_view{}); else Construct(*handle); } + if constexpr (std::is_same_v) _ZN3sce4Json5Value12s_nullstringE = handle; + else if constexpr (std::is_same_v) _ZN3sce4Json5Value11s_nullarrayE = handle; + else _ZN3sce4Json5Value12s_nullobjectE = handle; } int InitializeResources(void* self, const void* parameter, std::int32_t format, bool modern) { @@ -1559,9 +1568,8 @@ int InitializeResources(void* self, const void* parameter, std::int32_t format, state.fallback = static_cast(Allocate(sizeof(Value))); if (state.fallback) Construct(*state.fallback); state.internalMutex = CreateKernelMutex(); - const auto& defaults = SharedDefaults(); - const bool admitted = defaults.values[TypeString].string && defaults.values[TypeArray].array && - defaults.values[TypeObject].object && state.fallback && state.internalMutex; + const bool admitted = _ZN3sce4Json5Value12s_nullstringE && _ZN3sce4Json5Value11s_nullarrayE && + _ZN3sce4Json5Value12s_nullobjectE && state.fallback && state.internalMutex; if (admitted) state.recoveryMutex = ModernValues() ? CreateKernelMutex() : state.internalMutex; if (!admitted || !state.recoveryMutex) { ReleaseInitialization(); return JsonErrorOutOfMemory; } state.paddedStringGrowth = CompiledSdk() > 0x044fffff; @@ -1573,6 +1581,7 @@ int InitializeResources(void* self, const void* parameter, std::int32_t format, const Node& Typed(const Value* self, ValueType requested, bool* usedDefault = nullptr) { const auto fallback = [&]() -> const Node& { if (usedDefault) *usedDefault = true; + if (usedDefault && requested >= TypeString) return SharedDefaults().values[requested]; return Defaults(requested); }; if (self->type == requested || @@ -2658,6 +2667,9 @@ extern const bool _ZN3sce4Json5Value10s_nullboolE = false; extern const std::int64_t _ZN3sce4Json5Value13s_nullintegerE = 0; extern const std::uint64_t _ZN3sce4Json5Value14s_nulluintegerE = 0; extern const double _ZN3sce4Json5Value10s_nullrealE = 0; +String* _ZN3sce4Json5Value12s_nullstringE = nullptr; +Array* _ZN3sce4Json5Value11s_nullarrayE = nullptr; +Object* _ZN3sce4Json5Value12s_nullobjectE = nullptr; std::size_t _ZN3sce4Json6String4nposE = static_cast(-1); void APS5_VABI _ZN3sce4Json6StringC1Ev(String* self) { Construct(*self, std::string_view{}); } @@ -3053,9 +3065,9 @@ const double* APS5_VABI _ZNK3sce4Json5Value7getRealEv(const Value* self) { const auto& value = Typed(self, TypeReal, &usedDefault); return usedDefault ? &_ZN3sce4Json5Value10s_nullrealE : &value.real; } -const String* APS5_VABI _ZNK3sce4Json5Value9getStringEv(const Value* self) { return Typed(self, TypeString).string; } -const Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const Value* self) { return Typed(self, TypeArray).array; } -const Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const Value* self) { return Typed(self, TypeObject).object; } +const String* APS5_VABI _ZNK3sce4Json5Value9getStringEv(const Value* self) { bool fallback = false; const auto& value = Typed(self, TypeString, &fallback); return fallback ? _ZN3sce4Json5Value12s_nullstringE : value.string; } +const Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const Value* self) { bool fallback = false; const auto& value = Typed(self, TypeArray, &fallback); return fallback ? _ZN3sce4Json5Value11s_nullarrayE : value.array; } +const Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const Value* self) { bool fallback = false; const auto& value = Typed(self, TypeObject, &fallback); return fallback ? _ZN3sce4Json5Value12s_nullobjectE : value.object; } std::size_t APS5_VABI _ZNK3sce4Json5Value5countEv(const Value* self) { const Node& n = NodeOf(*self); if (n.type == TypeArray) return n.array->items->size(); diff --git a/core/libs/prx/libSceJson2/include/Api.hpp b/core/libs/prx/libSceJson2/include/Api.hpp index 7c01f7cdd4..4a7f077873 100644 --- a/core/libs/prx/libSceJson2/include/Api.hpp +++ b/core/libs/prx/libSceJson2/include/Api.hpp @@ -92,6 +92,9 @@ extern const bool _ZN3sce4Json5Value10s_nullboolE; extern const std::int64_t _ZN3sce4Json5Value13s_nullintegerE; extern const std::uint64_t _ZN3sce4Json5Value14s_nulluintegerE; extern const double _ZN3sce4Json5Value10s_nullrealE; +extern String* _ZN3sce4Json5Value12s_nullstringE; +extern Array* _ZN3sce4Json5Value11s_nullarrayE; +extern Object* _ZN3sce4Json5Value12s_nullobjectE; int APS5_VABI _ZN3sce4Json12MemAllocatorD0Ev(void); diff --git a/core/libs/tests/GuestJsonAllocator.cpp b/core/libs/tests/GuestJsonAllocator.cpp index 9fb38428b0..b1240022ba 100644 --- a/core/libs/tests/GuestJsonAllocator.cpp +++ b/core/libs/tests/GuestJsonAllocator.cpp @@ -20,13 +20,23 @@ struct AllocationTrace { bool failOnce = false; Parameter* changeInput = nullptr; void* replacementContext = nullptr; + std::size_t clearStringAt = 0; + String* displacedString = nullptr; std::map live; std::vector requests; std::vector notifications; + std::vector publishedDefaults; }; static void* APS5_VABI AllocateTracked(void*, std::size_t bytes, void* context) { auto& trace = *static_cast(context); trace.requests.push_back(bytes); + trace.publishedDefaults.push_back(unsigned(_ZN3sce4Json5Value12s_nullstringE != nullptr) | + (unsigned(_ZN3sce4Json5Value11s_nullarrayE != nullptr) << 1) | + (unsigned(_ZN3sce4Json5Value12s_nullobjectE != nullptr) << 2)); + if (trace.requests.size() == trace.clearStringAt) { + trace.displacedString = _ZN3sce4Json5Value12s_nullstringE; + _ZN3sce4Json5Value12s_nullstringE = nullptr; + } if (trace.changeInput && trace.requests.size() == 1) { trace.changeInput->context = trace.replacementContext; trace.changeInput->bufferSize = 32768; @@ -65,6 +75,10 @@ static void CheckResources(AllocationTrace& trace) { const std::vector requests{24, 8, 24, 8, 32, 48, 8, 32, 104, 32, 8, 8}; Require(trace.requests.size() >= requests.size() && std::equal(requests.begin(), requests.end(), trace.requests.end() - requests.size()), "Initialization must allocate its actual context, defaults, fallback and synchronization handles in reference order"); + const std::vector publication{0, 0, 0, 1, 1, 1, 3, 3, 3, 7, 7, 7}; + Require(trace.publishedDefaults.size() >= publication.size() && + std::equal(publication.begin(), publication.end(), trace.publishedDefaults.end() - publication.size()), + "Allocator callbacks must observe each default pointer only after its construction completes"); bool contextFound = false; for (const auto& [memory, bytes] : trace.live) { if (bytes != 24) continue; @@ -77,6 +91,19 @@ static void CheckResources(AllocationTrace& trace) { const auto* string = _ZNK3sce4Json5Value9getStringEv(&null); const auto* array = _ZNK3sce4Json5Value8getArrayEv(&null); const auto* object = _ZNK3sce4Json5Value9getObjectEv(&null); + Require(string == _ZN3sce4Json5Value12s_nullstringE && array == _ZN3sce4Json5Value11s_nullarrayE && + object == _ZN3sce4Json5Value12s_nullobjectE, "Getters must return the exported default pointers"); + auto* savedString = _ZN3sce4Json5Value12s_nullstringE; + auto* savedArray = _ZN3sce4Json5Value11s_nullarrayE; + auto* savedObject = _ZN3sce4Json5Value12s_nullobjectE; + _ZN3sce4Json5Value12s_nullstringE = nullptr; + _ZN3sce4Json5Value11s_nullarrayE = nullptr; + _ZN3sce4Json5Value12s_nullobjectE = nullptr; + Require(!_ZNK3sce4Json5Value9getStringEv(&null) && !_ZNK3sce4Json5Value8getArrayEv(&null) && + !_ZNK3sce4Json5Value9getObjectEv(&null), "Getters must observe writes to the exported pointers"); + _ZN3sce4Json5Value12s_nullstringE = savedString; + _ZN3sce4Json5Value11s_nullarrayE = savedArray; + _ZN3sce4Json5Value12s_nullobjectE = savedObject; Require(Owned(trace, string, 8) && Owned(trace, string->text, 24), "The default string must use the configured allocator"); Require(Owned(trace, array, 8) && Owned(trace, array->items, 32) && Owned(trace, *reinterpret_cast(array->items), 48), "The default array must own its allocator-backed header and sentinel"); @@ -167,6 +194,57 @@ static void CheckContextSnapshot() { Require(found && trace.live.size() == 12 && replacement.requests.empty(), "Context admission must cache allocator and user data before allocation and read the buffer setting afterward"); Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty() && replacement.live.empty(), "Termination must release the admitted context snapshot"); } +static void CheckReplacedDefaults() { + AllocationTrace trace; + TrackingAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, + "Initialize before replacing exported defaults"); + auto* oldString = _ZN3sce4Json5Value12s_nullstringE; + auto* oldArray = _ZN3sce4Json5Value11s_nullarrayE; + auto* oldObject = _ZN3sce4Json5Value12s_nullobjectE; + auto* replacementString = static_cast(AllocateTracked(&allocator, sizeof(String), &trace)); + auto* replacementArray = static_cast(AllocateTracked(&allocator, sizeof(Array), &trace)); + auto* replacementObject = static_cast(AllocateTracked(&allocator, sizeof(Object), &trace)); + _ZN3sce4Json6StringC1Ev(replacementString); + _ZN3sce4Json5ArrayC1Ev(replacementArray); + _ZN3sce4Json6ObjectC1Ev(replacementObject); + _ZN3sce4Json6StringD1Ev(oldString); + _ZN3sce4Json5ArrayD1Ev(oldArray); + _ZN3sce4Json6ObjectD1Ev(oldObject); + FreeTracked(&allocator, oldString, &trace); + FreeTracked(&allocator, oldArray, &trace); + FreeTracked(&allocator, oldObject, &trace); + _ZN3sce4Json5Value12s_nullstringE = replacementString; + _ZN3sce4Json5Value11s_nullarrayE = replacementArray; + _ZN3sce4Json5Value12s_nullobjectE = replacementObject; + Value null; + _ZN3sce4Json5ValueC1Ev(&null); + Require(_ZNK3sce4Json5Value9getStringEv(&null) == replacementString && + _ZNK3sce4Json5Value8getArrayEv(&null) == replacementArray && + _ZNK3sce4Json5Value9getObjectEv(&null) == replacementObject, + "Getters must observe replacement pointers"); + _ZN3sce4Json5ValueD1Ev(&null); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), + "Termination must destroy and release the replacement pointers"); +} +static void CheckAdmissionReadsPublishedPointer() { + AllocationTrace trace; + trace.clearStringAt = 10; + TrackingAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(static_cast(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter))) == 0x80848102 && !initializer, + "Admission must read the exported pointer after allocator callbacks"); + Require(trace.displacedString && trace.live.size() == 2 && Owned(trace, trace.displacedString, sizeof(String)) && + Owned(trace, trace.displacedString->text, 24), "The displaced default must remain owned by its allocator"); + FreeTracked(&allocator, trace.displacedString->text, &trace); + FreeTracked(&allocator, trace.displacedString, &trace); + Require(trace.live.empty(), "Fixture cleanup must release the displaced allocation"); +} int main() { for (int budget = 0; budget <= 12; ++budget) { AllocationTrace trace; @@ -179,6 +257,8 @@ int main() { if (budget < 12) { Require(static_cast(result) == 0x80848102 && initializer == 0 && trace.live.empty(), "Every initialization allocation failure must report OOM, roll back ownership and leave retry legal"); + Require(!_ZN3sce4Json5Value12s_nullstringE && !_ZN3sce4Json5Value11s_nullarrayE && !_ZN3sce4Json5Value12s_nullobjectE, + "Initialization rollback must clear the exported default pointers"); Require((budget == 0) == trace.notifications.empty(), "Only the first context-allocation failure omits notification"); trace.budget = -1; Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, @@ -192,8 +272,12 @@ int main() { parameter.context = &unrelated; Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && initializer == 0 && trace.live.empty() && unrelated.live.empty(), "Termination must release the snapshotted allocation context and every resource"); + Require(!_ZN3sce4Json5Value12s_nullstringE && !_ZN3sce4Json5Value11s_nullarrayE && !_ZN3sce4Json5Value12s_nullobjectE, + "Termination must clear the exported default pointers"); } CheckSingleFailures(); CheckContextSnapshot(); + CheckReplacedDefaults(); + CheckAdmissionReadsPublishedPointer(); return 0; }