| /* |
| * 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 |