mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
fix(libSceJson2): share serialization output and refresh live collection positions
This commit is contained in:
1 parent
295abeebb4
commit
3b93f433ed
3 files changed
+91
-13
No files matched your search
@@ -1993,6 +1993,9 @@ struct SerializationIterator {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TLink>
|
||||
struct SerializationPosition { TLink* first; TLink* head; bool invalid; };
|
||||
|
||||
class CallbackSerializer {
|
||||
public:
|
||||
CallbackSerializer(String& buffer, String* destination, SerializationCallback callback, void* context, bool collect)
|
||||
@@ -2022,17 +2025,25 @@ public:
|
||||
}
|
||||
if (value.type == TypeArray) {
|
||||
if (!value.array) { NotImplemented_nid_no_patch(__func__); return 0; }
|
||||
auto* items = value.array->items;
|
||||
auto* head = items ? items->head : nullptr;
|
||||
return Collection(head ? head->next : nullptr, head, items && !head, '[', ']',
|
||||
auto position = [&value] {
|
||||
if (value.type != TypeArray || !value.array) NotImplemented_nid_no_patch(__func__);
|
||||
auto* items = value.array->items;
|
||||
auto* head = items ? items->head : nullptr;
|
||||
return SerializationPosition<ArrayLink>{head ? head->next : nullptr, head, items && !head};
|
||||
};
|
||||
return Collection(position, '[', ']',
|
||||
[](ArrayLink* link) { return link->next; },
|
||||
[this](ArrayLink* link) { return SerializeValue(link->value); });
|
||||
}
|
||||
if (value.type == TypeObject) {
|
||||
if (!value.object) { NotImplemented_nid_no_patch(__func__); return 0; }
|
||||
auto* items = value.object->items;
|
||||
auto* head = items ? items->head : nullptr;
|
||||
return Collection(head ? head->left : nullptr, head, items && !head, '{', '}',
|
||||
auto position = [&value] {
|
||||
if (value.type != TypeObject || !value.object) NotImplemented_nid_no_patch(__func__);
|
||||
auto* items = value.object->items;
|
||||
auto* head = items ? items->head : nullptr;
|
||||
return SerializationPosition<ObjectLink>{head ? head->left : nullptr, head, items && !head};
|
||||
};
|
||||
return Collection(position, '{', '}',
|
||||
[](ObjectLink* link) { return NextObjectLink(link); },
|
||||
[this](ObjectLink* link) { Escape(link->pair.key); Character(':'); return SerializeValue(link->pair.value); });
|
||||
}
|
||||
@@ -2076,21 +2087,25 @@ private:
|
||||
}
|
||||
Character('"');
|
||||
}
|
||||
template<typename TLink, typename TNext, typename TSerialize>
|
||||
int Collection(TLink* first, TLink* head, bool invalid, char opening, char closing, TNext next, TSerialize serialize) {
|
||||
template<typename TPosition, typename TNext, typename TSerialize>
|
||||
int Collection(TPosition position, char opening, char closing, TNext next, TSerialize serialize) {
|
||||
Character(opening);
|
||||
int status = Emit();
|
||||
if (status < 0) return status;
|
||||
{
|
||||
SerializationIterator iterator(first, invalid);
|
||||
if (iterator.Different(head, invalid)) {
|
||||
auto current = position();
|
||||
SerializationIterator iterator(current.first, current.invalid);
|
||||
current = position();
|
||||
if (iterator.Different(current.head, current.invalid)) {
|
||||
do {
|
||||
if (iterator.Different(first, invalid)) Character(',');
|
||||
current = position();
|
||||
if (iterator.Different(current.first, current.invalid)) Character(',');
|
||||
if (!*iterator.state) { NotImplemented_nid_no_patch(__func__); return 0; }
|
||||
status = serialize(*iterator.state);
|
||||
if (status < 0) break;
|
||||
*iterator.state = next(*iterator.state);
|
||||
} while (iterator.Different(head, invalid));
|
||||
current = position();
|
||||
} while (iterator.Different(current.head, current.invalid));
|
||||
}
|
||||
}
|
||||
if (status < 0) return status;
|
||||
@@ -2099,6 +2114,8 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
int APS5_VABI SerializationSucceeded(String&, void*) { return 0; }
|
||||
|
||||
int SerializeCallbackValue(Value* value, String* destination, SerializationCallback callback, void* context, bool collect) {
|
||||
{
|
||||
auto& state = State();
|
||||
@@ -2935,6 +2952,12 @@ int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_(Value* self
|
||||
return SerializeCallbackValue(self, output, callback, context, true);
|
||||
}
|
||||
int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringE(Value* self, String* out) {
|
||||
{
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
if (!state.allocation) return JsonErrorNotInitialized;
|
||||
}
|
||||
if (ModernValues()) return SerializeCallbackValue(self, out, SerializationSucceeded, nullptr, true);
|
||||
std::string text;
|
||||
const int result = Serialize(text, *self);
|
||||
AppendText(*out, text);
|
||||
|
||||
@@ -19,6 +19,9 @@ struct Pair { String key; std::uint64_t reserved; Value value; };
|
||||
struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; };
|
||||
using SerializationCallback = int (APS5_VABI*)(String&, void*);
|
||||
extern "C" {
|
||||
void APS5_VABI _ZN3sce4Json5Array10push_frontERKNS0_5ValueE(Array*, const Value*);
|
||||
const Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const Value*);
|
||||
int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringE(Value*, String*);
|
||||
int APS5_VABI _ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(Value*, SerializationCallback, void*);
|
||||
int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_(Value*, String*, SerializationCallback, void*);
|
||||
String* APS5_VABI _ZN3sce4Json6String6appendEPKc(String*, const char*);
|
||||
@@ -116,18 +119,46 @@ static int APS5_VABI Observe(String& chunk, void* context) {
|
||||
if (trace.mutate) _ZN3sce4Json6String6appendEPKc(&chunk, "!");
|
||||
return static_cast<int>(trace.chunks.size()) == trace.stopAt ? -77 : trace.result;
|
||||
}
|
||||
struct CollectionChange {
|
||||
const Array* array;
|
||||
int mode;
|
||||
std::vector<std::string> chunks;
|
||||
};
|
||||
static int APS5_VABI PrependAtOpening(String& chunk, void* context) {
|
||||
auto& change = *static_cast<CollectionChange*>(context);
|
||||
change.chunks.emplace_back(_ZNK3sce4Json6String5c_strEv(&chunk));
|
||||
if (change.chunks.size() == 1) {
|
||||
Value value{}; _ZN3sce4Json5ValueC1El(&value, 0);
|
||||
if (change.mode == 0) _ZN3sce4Json5Array10push_frontERKNS0_5ValueE(const_cast<Array*>(change.array), &value);
|
||||
else if (change.mode == 1) _ZN3sce4Json5Array5clearEv(const_cast<Array*>(change.array));
|
||||
else _ZN3sce4Json5Array9push_backERKNS0_5ValueE(const_cast<Array*>(change.array), &value);
|
||||
_ZN3sce4Json5ValueD1Ev(&value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int main() {
|
||||
ArrayTrace allocation; ArrayAllocator allocator;
|
||||
Parameter parameter{&allocator, &allocation, 8192, 2, {}};
|
||||
Value root{}; _ZN3sce4Json5ValueC1Ev(&root);
|
||||
SerializationTrace before;
|
||||
Require(static_cast<unsigned>(_ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(&root, Observe, &before)) == 0x80848110 && before.chunks.empty(), "Callback serialization before initialization must refuse without invoking the callback");
|
||||
String beforeOutput{};
|
||||
Require(static_cast<unsigned>(_ZN3sce4Json5Value9serializeERNS0_6StringE(&root, &beforeOutput)) == 0x80848110 && !beforeOutput.storage, "Ordinary serialization before initialization must refuse without constructing output storage");
|
||||
std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer);
|
||||
Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, ¶meter) == 0, "Initialize serialization callbacks");
|
||||
const char json[] = R"({"a":[1,true,"x\n/\u007f"],"b":{}})";
|
||||
Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, json, sizeof(json) - 1) == 0, "Parse nested callback serialization input");
|
||||
const std::vector<std::string> chunks{"{", "\"a\":[", "1", ",true", ",\"x\\n\\/\\u007f\"", "]", ",\"b\":{", "}", "}"};
|
||||
const auto baseline = allocation.live.size();
|
||||
String ordinary{};
|
||||
_ZN3sce4Json6StringC1EPKc(&ordinary, "prefix");
|
||||
Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&root, &ordinary) == 0, "Ordinary serialization must use the collecting protocol with a successful internal sink");
|
||||
std::string expectedOrdinary = "prefix";
|
||||
for (const auto& chunk : chunks) expectedOrdinary += chunk;
|
||||
Require(std::string(_ZNK3sce4Json6String5c_strEv(&ordinary)) == expectedOrdinary, "Ordinary serialization must preserve the same slash and DEL escaping as callback serialization");
|
||||
_ZN3sce4Json6StringD1Ev(&ordinary);
|
||||
Require(allocation.live.size() == baseline, "Ordinary serialization must release its temporary buffer and collection iterators");
|
||||
|
||||
for (bool collect : {false, true}) for (bool mutate : {false, true}) for (int stop : {-1, 1, 3, 9}) {
|
||||
String output{}; _ZN3sce4Json6StringC1EPKc(&output, "prefix");
|
||||
SerializationTrace trace; trace.stopAt = stop; trace.mutate = mutate;
|
||||
@@ -146,6 +177,16 @@ int main() {
|
||||
_ZN3sce4Json6StringD1Ev(&output);
|
||||
Require(allocation.live.size() == baseline, "Successful and interrupted traversals must release all temporary buffers and iterator states");
|
||||
}
|
||||
for (int mode : {0, 1, 2}) {
|
||||
Value changing{}; _ZN3sce4Json5ValueC1Ev(&changing);
|
||||
const char arrayJson[] = "[1,2]";
|
||||
Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&changing, arrayJson, sizeof(arrayJson) - 1) == 0, "Parse collection mutation input");
|
||||
CollectionChange change{_ZNK3sce4Json5Value8getArrayEv(&changing), mode, {}};
|
||||
const auto expected = mode == 0 ? std::vector<std::string>({"[", "0", ",1", ",2", "]"}) : mode == 1 ? std::vector<std::string>({"[", "]"}) : std::vector<std::string>({"[", "1", ",2", ",0", "]"});
|
||||
Require(_ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(&changing, PrependAtOpening, &change) == 0 && change.chunks == expected, "Serialization must obtain the live collection after the opening callback prepends, clears or appends entries");
|
||||
_ZN3sce4Json5ValueD1Ev(&changing);
|
||||
Require(allocation.live.size() == baseline, "Collection mutation during serialization must leave no temporary or transferred allocations");
|
||||
}
|
||||
Value scalar{}; _ZN3sce4Json5ValueC1El(&scalar, 1234);
|
||||
for (bool collect : {false, true}) for (bool once : {false, true}) for (int budget : {0, 1, 2, 3}) {
|
||||
String output{}; _ZN3sce4Json6StringC1EPKc(&output, "p");
|
||||
@@ -165,6 +206,20 @@ int main() {
|
||||
_ZN3sce4Json6StringD1Ev(&output);
|
||||
Require(allocation.live.size() == baseline, "Rejected serialization allocations must not leak admitted storage");
|
||||
}
|
||||
for (bool once : {false, true}) for (int budget : {0, 1, 2, 3}) {
|
||||
String output{}; _ZN3sce4Json6StringC1EPKc(&output, "p");
|
||||
const auto requests = allocation.requests.size(); const auto notices = allocation.notifications;
|
||||
allocation.budget = budget; allocation.once = once;
|
||||
const int status = _ZN3sce4Json5Value9serializeERNS0_6StringE(&scalar, &output);
|
||||
allocation.budget = -1; allocation.once = false;
|
||||
Require(status == 0 && std::string(_ZNK3sce4Json6String5c_strEv(&output)) == (budget >= 3 ? "p1234" : "p"), "Ordinary serialization must preserve the native partial-output and return contracts under allocation failure");
|
||||
std::vector<std::size_t> expected{24};
|
||||
if (budget >= 1) expected.push_back(21);
|
||||
if (budget >= 2) expected.push_back(22);
|
||||
Require(std::vector<std::size_t>(allocation.requests.begin() + requests, allocation.requests.end()) == expected && allocation.notifications == notices + (budget < 3 ? 1 : 0), "Ordinary serialization must use the same allocator requests and failure notifications as the collecting callback path");
|
||||
_ZN3sce4Json6StringD1Ev(&output);
|
||||
Require(allocation.live.size() == baseline, "Ordinary serialization failures must not leak storage");
|
||||
}
|
||||
_ZN3sce4Json5ValueD1Ev(&scalar); _ZN3sce4Json5ValueD1Ev(&root);
|
||||
Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && allocation.live.empty(), "Terminate callback serialization allocation context");
|
||||
return 0;
|
||||
|
||||
@@ -62,4 +62,4 @@
|
||||
|
||||
- [Value keyed lookup](../../../core/libs/prx/libSceJson2/Export.cpp) - valid string-key overloads and C-string key-construction failures follow the observed iterator allocation and fallback protocol. Missing object handles, corrupted tree storage, lifecycle reentry during lookup and remaining older-SDK callback combinations remain unverified; invalid reference arguments are explicitly unsupported.
|
||||
|
||||
- [Value callback serialization](../../../core/libs/prx/libSceJson2/Export.cpp) - modern chunk boundaries, callback return propagation, output mutation and scalar allocation rejection are reference-tested. Older-SDK serialization is explicitly unsupported until its parent-rebinding protocol is verified. Structural mutation of the traversed collection, nested iterator failures, non-finite recovery allocation failures and lifecycle reentry require further audit. Null callback or collecting-output references are explicitly unsupported. The existing non-callback serializer still follows a separate path and needs the same streaming/allocation audit.
|
||||
- [Value callback serialization](../../../core/libs/prx/libSceJson2/Export.cpp) - modern chunk boundaries, callback return propagation, output mutation and scalar allocation rejection are reference-tested. Older-SDK serialization is explicitly unsupported until its parent-rebinding protocol is verified. Array prepend, clear and append during the opening callback are reference-tested; other structural mutations, nested iterator failures, non-finite recovery allocation failures and lifecycle reentry require further audit. Null callback or collecting-output references are explicitly unsupported. The modern non-callback serializer shares the verified collecting path, including escaping and scalar allocation failures. Its older-SDK path retains the previous implementation and still needs the streaming/allocation audit.
|
||||
Reference in new issue
Block a user