blob: cb92eeaa741a3a02ef04eb5e6c397a4b31c3c540 [file]
/*
* Copyright (C) 2009-2026 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <JavaScriptCore/BigIntObject.h>
#include <JavaScriptCore/BooleanObject.h>
#include <JavaScriptCore/CloneBase.h>
#include <JavaScriptCore/DateInstance.h>
#include <JavaScriptCore/JSArray.h>
#include <JavaScriptCore/JSBigInt.h>
#include <JavaScriptCore/JSCJSValue.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/JSGlobalObjectInlines.h>
#include <JavaScriptCore/JSMapInlines.h>
#include <JavaScriptCore/JSObjectInlines.h>
#include <JavaScriptCore/JSSetInlines.h>
#include <JavaScriptCore/JSString.h>
#include <JavaScriptCore/MarkedVector.h>
#include <JavaScriptCore/NumberObject.h>
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/RegExpObject.h>
#include <JavaScriptCore/StringObject.h>
#include <JavaScriptCore/TopExceptionScope.h>
#include <JavaScriptCore/YarrFlags.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>
#include <wtf/text/AtomString.h>
#include <wtf/text/WTFString.h>
namespace JSC {
template<typename Derived>
concept StructuredCloneDeserializerHandler = requires(Derived& d, SerializationTag t) {
{ d.readDerivedTerminal(t) } -> std::same_as<JSValue>;
{ d.isTagExposed(t) } -> std::same_as<bool>;
};
namespace StructuredCloneInternal {
#if JSC_ASSUME_LITTLE_ENDIAN
template<typename T> inline bool readLittleEndian(std::span<const uint8_t>& span, T& value)
{
if (span.size() < sizeof(value))
return false;
value = consumeAndReinterpretCastTo<const T>(span);
return true;
}
#else
template<typename T> inline bool readLittleEndian(std::span<const uint8_t>& span, T& value)
{
if (span.size() < sizeof(value))
return false;
if constexpr (sizeof(T) == 1)
value = consume(span);
else {
value = 0;
for (size_t i = 0; i < sizeof(T); ++i)
value += static_cast<T>(span[i]) << (i * 8);
skip(span, sizeof(T));
}
return true;
}
#endif
} // namespace StructuredCloneInternal
class CachedString {
public:
CachedString(String&& string)
: m_string(WTF::move(string))
{
}
JSValue jsString(CloneBase&);
const String& string() const { return m_string; }
String takeString() { return WTF::move(m_string); }
private:
String m_string;
JSValue m_jsString;
};
class CachedStringRef {
public:
CachedStringRef() = default;
CachedStringRef(Vector<CachedString>* base, size_t index)
: m_base(base)
, m_index(index)
{
}
CachedString* operator->() { ASSERT(m_base); return &m_base->at(m_index); }
private:
Vector<CachedString>* m_base { nullptr };
size_t m_index { 0 };
};
enum class ShouldAtomize : bool { No, Yes };
template<typename Derived>
class CloneDeserializerBase : public CloneBase {
protected:
CloneDeserializerBase(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, std::span<const uint8_t> data)
: CloneBase(lexicalGlobalObject)
, m_globalObject(globalObject)
, m_data(data)
{
}
template<typename T> bool readLittleEndian(T& value)
{
if (m_failed || !StructuredCloneInternal::readLittleEndian(m_data, value)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return false;
}
return true;
}
bool read(uint8_t& i) { return readLittleEndian(i); }
bool read(uint16_t& i) { return readLittleEndian(i); }
bool read(uint32_t& i) { return readLittleEndian(i); }
bool read(uint64_t& i) { return readLittleEndian(i); }
bool read(int32_t& i) { return readLittleEndian(*reinterpret_cast<uint32_t*>(&i)); }
bool read(double& d)
{
uint64_t bits;
if (!readLittleEndian(bits))
return false;
d = purifyNaN(std::bit_cast<double>(bits));
return true;
}
bool read(bool& b)
{
if (m_majorVersion >= 14) {
uint8_t integer;
if (!read(integer) || integer > 1)
return false;
b = !!integer;
return true;
}
int32_t integer;
if (!read(integer) || integer > 1)
return false;
b = !!integer;
return true;
}
SerializationTag readTag()
{
if (m_data.empty()) {
SERIALIZE_TRACE("FAIL deserialize");
return ErrorTag;
}
auto tag = static_cast<SerializationTag>(consume(m_data));
SERIALIZE_TRACE("deserialize ", tag);
return tag;
}
bool readAndStoreVersion()
{
unsigned version;
if (!read(version))
return false;
m_majorVersion = majorVersionFor(version);
m_minorVersion = minorVersionFor(version);
return true;
}
bool isValid() const
{
if (m_majorVersion > CurrentMajorVersion)
return false;
if (m_majorVersion == 12)
return m_minorVersion <= 1;
return !m_minorVersion;
}
bool shouldRetryWithVersionUpgrade() const
{
if (m_majorVersion == 14 && !m_minorVersion)
return true;
if (m_majorVersion == 12 && !m_minorVersion)
return true;
return false;
}
void upgradeVersion()
{
ASSERT(shouldRetryWithVersionUpgrade());
if (m_majorVersion == 14 && !m_minorVersion) {
m_majorVersion = 15;
return;
}
if (m_majorVersion == 12 && !m_minorVersion)
m_minorVersion = 1;
}
template<typename T>
std::optional<uint32_t> readConstantPoolIndex(const T& constantPool)
{
if (constantPool.size() <= 0xFF) {
uint8_t i8;
if (!read(i8))
return std::nullopt;
return i8;
}
if (constantPool.size() <= 0xFFFF) {
uint16_t i16;
if (!read(i16))
return std::nullopt;
return i16;
}
uint32_t i;
if (!read(i))
return std::nullopt;
return i;
}
std::optional<uint32_t> readStringIndex()
{
return readConstantPoolIndex(m_constantPool);
}
static bool readString(std::span<const uint8_t>& span, String& str, unsigned length, bool is8Bit, ShouldAtomize shouldAtomize)
{
if (length >= std::numeric_limits<int32_t>::max() / sizeof(char16_t))
return false;
if (is8Bit) {
if (span.size() < length)
return false;
if (shouldAtomize == ShouldAtomize::Yes)
str = AtomString(byteCast<Latin1Character>(consumeSpan(span, length)));
else
str = String(byteCast<Latin1Character>(consumeSpan(span, length)));
return true;
}
size_t size = length * sizeof(char16_t);
if (span.size() < size)
return false;
#if JSC_ASSUME_LITTLE_ENDIAN
auto stringSpan = consumeSpan(span, size);
if (shouldAtomize == ShouldAtomize::Yes)
str = AtomString(spanReinterpretCast<const char16_t>(stringSpan));
else
str = String(spanReinterpretCast<const char16_t>(stringSpan));
#else
std::span<char16_t> characters;
str = String::createUninitialized(length, characters);
for (unsigned i = 0; i < length; ++i) {
uint16_t c;
StructuredCloneInternal::readLittleEndian(span, c);
characters[i] = c;
}
if (shouldAtomize == ShouldAtomize::Yes)
str = AtomString { str };
#endif
return true;
}
bool readStringData(CachedStringRef& cachedString, ShouldAtomize shouldAtomize = ShouldAtomize::No)
{
bool scratch;
return readStringData(cachedString, scratch, shouldAtomize);
}
bool readStringData(CachedStringRef& cachedString, bool& wasTerminator, ShouldAtomize shouldAtomize = ShouldAtomize::No)
{
if (m_failed)
return false;
uint32_t length = 0;
if (!read(length))
return false;
if (length == TerminatorTag) {
wasTerminator = true;
return false;
}
if (length == StringPoolTag) {
auto index = readStringIndex();
if (!index || *index >= m_constantPool.size()) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return false;
}
cachedString = CachedStringRef(&m_constantPool, *index);
return true;
}
bool is8Bit = length & StringDataIs8BitFlag;
length &= ~StringDataIs8BitFlag;
String str;
if (!readString(m_data, str, length, is8Bit, shouldAtomize)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return false;
}
m_constantPool.append(WTF::move(str));
cachedString = CachedStringRef(&m_constantPool, m_constantPool.size() - 1);
return true;
}
bool readNullableString(String& nullableString, ShouldAtomize shouldAtomize = ShouldAtomize::No)
{
bool isNull;
if (!read(isNull))
return false;
if (isNull)
return true;
CachedStringRef stringData;
if (!readStringData(stringData, shouldAtomize))
return false;
nullableString = stringData->string();
return true;
}
JSValue readBigInt()
{
// Sign is always written as a single byte.
// FIXME: Why not read this back as a bool since it's written as one?
uint8_t signByte;
if (!read(signByte))
return JSValue();
bool sign = !!signByte;
uint32_t numberOfUint64Elements = 0;
if (!read(numberOfUint64Elements))
return JSValue();
if (!numberOfUint64Elements) {
#if USE(BIGINT32)
return jsBigInt32(0);
#else
JSBigInt* bigInt = JSBigInt::tryCreateZero(m_lexicalGlobalObject->vm());
if (!bigInt) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
return bigInt;
#endif
}
#if USE(BIGINT32)
static_assert(sizeof(JSBigInt::Digit) == sizeof(uint64_t));
if (numberOfUint64Elements == 1) {
uint64_t digit64 = 0;
if (!read(digit64))
return JSValue();
if (sign) {
if (digit64 <= static_cast<uint64_t>(-static_cast<int64_t>(INT32_MIN)))
return jsBigInt32(static_cast<int32_t>(-static_cast<int64_t>(digit64)));
} else {
if (digit64 <= INT32_MAX)
return jsBigInt32(static_cast<int32_t>(digit64));
}
ASSERT(digit64);
JSBigInt* bigInt = JSBigInt::tryCreateFrom(nullptr, m_lexicalGlobalObject->vm(), sign, std::span { &digit64, 1 });
if (!bigInt) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
return tryConvertToBigInt32(bigInt);
}
#endif
Vector<JSBigInt::Digit, 16> digits;
if constexpr (sizeof(JSBigInt::Digit) == sizeof(uint64_t)) {
digits.reserveInitialCapacity(numberOfUint64Elements);
for (uint32_t index = 0; index < numberOfUint64Elements; ++index) {
uint64_t digit64 = 0;
if (!read(digit64))
return JSValue();
digits.append(digit64);
}
} else {
ASSERT(sizeof(JSBigInt::Digit) == sizeof(uint32_t));
auto actualBigIntLength = WTF::checkedProduct<uint32_t>(numberOfUint64Elements, 2);
if (actualBigIntLength.hasOverflowed()) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
digits.reserveInitialCapacity(actualBigIntLength.value());
for (uint32_t index = 0; index < numberOfUint64Elements; ++index) {
uint64_t digit64 = 0;
if (!read(digit64))
return JSValue();
digits.append(static_cast<uint32_t>(digit64));
digits.append(static_cast<uint32_t>(digit64 >> 32));
}
}
auto* bigInt = JSBigInt::tryCreateFrom(nullptr, m_lexicalGlobalObject->vm(), sign, digits.span());
if (!bigInt) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
return tryConvertToBigInt32(bigInt);
}
template<SerializationTag tag>
void addToObjectPool(JSValue object)
{
static_assert(canBeAddedToObjectPool(tag));
m_objectPool.appendWithCrashOnOverflow(object);
appendObjectPoolTag(tag);
}
ALWAYS_INLINE JSValue readTerminalImpl(SerializationTag tag)
{
switch (tag) {
case UndefinedTag:
return jsUndefined();
case NullTag:
return jsNull();
case IntTag: {
int32_t i;
if (!read(i))
return JSValue();
return jsNumber(i);
}
case ZeroTag:
return jsNumber(0);
case OneTag:
return jsNumber(1);
case FalseTag:
return jsBoolean(false);
case TrueTag:
return jsBoolean(true);
case DoubleTag: {
double d;
if (!read(d))
return JSValue();
return jsNumber(d);
}
case StringTag: {
CachedStringRef cachedString;
if (!readStringData(cachedString))
return JSValue();
return cachedString->jsString(*this);
}
case EmptyStringTag:
return jsEmptyString(m_lexicalGlobalObject->vm());
case BigIntTag:
return readBigInt();
case DateTag: {
double d;
if (!read(d))
return JSValue();
return DateInstance::create(m_lexicalGlobalObject->vm(), m_globalObject->dateStructure(), d);
}
case RegExpTag: {
CachedStringRef pattern;
if (!readStringData(pattern))
return JSValue();
CachedStringRef flags;
if (!readStringData(flags))
return JSValue();
auto reFlags = Yarr::parseFlags(flags->string());
if (!reFlags.has_value())
return JSValue();
VM& vm = m_lexicalGlobalObject->vm();
RegExp* regExp = RegExp::create(vm, pattern->string(), reFlags.value());
return RegExpObject::create(vm, m_globalObject->regExpStructure(), regExp);
}
case NumberObjectTag: {
double d;
if (!read(d))
return JSValue();
NumberObject* obj = constructNumber(m_globalObject, jsNumber(d));
addToObjectPool<NumberObjectTag>(obj);
return obj;
}
case StringObjectTag: {
CachedStringRef cachedString;
if (!readStringData(cachedString))
return JSValue();
StringObject* obj = constructString(m_lexicalGlobalObject->vm(), m_globalObject, cachedString->jsString(*this));
addToObjectPool<StringObjectTag>(obj);
return obj;
}
case EmptyStringObjectTag: {
VM& vm = m_lexicalGlobalObject->vm();
StringObject* obj = constructString(vm, m_globalObject, jsEmptyString(vm));
addToObjectPool<EmptyStringObjectTag>(obj);
return obj;
}
case FalseObjectTag: {
BooleanObject* obj = BooleanObject::create(m_lexicalGlobalObject->vm(), m_globalObject->booleanObjectStructure());
obj->setInternalValue(m_lexicalGlobalObject->vm(), jsBoolean(false));
addToObjectPool<FalseObjectTag>(obj);
return obj;
}
case TrueObjectTag: {
BooleanObject* obj = BooleanObject::create(m_lexicalGlobalObject->vm(), m_globalObject->booleanObjectStructure());
obj->setInternalValue(m_lexicalGlobalObject->vm(), jsBoolean(true));
addToObjectPool<TrueObjectTag>(obj);
return obj;
}
case BigIntObjectTag: {
JSValue bigInt = readBigInt();
if (!bigInt)
return JSValue();
ASSERT(bigInt.isBigInt());
BigIntObject* obj = BigIntObject::create(m_lexicalGlobalObject->vm(), m_globalObject, bigInt);
addToObjectPool<BigIntObjectTag>(obj);
return obj;
}
case ErrorInstanceTag: {
SerializableErrorType serializedErrorType;
if (!readSerializableErrorType(serializedErrorType)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
String message;
if (!readNullableString(message)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
uint32_t line;
if (!read(line)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
uint32_t column;
if (!read(column)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
String sourceURL;
if (!readNullableString(sourceURL)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
String stackString;
if (!readNullableString(stackString)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
String causeString;
if (!readNullableString(causeString)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
return JSValue();
}
return ErrorInstance::create(m_lexicalGlobalObject, WTF::move(message), toErrorType(serializedErrorType), { line, column }, WTF::move(sourceURL), WTF::move(stackString), WTF::move(causeString));
}
default:
return static_cast<Derived*>(this)->readDerivedTerminal(tag);
}
}
ALWAYS_INLINE JSValue readTerminal()
{
// Note: This can't be a requirement on the template as, in the common usage,
// Derived will still be an incomplete type
static_assert(StructuredCloneDeserializerHandler<Derived>,
"Derived class must satisfy StructuredCloneDeserializerHandler");
if (!isSafeToRecurse()) {
fail();
return JSValue();
}
auto originalData = m_data;
SerializationTag tag = readTag();
if (!static_cast<Derived*>(this)->isTagExposed(tag)) {
fail();
return JSValue();
}
JSValue result = readTerminalImpl(tag);
if (!result) {
SERIALIZE_TRACE("push back ", tag);
m_data = originalData;
}
return result;
}
bool readSerializableErrorType(SerializableErrorType& errorType)
{
std::underlying_type_t<SerializableErrorType> errorTypeInt;
if (!read(errorTypeInt) || errorTypeInt > std::to_underlying(SerializableErrorType::Last))
return false;
errorType = static_cast<SerializableErrorType>(errorTypeInt);
return true;
}
void putProperty(JSObject* object, unsigned index, JSValue value)
{
object->putDirectIndex(m_lexicalGlobalObject, index, value);
}
void putProperty(JSObject* object, const Identifier& property, JSValue value)
{
object->putDirectMayBeIndex(m_lexicalGlobalObject, property, value);
}
template<SerializationTag Tag>
bool consumeCollectionDataTerminationIfPossible()
{
auto originalData = m_data;
if (readTag() == Tag)
return true;
m_data = originalData;
return false;
}
enum class VisitNamedMemberResult : uint8_t { Error, Break, Start, Unknown };
template<WalkerState endState>
ALWAYS_INLINE VisitNamedMemberResult startVisitNamedMember(MarkedVector<JSObject*, 32>& outputObjectStack, Vector<Identifier, 16>& propertyNameStack, Vector<WalkerState, 16>& stateStack, JSValue& outValue)
{
static_assert(endState == WalkerState::ArrayEndVisitNamedMember || endState == WalkerState::ObjectEndVisitNamedMember);
VM& vm = m_lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
CachedStringRef cachedString;
bool wasTerminator = false;
if (!readStringData(cachedString, wasTerminator, ShouldAtomize::Yes)) {
if (!wasTerminator) {
SERIALIZE_TRACE("FAIL deserialize");
return VisitNamedMemberResult::Error;
}
JSObject* outObject = outputObjectStack.last();
outValue = outObject;
outputObjectStack.removeLast();
return VisitNamedMemberResult::Break;
}
Identifier identifier = Identifier::fromString(vm, cachedString->string());
if constexpr (endState == WalkerState::ArrayEndVisitNamedMember)
RELEASE_ASSERT(identifier != vm.propertyNames->length);
JSValue terminal = readTerminal();
if (scope.exception()) [[unlikely]]
return VisitNamedMemberResult::Error;
if (terminal) {
putProperty(outputObjectStack.last(), identifier, terminal);
if (scope.exception()) [[unlikely]]
return VisitNamedMemberResult::Error;
return VisitNamedMemberResult::Start;
}
stateStack.append(endState);
propertyNameStack.append(identifier);
return VisitNamedMemberResult::Unknown;
}
ALWAYS_INLINE void objectEndVisitNamedMember(MarkedVector<JSObject*, 32>& outputObjectStack, Vector<Identifier, 16>& propertyNameStack, JSValue& outValue)
{
VM& vm = m_lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
putProperty(outputObjectStack.last(), propertyNameStack.last(), outValue);
if (scope.exception()) [[unlikely]]
return;
propertyNameStack.removeLast();
}
DeserializationResult deserialize()
{
VM& vm = m_lexicalGlobalObject->vm();
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
Vector<uint32_t, 16> indexStack;
Vector<Identifier, 16> propertyNameStack;
MarkedVector<JSObject*, 32> outputObjectStack;
MarkedVector<JSValue, 4> mapKeyStack;
MarkedVector<JSMap*, 4> mapStack;
MarkedVector<JSSet*, 4> setStack;
Vector<WalkerState, 16> stateStack;
WalkerState state = WalkerState::StateUnknown;
JSValue outValue;
while (true) {
switch (state) {
arrayStartState:
case WalkerState::ArrayStartState: {
uint32_t length;
if (!read(length)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
JSArray* outArray = constructEmptyArray(m_globalObject, static_cast<JSC::ArrayAllocationProfile*>(nullptr), length);
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
addToObjectPool<ArrayTag>(outArray);
outputObjectStack.append(outArray);
}
arrayStartVisitIndexedMember:
[[fallthrough]];
case WalkerState::ArrayStartVisitIndexedMember: {
uint32_t index;
if (!read(index)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
if (m_majorVersion >= 15 || (m_majorVersion == 12 && m_minorVersion == 1)) {
if (index == TerminatorTag) {
// We reached the end of the indexed properties section.
if (!read(index)) {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
// At this point, we're either done with the array or is starting the
// non-indexed property section.
if (index == TerminatorTag) {
JSObject* outArray = outputObjectStack.last();
outValue = outArray;
outputObjectStack.removeLast();
break;
}
if (index == NonIndexPropertiesTag)
goto arrayStartVisitNamedMember;
}
} else if (index == TerminatorTag) {
JSObject* outArray = outputObjectStack.last();
outValue = outArray;
outputObjectStack.removeLast();
break;
} else if (index == NonIndexPropertiesTag)
goto arrayStartVisitNamedMember;
JSValue terminal = readTerminal();
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
if (terminal) {
putProperty(outputObjectStack.last(), index, terminal);
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
goto arrayStartVisitIndexedMember;
}
if (m_failed)
goto error;
indexStack.append(index);
stateStack.append(WalkerState::ArrayEndVisitIndexedMember);
goto stateUnknown;
}
case WalkerState::ArrayEndVisitIndexedMember: {
JSObject* outArray = outputObjectStack.last();
putProperty(outArray, indexStack.last(), outValue);
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
indexStack.removeLast();
goto arrayStartVisitIndexedMember;
}
arrayStartVisitNamedMember:
case WalkerState::ArrayStartVisitNamedMember: {
auto result = startVisitNamedMember<WalkerState::ArrayEndVisitNamedMember>(outputObjectStack, propertyNameStack, stateStack, outValue);
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
switch (result) {
case VisitNamedMemberResult::Error:
goto error;
case VisitNamedMemberResult::Break:
break;
case VisitNamedMemberResult::Start:
goto arrayStartVisitNamedMember;
case VisitNamedMemberResult::Unknown:
goto stateUnknown;
}
break;
}
case WalkerState::ArrayEndVisitNamedMember: {
objectEndVisitNamedMember(outputObjectStack, propertyNameStack, outValue);
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
goto arrayStartVisitNamedMember;
}
objectStartState:
case WalkerState::ObjectStartState: {
if (outputObjectStack.size() > maximumFilterRecursion)
return { JSValue(), SerializationReturnCode::StackOverflowError };
JSObject* outObject = JSC::constructEmptyObject(m_lexicalGlobalObject, m_globalObject->objectPrototype());
addToObjectPool<ObjectTag>(outObject);
outputObjectStack.append(outObject);
}
startVisitNamedMember:
[[fallthrough]];
case WalkerState::ObjectStartVisitNamedMember: {
auto result = startVisitNamedMember<WalkerState::ObjectEndVisitNamedMember>(outputObjectStack, propertyNameStack, stateStack, outValue);
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
switch (result) {
case VisitNamedMemberResult::Error:
goto error;
case VisitNamedMemberResult::Break:
break;
case VisitNamedMemberResult::Start:
goto startVisitNamedMember;
case VisitNamedMemberResult::Unknown:
goto stateUnknown;
}
break;
}
case WalkerState::ObjectEndVisitNamedMember: {
objectEndVisitNamedMember(outputObjectStack, propertyNameStack, outValue);
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
goto startVisitNamedMember;
}
mapStartState: {
if (outputObjectStack.size() > maximumFilterRecursion) {
SERIALIZE_TRACE("FAIL deserialize");
return { JSValue(), SerializationReturnCode::StackOverflowError };
}
JSMap* map = JSMap::create(m_lexicalGlobalObject->vm(), m_globalObject->mapStructure());
addToObjectPool<MapObjectTag>(map);
outputObjectStack.append(map);
mapStack.append(map);
goto mapDataStartVisitEntry;
}
mapDataStartVisitEntry:
case WalkerState::MapDataStartVisitEntry: {
if (consumeCollectionDataTerminationIfPossible<NonMapPropertiesTag>()) {
mapStack.removeLast();
goto startVisitNamedMember;
}
stateStack.append(WalkerState::MapDataEndVisitKey);
goto stateUnknown;
}
case WalkerState::MapDataEndVisitKey: {
mapKeyStack.append(outValue);
stateStack.append(WalkerState::MapDataEndVisitValue);
goto stateUnknown;
}
case WalkerState::MapDataEndVisitValue: {
mapStack.last()->set(m_lexicalGlobalObject, mapKeyStack.last(), outValue);
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
mapKeyStack.removeLast();
goto mapDataStartVisitEntry;
}
setStartState: {
if (outputObjectStack.size() > maximumFilterRecursion) {
SERIALIZE_TRACE("FAIL deserialize");
return { JSValue(), SerializationReturnCode::StackOverflowError };
}
JSSet* set = JSSet::create(m_lexicalGlobalObject->vm(), m_globalObject->setStructure());
addToObjectPool<SetObjectTag>(set);
outputObjectStack.append(set);
setStack.append(set);
goto setDataStartVisitEntry;
}
setDataStartVisitEntry:
case WalkerState::SetDataStartVisitEntry: {
if (consumeCollectionDataTerminationIfPossible<NonSetPropertiesTag>()) {
setStack.removeLast();
goto startVisitNamedMember;
}
stateStack.append(WalkerState::SetDataEndVisitKey);
goto stateUnknown;
}
case WalkerState::SetDataEndVisitKey: {
JSSet* set = setStack.last();
set->add(m_lexicalGlobalObject, outValue);
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
goto setDataStartVisitEntry;
}
stateUnknown:
case WalkerState::StateUnknown:
JSValue terminal = readTerminal();
if (scope.exception()) [[unlikely]] {
SERIALIZE_TRACE("FAIL deserialize");
fail();
goto error;
}
if (terminal) {
outValue = terminal;
break;
}
SerializationTag tag = readTag();
if (tag == ArrayTag)
goto arrayStartState;
if (tag == ObjectTag)
goto objectStartState;
if (tag == MapObjectTag)
goto mapStartState;
if (tag == SetObjectTag)
goto setStartState;
goto error;
}
if (stateStack.isEmpty())
break;
state = stateStack.last();
stateStack.removeLast();
}
ASSERT(outValue);
ASSERT(!m_failed);
return { outValue, SerializationReturnCode::SuccessfullyCompleted };
error:
fail();
return { JSValue(), SerializationReturnCode::ValidationError };
}
JSGlobalObject* const m_globalObject;
std::span<const uint8_t> m_data;
Vector<CachedString> m_constantPool;
unsigned m_majorVersion { 0xFFFFFFFFu };
unsigned m_minorVersion { 0xFFFFFFFFu };
};
inline JSValue CachedString::jsString(CloneBase& deserializer)
{
if (!m_jsString) {
auto& vm = deserializer.m_lexicalGlobalObject->vm();
m_jsString = JSC::jsString(vm, m_string);
deserializer.m_keepAliveBuffer.appendWithCrashOnOverflow(m_jsString);
}
return m_jsString;
}
} // namespace JSC