Files
AnyPS5/core/libs/tests/GuestJsonCallbackCopies.cpp
T

155 lines
11 KiB
C++

#include "prx/libSceJson2/include/Api.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <map>
#include <string>
using namespace SceJson2;
struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; };
using NullHandler = const Value& (APS5_VABI*)(ValueType, const Value*, void*);
using MismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*);
using FloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*);
using ElementHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*);
static void Require(bool condition, const char* message) {
if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); }
}
struct CopyAllocations { std::map<void*, std::size_t> live; std::size_t generation = 0; };
static void* APS5_VABI AllocateCopy(void*, std::size_t bytes, void* context) {
auto& trace = *static_cast<CopyAllocations*>(context);
void* memory = std::malloc(bytes);
Require(memory != nullptr, "Tracking allocator requires host memory");
std::memset(memory, 0xa5, bytes);
Require(trace.live.emplace(memory, ++trace.generation).second, "Callback copy allocations must have independent live generations");
return memory;
}
static void APS5_VABI FreeCopy(void*, void* memory, void* context) {
if (!memory) return;
Require(static_cast<CopyAllocations*>(context)->live.erase(memory) == 1, "Callback copy teardown must release each allocation through its owner exactly once");
std::free(memory);
}
static void APS5_VABI NotifyCopy(void*, int, std::size_t, void*) { Require(false, "Healthy callback copies must not fail allocation"); }
static void* const CopyVtable[]{nullptr, nullptr, reinterpret_cast<void*>(AllocateCopy), reinterpret_cast<void*>(FreeCopy), reinterpret_cast<void*>(NotifyCopy)};
struct CopyAllocator { void* const* vtable = CopyVtable; };
struct CopyContext {
Value replacement{};
const Value* receiver = nullptr;
unsigned nulls = 0, mismatches = 0, floats = 0, elements = 0;
};
static const Value& APS5_VABI NullCopy(ValueType type, const Value* parent, void* context) {
Require(type == 2 && !parent, "Copied null recovery must preserve requested type and parent ABI");
auto& state = *static_cast<CopyContext*>(context); ++state.nulls;
return state.replacement;
}
static const Value& APS5_VABI MismatchCopy(ValueType type, const Value* self, const Value* parent, void* context) {
auto& state = *static_cast<CopyContext*>(context);
Require(type == 2 && self == state.receiver && !parent, "Copied mismatch recovery must preserve receiver, type and parent ABI");
++state.mismatches;
return state.replacement;
}
static Value* APS5_VABI FloatCopy(Value* result, std::int32_t function, double number, const Value* parent, void* context, bool* error) {
Require(function == 1 && number == std::numeric_limits<double>::infinity() && !parent && !error, "Copied float recovery must preserve its assignment ABI");
auto& state = *static_cast<CopyContext*>(context); ++state.floats;
_ZN3sce4Json5ValueC1El(result, static_cast<std::int64_t>(state.replacement.payload));
return result;
}
static void APS5_VABI ElementCopy(int error, const String* key, const std::size_t* index, const Value* parent, const Value* self, void* context) {
auto& state = *static_cast<CopyContext*>(context);
Require(static_cast<unsigned>(error) == 0x80848115 && key && !index && !parent && self == state.receiver && std::strcmp(_ZNK3sce4Json6String5c_strEv(key), "missing") == 0, "Copied element recovery must retain its exact key, receiver and parent ABI");
++state.elements;
}
static void Register(Value& value, CopyContext& context) {
context.receiver = &value;
Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&value, NullCopy, &context) == 0, "Register source null recovery");
Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&value, MismatchCopy, &context) == 0, "Register source mismatch recovery");
Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, FloatCopy, &context) == 0, "Register source float recovery");
Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&value, ElementCopy, &context) == 0, "Register source element recovery");
}
struct MetadataSnapshot {
std::array<std::byte, 72> bytes;
std::array<void*, 5> allocations;
std::array<std::size_t, 5> generations;
};
static MetadataSnapshot Capture(const Value& value, const CopyAllocations& trace) {
MetadataSnapshot snapshot{};
const auto* words = reinterpret_cast<void* const*>(value.auxiliary);
snapshot.allocations = {value.auxiliary, words[2], words[3], words[4], words[5]};
std::memcpy(snapshot.bytes.data(), value.auxiliary, snapshot.bytes.size());
for (unsigned i = 0; i < snapshot.allocations.size(); ++i) snapshot.generations[i] = trace.live.at(snapshot.allocations[i]);
return snapshot;
}
static void Verify(const Value& value, const MetadataSnapshot& snapshot, const CopyAllocations& trace) {
Require(value.auxiliary == snapshot.allocations[0] && std::memcmp(value.auxiliary, snapshot.bytes.data(), snapshot.bytes.size()) == 0, "Copying must preserve complete source/destination auxiliary bytes and handler/context identities");
for (unsigned i = 0; i < snapshot.allocations.size(); ++i) Require(trace.live.at(snapshot.allocations[i]) == snapshot.generations[i], "Copying must preserve auxiliary, fallback and all three callback-record allocation generations");
Require(reinterpret_cast<const Value*>(snapshot.allocations[1])->parent == &value, "Copied callback metadata must retain its original fallback owner");
}
static std::string Serialize(Value& value) {
String output{}; _ZN3sce4Json6StringC1EPKc(&output, "");
Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0, "Callback copy source must remain serializable");
std::string text = _ZNK3sce4Json6String5c_strEv(&output);
_ZN3sce4Json6StringD1Ev(&output);
return text;
}
int main() {
CopyAllocations allocations; CopyAllocator allocator; Parameter parameter{&allocator, &allocations, 8192, 2, {}};
std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer);
Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast<const InitParameter2*>(&parameter)) == 0, "Initialize callback copy contracts");
const auto baseline = allocations.live;
for (const char* document : {"null", "1.25", "{\"n\":null,\"s\":\"text\"}"}) for (unsigned mode = 0; mode < 5; ++mode) {
Value source{}, copy{}; Pair pair{}; String key{}; Array array{};
_ZN3sce4Json5ValueC1Ev(&source);
Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&source, document, std::strlen(document)) == 0, "Construct callback-bearing source");
CopyContext sourceContext, destinationContext;
_ZN3sce4Json5ValueC1El(&sourceContext.replacement, 77); _ZN3sce4Json5ValueC1El(&destinationContext.replacement, 99);
Register(source, sourceContext);
const auto sourceMetadata = Capture(source, allocations);
Value* target = &copy;
MetadataSnapshot destinationMetadata{};
if (mode == 0) _ZN3sce4Json5ValueC1ERKS1_(&copy, &source);
else if (mode == 1 || mode == 2) {
_ZN3sce4Json5ValueC1Ev(&copy);
if (mode == 2) { Register(copy, destinationContext); destinationMetadata = Capture(copy, allocations); }
_ZN3sce4Json5Value3setERKS1_(&copy, &source);
} else if (mode == 3) {
_ZN3sce4Json6StringC1EPKc(&key, "key");
_ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); target = &pair.value;
} else {
_ZN3sce4Json5ArrayC1Ev(&array);
_ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &source); target = const_cast<Value*>(_ZNK3sce4Json5Array5frontEv(&array));
}
Require(!target->parent && target->type == source.type && Serialize(*target) == document, "Copies must retain source contents with independent root ownership");
if (mode == 2) Verify(*target, destinationMetadata, allocations);
else Require(!target->auxiliary, "Modern constructors and unregistered destinations must not inherit source-local callback metadata");
const auto* sourceInteger = _ZNK3sce4Json5Value10getIntegerEv(&source);
const auto* targetInteger = _ZNK3sce4Json5Value10getIntegerEv(target);
Require(sourceInteger == reinterpret_cast<const std::int64_t*>(&sourceContext.replacement.payload) && *sourceInteger == 77, "Source recovery must retain its exact borrowed replacement identity");
Require(mode == 2 ? targetInteger == reinterpret_cast<const std::int64_t*>(&destinationContext.replacement.payload) && *targetInteger == 99 : *targetInteger == 0, "Copies must use destination handlers or typed defaults rather than source callbacks");
if (source.type == 7) { _ZNK3sce4Json5ValueixEPKc(&source, "missing"); _ZNK3sce4Json5ValueixEPKc(target, "missing"); }
_ZN3sce4Json5Value3setEd(target, std::numeric_limits<double>::infinity());
Require(target->type == (mode == 2 ? 2 : 4) && target->payload == (mode == 2 ? UINT64_C(99) : std::bit_cast<std::uint64_t>(std::numeric_limits<double>::infinity())), "Copies must retain destination float recovery without dispatching source handlers");
Require(sourceContext.nulls == unsigned(source.type == 0) && sourceContext.mismatches == unsigned(source.type != 0) && !sourceContext.floats && sourceContext.elements == unsigned(source.type == 7), "Copy consumers must not invoke source callbacks except explicit source operations");
Require(destinationContext.nulls == unsigned(mode == 2 && source.type == 0) && destinationContext.mismatches == unsigned(mode == 2 && source.type != 0) && destinationContext.floats == unsigned(mode == 2) && destinationContext.elements == unsigned(mode == 2 && source.type == 7), "Assignment must preserve each destination callback and context independently");
Verify(source, sourceMetadata, allocations);
Require(Serialize(source) == document, "Copied value mutation must preserve its independent source contents");
if (mode == 2) Verify(*target, destinationMetadata, allocations);
if (mode == 3) { _ZN3sce4Json6Object4PairD1Ev(&pair); _ZN3sce4Json6StringD1Ev(&key); }
else if (mode == 4) _ZN3sce4Json5ArrayD1Ev(&array);
else _ZN3sce4Json5ValueD1Ev(&copy);
Verify(source, sourceMetadata, allocations);
_ZN3sce4Json5Value3setEd(&source, std::numeric_limits<double>::infinity());
Require(sourceContext.floats == 1 && source.type == 2 && source.payload == 77, "Source callbacks must remain usable after copied storage and destination callbacks are destroyed");
Verify(source, sourceMetadata, allocations);
_ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ValueD1Ev(&sourceContext.replacement); _ZN3sce4Json5ValueD1Ev(&destinationContext.replacement);
Require(allocations.live == baseline, "Callback copy cleanup must reclaim every temporary generation without replacing module resources");
}
Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && allocations.live.empty(), "Release callback copy resources");
return 0;
}