SerializationReturnCode CloneSerializer::serialize()

in src/bun.js/bindings/webcore/SerializedScriptValue.cpp [2436:2693]


SerializationReturnCode CloneSerializer::serialize(JSValue in)
{
    VM& vm = m_lexicalGlobalObject->vm();
    Vector<uint32_t, 16> indexStack;
    Vector<uint32_t, 16> lengthStack;
    Vector<PropertyNameArray, 16> propertyStack;
    Vector<JSObject*, 32> inputObjectStack;
    Vector<JSMapIterator*, 4> mapIteratorStack;
    Vector<JSSetIterator*, 4> setIteratorStack;
    Vector<JSValue, 4> mapIteratorValueStack;
    Vector<WalkerState, 16> stateStack;
    WalkerState state = StateUnknown;
    JSValue inValue = in;
    auto scope = DECLARE_THROW_SCOPE(vm);
    while (1) {
        switch (state) {
        arrayStartState:
        case ArrayStartState: {
            ASSERT(isArray(inValue));
            if (inputObjectStack.size() > maximumFilterRecursion)
                return SerializationReturnCode::StackOverflowError;

            JSArray* inArray = asArray(inValue);
            unsigned length = inArray->length();
            if (!startArray(inArray))
                break;
            inputObjectStack.append(inArray);
            indexStack.append(0);
            lengthStack.append(length);
        }
        arrayStartVisitMember:
            FALLTHROUGH;
        case ArrayStartVisitMember: {
            JSObject* array = inputObjectStack.last();
            uint32_t index = indexStack.last();
            if (index == lengthStack.last()) {
                indexStack.removeLast();
                lengthStack.removeLast();

                propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude));
                array->getOwnNonIndexPropertyNames(m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude);
                if (UNLIKELY(scope.exception()))
                    return SerializationReturnCode::ExistingExceptionError;
                if (propertyStack.last().size()) {
                    write(NonIndexPropertiesTag);
                    indexStack.append(0);
                    goto objectStartVisitMember;
                }
                propertyStack.removeLast();

                endObject();
                inputObjectStack.removeLast();
                break;
            }
            inValue = array->getDirectIndex(m_lexicalGlobalObject, index);
            if (UNLIKELY(scope.exception()))
                return SerializationReturnCode::ExistingExceptionError;
            if (!inValue) {
                indexStack.last()++;
                goto arrayStartVisitMember;
            }

            write(index);
            auto terminalCode = SerializationReturnCode::SuccessfullyCompleted;
            if (dumpIfTerminal(inValue, terminalCode)) {
                if (terminalCode != SerializationReturnCode::SuccessfullyCompleted)
                    return terminalCode;
                indexStack.last()++;
                goto arrayStartVisitMember;
            }
            stateStack.append(ArrayEndVisitMember);
            goto stateUnknown;
        }
        case ArrayEndVisitMember: {
            indexStack.last()++;
            goto arrayStartVisitMember;
        }
        objectStartState:
        case ObjectStartState: {
            ASSERT(inValue.isObject());
            if (inputObjectStack.size() > maximumFilterRecursion)
                return SerializationReturnCode::StackOverflowError;
            JSObject* inObject = asObject(inValue);
            if (!startObject(inObject))
                break;
            // 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())
                return SerializationReturnCode::DataCloneError;
            inputObjectStack.append(inObject);
            indexStack.append(0);
            propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude));
            inObject->methodTable()->getOwnPropertyNames(inObject, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude);
            if (UNLIKELY(scope.exception()))
                return SerializationReturnCode::ExistingExceptionError;
        }
        objectStartVisitMember:
            FALLTHROUGH;
        case ObjectStartVisitMember: {
            JSObject* object = inputObjectStack.last();
            uint32_t index = indexStack.last();
            PropertyNameArray& properties = propertyStack.last();
            if (index == properties.size()) {
                endObject();
                inputObjectStack.removeLast();
                indexStack.removeLast();
                propertyStack.removeLast();
                break;
            }
            inValue = getProperty(object, properties[index]);
            if (UNLIKELY(scope.exception()))
                return SerializationReturnCode::ExistingExceptionError;

            if (!inValue) {
                // Property was removed during serialisation
                indexStack.last()++;
                goto objectStartVisitMember;
            }
            write(properties[index]);

            if (UNLIKELY(scope.exception()))
                return SerializationReturnCode::ExistingExceptionError;

            auto terminalCode = SerializationReturnCode::SuccessfullyCompleted;
            if (!dumpIfTerminal(inValue, terminalCode)) {
                stateStack.append(ObjectEndVisitMember);
                goto stateUnknown;
            }
            if (terminalCode != SerializationReturnCode::SuccessfullyCompleted)
                return terminalCode;
            FALLTHROUGH;
        }
        case ObjectEndVisitMember: {
            if (UNLIKELY(scope.exception()))
                return SerializationReturnCode::ExistingExceptionError;

            indexStack.last()++;
            goto objectStartVisitMember;
        }
        mapStartState : {
            ASSERT(inValue.isObject());
            if (inputObjectStack.size() > maximumFilterRecursion)
                return SerializationReturnCode::StackOverflowError;
            JSMap* inMap = jsCast<JSMap*>(inValue);
            if (!startMap(inMap))
                break;
            JSMapIterator* iterator = JSMapIterator::create(m_lexicalGlobalObject, m_lexicalGlobalObject->mapIteratorStructure(), inMap, IterationKind::Entries);
            if (UNLIKELY(scope.exception()))
                return SerializationReturnCode::ExistingExceptionError;
            m_gcBuffer.appendWithCrashOnOverflow(inMap);
            m_gcBuffer.appendWithCrashOnOverflow(iterator);
            mapIteratorStack.append(iterator);
            inputObjectStack.append(inMap);
            goto mapDataStartVisitEntry;
        }
        mapDataStartVisitEntry:
        case MapDataStartVisitEntry: {
            JSMapIterator* iterator = mapIteratorStack.last();
            JSValue key, value;
            if (!iterator->nextKeyValue(m_lexicalGlobalObject, key, value)) {
                mapIteratorStack.removeLast();
                JSObject* object = inputObjectStack.last();
                ASSERT(jsDynamicCast<JSMap*>(object));
                propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude));
                object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude);
                if (UNLIKELY(scope.exception()))
                    return SerializationReturnCode::ExistingExceptionError;
                write(NonMapPropertiesTag);
                indexStack.append(0);
                goto objectStartVisitMember;
            }
            inValue = key;
            m_gcBuffer.appendWithCrashOnOverflow(value);
            mapIteratorValueStack.append(value);
            stateStack.append(MapDataEndVisitKey);
            goto stateUnknown;
        }
        case MapDataEndVisitKey: {
            inValue = mapIteratorValueStack.last();
            mapIteratorValueStack.removeLast();
            stateStack.append(MapDataEndVisitValue);
            goto stateUnknown;
        }
        case MapDataEndVisitValue: {
            goto mapDataStartVisitEntry;
        }

        setStartState : {
            ASSERT(inValue.isObject());
            if (inputObjectStack.size() > maximumFilterRecursion)
                return SerializationReturnCode::StackOverflowError;
            JSSet* inSet = jsCast<JSSet*>(inValue);
            if (!startSet(inSet))
                break;
            JSSetIterator* iterator = JSSetIterator::create(m_lexicalGlobalObject, m_lexicalGlobalObject->setIteratorStructure(), inSet, IterationKind::Keys);
            if (UNLIKELY(scope.exception()))
                return SerializationReturnCode::ExistingExceptionError;
            m_gcBuffer.appendWithCrashOnOverflow(inSet);
            m_gcBuffer.appendWithCrashOnOverflow(iterator);
            setIteratorStack.append(iterator);
            inputObjectStack.append(inSet);
            goto setDataStartVisitEntry;
        }
        setDataStartVisitEntry:
        case SetDataStartVisitEntry: {
            JSSetIterator* iterator = setIteratorStack.last();
            JSValue key;
            if (!iterator->next(m_lexicalGlobalObject, key)) {
                setIteratorStack.removeLast();
                JSObject* object = inputObjectStack.last();
                ASSERT(jsDynamicCast<JSSet*>(object));
                propertyStack.append(PropertyNameArray(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude));
                object->methodTable()->getOwnPropertyNames(object, m_lexicalGlobalObject, propertyStack.last(), DontEnumPropertiesMode::Exclude);
                if (UNLIKELY(scope.exception()))
                    return SerializationReturnCode::ExistingExceptionError;
                write(NonSetPropertiesTag);
                indexStack.append(0);
                goto objectStartVisitMember;
            }
            inValue = key;
            stateStack.append(SetDataEndVisitKey);
            goto stateUnknown;
        }
        case SetDataEndVisitKey: {
            goto setDataStartVisitEntry;
        }

        stateUnknown:
        case StateUnknown: {
            auto terminalCode = SerializationReturnCode::SuccessfullyCompleted;
            if (dumpIfTerminal(inValue, terminalCode)) {
                if (terminalCode != SerializationReturnCode::SuccessfullyCompleted)
                    return terminalCode;
                break;
            }

            if (isArray(inValue))
                goto arrayStartState;
            if (isMap(inValue))
                goto mapStartState;
            if (isSet(inValue))
                goto setStartState;
            goto objectStartState;
        }
        }
        if (stateStack.isEmpty())
            break;

        state = stateStack.last();
        stateStack.removeLast();
    }
    if (m_failed)
        return SerializationReturnCode::UnspecifiedError;

    return SerializationReturnCode::SuccessfullyCompleted;
}