blob: 4d02873ef71897208bd6d81b0328ee08b9eb2f2a [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/Identifier.h>
#include <JavaScriptCore/JSArray.h>
#include <JavaScriptCore/JSBigInt.h>
#include <JavaScriptCore/JSCJSValue.h>
#include <JavaScriptCore/JSMapInlines.h>
#include <JavaScriptCore/JSMapIterator.h>
#include <JavaScriptCore/JSObjectInlines.h>
#include <JavaScriptCore/JSSetInlines.h>
#include <JavaScriptCore/JSSetIterator.h>
#include <JavaScriptCore/JSString.h>
#include <JavaScriptCore/NumberObject.h>
#include <JavaScriptCore/PropertyNameArray.h>
#include <JavaScriptCore/RegExpObject.h>
#include <JavaScriptCore/StringObject.h>
#include <JavaScriptCore/YarrFlags.h>
#include <wtf/HashMap.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>
#include <wtf/text/AtomString.h>
namespace JSC {
template<typename Derived>
concept StructuredCloneSerializerHandler = requires(Derived& d, JSObject* obj, SerializationReturnCode& code) {
{ d.dumpDerivedTerminal(obj, code) } -> std::same_as<bool>;
};
namespace StructuredCloneInternal {
#if JSC_ASSUME_LITTLE_ENDIAN
template<typename T> inline void writeLittleEndian(Vector<uint8_t>& buffer, T value)
{
buffer.append(asByteSpan(value));
}
#else
template<typename T> inline void writeLittleEndian(Vector<uint8_t>& buffer, T value)
{
for (unsigned i = 0; i < sizeof(T); i++) {
buffer.append(value & 0xFF);
value >>= 8;
}
}
#endif
template<> inline void writeLittleEndian<uint8_t>(Vector<uint8_t>& buffer, uint8_t value)
{
buffer.append(value);
}
template<typename T> inline bool writeLittleEndian(Vector<uint8_t>& buffer, std::span<const T> values)
{
if (values.size() > std::numeric_limits<uint32_t>::max() / sizeof(T))
return false;
#if JSC_ASSUME_LITTLE_ENDIAN
buffer.append(asBytes(values));
#else
for (unsigned i = 0; i < values.size(); i++) {
T value = values[i];
for (unsigned j = 0; j < sizeof(T); j++) {
buffer.append(static_cast<uint8_t>(value & 0xFF));
value >>= 8;
}
}
#endif
return true;
}
template<> inline bool writeLittleEndian<uint8_t>(Vector<uint8_t>& buffer, std::span<const uint8_t> values)
{
buffer.append(values);
return true;
}
} // namespace StructuredCloneInternal
template<typename Derived>
class CloneSerializerBase : public CloneBase {
protected:
using ObjectPoolMap = HashMap<JSObject*, uint32_t>;
using StringConstantPool = HashMap<RefPtr<AtomStringImpl>, uint32_t, IdentifierRepHash>;
CloneSerializerBase(JSGlobalObject* lexicalGlobalObject, Vector<uint8_t>& buffer)
: CloneBase(lexicalGlobalObject)
, m_buffer(buffer)
{
}
template<typename T> requires std::is_enum_v<T>
void write(T tag)
{
if constexpr (std::is_same_v<T, SerializationTag>)
SERIALIZE_TRACE("serialize ", tag);
StructuredCloneInternal::writeLittleEndian<uint8_t>(m_buffer, static_cast<uint8_t>(tag));
}
void write(bool b) { write(static_cast<uint8_t>(b)); }
void write(uint8_t c) { StructuredCloneInternal::writeLittleEndian(m_buffer, c); }
void write(uint16_t i) { StructuredCloneInternal::writeLittleEndian(m_buffer, i); }
void write(uint32_t i) { StructuredCloneInternal::writeLittleEndian(m_buffer, i); }
void write(int32_t i) { StructuredCloneInternal::writeLittleEndian(m_buffer, i); }
void write(uint64_t i) { StructuredCloneInternal::writeLittleEndian(m_buffer, i); }
void write(double d)
{
StructuredCloneInternal::writeLittleEndian(m_buffer, std::bit_cast<int64_t>(d));
}
template<class T>
void writeConstantPoolIndex(const T& constantPool, unsigned i)
{
ASSERT(i < constantPool.size());
if (constantPool.size() <= 0xFF)
write(static_cast<uint8_t>(i));
else if (constantPool.size() <= 0xFFFF)
write(static_cast<uint16_t>(i));
else
write(static_cast<uint32_t>(i));
}
void writeStringIndex(unsigned i)
{
writeConstantPoolIndex(m_constantPool, i);
}
void writeObjectIndex(unsigned i)
{
writeConstantPoolIndex(m_objectPoolMap, i);
}
template<SerializationTag tag1, SerializationTag tag2 = ErrorTag, SerializationTag tag3 = ErrorTag>
bool writeObjectReferenceIfDupe(JSObject* object)
{
static_assert(canBeAddedToObjectPool(tag1)
&& (canBeAddedToObjectPool(tag2) || tag2 == ErrorTag)
&& (canBeAddedToObjectPool(tag3) || tag3 == ErrorTag));
// Record object for graph reconstruction
auto found = m_objectPoolMap.find(object);
// Handle duplicate references
if (found != m_objectPoolMap.end()) {
write(ObjectReferenceTag);
ASSERT(found->value < m_objectPoolMap.size());
writeObjectIndex(found->value);
return true; // is dupe.
}
return false; // not dupe.
}
template<SerializationTag tag1, SerializationTag tag2 = ErrorTag, SerializationTag tag3 = ErrorTag>
bool addToObjectPool(JSObject* object)
{
static_assert(canBeAddedToObjectPool(tag1)
&& (canBeAddedToObjectPool(tag2) || tag2 == ErrorTag)
&& (canBeAddedToObjectPool(tag3) || tag3 == ErrorTag));
m_objectPoolMap.add(object, m_objectPoolMap.size());
m_objectPool.appendWithCrashOnOverflow(object);
if constexpr (tag2 == ErrorTag)
appendObjectPoolTag(tag1);
return true; // new object added.
}
template<SerializationTag tag1, SerializationTag tag2 = ErrorTag, SerializationTag tag3 = ErrorTag>
bool addToObjectPoolIfNotDupe(JSObject* object)
{
static_assert(canBeAddedToObjectPool(tag1)
&& (canBeAddedToObjectPool(tag2) || tag2 == ErrorTag)
&& (canBeAddedToObjectPool(tag3) || tag3 == ErrorTag));
if (writeObjectReferenceIfDupe<tag1, tag2, tag3>(object))
return false; // new object NOT added. It's a dupe.
addToObjectPool<tag1, tag2, tag3>(object);
return true; // new object added.
}
void write(const AtomString& ident)
{
const String& str = ident.string();
StringConstantPool::AddResult addResult = m_constantPool.add(ident.impl(), m_constantPool.size());
if (!addResult.isNewEntry) {
write(StringPoolTag);
writeStringIndex(addResult.iterator->value);
return;
}
unsigned length = str.length();
// Guard against overflow
if (length > (std::numeric_limits<uint32_t>::max() - sizeof(uint32_t)) / sizeof(char16_t)) {
fail();
return;
}
if (str.is8Bit())
StructuredCloneInternal::writeLittleEndian<uint32_t>(m_buffer, length | StringDataIs8BitFlag);
else
StructuredCloneInternal::writeLittleEndian<uint32_t>(m_buffer, length);
if (!length)
return;
if (str.is8Bit()) {
if (!StructuredCloneInternal::writeLittleEndian(m_buffer, str.span8()))
fail();
return;
}
if (!StructuredCloneInternal::writeLittleEndian(m_buffer, str.span16()))
fail();
}
void write(const String& str)
{
if (str.isNull())
write(emptyAtom());
else
write(AtomString(str));
}
void writeNullableString(const String& str)
{
bool isNull = str.isNull();
write(isNull);
if (!isNull)
write(AtomString(str));
}
void dumpString(const String& string)
{
if (string.isEmpty())
write(EmptyStringTag);
else {
write(StringTag);
write(string);
}
}
void dumpStringObject(const String& string)
{
if (string.isEmpty()) {
appendObjectPoolTag(EmptyStringObjectTag);
write(EmptyStringObjectTag);
} else {
appendObjectPoolTag(StringObjectTag);
write(StringObjectTag);
write(string);
}
}
void dumpBigIntData(JSValue value)
{
ASSERT(value.isBigInt());
#if USE(BIGINT32)
if (value.isBigInt32()) {
dumpBigInt32Data(value.bigInt32AsInt32());
return;
}
#endif
dumpHeapBigIntData(downcast<JSBigInt>(value));
}
#if USE(BIGINT32)
void dumpBigInt32Data(int32_t integer)
{
write(integer < 0);
if (!integer) {
write(static_cast<uint32_t>(0)); // Length-in-uint64_t
return;
}
write(static_cast<uint32_t>(1)); // Length-in-uint64_t
int64_t value = static_cast<int64_t>(integer);
if (value < 0)
value = -value;
write(static_cast<uint64_t>(value));
}
#endif
void dumpHeapBigIntData(JSBigInt* bigInt)
{
write(bigInt->sign());
if constexpr (sizeof(JSBigInt::Digit) == sizeof(uint64_t)) {
write(static_cast<uint32_t>(bigInt->length()));
for (unsigned index = 0; index < bigInt->length(); ++index)
write(static_cast<uint64_t>(bigInt->digit(index)));
} else {
ASSERT(sizeof(JSBigInt::Digit) == sizeof(uint32_t));
uint32_t numberOfUint64Elements = bigInt->length() / 2;
if (bigInt->length() & 0x1)
++numberOfUint64Elements;
write(numberOfUint64Elements);
uint64_t value = 0;
for (unsigned index = 0; index < bigInt->length(); ++index) {
if (!(index & 0x1))
value = bigInt->digit(index);
else {
value = (static_cast<uint64_t>(bigInt->digit(index)) << 32) | value;
write(static_cast<uint64_t>(value));
value = 0;
}
}
if (bigInt->length() & 0x1)
write(static_cast<uint64_t>(value));
}
}
ALWAYS_INLINE bool dumpIfTerminal(JSValue value, SerializationReturnCode& code)
{
// Note: This can't be a requirement on the template as, in the common usage,
// Derived will still be an incomplete type
static_assert(StructuredCloneSerializerHandler<Derived>,
"Derived class must satisfy StructuredCloneSerializerHandler");
if (value.isNull()) {
write(NullTag);
return true;
}
if (value.isUndefined()) {
write(UndefinedTag);
return true;
}
if (value.isInt32()) {
int32_t i = value.asInt32();
if (!i)
write(ZeroTag);
else if (i == 1)
write(OneTag);
else {
write(IntTag);
write(static_cast<uint32_t>(i));
}
return true;
}
if (value.isNumber()) {
write(DoubleTag);
write(value.asDouble());
return true;
}
if (value.isBoolean()) {
write(value.isTrue() ? TrueTag : FalseTag);
return true;
}
if (value.isString()) {
dumpString(asString(value)->value(m_lexicalGlobalObject));
return true;
}
if (value.isBigInt()) {
write(BigIntTag);
dumpBigIntData(value);
return true;
}
if (!value.isObject()) {
ASSERT(value.isSymbol());
code = SerializationReturnCode::DataCloneError;
return true;
}
auto* obj = asObject(value);
if (auto* dateObject = dynamicDowncast<DateInstance>(obj)) {
write(DateTag);
write(dateObject->internalNumber());
return true;
}
if (auto* regExp = dynamicDowncast<RegExpObject>(obj)) {
write(RegExpTag);
write(regExp->regExp()->pattern());
write(String::fromLatin1(JSC::Yarr::flagsString(regExp->regExp()->flags()).data()));
return true;
}
if (auto* booleanObject = dynamicDowncast<BooleanObject>(obj)) {
if (!addToObjectPoolIfNotDupe<TrueObjectTag, FalseObjectTag>(booleanObject))
return true;
auto tag = booleanObject->internalValue().toBoolean(m_lexicalGlobalObject) ? TrueObjectTag : FalseObjectTag;
write(tag);
appendObjectPoolTag(tag);
return true;
}
if (auto* stringObject = dynamicDowncast<StringObject>(obj)) {
if (!addToObjectPoolIfNotDupe<EmptyStringObjectTag, StringObjectTag>(stringObject))
return true;
auto str = asString(stringObject->internalValue())->value(m_lexicalGlobalObject);
dumpStringObject(str);
return true;
}
if (auto* numberObject = dynamicDowncast<NumberObject>(obj)) {
if (!addToObjectPoolIfNotDupe<NumberObjectTag>(numberObject))
return true;
write(NumberObjectTag);
write(numberObject->internalValue().asNumber());
return true;
}
if (auto* bigIntObject = dynamicDowncast<BigIntObject>(obj)) {
if (!addToObjectPoolIfNotDupe<BigIntObjectTag>(bigIntObject))
return true;
write(BigIntObjectTag);
JSValue bigIntValue = bigIntObject->internalValue();
ASSERT(bigIntValue.isBigInt());
dumpBigIntData(bigIntValue);
return true;
}
if (auto* errorInstance = dynamicDowncast<ErrorInstance>(obj)) {
auto errorInformation = extractErrorInformationFromErrorInstance(m_lexicalGlobalObject, *errorInstance);
if (!errorInformation)
return false;
write(ErrorInstanceTag);
write(static_cast<uint8_t>(errorNameToSerializableErrorType(errorInformation->errorTypeString)));
writeNullableString(errorInformation->message);
write(errorInformation->line);
write(errorInformation->column);
writeNullableString(errorInformation->sourceURL);
writeNullableString(errorInformation->stack);
writeNullableString(errorInformation->cause);
return true;
}
// The walker descends into JSArray/JSMap/JSSet; never let Derived claim
// them as terminals. Plain objects (JSFinalObject / ObjectPrototype) fall
// through to dumpDerivedTerminal so DOM types may opt in, with the walker
// catching anything Derived didn't claim.
if (is<JSArray>(*obj) || is<JSMap>(*obj) || is<JSSet>(*obj))
return false;
return static_cast<Derived*>(this)->dumpDerivedTerminal(obj, code);
}
void endObject()
{
write(TerminatorTag);
}
JSValue getProperty(JSObject* object, const Identifier& propertyName)
{
PropertySlot slot(object, PropertySlot::InternalMethodType::Get);
if (object->methodTable()->getOwnPropertySlot(object, m_lexicalGlobalObject, propertyName, slot))
return slot.getValue(m_lexicalGlobalObject, propertyName);
return JSValue();
}
SerializationReturnCode serialize(JSValue in)
{
VM& vm = m_lexicalGlobalObject->vm();
Vector<uint32_t, 16> indexStack;
Vector<uint32_t, 16> lengthStack;
Vector<PropertyNameArrayBuilder, 16> propertyStack;
Vector<JSObject*, 32> inputObjectStack;
Vector<JSMapIterator*, 4> mapIteratorStack;
Vector<JSSetIterator*, 4> setIteratorStack;
Vector<JSValue, 4> mapIteratorValueStack;
Vector<WalkerState, 16> stateStack;
WalkerState state = WalkerState::StateUnknown;
JSValue inValue = in;
auto scope = DECLARE_THROW_SCOPE(vm);
while (1) {
switch (state) {
arrayStartState:
case WalkerState::ArrayStartState: {
ASSERT(is<JSArray>(inValue));
if (inputObjectStack.size() > maximumFilterRecursion)
return SerializationReturnCode::StackOverflowError;
JSArray* inArray = asArray(inValue);
unsigned length = inArray->length();
if (!addToObjectPoolIfNotDupe<ArrayTag>(inArray))
break;
write(ArrayTag);
write(length);
inputObjectStack.append(inArray);
indexStack.append(0);
lengthStack.append(length);
}
arrayStartVisitIndexedMember:
[[fallthrough]];
case WalkerState::ArrayStartVisitIndexedMember: {
JSObject* array = inputObjectStack.last();
uint32_t index = indexStack.last();
if (index == lengthStack.last()) {
indexStack.removeLast();
lengthStack.removeLast();
write(TerminatorTag); // Terminate the indexed property section.
propertyStack.append(PropertyNameArrayBuilder(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude));
array->getOwnNonIndexPropertyNames(m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude);
if (scope.exception()) [[unlikely]]
return SerializationReturnCode::ExistingExceptionError;
if (propertyStack.last().size()) {
write(NonIndexPropertiesTag);
indexStack.append(0);
goto startVisitNamedMember;
}
propertyStack.removeLast();
endObject();
inputObjectStack.removeLast();
break;
}
inValue = array->getDirectIndex(m_lexicalGlobalObject, index);
if (scope.exception()) [[unlikely]]
return SerializationReturnCode::ExistingExceptionError;
if (!inValue) {
indexStack.last()++;
goto arrayStartVisitIndexedMember;
}
write(index);
auto terminalCode = SerializationReturnCode::SuccessfullyCompleted;
if (dumpIfTerminal(inValue, terminalCode)) {
if (terminalCode != SerializationReturnCode::SuccessfullyCompleted)
return terminalCode;
indexStack.last()++;
goto arrayStartVisitIndexedMember;
}
stateStack.append(WalkerState::ArrayEndVisitIndexedMember);
goto stateUnknown;
}
case WalkerState::ArrayEndVisitIndexedMember: {
indexStack.last()++;
goto arrayStartVisitIndexedMember;
}
case WalkerState::ArrayStartVisitNamedMember:
case WalkerState::ArrayEndVisitNamedMember:
RELEASE_ASSERT_NOT_REACHED();
objectStartState:
case WalkerState::ObjectStartState: {
ASSERT(inValue.isObject());
if (inputObjectStack.size() > maximumFilterRecursion)
return SerializationReturnCode::StackOverflowError;
JSObject* inObject = asObject(inValue);
if (!addToObjectPoolIfNotDupe<ObjectTag>(inObject))
break;
write(ObjectTag);
// At this point, all supported objects other than Object
// objects have been handled. If we reach this point and
// the input is not an Object object then we should throw
// a DataCloneError.
if (inObject->classInfo() != JSFinalObject::info() && inObject->classInfo() != ObjectPrototype::info())
return SerializationReturnCode::DataCloneError;
inputObjectStack.append(inObject);
indexStack.append(0);
propertyStack.append(PropertyNameArrayBuilder(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude));
inObject->methodTable()->getOwnPropertyNames(inObject, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude);
if (scope.exception()) [[unlikely]]
return SerializationReturnCode::ExistingExceptionError;
}
startVisitNamedMember:
[[fallthrough]];
case WalkerState::ObjectStartVisitNamedMember: {
JSObject* object = inputObjectStack.last();
uint32_t index = indexStack.last();
PropertyNameArrayBuilder& properties = propertyStack.last();
if (index == properties.size()) {
endObject();
inputObjectStack.removeLast();
indexStack.removeLast();
propertyStack.removeLast();
break;
}
inValue = getProperty(object, properties[index]);
if (scope.exception()) [[unlikely]]
return SerializationReturnCode::ExistingExceptionError;
if (!inValue) {
// Property was removed during serialisation
indexStack.last()++;
goto startVisitNamedMember;
}
write(properties[index].string());
if (scope.exception()) [[unlikely]]
return SerializationReturnCode::ExistingExceptionError;
auto terminalCode = SerializationReturnCode::SuccessfullyCompleted;
if (!dumpIfTerminal(inValue, terminalCode)) {
stateStack.append(WalkerState::ObjectEndVisitNamedMember);
goto stateUnknown;
}
if (terminalCode != SerializationReturnCode::SuccessfullyCompleted)
return terminalCode;
[[fallthrough]];
}
case WalkerState::ObjectEndVisitNamedMember: {
if (scope.exception()) [[unlikely]]
return SerializationReturnCode::ExistingExceptionError;
indexStack.last()++;
goto startVisitNamedMember;
}
mapStartState: {
ASSERT(inValue.isObject());
if (inputObjectStack.size() > maximumFilterRecursion)
return SerializationReturnCode::StackOverflowError;
JSMap* inMap = downcast<JSMap>(inValue);
if (!addToObjectPoolIfNotDupe<MapObjectTag>(inMap))
break;
write(MapObjectTag);
JSMapIterator* iterator = JSMapIterator::create(vm, m_lexicalGlobalObject->mapIteratorStructure(), inMap, IterationKind::Entries);
m_keepAliveBuffer.appendWithCrashOnOverflow(iterator);
mapIteratorStack.append(iterator);
inputObjectStack.append(inMap);
goto mapDataStartVisitEntry;
}
mapDataStartVisitEntry:
case WalkerState::MapDataStartVisitEntry: {
JSMapIterator* iterator = mapIteratorStack.last();
JSValue key, value;
if (!iterator->nextKeyValue(m_lexicalGlobalObject, key, value)) {
mapIteratorStack.removeLast();
JSObject* object = inputObjectStack.last();
ASSERT(is<JSMap>(*object));
propertyStack.append(PropertyNameArrayBuilder(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude));
object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude);
if (scope.exception()) [[unlikely]]
return SerializationReturnCode::ExistingExceptionError;
write(NonMapPropertiesTag);
indexStack.append(0);
goto startVisitNamedMember;
}
inValue = key;
m_keepAliveBuffer.appendWithCrashOnOverflow(value);
mapIteratorValueStack.append(value);
stateStack.append(WalkerState::MapDataEndVisitKey);
goto stateUnknown;
}
case WalkerState::MapDataEndVisitKey: {
inValue = mapIteratorValueStack.last();
mapIteratorValueStack.removeLast();
stateStack.append(WalkerState::MapDataEndVisitValue);
goto stateUnknown;
}
case WalkerState::MapDataEndVisitValue: {
goto mapDataStartVisitEntry;
}
setStartState: {
ASSERT(inValue.isObject());
if (inputObjectStack.size() > maximumFilterRecursion)
return SerializationReturnCode::StackOverflowError;
JSSet* inSet = downcast<JSSet>(inValue);
if (!addToObjectPoolIfNotDupe<SetObjectTag>(inSet))
break;
write(SetObjectTag);
JSSetIterator* iterator = JSSetIterator::create(vm, m_lexicalGlobalObject->setIteratorStructure(), inSet, IterationKind::Keys);
m_keepAliveBuffer.appendWithCrashOnOverflow(iterator);
setIteratorStack.append(iterator);
inputObjectStack.append(inSet);
goto setDataStartVisitEntry;
}
setDataStartVisitEntry:
case WalkerState::SetDataStartVisitEntry: {
JSSetIterator* iterator = setIteratorStack.last();
JSValue key;
if (!iterator->next(m_lexicalGlobalObject, key)) {
setIteratorStack.removeLast();
JSObject* object = inputObjectStack.last();
ASSERT(is<JSSet>(*object));
propertyStack.append(PropertyNameArrayBuilder(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude));
object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude);
if (scope.exception()) [[unlikely]]
return SerializationReturnCode::ExistingExceptionError;
write(NonSetPropertiesTag);
indexStack.append(0);
goto startVisitNamedMember;
}
inValue = key;
stateStack.append(WalkerState::SetDataEndVisitKey);
goto stateUnknown;
}
case WalkerState::SetDataEndVisitKey: {
goto setDataStartVisitEntry;
}
stateUnknown:
case WalkerState::StateUnknown: {
auto terminalCode = SerializationReturnCode::SuccessfullyCompleted;
if (dumpIfTerminal(inValue, terminalCode)) {
if (terminalCode != SerializationReturnCode::SuccessfullyCompleted)
return terminalCode;
break;
}
if (is<JSArray>(inValue))
goto arrayStartState;
if (is<JSMap>(inValue))
goto mapStartState;
if (is<JSSet>(inValue))
goto setStartState;
goto objectStartState;
}
}
if (stateStack.isEmpty())
break;
state = stateStack.last();
stateStack.removeLast();
}
if (m_failed)
return SerializationReturnCode::UnspecifiedError;
return SerializationReturnCode::SuccessfullyCompleted;
}
Vector<uint8_t>& m_buffer;
StringConstantPool m_constantPool;
ObjectPoolMap m_objectPoolMap;
};
} // namespace JSC