mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
610 lines
43 KiB
C++
610 lines
43 KiB
C++
#include "prx/libSceJson2/include/Api.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace SceJson2;
|
|
|
|
struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; };
|
|
|
|
using NullCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*);
|
|
using MismatchCallback = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*);
|
|
using FloatCallback = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*);
|
|
using ElementCallback = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*);
|
|
static void Require(bool valid, const char* message) {
|
|
if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); }
|
|
}
|
|
struct ValueAllocationTrace {
|
|
std::map<void*, std::size_t> live;
|
|
std::vector<std::size_t> requests;
|
|
std::vector<void*> frees;
|
|
int budget = -1;
|
|
bool once = false;
|
|
unsigned notifications = 0;
|
|
unsigned nullFrees = 0;
|
|
const Value* registering = nullptr;
|
|
};
|
|
static void* APS5_VABI ValueAllocationAllocate(void*, std::size_t bytes, void* context) {
|
|
auto& trace = *static_cast<ValueAllocationTrace*>(context); trace.requests.push_back(bytes);
|
|
if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; }
|
|
if (trace.budget > 0) --trace.budget;
|
|
auto* memory = std::malloc(bytes); Require(memory != nullptr, "Tracking allocator requires host memory");
|
|
std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory;
|
|
}
|
|
static void APS5_VABI ValueAllocationFree(void*, void* memory, void* context) {
|
|
if (!memory) { ++static_cast<ValueAllocationTrace*>(context)->nullFrees; return; }
|
|
Require(static_cast<ValueAllocationTrace*>(context)->live.erase(memory) == 1, "Value resources must return through their owning allocator");
|
|
static_cast<ValueAllocationTrace*>(context)->frees.push_back(memory);
|
|
std::free(memory);
|
|
}
|
|
static void APS5_VABI ValueAllocationNotify(void*, int error, std::size_t bytes, void* context) {
|
|
auto& trace = *static_cast<ValueAllocationTrace*>(context);
|
|
Require(static_cast<unsigned>(error) == 0x80848102 && trace.requests.back() == bytes, "Value failure notification must identify its allocation");
|
|
if (trace.registering && bytes != 16) Require(!trace.registering->auxiliary, "Auxiliary admission must remain unpublished during allocation failure notification");
|
|
++trace.notifications;
|
|
}
|
|
static void* const ValueAllocationVtable[]{nullptr, nullptr, reinterpret_cast<void*>(ValueAllocationAllocate), reinterpret_cast<void*>(ValueAllocationFree), reinterpret_cast<void*>(ValueAllocationNotify)};
|
|
struct ValueAllocationAllocator { void* const* vtable = ValueAllocationVtable; };
|
|
static bool Owned(const ValueAllocationTrace& trace, const void* pointer, std::size_t bytes) {
|
|
const auto found = trace.live.find(const_cast<void*>(pointer)); return found != trace.live.end() && found->second == bytes;
|
|
}
|
|
static void Check(const Value& value, int expected, const ValueAllocationTrace& trace) {
|
|
Require(value.type == expected && value.reserved == 0xa5a5a5a5, "Value type admission must preserve inactive metadata");
|
|
if (expected < 5) return;
|
|
const auto* handle = reinterpret_cast<void* const*>(value.payload);
|
|
Require(Owned(trace, handle, 8) && Owned(trace, *handle, expected == 5 ? 24 : 32), "Complex values must own an actual allocator-backed handle and header");
|
|
if (expected != 5) {
|
|
Value* owner = nullptr; std::memcpy(&owner, reinterpret_cast<const char*>(*handle) + 24, sizeof(owner));
|
|
Require(owner == &value, "Collection headers must bind to their enclosing value");
|
|
}
|
|
}
|
|
static const Value& APS5_VABI NullRecovery(ValueType, const Value* value, void*) { return *value; }
|
|
static const Value& APS5_VABI MismatchRecovery(ValueType, const Value* value, const Value*, void*) { return *value; }
|
|
static Value* APS5_VABI FloatRecovery(Value* value, std::int32_t, double, const Value*, void*, bool*) { return value; }
|
|
static void APS5_VABI ElementRecovery(int, const String*, const std::size_t*, const Value*, const Value*, void*) {}
|
|
static int Register(Value& value, unsigned kind, void* context, bool empty = false) {
|
|
switch (kind) {
|
|
case 0: return _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&value, empty ? nullptr : NullRecovery, context);
|
|
case 1: return _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&value, empty ? nullptr : MismatchRecovery, context);
|
|
case 2: return _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, empty ? nullptr : FloatRecovery, context);
|
|
default: return _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&value, empty ? nullptr : ElementRecovery, context);
|
|
}
|
|
}
|
|
static void CheckMetadata(ValueAllocationTrace& trace) {
|
|
const auto baseline = trace.live.size();
|
|
for (unsigned kind = 0; kind < 4; ++kind) for (int budget = 0; budget <= 3; ++budget) {
|
|
Value value; std::memset(&value, 0xa5, sizeof(value));
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast<ValueType>(0));
|
|
const auto payload = value.payload;
|
|
const auto notifications = trace.notifications;
|
|
trace.budget = budget;
|
|
trace.registering = &value;
|
|
const auto status = Register(value, kind, &trace);
|
|
trace.registering = nullptr;
|
|
trace.budget = -1;
|
|
const bool admitted = budget >= (kind == 0 ? 2 : 3);
|
|
Require(static_cast<unsigned>(status) == (admitted ? 0u : 0x80848102u) && trace.notifications - notifications == (admitted ? 0u : 1u), "Callback metadata admission must return and notify its actual allocation failure");
|
|
Require(value.payload == payload && value.reserved == 0xa5a5a5a5 && value.type == 0, "Callback registration must preserve the value payload");
|
|
Require((value.auxiliary != nullptr) == (budget >= 1), "Failed fallback allocation must retain its auxiliary record");
|
|
if (value.auxiliary) {
|
|
Require(Owned(trace, value.auxiliary, 72), "Modern auxiliary records must use the supplied allocator");
|
|
auto* slots = reinterpret_cast<void**>(value.auxiliary);
|
|
Require((slots[2] != nullptr) == (budget >= 2), "Fallback allocation failure must preserve a null fallback pointer");
|
|
if (slots[2]) {
|
|
auto* fallback = reinterpret_cast<Value*>(slots[2]);
|
|
Require(Owned(trace, fallback, 32) && fallback->parent == &value && !fallback->auxiliary && fallback->type == 0 && fallback->payload == UINT64_C(0xa5a5a5a5a5a5a5a5), "Fallback values must retain native ownership and inactive payload bytes");
|
|
}
|
|
for (unsigned index = 6; index < 9; ++index) Require(!slots[index], "Auxiliary reserved fields must initialize to zero");
|
|
if (kind && admitted) Require(Owned(trace, slots[kind + 2], 16), "Callback records must own their actual sixteen-byte allocation");
|
|
}
|
|
auto* auxiliary = value.auxiliary;
|
|
auto* fallback = auxiliary ? reinterpret_cast<void**>(auxiliary)[2] : nullptr;
|
|
const auto requests = trace.requests.size();
|
|
Require(Register(value, kind, &value) == 0, "Callback registration must remain usable after allocation failure");
|
|
Require(!auxiliary || (static_cast<void*>(value.auxiliary) == auxiliary && reinterpret_cast<void**>(auxiliary)[2] == fallback), "Registration retry must reuse partial auxiliary state without recreating a missing fallback");
|
|
if (admitted) Require(trace.requests.size() == requests, "Replacing an existing callback must not allocate");
|
|
auto* slots = reinterpret_cast<void**>(value.auxiliary);
|
|
auto* record = kind ? reinterpret_cast<void**>(slots[kind + 2]) : slots;
|
|
Require(record[0] && record[1] == &value, "Registration must retain its callback and replacement context");
|
|
const auto retained = trace.requests.size();
|
|
Require(Register(value, kind, &trace, true) == 0 && !record[0] && record[1] == &trace && trace.requests.size() == retained, "A null callback must replace its record without deleting or reallocating metadata");
|
|
_ZN3sce4Json5Value3setENS0_9ValueTypeE(&value, static_cast<ValueType>(2));
|
|
Require(static_cast<void*>(value.auxiliary) == slots, "Payload type changes must preserve root callback metadata");
|
|
Value parent{}; value.parent = &parent;
|
|
Require(static_cast<unsigned>(Register(value, kind, &trace)) == 0x80848104 && trace.requests.size() == retained, "Owned children must reject callback registration without allocating");
|
|
value.parent = nullptr;
|
|
std::vector<void*> expectedFrees;
|
|
if (kind) expectedFrees.push_back(record);
|
|
if (slots[2]) expectedFrees.push_back(slots[2]);
|
|
expectedFrees.push_back(slots);
|
|
const auto freed = trace.frees.size();
|
|
const auto nullFrees = trace.nullFrees;
|
|
_ZN3sce4Json5ValueD1Ev(&value);
|
|
Require(trace.nullFrees == nullFrees, "Metadata destruction must not free absent callback records");
|
|
Require(trace.frees.size() == freed + expectedFrees.size() && std::equal(expectedFrees.begin(), expectedFrees.end(), trace.frees.begin() + freed), "Metadata teardown must release callback records, fallback and auxiliary in ownership order");
|
|
Require(trace.live.size() == baseline, "Callback metadata destruction must release all admitted and partial resources");
|
|
}
|
|
}
|
|
static void CheckScalarCopies(ValueAllocationTrace& trace) {
|
|
const auto baseline = trace.live.size();
|
|
constexpr const char* text = "a string requiring independently allocated storage";
|
|
for (int previous = -1; previous <= 7; ++previous) for (int type = 0; type <= 5; ++type) for (int budget = 0; budget <= 3; ++budget) {
|
|
Value source; std::memset(&source, 0xa5, sizeof(source));
|
|
if (type == 5) _ZN3sce4Json5ValueC1EPKc(&source, text);
|
|
else {
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&source, static_cast<ValueType>(type));
|
|
if (type == 1) source.payload = UINT64_C(0xa5a5a5a5a5a5a501);
|
|
if (type == 2) source.payload = 42;
|
|
if (type == 3) source.payload = UINT64_C(0xfedcba9876543210);
|
|
if (type == 4) source.payload = UINT64_C(0x3fe8000000000000);
|
|
}
|
|
Value destination; std::memset(&destination, 0xa5, sizeof(destination));
|
|
void* auxiliary = nullptr;
|
|
std::uint64_t payload = destination.payload;
|
|
void* header = nullptr;
|
|
if (previous >= 0) {
|
|
if (previous == 5) _ZN3sce4Json5ValueC1EPKc(&destination, "previous");
|
|
else _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&destination, static_cast<ValueType>(previous));
|
|
Require(Register(destination, 0, &trace, true) == 0, "Prepare assignment metadata");
|
|
auxiliary = destination.auxiliary;
|
|
payload = destination.payload;
|
|
if (previous == 5) header = *reinterpret_cast<void* const*>(payload);
|
|
}
|
|
const auto requests = trace.requests.size();
|
|
trace.budget = budget;
|
|
if (previous < 0) _ZN3sce4Json5ValueC1ERKS1_(&destination, &source);
|
|
else Require(_ZN3sce4Json5ValueaSERKS1_(&destination, &source) == &destination, "Value assignment must return its destination");
|
|
trace.budget = -1;
|
|
const auto expected = type == 5 && previous != 5 && budget < 2 ? 0 : type;
|
|
Check(destination, expected, trace);
|
|
Require(!destination.parent && destination.auxiliary == auxiliary, "Copy and assignment must preserve their metadata contract");
|
|
if (type == 0) Require(destination.payload == (previous >= 5 ? 0 : payload), "Null copies must preserve inactive scalar payload bytes");
|
|
else if (type == 1) Require(destination.payload == (((previous >= 5 ? 0 : payload) & ~UINT64_C(255)) | 1), "Boolean copies must preserve inactive destination bytes");
|
|
else if (type < 5) Require(destination.payload == source.payload, "Numeric copies must retain the exact active payload");
|
|
else if (!expected) Require(destination.payload == 0, "Failed string admission must leave a null payload pointer");
|
|
else {
|
|
const auto* stored = *reinterpret_cast<void* const*>(destination.payload);
|
|
const auto* characters = *reinterpret_cast<const char* const*>(stored);
|
|
const bool complete = budget >= (previous == 5 ? 1 : 3);
|
|
Require(std::strcmp(characters, complete ? text : "") == 0, "String copy failure must retain its admitted empty header");
|
|
if (previous == 5) Require(stored == header, "String assignment must reuse the existing header");
|
|
}
|
|
if (type < 5) Require(trace.requests.size() == requests, "Scalar copies must not allocate");
|
|
if (type == 5) {
|
|
const auto* stored = *reinterpret_cast<void* const*>(source.payload);
|
|
Require(std::strcmp(*reinterpret_cast<const char* const*>(stored), text) == 0, "String copy failures must preserve the source");
|
|
}
|
|
_ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source);
|
|
Require(trace.live.size() == baseline, "Copy failures must reclaim destination and source resources");
|
|
}
|
|
}
|
|
static void CheckCollectionCopies(ValueAllocationTrace& trace) {
|
|
const auto baseline = trace.live.size();
|
|
for (bool specialized : {false, true}) for (int previous = -1; previous <= 7; ++previous) for (int type : {6, 7}) for (int budget = 0; budget <= 5; ++budget) {
|
|
Value source; std::memset(&source, 0xa5, sizeof(source));
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&source, static_cast<ValueType>(type));
|
|
const auto sourcePayload = source.payload;
|
|
Value destination; std::memset(&destination, 0xa5, sizeof(destination));
|
|
void* auxiliary = nullptr;
|
|
void* originalHeader = nullptr;
|
|
if (previous >= 0) {
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&destination, static_cast<ValueType>(previous));
|
|
Require(Register(destination, 0, &trace, true) == 0, "Prepare collection assignment metadata");
|
|
auxiliary = destination.auxiliary;
|
|
if (previous == type) originalHeader = *reinterpret_cast<void* const*>(destination.payload);
|
|
}
|
|
const auto notifications = trace.notifications;
|
|
trace.budget = budget;
|
|
if (specialized && type == 6) {
|
|
const auto* array = reinterpret_cast<const Array*>(source.payload);
|
|
if (previous < 0) _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&destination, array);
|
|
else _ZN3sce4Json5Value3setERKNS0_5ArrayE(&destination, array);
|
|
} else if (specialized) {
|
|
const auto* object = reinterpret_cast<const Object*>(source.payload);
|
|
if (previous < 0) _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&destination, object);
|
|
else _ZN3sce4Json5Value3setERKNS0_6ObjectE(&destination, object);
|
|
} else if (previous < 0) _ZN3sce4Json5ValueC1ERKS1_(&destination, &source);
|
|
else _ZN3sce4Json5ValueaSERKS1_(&destination, &source);
|
|
trace.budget = -1;
|
|
const auto threshold = type == 6 ? 2 : 3;
|
|
const auto expected = previous == type || budget >= threshold ? type : 0;
|
|
Check(destination, expected, trace);
|
|
Require(!destination.parent && destination.auxiliary == auxiliary, "Collection copies must preserve destination metadata");
|
|
if (!expected) Require(destination.payload == 0, "Failed collection admission must leave a null handle");
|
|
else {
|
|
auto* header = *reinterpret_cast<void* const*>(destination.payload);
|
|
const auto* words = reinterpret_cast<const std::uintptr_t*>(header);
|
|
Require(words[1] == 0 && (words[0] != 0) == !(type == 6 && previous != type && budget == 2), "Collection assignment must preserve the reference partial-header state");
|
|
if (previous == type) Require((header == originalHeader) == (budget < 2), "Same-type assignment must preserve the header only when replacement admission fails");
|
|
}
|
|
const auto expectedNotifications = budget == 5 || (previous == type && budget >= 2) ? 0u : type == 6 && previous != type && budget == 2 ? 2u : 1u;
|
|
Require(trace.notifications - notifications == expectedNotifications, "Collection copies must notify both type-admission and replacement allocation failures");
|
|
Require(source.payload == sourcePayload && source.type == type, "Collection assignment must preserve source ownership");
|
|
_ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source);
|
|
Require(trace.live.size() == baseline, "Collection copies must reclaim partial handles and replaced storage");
|
|
}
|
|
}
|
|
static void CheckMissingCollections(ValueAllocationTrace& trace) {
|
|
const auto baseline = trace.live.size();
|
|
const Array array{}; const Object object{};
|
|
for (int previous = -1; previous <= 7; ++previous) for (bool useArray : {false, true}) {
|
|
Value value; std::memset(&value, 0xa5, sizeof(value));
|
|
if (previous >= 0) {
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast<ValueType>(previous));
|
|
Require(Register(value, 0, &trace, true) == 0, "Prepare missing-source collection metadata");
|
|
}
|
|
const auto original = value;
|
|
const auto requests = trace.requests.size();
|
|
const auto notifications = trace.notifications;
|
|
trace.budget = 0;
|
|
if (previous < 0) {
|
|
if (useArray) _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&value, &array);
|
|
else _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&value, &object);
|
|
} else {
|
|
if (useArray) _ZN3sce4Json5Value3setERKNS0_5ArrayE(&value, &array);
|
|
else _ZN3sce4Json5Value3setERKNS0_6ObjectE(&value, &object);
|
|
}
|
|
trace.budget = -1;
|
|
Require(trace.requests.size() == requests && trace.notifications == notifications, "A missing input header must not allocate or notify");
|
|
if (previous >= 0) Require(std::memcmp(&value, &original, sizeof(value)) == 0, "A missing input header must preserve the entire destination");
|
|
else Require(value.type == 0 && !value.parent && !value.auxiliary && value.payload == original.payload && value.reserved == original.reserved, "Construction from a missing header must initialize null metadata and preserve inactive bytes");
|
|
_ZN3sce4Json5ValueD1Ev(&value);
|
|
Require(trace.live.size() == baseline, "Missing collection inputs must not leak ownership");
|
|
}
|
|
for (int type : {6, 7}) {
|
|
Value value; std::memset(&value, 0xa5, sizeof(value));
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast<ValueType>(type));
|
|
Require(Register(value, 0, &trace, true) == 0, "Prepare collection self-setting metadata");
|
|
const auto handle = value.payload;
|
|
const auto* header = *reinterpret_cast<void* const*>(handle);
|
|
auto* auxiliary = value.auxiliary;
|
|
if (type == 6) _ZN3sce4Json5Value3setERKNS0_5ArrayE(&value, reinterpret_cast<const Array*>(handle));
|
|
else _ZN3sce4Json5Value3setERKNS0_6ObjectE(&value, reinterpret_cast<const Object*>(handle));
|
|
Check(value, type, trace);
|
|
Require(value.payload == handle && *reinterpret_cast<void* const*>(handle) != header && static_cast<void*>(value.auxiliary) == auxiliary, "Collection self-setting must replace storage while retaining the handle and callback metadata");
|
|
_ZN3sce4Json5ValueD1Ev(&value);
|
|
Require(trace.live.size() == baseline, "Collection self-setting must release replaced storage");
|
|
}
|
|
}
|
|
static void PrepareNestedSource(Value& value) {
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast<ValueType>(6));
|
|
auto* array = reinterpret_cast<Array*>(value.payload);
|
|
Value item{};
|
|
_ZN3sce4Json5ValueC1EPKc(&item, "abc");
|
|
_ZN3sce4Json5Array9push_backERKNS0_5ValueE(array, &item);
|
|
_ZN3sce4Json5ValueD1Ev(&item);
|
|
_ZN3sce4Json5ValueC1El(&item, 42);
|
|
_ZN3sce4Json5Array9push_backERKNS0_5ValueE(array, &item);
|
|
_ZN3sce4Json5ValueD1Ev(&item);
|
|
Value nested{};
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&nested, static_cast<ValueType>(6));
|
|
_ZN3sce4Json5ValueC1EPKc(&item, "def");
|
|
_ZN3sce4Json5Array9push_backERKNS0_5ValueE(reinterpret_cast<Array*>(nested.payload), &item);
|
|
_ZN3sce4Json5ValueD1Ev(&item);
|
|
_ZN3sce4Json5Array9push_backERKNS0_5ValueE(array, &nested);
|
|
_ZN3sce4Json5ValueD1Ev(&nested);
|
|
}
|
|
static std::string NestedState(const Value& value, const Value* parent = nullptr) {
|
|
std::string state = std::to_string(value.type) + (value.parent == parent ? "p" : value.parent ? "x" : "-");
|
|
if (value.type == 0) return state;
|
|
if (value.type == 2) return state + std::to_string(value.payload);
|
|
if (value.type != 5 && value.type != 6) { Require(false, "Unexpected nested test value type"); return {}; }
|
|
if (!value.payload) return state + "!";
|
|
const auto* header = *reinterpret_cast<void* const*>(value.payload);
|
|
if (!header) return state + "?";
|
|
if (value.type == 5) return state + ":" + *reinterpret_cast<const char* const*>(header);
|
|
const auto* words = reinterpret_cast<const std::uintptr_t*>(header);
|
|
state += words[3] == reinterpret_cast<std::uintptr_t>(&value) ? "o" : "!";
|
|
state += std::to_string(words[1]) + "[";
|
|
if (!words[0]) return state + "?]";
|
|
const auto* head = reinterpret_cast<const std::uintptr_t*>(words[0]);
|
|
auto* node = reinterpret_cast<const std::uintptr_t*>(head[1]);
|
|
std::size_t count = 0;
|
|
const auto* previous = head;
|
|
while (node != head) {
|
|
Require(node[0] == reinterpret_cast<std::uintptr_t>(previous), "Nested array predecessor links must preserve ownership order");
|
|
Require(++count <= words[1], "Nested array nodes must close at their sentinel");
|
|
state += NestedState(*reinterpret_cast<const Value*>(node + 2), &value) + ";";
|
|
previous = node;
|
|
node = reinterpret_cast<const std::uintptr_t*>(node[1]);
|
|
}
|
|
Require(head[0] == reinterpret_cast<std::uintptr_t>(previous), "Nested array sentinel must retain its last live entry");
|
|
Require(count == words[1], "Nested array count must match its live links");
|
|
return state + "]";
|
|
}
|
|
static void CheckNestedCopies(ValueAllocationTrace& trace) {
|
|
struct Case { bool once; int budget; unsigned notifications; std::size_t requests; const char* state; };
|
|
const Case cases[]{
|
|
{0, 0, 1, 1, "0p"},
|
|
{0, 1, 1, 2, "0p"},
|
|
{0, 2, 2, 4, "6po0[?]"},
|
|
{0, 3, 1, 4, "6po0[]"},
|
|
{0, 4, 1, 5, "6po0[]"},
|
|
{0, 5, 5, 10, "6po0[]"},
|
|
{0, 6, 5, 11, "6po0[]"},
|
|
{0, 7, 4, 11, "6po0[]"},
|
|
{0, 8, 4, 12, "6po1[0-;]"},
|
|
{0, 9, 4, 13, "6po1[0-;]"},
|
|
{0, 10, 4, 14, "6po1[5p:;]"},
|
|
{0, 11, 3, 14, "6po1[5p:abc;]"},
|
|
{0, 12, 2, 14, "6po2[5p:abc;2p42;]"},
|
|
{0, 13, 2, 15, "6po2[5p:abc;2p42;]"},
|
|
{0, 14, 2, 16, "6po2[5p:abc;2p42;]"},
|
|
{0, 15, 1, 16, "6po2[5p:abc;2p42;]"},
|
|
{0, 16, 1, 17, "6po3[5p:abc;2p42;0-;]"},
|
|
{0, 17, 1, 18, "6po3[5p:abc;2p42;0-;]"},
|
|
{0, 18, 2, 20, "6po3[5p:abc;2p42;6-o0[?];]"},
|
|
{0, 19, 1, 20, "6po3[5p:abc;2p42;6-o0[];]"},
|
|
{0, 20, 1, 21, "6po3[5p:abc;2p42;6-o0[];]"},
|
|
{0, 21, 1, 22, "6po3[5p:abc;2p42;6po0[];]"},
|
|
{0, 22, 1, 23, "6po3[5p:abc;2p42;6po0[];]"},
|
|
{0, 23, 2, 25, "6po3[5p:abc;2p42;6po0[];]"},
|
|
{0, 24, 2, 26, "6po3[5p:abc;2p42;6po0[];]"},
|
|
{0, 25, 1, 26, "6po3[5p:abc;2p42;6po0[];]"},
|
|
{0, 26, 1, 27, "6po3[5p:abc;2p42;6po1[0-;];]"},
|
|
{0, 27, 1, 28, "6po3[5p:abc;2p42;6po1[0-;];]"},
|
|
{0, 28, 1, 29, "6po3[5p:abc;2p42;6po1[5p:;];]"},
|
|
{0, 29, 0, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
{1, 0, 1, 1, "0p"},
|
|
{1, 1, 1, 2, "0p"},
|
|
{1, 2, 1, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
{1, 3, 1, 4, "6po0[]"},
|
|
{1, 4, 1, 5, "6po0[]"},
|
|
{1, 5, 1, 28, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
{1, 6, 1, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
{1, 7, 1, 26, "6po2[2p42;6po1[5p:def;];]"},
|
|
{1, 8, 1, 27, "6po3[0-;2p42;6po1[5p:def;];]"},
|
|
{1, 9, 1, 28, "6po3[0-;2p42;6po1[5p:def;];]"},
|
|
{1, 10, 1, 29, "6po3[5p:;2p42;6po1[5p:def;];]"},
|
|
{1, 11, 1, 29, "6po2[5p:abc;6po1[5p:def;];]"},
|
|
{1, 12, 1, 25, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
{1, 13, 1, 26, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
{1, 14, 1, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
{1, 15, 1, 16, "6po2[5p:abc;2p42;]"},
|
|
{1, 16, 1, 17, "6po3[5p:abc;2p42;0-;]"},
|
|
{1, 17, 1, 18, "6po3[5p:abc;2p42;0-;]"},
|
|
{1, 18, 1, 21, "6po3[5p:abc;2p42;6-o0[];]"},
|
|
{1, 19, 1, 20, "6po3[5p:abc;2p42;6-o0[];]"},
|
|
{1, 20, 1, 21, "6po3[5p:abc;2p42;6-o0[];]"},
|
|
{1, 21, 1, 22, "6po3[5p:abc;2p42;6po0[];]"},
|
|
{1, 22, 1, 23, "6po3[5p:abc;2p42;6po0[];]"},
|
|
{1, 23, 1, 28, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
{1, 24, 1, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
{1, 25, 1, 26, "6po3[5p:abc;2p42;6po0[];]"},
|
|
{1, 26, 1, 27, "6po3[5p:abc;2p42;6po1[0-;];]"},
|
|
{1, 27, 1, 28, "6po3[5p:abc;2p42;6po1[0-;];]"},
|
|
{1, 28, 1, 29, "6po3[5p:abc;2p42;6po1[5p:;];]"},
|
|
{1, 29, 0, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"},
|
|
};
|
|
const auto baseline = trace.live.size();
|
|
for (bool constructor : {false, true}) for (const auto& test : cases) {
|
|
Value source{}; PrepareNestedSource(source);
|
|
const auto sourceState = NestedState(source);
|
|
Value destination{};
|
|
if (!constructor) _ZN3sce4Json5ValueC1EPKc(&destination, "previous");
|
|
const auto requests = trace.requests.size();
|
|
const auto notifications = trace.notifications;
|
|
trace.budget = test.budget; trace.once = test.once;
|
|
if (constructor) _ZN3sce4Json5ValueC1ERKS1_(&destination, &source);
|
|
else _ZN3sce4Json5ValueaSERKS1_(&destination, &source);
|
|
trace.budget = -1; trace.once = false;
|
|
const auto state = NestedState(destination);
|
|
const bool matches = state == test.state && trace.requests.size() - requests == test.requests && trace.notifications - notifications == test.notifications;
|
|
if (!matches) std::fprintf(stderr, "Nested copy constructor=%d once=%d budget=%d expected=%s actual=%s requests=%zu notifications=%u\n", constructor, test.once, test.budget, test.state, state.c_str(), trace.requests.size() - requests, trace.notifications - notifications);
|
|
Require(matches, "Nested copy must retain the reference partial state and allocation failure accounting");
|
|
Require(NestedState(source) == sourceState, "Nested allocation failure must preserve its source");
|
|
_ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source);
|
|
Require(trace.live.size() == baseline, "Nested partial-state teardown must release all admitted ownership");
|
|
}
|
|
}
|
|
static void CheckStringSetterAliases(ValueAllocationTrace& trace) {
|
|
const auto baseline = trace.live;
|
|
for (unsigned kind = 0; kind < 4; ++kind) for (bool registered : {false, true}) for (bool fail : {false, true}) {
|
|
Value value; std::memset(&value, 0xa5, sizeof(value));
|
|
if (!kind) _ZN3sce4Json5ValueC1El(&value, 9);
|
|
else _ZN3sce4Json5ValueC1EPKc(&value, "original");
|
|
if (registered) Require(Register(value, 0, &trace) == 0, "Prepare callback metadata before String setter alias testing");
|
|
String missing{}, shared{};
|
|
if (kind >= 2) shared.text = *reinterpret_cast<Text**>(value.payload);
|
|
const String* source = kind < 2 ? &missing : kind == 2 ? reinterpret_cast<const String*>(value.payload) : &shared;
|
|
const Value before = value; const auto live = trace.live;
|
|
std::map<void*, std::vector<unsigned char>> snapshots;
|
|
for (const auto& [pointer, size] : live) {
|
|
const auto* bytes = reinterpret_cast<const unsigned char*>(pointer); snapshots.emplace(pointer, std::vector<unsigned char>{bytes, bytes + size});
|
|
}
|
|
const auto requests = trace.requests.size(); const auto frees = trace.frees.size(); const auto notices = trace.notifications; const auto nullFrees = trace.nullFrees;
|
|
trace.budget = fail ? 0 : -1;
|
|
_ZN3sce4Json5Value3setERKNS0_6StringE(&value, source);
|
|
trace.budget = -1;
|
|
Require(std::memcmp(&value, &before, sizeof(value)) == 0 && trace.live == live && trace.requests.size() == requests && trace.frees.size() == frees && trace.notifications == notices && trace.nullFrees == nullFrees,
|
|
"Missing String headers and shared-header self-setting must preserve metadata, payload and allocation effects");
|
|
for (const auto& [pointer, bytes] : snapshots) Require(std::memcmp(pointer, bytes.data(), bytes.size()) == 0, "Ignored or aliased String setting must preserve all original allocation bytes");
|
|
_ZN3sce4Json5ValueD1Ev(&value);
|
|
Require(trace.live == baseline, "String setter alias cases must reclaim their actual payload and callback resources");
|
|
}
|
|
for (unsigned budget = 0; budget <= 1; ++budget) {
|
|
Value sourceValue, destination; std::memset(&sourceValue, 0xa5, sizeof(sourceValue)); std::memset(&destination, 0xa5, sizeof(destination));
|
|
_ZN3sce4Json5ValueC1EPKc(&sourceValue, "replacement"); _ZN3sce4Json5ValueC1EPKc(&destination, "old");
|
|
const Value originalSource = sourceValue;
|
|
const auto* source = reinterpret_cast<const String*>(sourceValue.payload);
|
|
const auto* sourceHeader = source->text; const auto* sourceCharacters = *reinterpret_cast<const char* const*>(sourceHeader);
|
|
const auto handle = destination.payload; const auto* header = *reinterpret_cast<void* const*>(handle); void* oldCharacters = *reinterpret_cast<void* const*>(header);
|
|
const auto requests = trace.requests.size(); const auto frees = trace.frees.size(); const auto notices = trace.notifications;
|
|
trace.budget = static_cast<int>(budget);
|
|
_ZN3sce4Json5Value3setERKNS0_6StringE(&destination, source);
|
|
trace.budget = -1;
|
|
Require(destination.type == 5 && destination.payload == handle && *reinterpret_cast<void* const*>(handle) == header &&
|
|
std::strcmp(*reinterpret_cast<const char* const*>(header), budget ? "replacement" : "") == 0 &&
|
|
trace.requests.size() == requests + 1 && trace.requests.back() == 12 && trace.notifications == notices + unsigned(!budget) &&
|
|
trace.frees.size() == frees + 1 && trace.frees.back() == oldCharacters,
|
|
"Independent String sources must copy through the existing handle/header and retain empty text after character allocation failure");
|
|
Require(std::memcmp(&sourceValue, &originalSource, sizeof(sourceValue)) == 0 && source->text == sourceHeader && *reinterpret_cast<const char* const*>(sourceHeader) == sourceCharacters &&
|
|
std::strcmp(sourceCharacters, "replacement") == 0, "Distinct-source assignment must preserve all source ownership and text bytes");
|
|
const auto retryRequests = trace.requests.size();
|
|
_ZN3sce4Json5Value3setERKNS0_6StringE(&destination, source);
|
|
Require(trace.requests.size() == retryRequests + 1 && trace.requests.back() == 12 && destination.payload == handle &&
|
|
std::strcmp(*reinterpret_cast<const char* const*>(header), "replacement") == 0, "Independent retry must complete the copy using the retained destination header");
|
|
_ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&sourceValue);
|
|
Require(trace.live == baseline, "Distinct String source cases must reclaim both real payloads");
|
|
}
|
|
|
|
}
|
|
|
|
static void CheckValueSelfSetting(ValueAllocationTrace& trace) {
|
|
const auto baseline = trace.live;
|
|
for (int type = 0; type <= 7; ++type) for (bool setter : {false, true}) for (unsigned budget = 0; budget <= 2; ++budget) {
|
|
Value value; std::memset(&value, 0xa5, sizeof(value));
|
|
if (type == 5) _ZN3sce4Json5ValueC1EPKc(&value, "self");
|
|
else _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast<ValueType>(type));
|
|
Require(Register(value, 0, &trace, true) == 0, "Prepare callback ownership for Value self-setting");
|
|
const Value before = value; const auto live = trace.live;
|
|
std::map<void*, std::vector<unsigned char>> snapshots;
|
|
for (const auto& [pointer, size] : live) {
|
|
const auto* bytes = reinterpret_cast<const unsigned char*>(pointer); snapshots.emplace(pointer, std::vector<unsigned char>{bytes, bytes + size});
|
|
}
|
|
void* header = type >= 6 ? *reinterpret_cast<void**>(value.payload) : nullptr;
|
|
void* sentinel = header ? *reinterpret_cast<void**>(header) : nullptr;
|
|
const auto requests = trace.requests.size(); const auto frees = trace.frees.size(); const auto notices = trace.notifications; const auto nullFrees = trace.nullFrees;
|
|
trace.budget = static_cast<int>(budget);
|
|
if (setter) _ZN3sce4Json5Value3setERKS1_(&value, &value);
|
|
else Require(_ZN3sce4Json5ValueaSERKS1_(&value, &value) == &value, "Self-assignment must return its original receiver");
|
|
trace.budget = -1;
|
|
Require(std::memcmp(&value, &before, sizeof(value)) == 0, "Every self-operation must preserve the inline Value, handle, callbacks and inactive bytes");
|
|
const bool rebuild = type >= 6;
|
|
if (!rebuild) {
|
|
Require(trace.live == live && trace.requests.size() == requests && trace.frees.size() == frees && trace.notifications == notices && trace.nullFrees == nullFrees,
|
|
"Scalar and String self-operations must preserve allocation ownership without effects");
|
|
} else {
|
|
std::vector<std::size_t> expected{32}; if (budget) expected.push_back(type == 6 ? 48 : 104);
|
|
Require(std::vector<std::size_t>(trace.requests.begin() + requests, trace.requests.end()) == expected && trace.notifications == notices + unsigned(budget < 2) &&
|
|
trace.nullFrees == nullFrees + unsigned(!budget) && trace.frees.size() == frees + budget && trace.live.size() == live.size(),
|
|
"Collection self-setting must attempt replacement storage with exact requests, null frees and rollback effects");
|
|
auto* replacement = *reinterpret_cast<void**>(value.payload);
|
|
Require((replacement == header) == (budget < 2) && reinterpret_cast<const std::uintptr_t*>(replacement)[1] == 0 &&
|
|
reinterpret_cast<const std::uintptr_t*>(replacement)[3] == reinterpret_cast<std::uintptr_t>(&value),
|
|
"Self-setting must preserve failed replacement storage or publish a fresh empty collection bound to its original owner");
|
|
if (budget == 2) Require(trace.frees[frees] == sentinel && trace.frees[frees + 1] == header, "Completed self-setting must reclaim the original sentinel before its header");
|
|
}
|
|
for (const auto& [pointer, bytes] : snapshots) {
|
|
if (rebuild && budget == 2 && (pointer == header || pointer == sentinel || pointer == reinterpret_cast<void*>(value.payload))) continue;
|
|
Require(trace.live.contains(pointer) && std::memcmp(pointer, bytes.data(), bytes.size()) == 0,
|
|
"Self-setting must preserve every unaffected allocation, callback record and fallback byte");
|
|
}
|
|
_ZN3sce4Json5ValueD1Ev(&value);
|
|
Require(trace.live == baseline, "Self-operations must preserve complete destruction of original or replacement resources");
|
|
}
|
|
for (bool array : {false, true}) for (bool setter : {false, true}) {
|
|
Value value; std::memset(&value, 0xa5, sizeof(value));
|
|
if (array) PrepareNestedSource(value);
|
|
else {
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast<ValueType>(7));
|
|
Value key{}, scalar{}; _ZN3sce4Json5ValueC1EPKc(&key, "key"); _ZN3sce4Json5ValueC1El(&scalar, 9);
|
|
auto* member = _ZN3sce4Json6ObjectixERKNS0_6StringE(reinterpret_cast<Object*>(value.payload), reinterpret_cast<const String*>(key.payload));
|
|
_ZN3sce4Json5ValueaSERKS1_(member, &scalar);
|
|
_ZN3sce4Json5ValueD1Ev(&scalar); _ZN3sce4Json5ValueD1Ev(&key);
|
|
}
|
|
Require(Register(value, 0, &trace, true) == 0, "Prepare metadata for nonempty collection self-use");
|
|
const Value before = value; const auto* header = *reinterpret_cast<void* const*>(value.payload);
|
|
const auto state = array ? NestedState(value) : std::string{};
|
|
const auto requests = trace.requests.size();
|
|
if (setter) _ZN3sce4Json5Value3setERKS1_(&value, &value);
|
|
else Require(_ZN3sce4Json5ValueaSERKS1_(&value, &value) == &value, "Nonempty assignment must return its original receiver");
|
|
Require(std::memcmp(&value, &before, sizeof(value)) == 0 && *reinterpret_cast<void* const*>(value.payload) != header && trace.requests.size() > requests,
|
|
"Nonempty collection self-use must rebuild storage while retaining the Value handle and callback metadata");
|
|
if (array) Require(state == "6po3[5p:abc;2p42;6p!1[5p:def;];]" && NestedState(value) == "6po3[5p:abc;2p42;6po1[5p:def;];]", "Nested self-use must preserve contents and parent links while rebinding the copied nested header owner");
|
|
else {
|
|
auto* replacement = *reinterpret_cast<void* const*>(value.payload);
|
|
auto* head = *reinterpret_cast<void* const*>(replacement);
|
|
auto* node = *reinterpret_cast<void* const*>(head);
|
|
const auto* bytes = reinterpret_cast<const unsigned char*>(node);
|
|
const auto* child = reinterpret_cast<const Value*>(bytes + 64);
|
|
const auto* keyHeader = *reinterpret_cast<void* const*>(bytes + 48);
|
|
Require(reinterpret_cast<const std::uintptr_t*>(replacement)[1] == 1 && child->type == 2 && child->payload == 9 && child->parent == &value &&
|
|
std::strcmp(*reinterpret_cast<const char* const*>(bytes + 24), "key") == 0 && std::strcmp(*reinterpret_cast<const char* const*>(keyHeader), "key") == 0,
|
|
"Object self-use must preserve ordered internal/public keys, child contents and owner binding");
|
|
}
|
|
_ZN3sce4Json5ValueD1Ev(&value);
|
|
Require(trace.live == baseline, "Nonempty self-use must reclaim both replaced and final collection storage");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
ValueAllocationTrace trace; ValueAllocationAllocator allocator; Parameter parameter{&allocator, &trace, 16384, 0, {}};
|
|
std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer);
|
|
Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast<const InitParameter2*>(¶meter)) == 0, "Initialize value allocation contract");
|
|
const auto baseline = trace.live.size();
|
|
CheckMetadata(trace);
|
|
CheckScalarCopies(trace);
|
|
CheckCollectionCopies(trace);
|
|
CheckMissingCollections(trace);
|
|
CheckNestedCopies(trace);
|
|
for (int target = 0; target <= 7; ++target) for (int budget = 0; budget <= 4; ++budget) {
|
|
Value value; std::memset(&value, 0xa5, sizeof(value));
|
|
trace.budget = budget;
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast<ValueType>(target));
|
|
trace.budget = -1;
|
|
const auto expected = target < 5 ? target : budget < (target == 7 ? 3 : 2) ? 0 : target;
|
|
Check(value, expected, trace);
|
|
Require(!value.parent && !value.auxiliary, "Typed construction must clear ownership metadata");
|
|
if (target >= 5 && expected == 0) Require(value.payload == 0, "Failed complex construction must clear the payload pointer");
|
|
_ZN3sce4Json5ValueD1Ev(&value);
|
|
Require(trace.live.size() == baseline, "Typed construction must release partial owned resources");
|
|
}
|
|
for (int previous = 0; previous <= 7; ++previous) for (int target = 0; target <= 7; ++target) for (int budget = 0; budget <= 4; ++budget) {
|
|
Value value; std::memset(&value, 0xa5, sizeof(value));
|
|
_ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast<ValueType>(previous));
|
|
Value owner{}; value.parent = &owner;
|
|
const auto payload = value.payload;
|
|
const auto requests = trace.requests.size();
|
|
const auto notifications = trace.notifications;
|
|
trace.budget = budget;
|
|
_ZN3sce4Json5Value3setENS0_9ValueTypeE(&value, static_cast<ValueType>(target));
|
|
trace.budget = -1;
|
|
const auto expected = previous == target || target < 5 ? target : budget < (target == 7 ? 3 : 2) ? 0 : target;
|
|
Check(value, expected, trace);
|
|
Require(value.parent == &owner && !value.auxiliary, "Type changes must preserve parent and auxiliary ownership");
|
|
if (previous == target) Require(value.payload == payload && trace.requests.size() == requests && trace.notifications == notifications, "Retaining the same type must preserve storage without allocating");
|
|
else if (target >= 5 && expected == 0) Require(value.payload == 0 && trace.notifications > notifications, "Failed type changes must clear the previous payload and notify failure");
|
|
else if (target == 0) Require(value.payload == (previous >= 5 ? 0 : payload), "Clearing a scalar must preserve its inactive payload while complex values clear their handle");
|
|
else if (target == 1) Require(value.payload == ((previous >= 5 ? 0 : payload) & ~UINT64_C(255)), "Boolean type changes must clear only their active byte");
|
|
else if (target < 5) Require(value.payload == 0, "Numeric type changes must initialize their active payload");
|
|
_ZN3sce4Json5ValueD1Ev(&value);
|
|
Require(trace.live.size() == baseline, "Type changes must reclaim the old payload even when replacement fails");
|
|
}
|
|
for (bool constructor : {false, true}) for (int budget = 0; budget <= 5; ++budget) {
|
|
Value value; std::memset(&value, 0xa5, sizeof(value));
|
|
if (!constructor) _ZN3sce4Json5ValueC1Ed(&value, 0.125);
|
|
const auto payload = value.payload;
|
|
const auto notifications = trace.notifications;
|
|
trace.budget = budget;
|
|
if (constructor) _ZN3sce4Json5ValueC1Ed(&value, std::numeric_limits<double>::infinity());
|
|
else _ZN3sce4Json5Value3setEd(&value, std::numeric_limits<double>::infinity());
|
|
trace.budget = -1;
|
|
const auto expected = budget == 0 ? (constructor ? 0 : 4) : budget < 4 ? 0 : 5;
|
|
Check(value, expected, trace);
|
|
Require(trace.notifications - notifications == (budget == 5 ? 0u : budget == 1 ? 2u : 1u), "Float conversion must report temporary and destination allocation failures");
|
|
if (budget == 0) Require(value.payload == payload, "Failure to allocate the conversion temporary must preserve the prior payload");
|
|
else if (expected == 0) Require(value.payload == 0, "Failed destination conversion must leave a null handle");
|
|
else {
|
|
const auto* header = *reinterpret_cast<void* const*>(value.payload);
|
|
const auto* characters = *reinterpret_cast<const char* const*>(header);
|
|
Require(std::strcmp(characters, budget == 4 ? "" : "inf") == 0, "Float conversion must retain the admitted string and character failure state");
|
|
}
|
|
_ZN3sce4Json5ValueD1Ev(&value);
|
|
Require(trace.live.size() == baseline, "Float conversion must release temporary and destination allocations");
|
|
}
|
|
CheckStringSetterAliases(trace);
|
|
CheckValueSelfSetting(trace);
|
|
Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Terminate value allocation contract");
|
|
return 0;
|
|
}
|