/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ecmascript/serializer/value_serializer.h"

#include "ecmascript/base/config.h"
#include "ecmascript/checkpoint/thread_state_transition.h"
#include "ecmascript/base/array_helper.h"

namespace panda::ecmascript {

bool ValueSerializer::CheckObjectCanSerialize(TaggedObject *object, bool &findSharedObject)
{
    JSType type = object->GetClass()->GetObjectType();
    if (IsInternalJSType(type)) {
        return true;
    }
    switch (type) {
        case JSType::JS_PRIMITIVE_REF:
        case JSType::JS_ERROR:
        case JSType::JS_EVAL_ERROR:
        case JSType::JS_RANGE_ERROR:
        case JSType::JS_REFERENCE_ERROR:
        case JSType::JS_TYPE_ERROR:
        case JSType::JS_AGGREGATE_ERROR:
        case JSType::JS_URI_ERROR:
        case JSType::JS_SYNTAX_ERROR:
        case JSType::JS_OOM_ERROR:
        case JSType::JS_TERMINATION_ERROR:
        case JSType::JS_DATE:
        case JSType::JS_ARRAY:
        case JSType::JS_MAP:
        case JSType::JS_SET:
        case JSType::JS_REG_EXP:
        case JSType::JS_INT8_ARRAY:
        case JSType::JS_UINT8_ARRAY:
        case JSType::JS_UINT8_CLAMPED_ARRAY:
        case JSType::JS_INT16_ARRAY:
        case JSType::JS_UINT16_ARRAY:
        case JSType::JS_INT32_ARRAY:
        case JSType::JS_UINT32_ARRAY:
        case JSType::JS_FLOAT32_ARRAY:
        case JSType::JS_FLOAT64_ARRAY:
        case JSType::JS_BIGINT64_ARRAY:
        case JSType::JS_BIGUINT64_ARRAY:
        case JSType::JS_ARRAY_BUFFER:
        case JSType::JS_SHARED_ARRAY_BUFFER:
        case JSType::JS_API_TREE_SET:
        case JSType::LINE_STRING:
        case JSType::TREE_STRING:
        case JSType::SLICED_STRING:
        case JSType::CACHED_EXTERNAL_STRING:
        case JSType::JS_OBJECT:
        case JSType::JS_WRAPPED_NAPI_OBJECT:
        case JSType::JS_ASYNC_FUNCTION:  // means CONCURRENT_FUNCTION
            return true;
        case JSType::JS_API_BITVECTOR:
        case JSType::JS_SHARED_SET:
        case JSType::JS_SHARED_MAP:
        case JSType::JS_SENDABLE_ARRAY_BUFFER:
        case JSType::JS_SHARED_ARRAY:
        case JSType::JS_SHARED_INT8_ARRAY:
        case JSType::JS_SHARED_UINT8_ARRAY:
        case JSType::JS_SHARED_UINT8_CLAMPED_ARRAY:
        case JSType::JS_SHARED_INT16_ARRAY:
        case JSType::JS_SHARED_UINT16_ARRAY:
        case JSType::JS_SHARED_INT32_ARRAY:
        case JSType::JS_SHARED_UINT32_ARRAY:
        case JSType::JS_SHARED_FLOAT32_ARRAY:
        case JSType::JS_SHARED_FLOAT64_ARRAY:
        case JSType::JS_SHARED_BIGINT64_ARRAY:
        case JSType::JS_SHARED_BIGUINT64_ARRAY:
        case JSType::JS_SHARED_OBJECT:
        case JSType::JS_SHARED_FUNCTION:
        case JSType::JS_SHARED_ASYNC_FUNCTION: {
            if (serializeSharedEvent_ > 0) {
                return true;
            }
            if (defaultCloneShared_ || cloneSharedSet_.find(ToUintPtr(object)) != cloneSharedSet_.end()) {
                findSharedObject = true;
                serializeSharedEvent_++;
            }
            return true;
        }
        default:
            break;
    }
    std::string errorMessage = "Serialize don't support object type: " + ConvertToStdString(JSHClass::DumpJSType(type));
    PrintAndRecordErrorMessage(errorMessage);
    return false;
}

bool ValueSerializer::WriteValue(JSThread *thread,
                                 const JSHandle<JSTaggedValue> &value,
                                 const JSHandle<JSTaggedValue> &transfer,
                                 const JSHandle<JSTaggedValue> &cloneList)
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "ValueSerializer::WriteValue", "");
    ASSERT(!value->IsWeak());
    if (!defaultTransfer_ && !PrepareTransfer(thread, transfer)) {
        std::string errorMessage = "Serialize PrepareTransfer fail";
        PrintAndRecordErrorMessage(errorMessage);
        data_->SetIncompleteData(true);
        return false;
    }
    if (!defaultCloneShared_ && !PrepareClone(thread, cloneList)) {
        std::string errorMessage = "Serialize PrepareClone fail";
        PrintAndRecordErrorMessage(errorMessage);
        data_->SetIncompleteData(true);
        return false;
    }
    SerializeJSTaggedValue(value.GetTaggedValue());
    // ThreadNativeScope may trigger moving gc, so PushSerializationRoot must do before native state.
    // Push share root object to runtime map
    uint32_t index = data_->GetDataIndex();
    bool chunkEmpty = sharedObjChunk_->Empty();
    if (!chunkEmpty) {
        index = Runtime::GetInstance()->PushSerializationRoot(thread_, std::move(sharedObjChunk_));
    }
    {
        ThreadNativeScope nativeScope(thread);
        for (auto &entry : detachCallbackInfo_) {
            auto info = entry.second;
            DetachFunc detachNative = reinterpret_cast<DetachFunc>(info->detachFunc);
            if (detachNative == nullptr || entry.first < 0) {
                std::string errorMessage = "Serialize don't support NativeBindingObject detachNative is nullptr";
                PrintAndRecordErrorMessage(errorMessage);
                notSupport_ = true;
                break;
            }
            void *buffer = detachNative(info->env, info->nativeValue, info->hint, info->detachData);
            if (info->detachedFinalizer != nullptr) {
                data_->AddNativeBindingDetachInfo(info, buffer);
            }
            data_->EmitU64(reinterpret_cast<uint64_t>(buffer), static_cast<size_t>(entry.first));
        }
    }
    if (notSupport_) {
        LOG_ECMA(ERROR) << "ValueSerialize: serialize data is incomplete";
        data_->SetIncompleteData(true);
        if (!chunkEmpty) {
            // If notSupport, serializeRoot should be removed.
            Runtime::GetInstance()->RemoveSerializationRoot(thread_, index);
        }
        return false;
    }
    if (!chunkEmpty) {
        [[maybe_unused]] EcmaHandleScope scope(thread_);
        JSMutableHandle<JSTaggedValue> strHandle(thread_, JSTaggedValue::Undefined());
        SerializationChunk *chunk = Runtime::GetInstance()->GetSerializeRootMapValue(thread_, index);
        size_t size = chunk->Size();
        for (size_t i = 0; i < size; ++i) {
            JSTaggedValue val(chunk->Get(i));
            if (UNLIKELY(val.IsTreeString())) {
                strHandle.Update(val);
                JSHandle<JSTaggedValue> flattenStr(JSTaggedValue::PublishSharedValue(thread, strHandle));
                chunk->Set(i, flattenStr.GetTaggedType());
            }
        }
        data_->SetDataIndex(index);
    }
    size_t maxSerializerSize = GetMaxJSSerializerSize(vm_);
    if (data_->Size() > maxSerializerSize) {
        std::ostringstream errorMessage;
        errorMessage << "The serialization data size has exceed limit Size, current size is: " << data_->Size()
                     << " max size is: " << maxSerializerSize;
        PrintAndRecordErrorMessage(errorMessage.str());
        return false;
    }
    return true;
}

bool ValueSerializer::SerializeSharedObj([[maybe_unused]] TaggedObject *object)
{
    [[maybe_unused]] Region *region = Region::ObjectAddressToRange(object);
    JSHClass *objClass = object->GetClass();
    if (g_isEnableCMCGC) {
        if (objClass->IsString() || objClass->IsMethod() || objClass->IsJSSharedFunction() ||
            objClass->IsJSSharedAsyncFunction() ||
            // add shared read only
            (serializeSharedEvent_ == 0 && object->IsInSharedHeap())) {
            SerializeSharedObject(object);
            return true;
        }
        return false;
    } else {
        if (objClass->IsString() || objClass->IsMethod() || objClass->IsJSSharedFunction() ||
            objClass->IsJSSharedAsyncFunction() ||
            region->InSharedReadOnlySpace() || (serializeSharedEvent_ == 0 && region->InSharedHeap())) {
            SerializeSharedObject(object);
            return true;
        }
        return false;
    }
}

void ValueSerializer::SerializeObjectImpl(TaggedObject *object, bool isWeak)
{
    if (notSupport_) {
        return;
    }
    bool cloneSharedObject = false;
    if (!CheckObjectCanSerialize(object, cloneSharedObject)) {
        notSupport_ = true;
        return;
    }
    if (isWeak) {
        data_->WriteEncodeFlag(EncodeFlag::WEAK);
    }
    if (SerializeReference(object) || SerializeRootObject(object)) {
        return;
    }

    if (SerializeSharedObj(object)) {
        return;
    }
    JSHClass *objClass = object->GetClass();
    if (objClass->IsNativeBindingObject()) {
        SerializeNativeBindingObject(object);
        return;
    }
    if (objClass->IsJSError()) {
        SerializeJSError(object);
        return;
    }
    bool arrayBufferDeferDetach = false;
    JSTaggedValue trackInfo;
    JSTaggedType hashfield = JSTaggedValue::VALUE_ZERO;
    JSTaggedValue nativePointerField = JSTaggedValue(JSTaggedValue::VALUE_ZERO);
    JSType type = objClass->GetObjectType();
    SourceTextModule::MutableFields moduleMutableFields;
    // serialize prologue
    switch (type) {
        case JSType::JS_ARRAY_BUFFER: {
            supportJSNativePointer_ = true;
            arrayBufferDeferDetach = SerializeJSArrayBufferPrologue(object);
            break;
        }
        case JSType::JS_SHARED_ARRAY_BUFFER: {
            supportJSNativePointer_ = true;
            SerializeJSSharedArrayBufferPrologue(object);
            break;
        }
        case JSType::JS_SENDABLE_ARRAY_BUFFER: {
            supportJSNativePointer_ = true;
            SerializeJSSendableArrayBufferPrologue(object);
            break;
        }
        case JSType::JS_ARRAY: {
            JSArray *array = reinterpret_cast<JSArray *>(object);
            trackInfo = array->GetTrackInfo(thread_);
            array->SetTrackInfo(thread_, JSTaggedValue::Undefined());
            break;
        }
        case JSType::JS_REG_EXP: {
            supportJSNativePointer_ = true;
            SerializeJSRegExpPrologue(reinterpret_cast<JSRegExp *>(object));
            break;
        }
        case JSType::JS_OBJECT: {
            hashfield = Barriers::GetTaggedValue(thread_, object, JSObject::HASH_OFFSET);
            Barriers::SetPrimitive<JSTaggedType>(object, JSObject::HASH_OFFSET, JSTaggedValue::VALUE_ZERO);
            break;
        }
        case JSType::JS_WRAPPED_NAPI_OBJECT: {
            hashfield = Barriers::GetTaggedValue(thread_, object, JSObject::HASH_OFFSET);
            Barriers::SetPrimitive<JSTaggedType>(object, JSObject::HASH_OFFSET, JSTaggedValue::VALUE_ZERO);
            JSWrappedNapiObject *wrappedObj = JSWrappedNapiObject::Cast(object);
            nativePointerField = wrappedObj->GetNativePointers(thread_);
            wrappedObj->SetNativePointers(thread_, JSTaggedValue(JSTaggedValue::VALUE_ZERO));
            break;
        }
        case JSType::SOURCE_TEXT_MODULE_RECORD: {
            if (!SerializeModuleCNativeObjects(object)) {
                notSupport_ = true;
                return;
            }
            break;
        }
        default:
            break;
    }

    // serialize object here
    SerializeTaggedObject<SerializeType::VALUE_SERIALIZE>(object);

    // serialize epilogue
    switch (type) {
        case JSType::JS_ARRAY_BUFFER:
        case JSType::JS_SHARED_ARRAY_BUFFER:
        case JSType::JS_SENDABLE_ARRAY_BUFFER:
        case JSType::JS_REG_EXP:
            // JSNativePointer supports serialization only during serialize JSArrayBuffer,
            // JSSharedArrayBuffer and JSRegExp
            supportJSNativePointer_ = false;
            break;
        case JSType::JS_ARRAY: {
            JSArray *array = reinterpret_cast<JSArray *>(object);
            array->SetTrackInfo(thread_, trackInfo);
            break;
        }
        case JSType::JS_OBJECT: {
            if (JSTaggedValue(hashfield).IsHeapObject()) {
                Barriers::SetObject<true>(thread_, object, JSObject::HASH_OFFSET, hashfield);
            } else {
                Barriers::SetPrimitive<JSTaggedType>(object, JSObject::HASH_OFFSET, hashfield);
            }
            break;
        }
        case JSType::JS_WRAPPED_NAPI_OBJECT: {
            if (JSTaggedValue(hashfield).IsHeapObject()) {
                Barriers::SetObject<true>(thread_, object, JSObject::HASH_OFFSET, hashfield);
            } else {
                Barriers::SetPrimitive<JSTaggedType>(object, JSObject::HASH_OFFSET, hashfield);
            }
            JSWrappedNapiObject::Cast(object)->SetNativePointers(thread_, nativePointerField);
            break;
        }
        default:
            break;
    }
    if (cloneSharedObject) {
        serializeSharedEvent_--;
    }
    if (arrayBufferDeferDetach) {
        ASSERT(objClass->IsArrayBuffer());
        JSArrayBuffer *arrayBuffer = reinterpret_cast<JSArrayBuffer *>(object);
        arrayBuffer->Detach(thread_, arrayBuffer->GetWithNativeAreaAllocator(), true);
    }
}

void ValueSerializer::SerializeJSError(TaggedObject *object)
{
    [[maybe_unused]] EcmaHandleScope scope(thread_);
    data_->WriteEncodeFlag(EncodeFlag::JS_ERROR);
    JSType type = object->GetClass()->GetObjectType();
    ASSERT(type >= JSType::JS_ERROR_FIRST && type <= JSType::JS_ERROR_LAST);
    data_->WriteUint8(static_cast<uint8_t>(type));
    auto globalConst = thread_->GlobalConstants();
    JSHandle<JSTaggedValue> handleMsg = globalConst->GetHandledMessageString();
    JSHandle<JSTaggedValue> msg =
        JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(thread_, object), handleMsg).GetValue();
    JSHandle<JSTaggedValue> handleStack = globalConst->GetHandledStackString();
    JSHandle<JSTaggedValue> stack =
        JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(thread_, object), handleStack).GetValue();
    if (msg->IsString()) {
        if (needSerializeStack_ && stack->IsString()) {
            data_->WriteUint8(static_cast<uint8_t>(SerializeErrorInfo::MSG_AND_STACK));
            SerializeSharedObject(msg->GetTaggedObject());
            SerializeSharedObject(stack->GetTaggedObject());
        } else {
            data_->WriteUint8(static_cast<uint8_t>(SerializeErrorInfo::ONLY_MSG));
            SerializeSharedObject(msg->GetTaggedObject());
        }
    } else {
        data_->WriteUint8(0);
    }
}

void ValueSerializer::SerializeNativeBindingObject(TaggedObject *object)
{
    [[maybe_unused]] EcmaHandleScope scope(thread_);
    JSHandle<GlobalEnv> env = vm_->GetGlobalEnv();
    JSHandle<JSTaggedValue> nativeBindingSymbol = thread_->GlobalConstants()->GetHandledNativeBindingSymbol();
    JSHandle<JSTaggedValue> nativeBindingValue =
        JSObject::GetProperty(thread_, JSHandle<JSObject>(thread_, object), nativeBindingSymbol).GetRawValue();
    if (!nativeBindingValue->IsJSNativePointer()) {
        std::string errorMessage = "Serialize don't support nativeBindingValue is not JSNativePointer";
        PrintAndRecordErrorMessage(errorMessage);
        notSupport_ = true;
        return;
    }
    auto info = reinterpret_cast<panda::JSNApi::NativeBindingInfo *>(
        JSNativePointer::Cast(nativeBindingValue->GetTaggedObject())->GetExternalPointer());
    if (info == nullptr) {
        std::string errorMessage = "Serialize don't support NativeBindingInfo is nullptr";
        PrintAndRecordErrorMessage(errorMessage);
        notSupport_ = true;
        return;
    }
    void *hint = info->hint;
    void *attachData = info->attachData;
    AttachFunc attachNative = reinterpret_cast<AttachFunc>(info->attachFunc);
    data_->WriteEncodeFlag(EncodeFlag::NATIVE_BINDING_OBJECT);
    data_->WriteJSTaggedType(reinterpret_cast<JSTaggedType>(attachNative));
    ssize_t offset = data_->EmitU64(0); // 0 is a placeholder which will be filled later
    detachCallbackInfo_.push_back({offset, info});
    data_->WriteJSTaggedType(reinterpret_cast<JSTaggedType>(hint));
    data_->WriteJSTaggedType(reinterpret_cast<JSTaggedType>(attachData));
}

bool ValueSerializer::SerializeJSArrayBufferPrologue(TaggedObject *object)
{
    ASSERT(object->GetClass()->IsArrayBuffer());
    JSArrayBuffer *arrayBuffer = reinterpret_cast<JSArrayBuffer *>(object);
    if (arrayBuffer->IsDetach(thread_)) {
        std::string errorMessage = "Serialize don't support detached array buffer";
        PrintAndRecordErrorMessage(errorMessage);
        notSupport_ = true;
        return false;
    }
    bool transfer = transferDataSet_.find(ToUintPtr(object)) != transferDataSet_.end();
    bool clone = cloneArrayBufferSet_.find(ToUintPtr(object)) != cloneArrayBufferSet_.end();
    size_t arrayLength = arrayBuffer->GetArrayBufferByteLength();
    if (arrayLength > 0) {
        if (transfer) {
            if (clone) {
                notSupport_ = true;
                std::string errorMessage = "Serialize don't support arraybuffer in both transfer list and clone list";
                PrintAndRecordErrorMessage(errorMessage);
                return false;
            }
            data_->WriteEncodeFlag(EncodeFlag::TRANSFER_ARRAY_BUFFER);
            return true;
        } else if (clone || !defaultTransfer_) {
            bool nativeAreaAllocated = arrayBuffer->GetWithNativeAreaAllocator();
            if (!nativeAreaAllocated) {
                std::string errorMessage = "Serialize don't support clone arraybuffer has external allocated buffer, "
                    "considering transfer it";
                PrintAndRecordErrorMessage(errorMessage);
                notSupport_ = true;
                return false;
            }
            data_->WriteEncodeFlag(EncodeFlag::ARRAY_BUFFER);
            data_->WriteUint32(arrayLength);
            JSNativePointer *np =
                reinterpret_cast<JSNativePointer *>(arrayBuffer->GetArrayBufferData(thread_).GetTaggedObject());
            data_->WriteRawData(static_cast<uint8_t *>(np->GetExternalPointer()), arrayLength);
            return false;
        } else {
            data_->WriteEncodeFlag(EncodeFlag::TRANSFER_ARRAY_BUFFER);
            return true;
        }
    }
    return false;
}

void ValueSerializer::SerializeJSSharedArrayBufferPrologue(TaggedObject *object)
{
    ASSERT(object->GetClass()->IsSharedArrayBuffer());
    JSArrayBuffer *arrayBuffer = reinterpret_cast<JSArrayBuffer *>(object);
    bool transfer = transferDataSet_.find(ToUintPtr(object)) != transferDataSet_.end();
    if (arrayBuffer->IsDetach(thread_) || transfer) {
        std::string errorMessage =  "Serialize don't support detached or transfer shared array buffer";
        PrintAndRecordErrorMessage(errorMessage);
        notSupport_ = true;
        return;
    }
    size_t arrayLength = arrayBuffer->GetArrayBufferByteLength();
    if (arrayLength > 0) {
        JSNativePointer *np =
            reinterpret_cast<JSNativePointer *>(arrayBuffer->GetArrayBufferData(thread_).GetTaggedObject());
        void *buffer = np->GetExternalPointer();
        if (JSSharedMemoryManager::GetInstance()->CreateOrLoad(&buffer, arrayLength)) {
            std::string errorMessage =  "Serialize can't find buffer from shared memory pool";
            PrintAndRecordErrorMessage(errorMessage);
            notSupport_ = true;
            return;
        }
        data_->WriteEncodeFlag(EncodeFlag::SHARED_ARRAY_BUFFER);
        data_->insertSharedArrayBuffer(reinterpret_cast<uintptr_t>(buffer));
    }
}

void ValueSerializer::SerializeJSSendableArrayBufferPrologue(TaggedObject *object)
{
    ASSERT(object->GetClass()->IsSendableArrayBuffer());
    JSSendableArrayBuffer *arrayBuffer = reinterpret_cast<JSSendableArrayBuffer *>(object);
    if (arrayBuffer->IsDetach(thread_)) {
        std::string errorMessage =  "Serialize don't support serialize detached sendable array buffer";
        PrintAndRecordErrorMessage(errorMessage);
        notSupport_ = true;
        return;
    }
    size_t arrayLength = arrayBuffer->GetArrayBufferByteLength();
    if (arrayLength > 0) {
        bool nativeAreaAllocated = arrayBuffer->GetWithNativeAreaAllocator();
        if (!nativeAreaAllocated) {
            std::string errorMessage =  "Serialize don't support clone sendablearraybuffer has external allocated buffer";
            PrintAndRecordErrorMessage(errorMessage);
            notSupport_ = true;
            return;
        }
        data_->WriteEncodeFlag(EncodeFlag::SENDABLE_ARRAY_BUFFER);
        data_->WriteUint32(arrayLength);
        JSNativePointer *np =
            reinterpret_cast<JSNativePointer *>(arrayBuffer->GetArrayBufferData(thread_).GetTaggedObject());
        data_->WriteRawData(static_cast<uint8_t *>(np->GetExternalPointer()), arrayLength);
    }
}

void ValueSerializer::SerializeJSRegExpPrologue(JSRegExp *jsRegExp)
{
    uint32_t bufferSize = jsRegExp->GetLength();
    if (bufferSize == 0) {
        std::string errorMessage =  "Serialize don't support JSRegExp buffer size is 0";
        PrintAndRecordErrorMessage(errorMessage);
        notSupport_ = true;
        return;
    }

    data_->WriteEncodeFlag(EncodeFlag::JS_REG_EXP);
    data_->WriteUint32(bufferSize);
    JSNativePointer *np = reinterpret_cast<JSNativePointer *>(jsRegExp->GetByteCodeBuffer(thread_).GetTaggedObject());
    data_->WriteRawData(static_cast<uint8_t *>(np->GetExternalPointer()), bufferSize);
}

bool ValueSerializer::PrepareTransfer(JSThread *thread, const JSHandle<JSTaggedValue> &transfer)
{
    if (transfer->IsUndefined()) {
        return true;
    }
    if (!transfer->IsJSArray()) {
        return false;
    }
    int len = base::ArrayHelper::GetArrayLength(thread, transfer);
    int k = 0;
    while (k < len) {
        bool exists = JSTaggedValue::HasProperty(thread, transfer, k);
        if (exists) {
            JSHandle<JSTaggedValue> element = JSArray::FastGetPropertyByValue(thread, transfer, k);
            if (!element->IsArrayBuffer()) {
                return false;
            }
            transferDataSet_.insert(static_cast<uintptr_t>(element.GetTaggedType()));
        }
        k++;
    }
    return true;
}

bool ValueSerializer::PrepareClone(JSThread *thread, const JSHandle<JSTaggedValue> &cloneList)
{
    if (cloneList->IsUndefined()) {
        return true;
    }
    if (!cloneList->IsJSArray()) {
        return false;
    }
    int len = base::ArrayHelper::GetArrayLength(thread, cloneList);
    int index = 0;
    while (index < len) {
        bool exists = JSTaggedValue::HasProperty(thread, cloneList, index);
        if (exists) {
            JSHandle<JSTaggedValue> element = JSArray::FastGetPropertyByValue(thread, cloneList, index);
            if (element->IsArrayBuffer()) {
                cloneArrayBufferSet_.insert(static_cast<uintptr_t>(element.GetTaggedType()));
            } else if (element->IsJSShared()) {
                cloneSharedSet_.insert(static_cast<uintptr_t>(element.GetTaggedType()));
            } else {
                return false;
            }
        }
        index++;
    }
    return true;
}

// especially process cpp native object in SourceTextModule
bool ValueSerializer::SerializeModuleCNativeObjects(TaggedObject *object)
{
    JSHandle<SourceTextModule> module = JSHandle<SourceTextModule>(thread_, object);
    CString moduleFileName = module->GetEcmaModuleFilenameString();
    data_->WriteEncodeFlag(EncodeFlag::MODULE_FILE_NAME);
    if (!moduleFileName.empty()) {
        data_->WriteUint32(moduleFileName.size());
        data_->WriteRawData(reinterpret_cast<uint8_t *>(moduleFileName.data()), moduleFileName.size());
    } else {
        data_->WriteUint32(0);
    }

    CString moduleRecordName = module->GetEcmaModuleRecordNameString();
    data_->WriteEncodeFlag(EncodeFlag::MODULE_RECORD_NAME);
    if (!moduleRecordName.empty()) {
        data_->WriteUint32(moduleRecordName.size());
        data_->WriteRawData(reinterpret_cast<uint8_t *>(moduleRecordName.data()), moduleRecordName.size());
    } else {
        data_->WriteUint32(0);
    }

    if (moduleFileName.empty() && moduleRecordName.empty()) {
        LOG_ECMA(ERROR) << "ValueSerialize::SerializeModuleCNativeObjects moduleFileName and " <<
             "moduleRecordName are both empty";
        return false;
    }

    bool *lazyArray = module->GetLazyImportStatusArray();
    // ModuleRequests size is equal to lazy array size.
    JSTaggedValue requests = module->GetModuleRequests(thread_);
    data_->WriteEncodeFlag(EncodeFlag::MODULE_LAZY_ARRAY);
    if (lazyArray && requests.IsTaggedArray()) {
        JSHandle<TaggedArray> moduleRequests(thread_, requests);
        size_t lazyArraySpaceSize = moduleRequests->GetLength() * sizeof(bool);
        if (lazyArraySpaceSize != 0) {
            data_->WriteUint32(lazyArraySpaceSize);
            data_->WriteRawData(reinterpret_cast<uint8_t *>(lazyArray), lazyArraySpaceSize);
        } else {
            data_->WriteUint32(0);
        }
    } else {
        data_->WriteUint32(0);
    }
    return true;
}
}  // namespace panda::ecmascript