/*
 * Copyright (c) 2021-2025 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 "common_components/heap/heap.h"
#include "ecmascript/base/config.h"
#include "ecmascript/dfx/hprof/rawheap_dump.h"
#include "ecmascript/dfx/hprof/rawheap_translate/common.h"
#include "ecmascript/object_fast_operator-inl.h"

#ifdef ENABLE_HISYSEVENT
    #include "hisysevent.h"
    #include "dfx_signal_handler.h"
#endif

namespace panda::ecmascript {
void ObjectMarker::VisitRoot([[maybe_unused]]Root type, ObjectSlot slot)
{
    JSTaggedValue value(slot.GetTaggedType());
    if (value.IsHeapObject()) {
        MarkObject(slot.GetTaggedType());
        switch (type) {
            case Root::ROOT_LOCAL_HANDLE:
                localHandleRoots_.insert(slot.GetTaggedType());
                break;
            case Root::ROOT_GLOBAL_HANDLE:
                globalHandleRoots_.insert(slot.GetTaggedType());
                break;
            case Root::ROOT_VM:
                vmRoots_.insert(slot.GetTaggedType());
                break;
            case Root::ROOT_FRAME:
                frameRoots_.insert(slot.GetTaggedType());
                break;
            default:
                break;
        }
    }
}

void ObjectMarker::VisitRangeRoot(Root type, ObjectSlot start, ObjectSlot end)
{
    for (ObjectSlot slot = start; slot < end; slot++) {
        VisitRoot(type, slot);
    }
}

void ObjectMarker::VisitBaseAndDerivedRoot(Root type, ObjectSlot base, ObjectSlot derived, uintptr_t baseOldObject)
{
}

void ObjectMarker::VisitObjectRangeImpl([[maybe_unused]]BaseObject *root, uintptr_t start,
                                        uintptr_t endAddr, [[maybe_unused]]VisitObjectArea area)
{
    ObjectSlot end(endAddr);
    for (ObjectSlot slot(start); slot < end; slot++) {
        JSTaggedValue value(slot.GetTaggedType());
        if (value.GetRawData() != 0 && value.IsHeapObject() && !value.IsWeak()) {
            MarkObject(slot.GetTaggedType());
        }
    }
}

static void ProcessJSWrappedNapiObject(const EcmaVM *vm, JSTaggedValue value,
    CUnorderedSet<JSTaggedType> &nativePointerAddrs)
{
    auto wrappedObj = JSWrappedNapiObject::Cast(value.GetTaggedObject());
    JSTaggedValue nativePointers = wrappedObj->GetNativePointers(vm->GetAssociatedJSThread());
    if (!nativePointers.IsTaggedArray()) {
        return;
    }
    TaggedArray* array = TaggedArray::Cast(nativePointers.GetTaggedObject());
    uint32_t length = array->GetExtraLength();
    for (uint32_t i = 0; i < length; i++) {
        JSTaggedValue itemValue = array->Get(vm->GetAssociatedJSThread(), i);
        if (itemValue.IsJSNativePointer()) {
            nativePointerAddrs.insert(itemValue.GetRawData());
        }
    }
}

void ObjectMarker::ProcessMarkObjectsFromRoot()
{
    while (!bfsQueue_.empty()) {
        JSTaggedType addr = bfsQueue_.front();
        bfsQueue_.pop();

        JSTaggedValue value(addr);
        TaggedObject *object = value.GetTaggedObject();
        JSHClass *hclass = object->GetClass();
        MarkObject(reinterpret_cast<JSTaggedType>(hclass));
        if (hclass->IsString()) {
            continue;
        }
        // Handle JSWrappedNapiObject: collect JSNativePointer addresses from NativePointers
        if (value.IsJSWrappedNapiObject()) {
            ProcessJSWrappedNapiObject(vm_, value, nativePointerAddrs_);
        }
        ObjectXRay::VisitObjectBody<VisitType::OLD_GC_VISIT>(object, hclass, *this);
    }
}

void ObjectMarker::IterateMarkedObjects(const std::function<void(JSTaggedType)> &visitor)
{
    for (auto addr : markedObjects_) {
        visitor(addr);
    }
}

void ObjectMarker::MarkObject(JSTaggedType addr)
{
    if (Mark(addr)) {
        markedObjects_.push_back(addr);
        bfsQueue_.push(addr);
        heapSize_ += reinterpret_cast<TaggedObject *>(addr)->GetSize();
    }
}

void ObjectMarker::MarkRootObjects()
{
    HeapRootVisitor rootVisitor;
    if (option_->isProcDump) {
        Runtime::GetInstance()->GCIterateThreadList([&rootVisitor, this](JSThread *thread) {
            rootVisitor.VisitHeapRoots(thread, *this);
        });
        LOG_ECMA(INFO) << "rawheap dump, process dump";
    } else {
        rootVisitor.VisitHeapRoots(vm_->GetAssociatedJSThread(), *this);
    }
    SharedModuleManager::GetInstance()->Iterate(*this);
    Runtime::GetInstance()->IterateCachedStringRoot(*this);
    Runtime::GetInstance()->IterateSendableGlobalStorage(*this);

    LOG_ECMA(INFO) << "rawheap dump, local handle count " << localHandleRoots_.size()
                   << ", global handle count " << globalHandleRoots_.size()
                   << ", vm root count " << vmRoots_.size()
                   << ", frame root count " << frameRoots_.size();
}

RawHeapDump::RawHeapDump(const EcmaVM *vm, Stream *stream, HeapSnapshot *snapshot,
                         EntryIdMap *entryIdMap, const DumpSnapShotOption &dumpOption)
    : vm_(vm), dumpOption_(&dumpOption), snapshot_(snapshot), entryIdMap_(entryIdMap),
      writer_(stream), marker_(vm, &dumpOption)
{
    startTime_ = std::chrono::steady_clock::now();
}

RawHeapDump::~RawHeapDump()
{
    writer_.EndOfWriteBinBlock();
    secIndexVec_.clear();
    auto endTime = std::chrono::steady_clock::now();
    double duration = std::chrono::duration<double>(endTime - startTime_).count();
    LOG_ECMA(INFO) << "rawheap dump success, cost " << duration << "s, " << "file size " << GetRawHeapFileOffset();
    if (dumpOption_->isDumpOOM) {
        SEND_HISYSEVENT(ARKTS_RUNTIME, ARK_STATS_OOM, STATISTIC, "STATUS", 0, "MESSAGE", "OK",
                        "OOM_TYPE", dumpOption_->isForSharedOOM ? "SHARED_OOM" : "LOCAL_OOM",
                        "OBJ_COUNT", GetObjectCount(),
                        "STR_COUNT", GetEcmaStringTable()->GetCapcity(),
                        "HEAP_SIZE", marker_.GetHeapSize(),
                        "FILE_SIZE", GetRawHeapFileOffset(),
                        "DURATION", duration, "VERSION", GetRawheapVersion());
    }
}

/*
 *  |--4 bytes--|--4 bytes--|
 *  |-----------------------|
 *  |       versionId       |
 *  |-----------------------|
 *  |       timestamp       |
 *  |-----------------------|
 *  |  rootCnt  |  rootUnit |
 *  |-----------------------|
 *  |                       |
 *  |                       |
 *  |-----------------------|
 *  | stringCnt | 0(unused) |
 *  |-----------------------|
 *  |                       |
 *  |                       |
 *  |-----------------------|
 *  |objTotalCnt| tableUnit |
 *  |-----------------------|
 *  |                       |
 *  |                       |
 *  |-----------------------|
 *  | rootOffset|  rootSize |
 *  |-----------------------|
 *  | strOffset |  strSize  |
 *  |-----------------------|
 *  | objOffset |  objSize  |
 *  |-----------------------|
*/
void RawHeapDump::BinaryDump()
{
    DumpVersion(GetRawheapVersion());
    DumpMetadataFields();

    marker_.MarkRootObjects();
    DumpRootTable();

    marker_.ProcessMarkObjectsFromRoot();
    // properties name was only exempted in mode RawHeapDumpCropLevel::LEVEL_V1
    if (Runtime::GetInstance()->GetRawHeapDumpCropLevel() == RawHeapDumpCropLevel::LEVEL_V1) {
        AddExemptedStringNode();
    }

    UpdateStringTable();
    DumpStringTable();
    DumpObjectTable();
    DumpObjectMemory();

    DumpSectionIndex();
}

void RawHeapDump::IterateMarkedObjects(const std::function<void(JSTaggedType)> &visitor)
{
    marker_.IterateMarkedObjects(visitor);
}

void RawHeapDump::UpdateSourceTextModuleStringTable(JSTaggedType addr, int &strCnt)
{
    JSTaggedValue entry(addr);
    if (!entry.IsSourceTextModule()) {
        return;
    }

    SourceTextModule *module = SourceTextModule::Cast(entry.GetTaggedObject());
    // Add EcmaModuleRecordName string to the snapshot
    CString moduleRecordName = module->GetEcmaModuleRecordNameString();
    StringId moduleRecordNameStrId = snapshot_->InsertString(moduleRecordName);
    JSTaggedType moduleRecordNameAddr = static_cast<JSTaggedType>(module->GetEcmaModuleRecordName());
    UpdateStringTable(moduleRecordNameAddr, moduleRecordNameStrId);
    moduleRecordNameStrId != 1 ? strCnt++ : 0;
    // Add EcmaModuleFilename string to the snapshot
    CString moduleFileName = module->GetEcmaModuleFilenameString();
    StringId moduleFileNameStrId = snapshot_->InsertString(moduleFileName);
    JSTaggedType moduleFileNameAddr = static_cast<JSTaggedType>(module->GetEcmaModuleFilename());
    UpdateStringTable(moduleFileNameAddr, moduleFileNameStrId);
    moduleFileNameStrId != 1 ? strCnt++ : 0;
}

// Update string table for all marked objects and property name strings.
// String objects are normally skipped due to content trimming, but property name strings are exempted.
// For each object, generate a string ID and call the virtual UpdateStringTable(addr, strId).
// This function handles both regular objects and exempted string nodes.
void RawHeapDump::UpdateStringTable()
{
    int strCnt = 0;
    IterateMarkedObjects([&strCnt, this](JSTaggedType addr) {
        StringId strId = GenerateStringId(reinterpret_cast<TaggedObject *>(addr));
        UpdateStringTable(addr, strId);
        strId != 1 ? strCnt++ : 0;

        // Update string table for SourceTextModule's EcmaModuleRecordName and EcmaModuleFilename
        UpdateSourceTextModuleStringTable(addr, strCnt);
    });
    IterateExemptedStringNode([&strCnt, this](JSTaggedType addr) {
        StringId strId = GenerateStringId(reinterpret_cast<TaggedObject *>(addr), true);
        UpdateStringTable(addr, strId);
        strId != 1 ? strCnt++ : 0;
    });
    LOG_ECMA(INFO) << "rawheap dump, update string table count " << strCnt;
}

void RawHeapDump::DumpVersion(const std::string &version)
{
    char versionId[8] = { 0 };  // 8: size of rawheap version
    if (strcpy_s(versionId, sizeof(versionId), version.c_str()) != 0) {
        LOG_ECMA(ERROR) << "rawheap dump, version id strcpy_s failed!";
        return;
    }
    WriteChunk(versionId, sizeof(versionId));

    char timeStamp[8] = { 0 };  // 8: size of current timestamp
    *reinterpret_cast<uint64_t *>(timeStamp) = std::chrono::system_clock::now().time_since_epoch().count();
    WriteChunk(timeStamp, sizeof(timeStamp));
    LOG_ECMA(INFO) << "rawheap dump, version " << version;
}

void RawHeapDump::DumpMetadataFields()
{
    DumpStringField(dumpOption_->spaceType, 32, "space type");  // 32: space type size
    DumpStringField(dumpOption_->heapType, 16, "heap type");    // 16: heap type size
    DumpStringField("dynamic", 8, "vm type");                   // 8: vm type size
}

void RawHeapDump::DumpStringField(const std::string &value, size_t bufSize, const char *fieldName)
{
    std::vector<char> buffer(bufSize);
    std::fill(buffer.begin(), buffer.end(), '\0');
    if (!value.empty() && strcpy_s(buffer.data(), bufSize, value.c_str()) != 0) {
        LOG_ECMA(ERROR) << "rawheap dump, " << fieldName << " strcpy_s failed!" << value;
    }
    WriteChunk(buffer.data(), bufSize);
}

void RawHeapDump::DumpSectionIndex()
{
    AddSectionRecord(secIndexVec_.size());
    AddSectionRecord(sizeof(uint32_t));
    WriteChunk(reinterpret_cast<char *>(secIndexVec_.data()), secIndexVec_.size() * sizeof(uint32_t));
    LOG_ECMA(INFO) << "rawheap dump, section count " << secIndexVec_.size();
}

/*
* mixed hash in NodeId, for example:
* |----------------- uint64_t -----------------|
* |-----high-32-bits-----|----lower-32-bits----|
* |--------------------------------------------|
* |         hash         | nodeId & 0xFFFFFFFF |
* |--------------------------------------------|
*/
NodeId RawHeapDump::GenerateNodeId(JSTaggedType addr)
{
    NodeId nodeId = dumpOption_->isDumpOOM ? entryIdMap_->GetNextId() : entryIdMap_->FindOrInsertNodeId(addr);
    if (!dumpOption_->isJSLeakWatcher) {
        return nodeId;
    }

    JSTaggedValue value {addr};
    int32_t hash = value.IsJSObject() ? JSObject::Cast(value)->GetHash(vm_->GetJSThreadNoCheck()) : 0;
    return (static_cast<uint64_t>(hash) << 32) | (nodeId & 0xFFFFFFFF);  // 32: 32-bits means a half of uint64_t
}

void RawHeapDump::WriteChunk(char *data, size_t size)
{
    writer_.WriteBinBlock(data, size);
}

void RawHeapDump::WriteU64(uint64_t value)
{
    writer_.WriteUInt64(value);
}

void RawHeapDump::WriteU32(uint32_t value)
{
    writer_.WriteUInt32(value);
}

void RawHeapDump::WriteU16(uint16_t value)
{
    writer_.WriteUInt16(value);
}

void RawHeapDump::WriteU8(uint8_t value)
{
    writer_.WriteUInt8(value);
}

void RawHeapDump::WriteHeader(uint32_t offset, uint32_t size)
{
    uint32_t header[2] = {offset, size};
    WriteChunk(reinterpret_cast<char *>(header), sizeof(header));
}

void RawHeapDump::WritePadding()
{
    uint32_t padding = (8 - GetRawHeapFileOffset() % 8) % 8;
    if (padding > 0) {
        char pad[8] = {0};
        WriteChunk(pad, padding);
    }
}

void RawHeapDump::AddSectionRecord(uint32_t value)
{
    secIndexVec_.push_back(value);
}

void RawHeapDump::AddSectionOffset()
{
    secIndexVec_.push_back(GetRawHeapFileOffset());
    preOffset_ = GetRawHeapFileOffset();
}

void RawHeapDump::AddSectionBlockSize()
{
    secIndexVec_.push_back(GetRawHeapFileOffset() - preOffset_);
}

StringId RawHeapDump::GenerateStringIdForJSObject(TaggedObject *object, JSThread *thread)
{
    JSTaggedValue entry(object);
    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
    bool isCallGetter = false;
    JSHandle<JSTaggedValue> contructorKey = globalConst->GetHandledConstructorString();
    JSTaggedValue objConstructor = ObjectFastOperator::GetPropertyByName(thread, entry,
                                                                         contructorKey.GetTaggedValue(), true,
                                                                         &isCallGetter);
    auto it = objectStrIds_.find(objConstructor.GetRawData());
    if (it != objectStrIds_.end()) {
        return it->second;
    }
    StringId strId = snapshot_->GenerateStringId(object);
    objectStrIds_.emplace(objConstructor.GetRawData(), strId);
    return strId;
}

StringId RawHeapDump::GenerateStringIdForJSFunction(TaggedObject *object, JSThread *thread)
{
    JSFunctionBase *func = JSFunctionBase::Cast(object);
    Method *method = Method::Cast(func->GetMethod(thread).GetTaggedObject());
    auto it = functionStrIds_.find(method);
    if (it != functionStrIds_.end()) {
        return it->second;
    }
    StringId strId = snapshot_->GenerateStringId(object);
    functionStrIds_.emplace(method, strId);
    return strId;
}

StringId RawHeapDump::GenerateStringIdForString(TaggedObject *object, JSThread *thread)
{
    EcmaString *str = EcmaString::Cast(object);
    if (EcmaStringAccessor(str).GetLength() > 0) {
        CString string = ConvertToString(vm_->GetJSThreadNoCheck(), str);
        return snapshot_->InsertString(string);
    }
    return 1;  // 1 : invalid id
}

StringId RawHeapDump::GenerateStringIdForJSProxy(TaggedObject *object)
{
    CString suffix = snapshot_->GetProxyClassNameSuffix(object);
    if (!suffix.empty()) {
        CString nodeName = HeapSnapshot::GetNodeName(JSType::JS_PROXY, snapshot_->IsInVmMode()) + suffix;
        return const_cast<StringHashMap*>(snapshot_->GetEcmaStringTable())->InsertStrAndGetStringId(nodeName);
    }
    return 1;  // 1 : invalid id
}

// Generate a string ID for an object. Normally string objects are skipped due to content trimming,
// but when strAllowed is true, property name strings are allowed in the string table.
StringId RawHeapDump::GenerateStringId(TaggedObject *object, bool strAllowed)
{
    JSTaggedValue entry(object);
    JSThread *thread = vm_->GetAssociatedJSThread();

    if (entry.IsJSProxy()) {
        return GenerateStringIdForJSProxy(object);
    }

    if (entry.IsOnlyJSObject()) {
        return GenerateStringIdForJSObject(object, thread);
    }

    if (entry.IsJSFunction()) {
        return GenerateStringIdForJSFunction(object, thread);
    }

    if (strAllowed && entry.IsString()) {
        return GenerateStringIdForString(object, thread);
    }

    return 1;  // 1 : invalid id
}

const StringHashMap *RawHeapDump::GetEcmaStringTable()
{
    return snapshot_->GetEcmaStringTable();
}

// Collect string objects that are used as property names (keys) in JSObjects.
// String objects are normally skipped because their content is often trimmed/compressed,
// but property name strings need to be exempted and included in the string table for heap dump.
void RawHeapDump::AddExemptedStringNode()
{
    IterateMarkedObjects([this](JSTaggedType addr) {
        JSTaggedValue value(addr);
        if (!value.IsJSObject()) {
            return;
        }

        std::vector<Reference> refs {};
        value.DumpForSnapshot(vm_->GetJSThreadNoCheck(), refs, true);
        for (auto &ref : refs) {
            if (ref.key_.IsHole()) {
                continue;
            }
            if (ref.key_.IsWeak()) {
                ref.key_.RemoveWeakTag();
            }
            JSTaggedType keyAddr = ref.key_.GetRawData();
            exemptedStrNodes_.insert(keyAddr);
        }
    });
    LOG_ECMA(INFO) << "rawheap dump, caught exempted string count " << exemptedStrNodes_.size();
}

// Iterate over the collected property name string nodes (exempted from normal string trimming).
void RawHeapDump::IterateExemptedStringNode(const std::function<void(JSTaggedType)> &visitor)
{
    for (auto addr : exemptedStrNodes_) {
        visitor(addr);
    }
}

RawHeapDumpV1::RawHeapDumpV1(const EcmaVM *vm, Stream *stream, HeapSnapshot *snapshot,
                             EntryIdMap *entryIdMap, const DumpSnapShotOption &dumpOption)
    : RawHeapDump(vm, stream, snapshot, entryIdMap, dumpOption)
{
    SetRawheapVersion(std::string(RAWHEAP_VERSION));
}

RawHeapDumpV1::~RawHeapDumpV1()
{
    strIdMapObjVec_.clear();
}

void RawHeapDumpV1::DumpRootTable()
{
    AddSectionOffset();
    WriteHeader(GetObjectCount(), sizeof(JSTaggedType));
    IterateMarkedObjects([this](JSTaggedType addr) {
        WriteU64(addr);
    });
    AddSectionBlockSize();

    // dump address of various root nodes
    CollectRootAddrByType(GetLocalHandleRoots()); // ROOT_LOCAL_HANDLE
    CollectRootAddrByType(GetGlobalHandleRoots()); // ROOT_GLOBAL_HANDLE
    CollectRootAddrByType(GetVmRoots()); // ROOT_VM
    CollectRootAddrByType(GetFrameRoots()); // ROOT_FRAME

    LOG_ECMA(INFO) << "rawheap dump, root count " << GetObjectCount();
}

void RawHeapDumpV1::CollectRootAddrByType(const CSet<JSTaggedType>& rootSet)
{
    WriteU32(static_cast<uint32_t>(rootSet.size()));
    for (auto addr : rootSet) {
        WriteU64(addr);
    }
}

void RawHeapDumpV1::DumpStringTable()
{
    auto strTable = GetEcmaStringTable();
    AddSectionOffset();
    WriteHeader(strTable->GetCapcity(), 0);
    for (auto key : strTable->GetOrderedKeyStorage()) {
        auto [strId, str] = strTable->GetStringAndIdPair(key);
        auto objVec = strIdMapObjVec_[strId];
        WriteHeader(str->size(), objVec.size());
        WriteChunk(reinterpret_cast<char *>(objVec.data()), objVec.size() * sizeof(JSTaggedType));
        WriteChunk(const_cast<char *>(str->c_str()), str->size() + 1);
    }

    WritePadding();
    AddSectionBlockSize();
    LOG_ECMA(INFO) << "rawheap dump, string table capcity " << strTable->GetCapcity();
}

void RawHeapDumpV1::DumpObjectTable()
{
    AddSectionOffset();
    WriteHeader(GetObjectCount(), sizeof(AddrTableItem));
    uint32_t memOffset = GetObjectCount() * sizeof(AddrTableItem);
    IterateMarkedObjects([this, &memOffset](JSTaggedType addr) {
        TaggedObject *obj = reinterpret_cast<TaggedObject *>(addr);
        AddrTableItem table = { addr, GenerateNodeId(addr), obj->GetSize(), memOffset };
        if (obj->GetClass()->IsString()) {
            memOffset += sizeof(JSHClass *);
            EcmaString *str = EcmaString::Cast(obj);
            table.objSize = EcmaStringAccessor(str).GetLength();
        } else {
            memOffset += table.objSize;
        }
        WriteChunk(reinterpret_cast<char *>(&table), sizeof(AddrTableItem));
    });
    LOG_ECMA(INFO) << "rawheap dump, objects total count " << GetObjectCount();
}

void RawHeapDumpV1::DumpObjectMemory()
{
    uint32_t memSize = 0;
    const CUnorderedSet<JSTaggedType> &nativePointerAddrs = GetNativePointerAddrs();
    IterateMarkedObjects([this, &memSize, &nativePointerAddrs](JSTaggedType addr) {
        auto obj = reinterpret_cast<TaggedObject *>(addr);
        size_t size = obj->GetSize();
        memSize += size;
        if (obj->GetClass()->IsString()) {
            size = sizeof(JSHClass *);
        }
        if (g_isEnableCMCGC) {
            WriteU64(reinterpret_cast<JSTaggedType>(obj->GetClass()));
            WriteChunk(reinterpret_cast<char *>(addr + sizeof(JSTaggedType)), size - sizeof(JSTaggedType));
        } else {
            DumpNonCMGCObject(addr, size, nativePointerAddrs);
        }
    });
    AddSectionBlockSize();
    LOG_ECMA(INFO) << "rawheap dump, objects memory size " << memSize;
}

void RawHeapDumpV1::DumpNonCMGCObject(JSTaggedType addr, size_t size,
    const CUnorderedSet<JSTaggedType> &nativePointerAddrs)
{
    void* externalData = nullptr;
    if (nativePointerAddrs.find(addr) != nativePointerAddrs.end()) {
        JSNativePointer *native = JSNativePointer::Cast(reinterpret_cast<TaggedObject *>(addr));
        void* externalPointer = native->GetExternalPointer();
        if (externalPointer != nullptr) {
            auto cb = vm_->GetNativeReferenceDataCallbackGetter();
            if (cb != nullptr) {
                externalData = cb(externalPointer);
            }
        }
        WriteU64(reinterpret_cast<JSTaggedType>(reinterpret_cast<TaggedObject *>(addr)->GetClass()));
        WriteU64(reinterpret_cast<JSTaggedType>(externalData));
        WriteChunk(reinterpret_cast<char *>(addr + 2 * sizeof(JSTaggedType)),
            size - 2 * sizeof(JSTaggedType));  // 2 : Skip the addresses of hclass and externalData
    } else {
        if constexpr (G_USE_STICKY_CMS_GC) {
            WriteU64(reinterpret_cast<JSTaggedType>(reinterpret_cast<TaggedObject *>(addr)->GetClass()));
            WriteChunk(reinterpret_cast<char *>(addr + sizeof(JSTaggedType)),
                size - sizeof(JSTaggedType));
        } else {
            WriteChunk(reinterpret_cast<char *>(addr), size);
        }
    }
}

void RawHeapDumpV1::UpdateStringTable(JSTaggedType addr, StringId strId)
{
    if (strId == 1) {  // 1 : invalid str id
        return;
    }

    auto vec = strIdMapObjVec_.find(strId);
    if (vec != strIdMapObjVec_.end()) {
        vec->second.push_back(addr);
    } else {
        CVector<uint64_t> objVec;
        objVec.push_back(addr);
        strIdMapObjVec_.emplace(strId, objVec);
    }
}

RawHeapDumpV2::RawHeapDumpV2(const EcmaVM *vm, Stream *stream, HeapSnapshot *snapshot,
                             EntryIdMap *entryIdMap, const DumpSnapShotOption &dumpOption)
    : RawHeapDump(vm, stream, snapshot, entryIdMap, dumpOption)
{
    SetRawheapVersion(std::string(RAWHEAP_VERSION_V2));
}

RawHeapDumpV2::~RawHeapDumpV2()
{
    regionIdMap_.clear();
}

void RawHeapDumpV2::DumpRootTable()
{
    AddSectionOffset();
    WriteHeader(GetObjectCount(), sizeof(uint32_t));
    IterateMarkedObjects([this](JSTaggedType addr) {
        WriteU32(GenerateSyntheticAddr(addr));
    });
    AddSectionBlockSize();

    // dump address of various root nodes
    CollectRootAddrByType(GetLocalHandleRoots()); // ROOT_LOCAL_HANDLE
    CollectRootAddrByType(GetGlobalHandleRoots()); // ROOT_GLOBAL_HANDLE
    CollectRootAddrByType(GetVmRoots()); // ROOT_VM
    CollectRootAddrByType(GetFrameRoots()); // ROOT_FRAME

    LOG_ECMA(INFO) << "rawheap dump, root count " << GetObjectCount();
}

void RawHeapDumpV2::CollectRootAddrByType(const CSet<JSTaggedType>& rootSet)
{
    WriteU32(static_cast<uint32_t>(rootSet.size()));
    for (auto addr : rootSet) {
        WriteU32(GenerateSyntheticAddr(addr));
    }
}

void RawHeapDumpV2::DumpStringTable()
{
    auto strTable = GetEcmaStringTable();
    AddSectionOffset();
    WriteHeader(strTable->GetCapcity(), 0);
    for (auto key : strTable->GetOrderedKeyStorage()) {
        auto [strId, str] = strTable->GetStringAndIdPair(key);
        auto objVec = strIdMapObjVec_[strId];
        WriteHeader(str->size(), objVec.size());
        WriteChunk(reinterpret_cast<char *>(objVec.data()), objVec.size() * sizeof(uint32_t));
        WriteChunk(const_cast<char *>(str->c_str()), str->size() + 1);
    }

    WritePadding();
    AddSectionBlockSize();
    LOG_ECMA(INFO) << "rawheap dump, string table capcity " << strTable->GetCapcity();
}

void RawHeapDumpV2::DumpObjectTable()
{
    AddSectionOffset();
    WriteHeader(GetObjectCount(), sizeof(AddrTableItem));
    uint32_t memOffset = GetObjectCount() * sizeof(AddrTableItem);
    IterateMarkedObjects([this, &memOffset](JSTaggedType addr) {
        TaggedObject *obj = reinterpret_cast<TaggedObject *>(addr);
        AddrTableItem table = {
            GenerateSyntheticAddr(addr),
            obj->GetSize(),
            GenerateNodeId(addr),
            obj->GetClass()->IsJSNativePointer() ? JSNativePointer::Cast(obj)->GetBindingSize() : 0,
            static_cast<uint32_t>(obj->GetClass()->GetObjectType())
        };
        if (obj->GetClass()->IsString()) {
            EcmaString *str = EcmaString::Cast(obj);
            table.size = EcmaStringAccessor(str).GetLength();
        }
        WriteChunk(reinterpret_cast<char *>(&table), sizeof(AddrTableItem));
    });
    LOG_ECMA(INFO) << "rawheap dump, objects total count " << GetObjectCount();
}

void RawHeapDumpV2::DumpObjectMemory()
{
    uint32_t memSize = 0;
    IterateMarkedObjects([this, &memSize](JSTaggedType addr) {
        TaggedObject *object = reinterpret_cast<TaggedObject *>(addr);
        memSize += object->GetSize();

        WriteU32(GenerateSyntheticAddr(reinterpret_cast<JSTaggedType>(object->GetClass())));
        if (object->GetClass()->IsString()) {
            return;
        }
        ObjectSlot end(static_cast<uintptr_t>(addr + object->GetSize()));
        ObjectSlot slot(static_cast<uintptr_t>(addr + sizeof(JSTaggedType)));
        for (; slot < end; slot++) {
            JSTaggedValue value(slot.GetTaggedType());
            if (value.GetRawData() != 0 && value.IsHeapObject() && !value.IsWeak()) {
                WriteU32(GenerateSyntheticAddr(value.GetRawData()));
            } else {
                WriteU8(rawheap_translate::ZERO_VALUE);
            }
        }
    });
    AddSectionBlockSize();
    LOG_ECMA(INFO) << "rawheap dump, objects memory size " << memSize;
}

void RawHeapDumpV2::UpdateStringTable(JSTaggedType addr, StringId strId)
{
    if (strId == 1) {  // 1 : invalid str id
        return;
    }

    uint32_t syntheticAddr = GenerateSyntheticAddr(addr);
    auto vec = strIdMapObjVec_.find(strId);
    if (vec != strIdMapObjVec_.end()) {
        vec->second.push_back(syntheticAddr);
    } else {
        CVector<uint32_t> objVec;
        objVec.push_back(syntheticAddr);
        strIdMapObjVec_.emplace(strId, objVec);
    }
}

uint32_t RawHeapDumpV2::GenerateRegionId(JSTaggedType addr)
{
    Region *region = Region::ObjectAddressToRange(addr);
    auto [it, inserted] = regionIdMap_.try_emplace(region, regionId_);
    if (inserted) {
        regionId_ += sizeof(JSTaggedType);
    }
    return it->second;
}

/* ------------------------------
 * |  regionId  | indexInRegion |
 * ------------------------------  ==> uint32_t synthetic addr
 * |  uint16_t  |   uint16_t    |
 * ------------------------------
 */
uint32_t RawHeapDumpV2::GenerateSyntheticAddr(JSTaggedType addr)
{
#ifdef OHOS_UNIT_TEST
    return static_cast<uint32_t>(addr);
#else
    if (g_isEnableCMCGC) {
        return static_cast<uint32_t>(addr);
    } else {
        uint16_t syntheticAddr[2];
        syntheticAddr[0] = static_cast<uint16_t>(GenerateRegionId(addr));
        syntheticAddr[1] = static_cast<uint16_t>((addr & DEFAULT_REGION_MASK) >> TAGGED_TYPE_SIZE_LOG);
        return *reinterpret_cast<uint32_t *>(syntheticAddr);
    }
#endif
}
} // namespace panda::ecmascript