Add funcitonality to take heap snapshots

Signed-off-by: Ádám László Kulcsár <kuladam@inf.u-szeged.hu>
This commit is contained in:
Ádám László Kulcsár 2026-03-05 10:00:01 +01:00 committed by Patrick Kim
commit 633fe63795
9 changed files with 703 additions and 3 deletions

View file

@ -29,6 +29,7 @@
#include "runtime/SandBox.h"
#include "parser/Script.h"
#include "DebuggerEscargot.h"
#include "HeapSnapshot.h"
#ifdef ESCARGOT_DEBUGGER
namespace Escargot {
@ -999,6 +1000,11 @@ bool DebuggerEscargot::processEvents(ExecutionState* state, Optional<ByteCodeBlo
m_waitForResume = false;
return true;
}
case ESCARGOT_DEBUGGER_TAKE_HEAP_SNAPSHOT: {
HeapSnapshot snapshot;
snapshot.takeHeapSnapshot(state);
return true;
}
}
ESCARGOT_LOG_ERROR("Invalid message received. Closing connection.\n");

View file

@ -136,7 +136,8 @@ public:
ESCARGOT_DEBUGGER_PENDING_CONFIG = 27,
ESCARGOT_DEBUGGER_PENDING_RESUME = 28,
ESCARGOT_DEBUGGER_WAIT_BEFORE_EXIT = 29,
ESCARGOT_DEBUGGER_STOP = 30
ESCARGOT_DEBUGGER_TAKE_HEAP_SNAPSHOT = 30,
ESCARGOT_DEBUGGER_STOP = 31
};

View file

@ -0,0 +1,533 @@
/*
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "Escargot.h"
#include "HeapSnapshot.h"
#include "Debugger.h"
#include "runtime/Context.h"
#include "runtime/Environment.h"
#include "runtime/EnvironmentRecord.h"
#include "runtime/GlobalObject.h"
#include "parser/Script.h"
#include "util/Util.h"
#include "cstdio"
#include "rapidjson/document.h"
#include "rapidjson/filewritestream.h"
#include "rapidjson/writer.h"
#include "rapidjson/prettywriter.h"
#ifdef ESCARGOT_DEBUGGER
namespace Escargot {
uint64_t HeapSnapshot::addNode(Node::nodeType type, const std::string& name, uint64_t& id, uint64_t size, bool detachedness)
{
auto iter = m_strings.find(name);
if (iter == m_strings.end()) {
auto result = m_strings.insert(name);
iter = result.first;
}
m_nodes.push_back(Node(type, &(*iter), id, size, detachedness));
id += 1;
m_nodeCount++;
return id - 1;
}
void HeapSnapshot::addEdge(Node::Edge::edgeType type, uint64_t from, uint64_t to, const std::string& name)
{
Node& node = m_nodes[from];
node.m_edges.push_back(Node::Edge(type, &name, to * heapSnapshotNodeFieldCount));
m_edgeCount++;
}
uint64_t HeapSnapshot::addNodeAndEdge(Node::nodeType nodeType, Node::Edge::edgeType edgeType, const std::string& name, uint64_t& id, uint64_t ownerId, uint64_t size, bool detachedness)
{
uint64_t node = addNode(nodeType, name, id, size, detachedness);
addEdge(edgeType, ownerId, node, name);
return node;
}
void HeapSnapshot::addObjectProperties(ExecutionState* state, uint64_t& id)
{
Object* current = nullptr;
uint64_t owner = 0;
while (!m_objects.empty()) {
current = m_objects.front().first;
owner = m_objects.front().second;
m_objects.pop();
Object::OwnPropertyKeyVector objectKeys = current->ownPropertyKeys(*state);
for (size_t i = 0; i < objectKeys.size(); i++) {
ObjectPropertyName currentName(*state, objectKeys[i]);
ObjectGetResult ownProperty = current->getOwnProperty(*state, currentName);
if (ownProperty.isDataAccessorProperty() && !ownProperty.isDataProperty()) {
continue;
}
Value val = ownProperty.value(*state, val);
Value currPropertyName = currentName.toPlainValue();
std::string name = "";
if (currPropertyName.isString()) {
name = currPropertyName.asString()->toNonGCUTF8StringData();
} else if (currPropertyName.isSymbol()) {
name = currPropertyName.asSymbol()->descriptionString()->toNonGCUTF8StringData();
}
Node::Edge::edgeType edgeType = Node::Edge::property;
if (m_nodes.at(owner).m_type == Node::array) {
edgeType = Node::Edge::element;
}
uint64_t propertyId = 0;
if (val.isString()) {
propertyId = addNode(Node::string, name, id, sizeof(String));
addEdge(Node::Edge::property, owner, propertyId, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isNumber() || val.isBoolean()) {
propertyId = addNode(Node::number, name, id, sizeof(val.asNumber()));
addEdge(Node::Edge::property, owner, propertyId, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isSymbol()) {
propertyId = addNode(Node::symbol, name, id, sizeof(*val.asSymbol()));
std::string str = val.asSymbol()->symbolDescriptiveString()->toNonGCUTF8StringData();
addEdge(Node::Edge::property, owner, propertyId, str);
} else if (val.isUndefined()) {
propertyId = addNode(Node::hidden, name, id, sizeof(Value));
addEdge(Node::Edge::property, owner, propertyId, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isObject()) {
if (m_seenObjects.find(val.asObject()) != m_seenObjects.end()) {
continue;
}
if (val.isFunction()) {
propertyId = addNode(Node::closure, name, id, sizeof(*val.asFunction()));
addEdge(Node::Edge::property, owner, propertyId, "Function");
} else if (val.isBigInt()) {
propertyId = addNode(Node::bigint, name, id, sizeof(*val.asBigInt()));
addEdge(Node::Edge::property, owner, propertyId, "BigInt");
} else if (val.asObject()->isArray(*state)) {
propertyId = addNode(Node::array, name, id, sizeof(*val.asObject()));
addEdge(Node::Edge::property, owner, propertyId, "Array");
} else {
propertyId = addNode(Node::object, name, id, sizeof(*val.asObject()));
addEdge(Node::Edge::property, owner, propertyId, "Object");
}
m_seenObjects.insert(val.asObject());
m_objects.push(std::pair<Object*, uint64_t>(val.asObject(), propertyId));
}
}
}
}
void HeapSnapshot::addGlobalEnvrionmentRecord(ExecutionState* state, GlobalEnvironmentRecord* env, uint64_t& id, uint64_t LexicalEnvIdx)
{
GlobalObject* globalObj = env->globalObject();
uint64_t gObj = addNodeAndEdge(Node::hidden, Node::Edge::property, "Global Object", id, LexicalEnvIdx, sizeof(GlobalObject));
uint64_t varDeclarations = addNodeAndEdge(Node::synthetic, Node::Edge::property, "Var Declarations", id, gObj, 0);
InterpretedCodeBlock* gCodeBlock = env->globalCodeBlock();
for (size_t i = 0; i < gCodeBlock->identifierInfos().size(); i++) {
if (!gCodeBlock->identifierInfos()[i].m_isVarDeclaration) {
continue;
}
uint64_t propertyId = 0;
std::string name = gCodeBlock->identifierInfos()[i].m_name.string()->toNonGCUTF8StringData();
auto result = env->getBindingValue(*state, gCodeBlock->identifierInfos()[i].m_name);
if (result.m_hasBindingValue) {
Value val = result.m_value;
if (val.isString()) {
propertyId = addNode(Node::string, name, id, sizeof(String));
addEdge(Node::Edge::property, varDeclarations, propertyId, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isNumber() || val.isBoolean()) {
propertyId = addNode(Node::number, name, id, sizeof(val.asNumber()));
addEdge(Node::Edge::property, varDeclarations, propertyId, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isSymbol()) {
propertyId = addNode(Node::symbol, name, id, sizeof(*val.asSymbol()));
std::string str = val.asSymbol()->symbolDescriptiveString()->toNonGCUTF8StringData();
addEdge(Node::Edge::property, varDeclarations, propertyId, str);
} else if (val.isUndefined()) {
propertyId = addNode(Node::hidden, name, id, sizeof(Value));
addEdge(Node::Edge::property, varDeclarations, propertyId, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isObject()) {
propertyId = addNode(Node::object, name, id, sizeof(Value));
m_seenObjects.insert(val.asObject());
m_objects.push(std::pair<Object*, uint64_t>(val.asObject(), propertyId));
addObjectProperties(state, id);
}
} else {
propertyId = addNode(Node::hidden, name, id, sizeof(0));
}
addEdge(Node::Edge::property, varDeclarations, propertyId, "Var Declaration");
}
m_objects.push(std::pair<Object*, uint64_t>(env->globalObject(), gObj));
addObjectProperties(state, id);
}
void HeapSnapshot::addObjectEnvironmentRecord(ExecutionState* state, ObjectEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx)
{
Object* bindingObj = env->bindingObject();
uint64_t objId = addNodeAndEdge(Node::object, Node::Edge::element, "Object", id, LexicalEnvIdx, sizeof(Object));
m_objects.push(std::pair<Object*, uint64_t>(bindingObj, objId));
addObjectProperties(state, id);
}
void HeapSnapshot::addFunctionEnvironmentRecord(ExecutionState* state, FunctionEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx)
{
ScriptFunctionObject* obj = env->functionObject();
uint64_t funcObj = addNodeAndEdge(Node::object, Node::Edge::element, "Function Object", id, LexicalEnvIdx, sizeof(ScriptFunctionObject));
m_objects.push(std::pair<Object*, uint64_t>(obj, funcObj));
addObjectProperties(state, id);
}
void HeapSnapshot::addModuleEnvironmentRecord(ExecutionState* state, ModuleEnvironmentRecord* env, uint64_t& id, uint64_t& owner)
{
std::string scriptName = env->script()->srcName()->toNonGCUTF8StringData();
uint64_t module = addNodeAndEdge(Node::code, Node::Edge::property, scriptName, id, owner, sizeof(ScriptFunctionObject));
for (uint64_t i = 0; i < env->moduleBindings().size(); i++) {
std::string name = env->moduleBindings()[i].m_localName.string()->toNonGCUTF8StringData();
Value val = env->moduleBindings()[i].m_value.toValue();
if (val.isEmpty()) {
uint64_t node = addNode(Node::object, name, id, sizeof(String));
addEdge(Node::Edge::property, module, node, "Empty");
} else if (val.isString()) {
uint64_t node = addNode(Node::string, name, id, sizeof(String));
addEdge(Node::Edge::property, module, node, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isNumber() || val.isBoolean()) {
uint64_t node = addNode(Node::number, name, id, sizeof(val.asNumber()));
addEdge(Node::Edge::property, module, node, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isSymbol()) {
uint64_t node = addNode(Node::symbol, name, id, sizeof(val.asSymbol()));
addEdge(Node::Edge::property, module, node, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isUndefined()) {
uint64_t node = addNode(Node::hidden, name, id, sizeof(Value));
addEdge(Node::Edge::property, module, node, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isObject()) {
uint64_t node = addNode(Node::object, name, id, sizeof(Object));
addEdge(Node::Edge::property, module, node, "Object");
if (m_seenObjects.find(val.asObject()) != m_seenObjects.end()) {
continue;
}
m_seenObjects.insert(val.asObject());
m_objects.push(std::pair<Object*, uint64_t>(val.asObject(), node));
addObjectProperties(state, id);
}
}
}
void HeapSnapshot::addIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx)
{
uint64_t declEnv = addNode(Node::object, "declarative environment record", id, sizeof(*env), false);
addEdge(Node::Edge::element, declEnv, LexicalEnvIdx, "");
}
void HeapSnapshot::addNotIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordNotIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx)
{
uint64_t declEnv = addNode(Node::object, "not indexed declarative environment record", id, sizeof(*env), false);
addEdge(Node::Edge::element, LexicalEnvIdx, declEnv, "");
IdentifierRecordVector recordVector = env->getIdentifierRecordVector();
for (uint64_t i = 0; i < recordVector.size(); i++) {
uint64_t identifier = addNode(Node::object, "identifier record vector element", id, sizeof(recordVector[i]), false);
addEdge(Node::Edge::element, declEnv, identifier, "");
uint64_t name = addNode(Node::string, recordVector[i].m_name.string()->toUTF8StringData().data(), id, sizeof(recordVector[i].m_name), false);
addEdge(Node::Edge::element, identifier, name, "");
}
}
bool HeapSnapshot::prepareHeapSnapshotFile()
{
auto addFields = [](rapidjson::Value& array, Vector<std::string, GCUtil::gc_malloc_allocator<std::string>>& strings, rapidjson::Document::AllocatorType& allocator) {
for (const std::string& elem : strings) {
rapidjson::Value s;
s.SetString(elem.c_str(), elem.length(), allocator);
array.PushBack(s, allocator);
}
};
rapidjson::Document jsonDoc;
rapidjson::Document::AllocatorType& allocator = jsonDoc.GetAllocator();
jsonDoc.SetObject();
rapidjson::Value meta(rapidjson::kObjectType);
rapidjson::Value node_fields(rapidjson::kArrayType);
{
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{
"type",
"name",
"id",
"self_size",
"edge_count",
"trace_node_id",
"detachedness",
"script_id",
"line",
"column"
};
addFields(node_fields, strings, allocator);
meta.AddMember("node_fields", node_fields, allocator);
}
rapidjson::Value types(rapidjson::kArrayType);
{
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{
"hidden", "array", "string", "object", "code", "closure", "regexp",
"number", "native", "synthetic", "concatenated string",
"sliced string", "symbol", "bigint", "object shape"
};
addFields(types, strings, allocator);
}
rapidjson::Value node_types(rapidjson::kArrayType);
{
node_types.PushBack(types, allocator);
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{
"string", "number", "number", "number", "number"
};
addFields(node_types, strings, allocator);
meta.AddMember("node_types", node_types, allocator);
}
rapidjson::Value edge_fields(rapidjson::kArrayType);
{
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "type", "name_or_index", "to_node" };
addFields(edge_fields, strings, allocator);
meta.AddMember("edge_fields", edge_fields, allocator);
}
rapidjson::Value edge_types_inner(rapidjson::kArrayType);
{
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "type", "name_or_index", "to_node" };
addFields(edge_types_inner, strings, allocator);
}
rapidjson::Value edge_types(rapidjson::kArrayType);
{
edge_types.PushBack(edge_types_inner, allocator);
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "string_or_number", "node" };
addFields(edge_types, strings, allocator);
meta.AddMember("edge_types", edge_types, allocator);
}
rapidjson::Value trace_function_info_fields(rapidjson::kArrayType);
{
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "function_id", "name", "script_name", "script_id", "line", "column" };
addFields(trace_function_info_fields, strings, allocator);
meta.AddMember("trace_function_info_fields", trace_function_info_fields, allocator);
}
rapidjson::Value trace_node_fields(rapidjson::kArrayType);
{
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "id", "function_info_index", "count", "size", "children" };
addFields(trace_node_fields, strings, allocator);
meta.AddMember("trace_node_fields", trace_node_fields, allocator);
}
rapidjson::Value sample_fields(rapidjson::kArrayType);
{
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "timestamp_us", "last_assigned_id" };
addFields(sample_fields, strings, allocator);
meta.AddMember("sample_fields", sample_fields, allocator);
}
rapidjson::Value location_fields(rapidjson::kArrayType);
{
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "object_index", "script_id", "line", "column" };
addFields(location_fields, strings, allocator);
meta.AddMember("location_fields", location_fields, allocator);
}
rapidjson::Value snapshot(rapidjson::kObjectType);
{
snapshot.AddMember("meta", meta, allocator);
snapshot.AddMember("node_count", rapidjson::Value(m_nodeCount * heapSnapshotNodeFieldCount), allocator);
snapshot.AddMember("edge_count", rapidjson::Value(m_edgeCount * heapSnapshotEdgeFieldCount), allocator);
snapshot.AddMember("extra_native_bytes", 0, allocator);
snapshot.AddMember("trace_function_count", 0, allocator);
}
rapidjson::Value nodes(rapidjson::kArrayType);
rapidjson::Value edges(rapidjson::kArrayType);
for (Node& node : m_nodes) {
nodes.PushBack(node.m_type, allocator);
auto iter = m_strings.find(*node.m_name);
auto index = std::distance(m_strings.begin(), iter);
nodes.PushBack(index, allocator);
nodes.PushBack(node.m_id, allocator);
nodes.PushBack(node.m_self_size, allocator);
nodes.PushBack(node.m_edges.size(), allocator);
nodes.PushBack(0, allocator);
nodes.PushBack((uint32_t)node.m_detachedness, allocator);
nodes.PushBack(0, allocator);
nodes.PushBack(0, allocator);
nodes.PushBack(0, allocator);
for (Node::Edge& edge : node.m_edges) {
edges.PushBack(edge.m_type, allocator);
edges.PushBack(index, allocator);
edges.PushBack(edge.m_to_node_idx, allocator);
}
}
rapidjson::Value trace_function_infos(rapidjson::kArrayType);
rapidjson::Value trace_tree(rapidjson::kArrayType);
rapidjson::Value samples(rapidjson::kArrayType);
rapidjson::Value locations(rapidjson::kArrayType);
rapidjson::Value strings(rapidjson::kArrayType);
for (const std::string& str : m_strings) {
rapidjson::Value s;
s = rapidjson::StringRef(str.c_str());
strings.PushBack(s, allocator);
}
jsonDoc.AddMember("snapshot", snapshot, allocator);
jsonDoc.AddMember("nodes", nodes, allocator);
jsonDoc.AddMember("edges", edges, allocator);
jsonDoc.AddMember("trace_function_infos", trace_function_infos, allocator);
jsonDoc.AddMember("trace_tree", trace_tree, allocator);
jsonDoc.AddMember("samples", samples, allocator);
jsonDoc.AddMember("locations", locations, allocator);
jsonDoc.AddMember("strings", strings, allocator);
std::string outputName = "output-" + std::to_string(timestamp()) + ".heapsnapshot";
auto deleter = [&](std::FILE* fp) { fclose(fp); };
std::unique_ptr<std::FILE, decltype(deleter)> output(fopen(outputName.c_str(), "w"), deleter);
if (output.get() == nullptr) {
ESCARGOT_LOG_ERROR("Could not create heap snapshot file.\n");
return false;
}
char buffer[65536];
rapidjson::FileWriteStream os(output.get(), buffer, sizeof(buffer));
rapidjson::Writer<rapidjson::FileWriteStream> writer(os);
jsonDoc.Accept(writer);
return true;
}
void HeapSnapshot::takeHeapSnapshot(ExecutionState* state)
{
uint64_t id = 0;
uint64_t stateIdx = 0;
uint64_t root = addNode(HeapSnapshot::Node::hidden, "Root", id, sizeof(ExecutionState));
uint64_t context = addNodeAndEdge(Node::hidden, Node::Edge::property, "Context", id, root, sizeof(Context));
uint64_t staticStrings = addNodeAndEdge(Node::hidden, Node::Edge::property, "Static Strings", id, context, sizeof(StaticStrings));
for (auto& str : *state->context()->atomicStringMap()) {
addNodeAndEdge(Node::string, Node::Edge::property, str->toNonGCUTF8StringData(), id, staticStrings, sizeof(String));
}
uint64_t userDefined = addNodeAndEdge(Node::synthetic, Node::Edge::property, "User Defined variables", id, root, 0);
for (size_t i = 0; i < state->context()->globalDeclarativeRecord()->size(); i++) {
std::string name = state->context()->globalDeclarativeRecord()->at(i).m_name.string()->toNonGCUTF8StringData();
if (state->context()->globalDeclarativeStorage()->at(i).isEmpty()) {
addNodeAndEdge(Node::hidden, Node::Edge::property, name, id, userDefined, 0);
continue;
}
Value val = state->context()->globalDeclarativeStorage()->at(i).toValue();
if (val.isString()) {
uint64_t node = addNode(Node::string, name, id, sizeof(String));
addEdge(Node::Edge::property, userDefined, node, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isNumber() || val.isBoolean()) {
uint64_t node = addNode(Node::number, name, id, sizeof(val.asNumber()));
addEdge(Node::Edge::property, userDefined, node, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isSymbol()) {
uint64_t node = addNode(Node::symbol, name, id, sizeof(val.asSymbol()));
addEdge(Node::Edge::property, userDefined, node, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isUndefined()) {
uint64_t node = addNode(Node::hidden, name, id, sizeof(Value));
addEdge(Node::Edge::property, userDefined, node, val.toString(*state)->toNonGCUTF8StringData());
} else if (val.isObject()) {
uint64_t node = addNode(Node::object, name, id, sizeof(Object));
addEdge(Node::Edge::property, userDefined, node, "Object");
if (m_seenObjects.find(val.asObject()) != m_seenObjects.end()) {
continue;
}
m_seenObjects.insert(val.asObject());
m_objects.push(std::pair<Object*, uint64_t>(val.asObject(), node));
addObjectProperties(state, id);
}
}
LexicalEnvironment* lexEnv = state->lexicalEnvironment();
while (lexEnv) {
uint64_t lexEnvId = addNodeAndEdge(Node::hidden, Node::Edge::property, "LexicalEnvironment", id, root, sizeof(LexicalEnvironment));
EnvironmentRecord* envRecord = lexEnv->record();
if (envRecord->isGlobalEnvironmentRecord()) {
addGlobalEnvrionmentRecord(state, envRecord->asGlobalEnvironmentRecord(), id, lexEnvId);
} else if (envRecord->isObjectEnvironmentRecord()) {
addObjectEnvironmentRecord(state, envRecord->asObjectEnvironmentRecord(), id, lexEnvId);
} else if (envRecord->isDeclarativeEnvironmentRecord()) {
DeclarativeEnvironmentRecord* declarativeRecord = envRecord->asDeclarativeEnvironmentRecord();
if (declarativeRecord->isFunctionEnvironmentRecord()) {
FunctionEnvironmentRecord* funcEnv = envRecord->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord();
addFunctionEnvironmentRecord(state, funcEnv, id, lexEnvId);
} else if (envRecord->isModuleEnvironmentRecord()) {
ModuleEnvironmentRecord* moduleEnv = envRecord->asDeclarativeEnvironmentRecord()->asModuleEnvironmentRecord();
uint64_t modules = addNodeAndEdge(Node::synthetic, Node::Edge::property, "Modules", id, root, 0);
addModuleEnvironmentRecord(state, moduleEnv, id, modules);
} else if (envRecord->asDeclarativeEnvironmentRecord()->isDeclarativeEnvironmentRecordIndexed()) {
DeclarativeEnvironmentRecordIndexed* declEnv = envRecord->asDeclarativeEnvironmentRecord()->asDeclarativeEnvironmentRecordIndexed();
addIndexedDeclarativeEnvironmentRecord(state, declEnv, id, lexEnvId);
} else {
DeclarativeEnvironmentRecordNotIndexed* declEnv = envRecord->asDeclarativeEnvironmentRecord()->asDeclarativeEnvironmentRecordNotIndexed();
addNotIndexedDeclarativeEnvironmentRecord(state, declEnv, id, lexEnvId);
}
}
lexEnv = lexEnv->outerEnvironment();
}
ESCARGOT_LOG_INFO("nodes: %ld | edges: %ld | strings: %ld", m_nodeCount, m_edgeCount, m_strings.size());
if (prepareHeapSnapshotFile() != true) {
ESCARGOT_LOG_ERROR("Error happened during heap snapshot creation. Aborting now.\n");
abort();
}
}
} // namespace Escargot
#endif

148
src/debugger/HeapSnapshot.h Normal file
View file

@ -0,0 +1,148 @@
/*
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifndef __EscargotHeapSnapshot__
#define __EscargotHeapSnapshot__
#include "runtime/Object.h"
#include "util/Vector.h"
namespace Escargot {
class Object;
class EnvironmentRecord;
class ObjectEnvironmentRecord;
class GlobalEnvironmentRecord;
class ModuleEnvironmentRecord;
class FunctionEnvironmentRecord;
class DeclarativeEnvironmentRecord;
class DeclarativeEnvironmentRecordIndexed;
class DeclarativeEnvironmentRecordNotIndexed;
class HeapSnapshot : public gc {
public:
const uint32_t heapSnapshotNodeFieldCount = 10;
const uint32_t heapSnapshotEdgeFieldCount = 3;
struct Node {
// /"edge_types":[["context","element","property","internal","hidden","shortcut","weak"]
struct Edge {
enum edgeType {
context,
element,
property,
internal,
hidden,
shortcut,
weak,
};
Edge(edgeType type, const std::string* name, uint64_t to_node_idx)
: m_type(type)
, m_name(const_cast<std::string*>(name))
, m_to_node_idx(to_node_idx)
{
}
edgeType m_type;
std::string* m_name;
uint64_t m_to_node_idx;
};
enum nodeType {
hidden,
array,
string,
object,
code,
closure,
regexp,
number,
native,
synthetic,
concatenated_string,
sliced_string,
symbol,
bigint,
object_shape
};
Node(nodeType type, const std::string* name, uint64_t id, uint64_t self_size, bool detachedness)
: m_type(type)
, m_name(const_cast<std::string*>(name))
, m_id(id)
, m_self_size(self_size)
, m_detachedness(detachedness)
{
}
~Node()
{
m_edges.clear();
}
nodeType m_type;
const std::string* m_name;
uint64_t m_id;
uint64_t m_self_size;
bool m_detachedness;
Vector<Edge, GCUtil::gc_malloc_allocator<Edge>> m_edges;
};
HeapSnapshot()
{
m_nodeCount = 0;
m_edgeCount = 0;
}
~HeapSnapshot()
{
m_nodes.clear();
m_strings.clear();
}
uint64_t addNode(Node::nodeType type, const std::string& name, uint64_t& id, uint64_t size, bool detachedness = false);
void addEdge(Node::Edge::edgeType type, uint64_t from, uint64_t to, const std::string& name);
uint64_t addNodeAndEdge(Node::nodeType nodeType, Node::Edge::edgeType edgeType, const std::string& name, uint64_t& id, uint64_t ownerId, uint64_t size, bool detachedness = false);
void addObjectProperties(ExecutionState* state, uint64_t& id);
void addGlobalEnvrionmentRecord(ExecutionState* state, GlobalEnvironmentRecord* env, uint64_t& id, uint64_t LexicalEnvIdx);
void addObjectEnvironmentRecord(ExecutionState* state, ObjectEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx);
void addFunctionEnvironmentRecord(ExecutionState* state, FunctionEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx);
void addModuleEnvironmentRecord(ExecutionState* state, ModuleEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx);
void addIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx);
void addNotIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordNotIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx);
bool prepareHeapSnapshotFile();
void takeHeapSnapshot(ExecutionState* state);
private:
uint64_t m_nodeCount;
uint64_t m_edgeCount;
Vector<Node, GCUtil::gc_malloc_allocator<Node>> m_nodes;
std::unordered_set<std::string> m_strings;
std::unordered_set<Object*> m_seenObjects;
std::queue<std::pair<Object*, uint64_t>> m_objects;
};
} // namespace Escargot
#endif

View file

@ -25,6 +25,10 @@
#include "runtime/RegExpObject.h"
#include "runtime/StaticStrings.h"
#if defined(ESCARGOT_DEBUGGER)
#include "debugger/HeapSnapshot.h"
#endif
namespace Escargot {
class VMInstance;

View file

@ -17,8 +17,8 @@
* USA
*/
#ifndef __Escargot_Environment_h
#define __Escargot_Environment_h
#ifndef __Escargot_Environment__
#define __Escargot_Environment__
#include "runtime/AtomicString.h"
#include "runtime/String.h"

View file

@ -642,6 +642,11 @@ public:
virtual void initializeBinding(ExecutionState& state, const AtomicString& name, const Value& V) override;
IdentifierRecordVector getIdentifierRecordVector()
{
return m_recordVector;
}
void* operator new(size_t size)
{
static MAY_THREAD_LOCAL bool typeInited = false;
@ -1207,6 +1212,7 @@ class ModuleEnvironmentRecord : public DeclarativeEnvironmentRecord {
friend class DebuggerEscargot;
friend class DebuggerDevtools;
friend class DebuggerAPI;
friend class HeapSnapshot;
#endif /* ESCARGOT_DEBUGGER */
public:
struct ModuleBindingRecord {

View file

@ -25,6 +25,7 @@
#include "FunctionObject.h"
#include "NativeFunctionObject.h"
#include "ScriptClassConstructorFunctionObject.h"
#include "../src/debugger/HeapSnapshot.h"
namespace Escargot {

View file

@ -36,6 +36,7 @@ class Script;
class GeneratorObject;
class FunctionObject;
class NativeFunctionObject;
class HeapSnapshot;
typedef VectorWithInlineStorage<2, ControlFlowRecord*, GCUtil::gc_malloc_allocator<ControlFlowRecord*>> ControlFlowRecordVector;