inline Structure* getBoundFunctionStructure(VM& vm, ExecState* exec, JSGlobalObject* globalObject, JSObject* targetFunction) { auto scope = DECLARE_THROW_SCOPE(vm); JSValue prototype = targetFunction->getPrototype(vm, exec); RETURN_IF_EXCEPTION(scope, nullptr); JSFunction* targetJSFunction = jsDynamicCast<JSFunction*>(vm, targetFunction); // We only cache the structure of the bound function if the bindee is a JSFunction since there // isn't any good place to put the structure on Internal Functions. if (targetJSFunction) { Structure* structure = targetJSFunction->rareData(vm)->getBoundFunctionStructure(); if (structure && structure->storedPrototype() == prototype && structure->globalObject() == globalObject) return structure; } Structure* result = globalObject->boundFunctionStructure(); // It would be nice if the structure map was keyed global objects in addition to the other things. Unfortunately, it is not // currently. Whoever works on caching structure changes for prototype transistions should consider this problem as well. // See: https://bugs.webkit.org/show_bug.cgi?id=152738 if (prototype.isObject() && prototype.getObject()->globalObject() == globalObject) { result = vm.prototypeMap.emptyStructureForPrototypeFromBaseStructure(globalObject, prototype.getObject(), result); ASSERT_WITH_SECURITY_IMPLICATION(result->globalObject() == globalObject); } else result = Structure::create(vm, globalObject, prototype, result->typeInfo(), result->classInfo()); if (targetJSFunction) targetJSFunction->rareData(vm)->setBoundFunctionStructure(vm, result); return result; }
void WorkerScriptController::initScript() { ASSERT(!m_workerGlobalScopeWrapper); JSLockHolder lock(m_vm.get()); // Explicitly protect the global object's prototype so it isn't collected // when we allocate the global object. (Once the global object is fully // constructed, it can mark its own prototype.) Structure* workerGlobalScopePrototypeStructure = JSWorkerGlobalScopePrototype::createStructure(*m_vm, 0, jsNull()); Strong<JSWorkerGlobalScopePrototype> workerGlobalScopePrototype(*m_vm, JSWorkerGlobalScopePrototype::create(*m_vm, 0, workerGlobalScopePrototypeStructure)); if (m_workerGlobalScope->isDedicatedWorkerGlobalScope()) { Structure* dedicatedContextPrototypeStructure = JSDedicatedWorkerGlobalScopePrototype::createStructure(*m_vm, 0, workerGlobalScopePrototype.get()); Strong<JSDedicatedWorkerGlobalScopePrototype> dedicatedContextPrototype(*m_vm, JSDedicatedWorkerGlobalScopePrototype::create(*m_vm, 0, dedicatedContextPrototypeStructure)); Structure* structure = JSDedicatedWorkerGlobalScope::createStructure(*m_vm, 0, dedicatedContextPrototype.get()); m_workerGlobalScopeWrapper.set(*m_vm, JSDedicatedWorkerGlobalScope::create(*m_vm, structure, static_cast<DedicatedWorkerGlobalScope&>(*m_workerGlobalScope))); workerGlobalScopePrototypeStructure->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); dedicatedContextPrototypeStructure->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); ASSERT(structure->globalObject() == m_workerGlobalScopeWrapper); ASSERT(m_workerGlobalScopeWrapper->structure()->globalObject() == m_workerGlobalScopeWrapper); workerGlobalScopePrototype->structure()->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); workerGlobalScopePrototype->structure()->setPrototypeWithoutTransition(*m_vm, JSEventTarget::prototype(*m_vm, m_workerGlobalScopeWrapper.get())); dedicatedContextPrototype->structure()->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); } ASSERT(m_workerGlobalScopeWrapper->globalObject() == m_workerGlobalScopeWrapper); ASSERT(asObject(m_workerGlobalScopeWrapper->getPrototypeDirect())->globalObject() == m_workerGlobalScopeWrapper); m_consoleClient = std::make_unique<WorkerConsoleClient>(*m_workerGlobalScope); m_workerGlobalScopeWrapper->setConsoleClient(m_consoleClient.get()); }
void InspectorHeapAgent::getRemoteObject(ErrorString& errorString, int heapObjectId, const String* optionalObjectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result) { // Prevent the cell from getting collected as we look it up. VM& vm = m_environment.vm(); JSLockHolder lock(vm); DeferGC deferGC(vm.heap); unsigned heapObjectIdentifier = static_cast<unsigned>(heapObjectId); const Optional<HeapSnapshotNode> optionalNode = nodeForHeapObjectIdentifier(errorString, heapObjectIdentifier); if (!optionalNode) return; JSCell* cell = optionalNode->cell; Structure* structure = cell->structure(m_environment.vm()); if (!structure) { errorString = ASCIILiteral("Unable to get object details"); return; } JSGlobalObject* globalObject = structure->globalObject(); if (!globalObject) { errorString = ASCIILiteral("Unable to get object details"); return; } InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(globalObject->globalExec()); if (injectedScript.hasNoValue()) { errorString = ASCIILiteral("Unable to get object details - InjectedScript"); return; } Deprecated::ScriptValue cellScriptValue(m_environment.vm(), JSValue(cell)); String objectGroup = optionalObjectGroup ? *optionalObjectGroup : String(); result = injectedScript.wrapObject(cellScriptValue, objectGroup, true); }
void InspectorHeapAgent::getPreview(ErrorString& errorString, int heapObjectId, Inspector::Protocol::OptOutput<String>* resultString, RefPtr<Inspector::Protocol::Debugger::FunctionDetails>& functionDetails, RefPtr<Inspector::Protocol::Runtime::ObjectPreview>& objectPreview) { // Prevent the cell from getting collected as we look it up. VM& vm = m_environment.vm(); JSLockHolder lock(vm); DeferGC deferGC(vm.heap); unsigned heapObjectIdentifier = static_cast<unsigned>(heapObjectId); const Optional<HeapSnapshotNode> optionalNode = nodeForHeapObjectIdentifier(errorString, heapObjectIdentifier); if (!optionalNode) return; // String preview. JSCell* cell = optionalNode->cell; if (cell->isString()) { *resultString = cell->getString(nullptr); return; } // FIXME: Provide preview information for Internal Objects? CodeBlock, Executable, etc. Structure* structure = cell->structure(m_environment.vm()); if (!structure) { errorString = ASCIILiteral("Unable to get object details - Structure"); return; } JSGlobalObject* globalObject = structure->globalObject(); if (!globalObject) { errorString = ASCIILiteral("Unable to get object details - GlobalObject"); return; } InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(globalObject->globalExec()); if (injectedScript.hasNoValue()) { errorString = ASCIILiteral("Unable to get object details - InjectedScript"); return; } // Function preview. if (cell->inherits(JSFunction::info())) { Deprecated::ScriptValue functionScriptValue(m_environment.vm(), JSValue(cell)); injectedScript.functionDetails(errorString, functionScriptValue, &functionDetails); return; } // Object preview. Deprecated::ScriptValue cellScriptValue(m_environment.vm(), JSValue(cell)); objectPreview = injectedScript.previewValue(cellScriptValue); }
void WorkerScriptController::initScript() { ASSERT(!m_workerGlobalScopeWrapper); JSLockHolder lock(m_vm.get()); // Explicitly protect the global object's prototype so it isn't collected // when we allocate the global object. (Once the global object is fully // constructed, it can mark its own prototype.) Structure* workerGlobalScopePrototypeStructure = JSWorkerGlobalScopePrototype::createStructure(*m_vm, 0, jsNull()); Strong<JSWorkerGlobalScopePrototype> workerGlobalScopePrototype(*m_vm, JSWorkerGlobalScopePrototype::create(*m_vm, 0, workerGlobalScopePrototypeStructure)); if (m_workerGlobalScope->isDedicatedWorkerGlobalScope()) { Structure* dedicatedContextPrototypeStructure = JSDedicatedWorkerGlobalScopePrototype::createStructure(*m_vm, 0, workerGlobalScopePrototype.get()); Strong<JSDedicatedWorkerGlobalScopePrototype> dedicatedContextPrototype(*m_vm, JSDedicatedWorkerGlobalScopePrototype::create(*m_vm, 0, dedicatedContextPrototypeStructure)); Structure* structure = JSDedicatedWorkerGlobalScope::createStructure(*m_vm, 0, dedicatedContextPrototype.get()); m_workerGlobalScopeWrapper.set(*m_vm, JSDedicatedWorkerGlobalScope::create(*m_vm, structure, static_cast<DedicatedWorkerGlobalScope*>(m_workerGlobalScope))); workerGlobalScopePrototypeStructure->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); dedicatedContextPrototypeStructure->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); ASSERT(structure->globalObject() == m_workerGlobalScopeWrapper); ASSERT(m_workerGlobalScopeWrapper->structure()->globalObject() == m_workerGlobalScopeWrapper); workerGlobalScopePrototype->structure()->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); dedicatedContextPrototype->structure()->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); #if ENABLE(SHARED_WORKERS) } else { ASSERT(m_workerGlobalScope->isSharedWorkerGlobalScope()); Structure* sharedContextPrototypeStructure = JSSharedWorkerGlobalScopePrototype::createStructure(*m_vm, 0, workerGlobalScopePrototype.get()); Strong<JSSharedWorkerGlobalScopePrototype> sharedContextPrototype(*m_vm, JSSharedWorkerGlobalScopePrototype::create(*m_vm, 0, sharedContextPrototypeStructure)); Structure* structure = JSSharedWorkerGlobalScope::createStructure(*m_vm, 0, sharedContextPrototype.get()); m_workerGlobalScopeWrapper.set(*m_vm, JSSharedWorkerGlobalScope::create(*m_vm, structure, static_cast<SharedWorkerGlobalScope*>(m_workerGlobalScope))); workerGlobalScopePrototype->structure()->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); sharedContextPrototype->structure()->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get()); #endif } ASSERT(m_workerGlobalScopeWrapper->globalObject() == m_workerGlobalScopeWrapper); ASSERT(asObject(m_workerGlobalScopeWrapper->prototype())->globalObject() == m_workerGlobalScopeWrapper); if (initScriptCallback()) initScriptCallback()(m_workerGlobalScopeWrapper->globalExec(), m_workerGlobalScope->isDedicatedWorkerGlobalScope()); }
String HeapSnapshotBuilder::json(std::function<bool (const HeapSnapshotNode&)> allowNodeCallback) { VM& vm = m_profiler.vm(); DeferGCForAWhile deferGC(vm.heap); // Build a node to identifier map of allowed nodes to use when serializing edges. HashMap<JSCell*, unsigned> allowedNodeIdentifiers; // Build a list of used class names. HashMap<const char*, unsigned> classNameIndexes; classNameIndexes.set("<root>", 0); unsigned nextClassNameIndex = 1; // Build a list of used edge names. HashMap<UniquedStringImpl*, unsigned> edgeNameIndexes; unsigned nextEdgeNameIndex = 0; StringBuilder json; auto appendNodeJSON = [&] (const HeapSnapshotNode& node) { // Let the client decide if they want to allow or disallow certain nodes. if (!allowNodeCallback(node)) return; allowedNodeIdentifiers.set(node.cell, node.identifier); auto result = classNameIndexes.add(node.cell->classInfo()->className, nextClassNameIndex); if (result.isNewEntry) nextClassNameIndex++; unsigned classNameIndex = result.iterator->value; bool isInternal = false; if (!node.cell->isString()) { Structure* structure = node.cell->structure(vm); isInternal = !structure || !structure->globalObject(); } // <nodeId>, <sizeInBytes>, <className>, <optionalInternalBoolean> json.append(','); json.appendNumber(node.identifier); json.append(','); json.appendNumber(node.cell->estimatedSizeInBytes()); json.append(','); json.appendNumber(classNameIndex); json.append(','); json.append(isInternal ? '1' : '0'); }; bool firstEdge = true; auto appendEdgeJSON = [&] (const HeapSnapshotEdge& edge) { if (!firstEdge) json.append(','); firstEdge = false; // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData> json.appendNumber(edge.from.identifier); json.append(','); json.appendNumber(edge.to.identifier); json.append(','); json.appendNumber(edgeTypeToNumber(edge.type)); json.append(','); switch (edge.type) { case EdgeType::Property: case EdgeType::Variable: { auto result = edgeNameIndexes.add(edge.u.name, nextEdgeNameIndex); if (result.isNewEntry) nextEdgeNameIndex++; unsigned edgeNameIndex = result.iterator->value; json.appendNumber(edgeNameIndex); break; } case EdgeType::Index: json.appendNumber(edge.u.index); break; default: // No data for this edge type. json.append('0'); break; } }; json.append('{'); // version json.appendLiteral("\"version\":1"); // nodes json.append(','); json.appendLiteral("\"nodes\":"); json.append('['); json.appendLiteral("0,0,0,0"); // <root> for (HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); snapshot; snapshot = snapshot->previous()) { for (auto& node : snapshot->m_nodes) appendNodeJSON(node); } json.append(']'); // node class names json.append(','); json.appendLiteral("\"nodeClassNames\":"); json.append('['); Vector<const char *> orderedClassNames(classNameIndexes.size()); for (auto& entry : classNameIndexes) orderedClassNames[entry.value] = entry.key; classNameIndexes.clear(); bool firstClassName = true; for (auto& className : orderedClassNames) { if (!firstClassName) json.append(','); firstClassName = false; json.appendQuotedJSONString(className); } orderedClassNames.clear(); json.append(']'); // Process edges. // Replace pointers with identifiers. // Remove any edges that we won't need. m_edges.removeAllMatching([&] (HeapSnapshotEdge& edge) { // If the from cell is null, this means a <root> edge. if (!edge.from.cell) edge.from.identifier = 0; else { auto fromLookup = allowedNodeIdentifiers.find(edge.from.cell); if (fromLookup == allowedNodeIdentifiers.end()) return true; edge.from.identifier = fromLookup->value; } if (!edge.to.cell) edge.to.identifier = 0; else { auto toLookup = allowedNodeIdentifiers.find(edge.to.cell); if (toLookup == allowedNodeIdentifiers.end()) return true; edge.to.identifier = toLookup->value; } return false; }); allowedNodeIdentifiers.clear(); m_edges.shrinkToFit(); // Sort edges based on from identifier. std::sort(m_edges.begin(), m_edges.end(), [&] (const HeapSnapshotEdge& a, const HeapSnapshotEdge& b) { return a.from.identifier < b.from.identifier; }); // edges json.append(','); json.appendLiteral("\"edges\":"); json.append('['); for (auto& edge : m_edges) appendEdgeJSON(edge); json.append(']'); // edge types json.append(','); json.appendLiteral("\"edgeTypes\":"); json.append('['); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Internal)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Property)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Index)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Variable)); json.append(']'); // edge names json.append(','); json.appendLiteral("\"edgeNames\":"); json.append('['); Vector<UniquedStringImpl*> orderedEdgeNames(edgeNameIndexes.size()); for (auto& entry : edgeNameIndexes) orderedEdgeNames[entry.value] = entry.key; edgeNameIndexes.clear(); bool firstEdgeName = true; for (auto& edgeName : orderedEdgeNames) { if (!firstEdgeName) json.append(','); firstEdgeName = false; json.appendQuotedJSONString(edgeName); } orderedEdgeNames.clear(); json.append(']'); json.append('}'); return json.toString(); }
String HeapSnapshotBuilder::json(Function<bool (const HeapSnapshotNode&)> allowNodeCallback) { VM& vm = m_profiler.vm(); DeferGCForAWhile deferGC(vm.heap); // Build a node to identifier map of allowed nodes to use when serializing edges. HashMap<JSCell*, NodeIdentifier> allowedNodeIdentifiers; // Build a list of used class names. HashMap<String, unsigned> classNameIndexes; classNameIndexes.set("<root>"_s, 0); unsigned nextClassNameIndex = 1; // Build a list of labels (this is just a string table). HashMap<String, unsigned> labelIndexes; labelIndexes.set(emptyString(), 0); unsigned nextLabelIndex = 1; // Build a list of used edge names. HashMap<UniquedStringImpl*, unsigned> edgeNameIndexes; unsigned nextEdgeNameIndex = 0; StringBuilder json; auto appendNodeJSON = [&] (const HeapSnapshotNode& node) { // Let the client decide if they want to allow or disallow certain nodes. if (!allowNodeCallback(node)) return; unsigned flags = 0; allowedNodeIdentifiers.set(node.cell, node.identifier); String className = node.cell->classInfo(vm)->className; if (node.cell->isObject() && className == JSObject::info()->className) { flags |= static_cast<unsigned>(NodeFlags::ObjectSubtype); // Skip calculating a class name if this object has a `constructor` own property. // These cases are typically F.prototype objects and we want to treat these as // "Object" in snapshots and not get the name of the prototype's parent. JSObject* object = asObject(node.cell); if (JSGlobalObject* globalObject = object->globalObject(vm)) { ExecState* exec = globalObject->globalExec(); PropertySlot slot(object, PropertySlot::InternalMethodType::VMInquiry); if (!object->getOwnPropertySlot(object, exec, vm.propertyNames->constructor, slot)) className = JSObject::calculatedClassName(object); } } auto result = classNameIndexes.add(className, nextClassNameIndex); if (result.isNewEntry) nextClassNameIndex++; unsigned classNameIndex = result.iterator->value; void* wrappedAddress = 0; unsigned labelIndex = 0; if (!node.cell->isString()) { Structure* structure = node.cell->structure(vm); if (!structure || !structure->globalObject()) flags |= static_cast<unsigned>(NodeFlags::Internal); if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { String nodeLabel; auto it = m_cellLabels.find(node.cell); if (it != m_cellLabels.end()) nodeLabel = it->value; if (nodeLabel.isEmpty()) { if (auto* object = jsDynamicCast<JSObject*>(vm, node.cell)) { if (auto* function = jsDynamicCast<JSFunction*>(vm, object)) nodeLabel = function->calculatedDisplayName(vm); } } String description = descriptionForCell(node.cell); if (description.length()) { if (nodeLabel.length()) nodeLabel.append(' '); nodeLabel.append(description); } if (!nodeLabel.isEmpty() && m_snapshotType == SnapshotType::GCDebuggingSnapshot) { auto result = labelIndexes.add(nodeLabel, nextLabelIndex); if (result.isNewEntry) nextLabelIndex++; labelIndex = result.iterator->value; } wrappedAddress = m_wrappedObjectPointers.get(node.cell); } } // <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>, [<labelIndex>, <cellEddress>, <wrappedAddress>] json.append(','); json.appendNumber(node.identifier); json.append(','); json.appendNumber(node.cell->estimatedSizeInBytes(vm)); json.append(','); json.appendNumber(classNameIndex); json.append(','); json.appendNumber(flags); if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { json.append(','); json.appendNumber(labelIndex); json.appendLiteral(",\"0x"); appendUnsignedAsHex(reinterpret_cast<uintptr_t>(node.cell), json, Lowercase); json.appendLiteral("\",\"0x"); appendUnsignedAsHex(reinterpret_cast<uintptr_t>(wrappedAddress), json, Lowercase); json.append('"'); } }; bool firstEdge = true; auto appendEdgeJSON = [&] (const HeapSnapshotEdge& edge) { if (!firstEdge) json.append(','); firstEdge = false; // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData> json.appendNumber(edge.from.identifier); json.append(','); json.appendNumber(edge.to.identifier); json.append(','); json.appendNumber(edgeTypeToNumber(edge.type)); json.append(','); switch (edge.type) { case EdgeType::Property: case EdgeType::Variable: { auto result = edgeNameIndexes.add(edge.u.name, nextEdgeNameIndex); if (result.isNewEntry) nextEdgeNameIndex++; unsigned edgeNameIndex = result.iterator->value; json.appendNumber(edgeNameIndex); break; } case EdgeType::Index: json.appendNumber(edge.u.index); break; default: // No data for this edge type. json.append('0'); break; } }; json.append('{'); // version json.appendLiteral("\"version\":2"); // type json.append(','); json.appendLiteral("\"type\":"); json.appendQuotedJSONString(snapshotTypeToString(m_snapshotType)); // nodes json.append(','); json.appendLiteral("\"nodes\":"); json.append('['); // <root> if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) json.appendLiteral("0,0,0,0,0,\"0x0\",\"0x0\""); else json.appendLiteral("0,0,0,0"); for (HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); snapshot; snapshot = snapshot->previous()) { for (auto& node : snapshot->m_nodes) appendNodeJSON(node); } json.append(']'); // node class names json.append(','); json.appendLiteral("\"nodeClassNames\":"); json.append('['); Vector<String> orderedClassNames(classNameIndexes.size()); for (auto& entry : classNameIndexes) orderedClassNames[entry.value] = entry.key; classNameIndexes.clear(); bool firstClassName = true; for (auto& className : orderedClassNames) { if (!firstClassName) json.append(','); firstClassName = false; json.appendQuotedJSONString(className); } orderedClassNames.clear(); json.append(']'); // Process edges. // Replace pointers with identifiers. // Remove any edges that we won't need. m_edges.removeAllMatching([&] (HeapSnapshotEdge& edge) { // If the from cell is null, this means a <root> edge. if (!edge.from.cell) edge.from.identifier = 0; else { auto fromLookup = allowedNodeIdentifiers.find(edge.from.cell); if (fromLookup == allowedNodeIdentifiers.end()) { if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) WTFLogAlways("Failed to find node for from-edge cell %p", edge.from.cell); return true; } edge.from.identifier = fromLookup->value; } if (!edge.to.cell) edge.to.identifier = 0; else { auto toLookup = allowedNodeIdentifiers.find(edge.to.cell); if (toLookup == allowedNodeIdentifiers.end()) { if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) WTFLogAlways("Failed to find node for to-edge cell %p", edge.to.cell); return true; } edge.to.identifier = toLookup->value; } return false; }); allowedNodeIdentifiers.clear(); m_edges.shrinkToFit(); // Sort edges based on from identifier. std::sort(m_edges.begin(), m_edges.end(), [&] (const HeapSnapshotEdge& a, const HeapSnapshotEdge& b) { return a.from.identifier < b.from.identifier; }); // edges json.append(','); json.appendLiteral("\"edges\":"); json.append('['); for (auto& edge : m_edges) appendEdgeJSON(edge); json.append(']'); // edge types json.append(','); json.appendLiteral("\"edgeTypes\":"); json.append('['); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Internal)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Property)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Index)); json.append(','); json.appendQuotedJSONString(edgeTypeToString(EdgeType::Variable)); json.append(']'); // edge names json.append(','); json.appendLiteral("\"edgeNames\":"); json.append('['); Vector<UniquedStringImpl*> orderedEdgeNames(edgeNameIndexes.size()); for (auto& entry : edgeNameIndexes) orderedEdgeNames[entry.value] = entry.key; edgeNameIndexes.clear(); bool firstEdgeName = true; for (auto& edgeName : orderedEdgeNames) { if (!firstEdgeName) json.append(','); firstEdgeName = false; json.appendQuotedJSONString(edgeName); } orderedEdgeNames.clear(); json.append(']'); if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { json.append(','); json.appendLiteral("\"roots\":"); json.append('['); HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); bool firstNode = true; for (auto it : m_rootData) { auto snapshotNode = snapshot->nodeForCell(it.key); if (!snapshotNode) { WTFLogAlways("Failed to find snapshot node for cell %p", it.key); continue; } if (!firstNode) json.append(','); firstNode = false; json.appendNumber(snapshotNode.value().identifier); // Maybe we should just always encode the root names. const char* rootName = rootTypeToString(it.value.markReason); auto result = labelIndexes.add(rootName, nextLabelIndex); if (result.isNewEntry) nextLabelIndex++; unsigned labelIndex = result.iterator->value; json.append(','); json.appendNumber(labelIndex); unsigned reachabilityReasonIndex = 0; if (it.value.reachabilityFromOpaqueRootReasons) { auto result = labelIndexes.add(it.value.reachabilityFromOpaqueRootReasons, nextLabelIndex); if (result.isNewEntry) nextLabelIndex++; reachabilityReasonIndex = result.iterator->value; } json.append(','); json.appendNumber(reachabilityReasonIndex); } json.append(']'); } if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) { // internal node descriptions json.append(','); json.appendLiteral("\"labels\":"); json.append('['); Vector<String> orderedLabels(labelIndexes.size()); for (auto& entry : labelIndexes) orderedLabels[entry.value] = entry.key; labelIndexes.clear(); bool firstLabel = true; for (auto& label : orderedLabels) { if (!firstLabel) json.append(','); firstLabel = false; json.appendQuotedJSONString(label); } orderedLabels.clear(); json.append(']'); } json.append('}'); return json.toString(); }