Remove std::lock_guard
[WebKit-https.git] / Source / JavaScriptCore / heap / HeapSnapshotBuilder.cpp
1 /*
2  * Copyright (C) 2016-2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "HeapSnapshotBuilder.h"
28
29 #include "DeferGC.h"
30 #include "Heap.h"
31 #include "HeapProfiler.h"
32 #include "HeapSnapshot.h"
33 #include "JSCInlines.h"
34 #include "JSCast.h"
35 #include "PreventCollectionScope.h"
36 #include "VM.h"
37 #include <wtf/HexNumber.h>
38 #include <wtf/text/StringBuilder.h>
39
40 namespace JSC {
41
42 static const char* rootTypeToString(SlotVisitor::RootMarkReason);
43
44 NodeIdentifier HeapSnapshotBuilder::nextAvailableObjectIdentifier = 1;
45 NodeIdentifier HeapSnapshotBuilder::getNextObjectIdentifier() { return nextAvailableObjectIdentifier++; }
46 void HeapSnapshotBuilder::resetNextAvailableObjectIdentifier() { HeapSnapshotBuilder::nextAvailableObjectIdentifier = 1; }
47
48 HeapSnapshotBuilder::HeapSnapshotBuilder(HeapProfiler& profiler, SnapshotType type)
49     : HeapAnalyzer()
50     , m_profiler(profiler)
51     , m_snapshotType(type)
52 {
53 }
54
55 HeapSnapshotBuilder::~HeapSnapshotBuilder()
56 {
57     if (m_snapshotType == SnapshotType::GCDebuggingSnapshot)
58         m_profiler.clearSnapshots();
59 }
60
61 void HeapSnapshotBuilder::buildSnapshot()
62 {
63     // GCDebuggingSnapshot are always full snapshots, so clear any existing snapshots.
64     if (m_snapshotType == SnapshotType::GCDebuggingSnapshot)
65         m_profiler.clearSnapshots();
66
67     PreventCollectionScope preventCollectionScope(m_profiler.vm().heap);
68
69     m_snapshot = makeUnique<HeapSnapshot>(m_profiler.mostRecentSnapshot());
70     {
71         ASSERT(!m_profiler.activeHeapAnalyzer());
72         m_profiler.setActiveHeapAnalyzer(this);
73         m_profiler.vm().heap.collectNow(Sync, CollectionScope::Full);
74         m_profiler.setActiveHeapAnalyzer(nullptr);
75     }
76     m_snapshot->finalize();
77
78     m_profiler.appendSnapshot(WTFMove(m_snapshot));
79 }
80
81 void HeapSnapshotBuilder::analyzeNode(JSCell* cell)
82 {
83     ASSERT(m_profiler.activeHeapAnalyzer() == this);
84
85     ASSERT(m_profiler.vm().heap.isMarked(cell));
86
87     NodeIdentifier identifier;
88     if (previousSnapshotHasNodeForCell(cell, identifier))
89         return;
90
91     auto locker = holdLock(m_buildingNodeMutex);
92     m_snapshot->appendNode(HeapSnapshotNode(cell, getNextObjectIdentifier()));
93 }
94
95 void HeapSnapshotBuilder::analyzeEdge(JSCell* from, JSCell* to, SlotVisitor::RootMarkReason rootMarkReason)
96 {
97     ASSERT(m_profiler.activeHeapAnalyzer() == this);
98     ASSERT(to);
99
100     // Avoid trivial edges.
101     if (from == to)
102         return;
103
104     auto locker = holdLock(m_buildingEdgeMutex);
105
106     if (m_snapshotType == SnapshotType::GCDebuggingSnapshot && !from) {
107         if (rootMarkReason == SlotVisitor::RootMarkReason::None && m_snapshotType == SnapshotType::GCDebuggingSnapshot)
108             WTFLogAlways("Cell %p is a root but no root marking reason was supplied", to);
109
110         m_rootData.ensure(to, [] () -> RootData {
111             return { };
112         }).iterator->value.markReason = rootMarkReason;
113     }
114
115     m_edges.append(HeapSnapshotEdge(from, to));
116 }
117
118 void HeapSnapshotBuilder::analyzePropertyNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* propertyName)
119 {
120     ASSERT(m_profiler.activeHeapAnalyzer() == this);
121     ASSERT(to);
122
123     auto locker = holdLock(m_buildingEdgeMutex);
124
125     m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Property, propertyName));
126 }
127
128 void HeapSnapshotBuilder::analyzeVariableNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* variableName)
129 {
130     ASSERT(m_profiler.activeHeapAnalyzer() == this);
131     ASSERT(to);
132
133     auto locker = holdLock(m_buildingEdgeMutex);
134
135     m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Variable, variableName));
136 }
137
138 void HeapSnapshotBuilder::analyzeIndexEdge(JSCell* from, JSCell* to, uint32_t index)
139 {
140     ASSERT(m_profiler.activeHeapAnalyzer() == this);
141     ASSERT(to);
142
143     auto locker = holdLock(m_buildingEdgeMutex);
144
145     m_edges.append(HeapSnapshotEdge(from, to, index));
146 }
147
148 void HeapSnapshotBuilder::setOpaqueRootReachabilityReasonForCell(JSCell* cell, const char* reason)
149 {
150     if (!reason || !*reason || m_snapshotType != SnapshotType::GCDebuggingSnapshot)
151         return;
152
153     auto locker = holdLock(m_buildingEdgeMutex);
154
155     m_rootData.ensure(cell, [] () -> RootData {
156         return { };
157     }).iterator->value.reachabilityFromOpaqueRootReasons = reason;
158 }
159
160 void HeapSnapshotBuilder::setWrappedObjectForCell(JSCell* cell, void* wrappedPtr)
161 {
162     m_wrappedObjectPointers.set(cell, wrappedPtr);
163 }
164
165 bool HeapSnapshotBuilder::previousSnapshotHasNodeForCell(JSCell* cell, NodeIdentifier& identifier)
166 {
167     if (!m_snapshot->previous())
168         return false;
169
170     auto existingNode = m_snapshot->previous()->nodeForCell(cell);
171     if (existingNode) {
172         identifier = existingNode.value().identifier;
173         return true;
174     }
175
176     return false;
177 }
178
179 // Heap Snapshot JSON Format:
180 //
181 //  Inspector snapshots:
182 //
183 //   {
184 //      "version": 2,
185 //      "type": "Inspector",
186 //      // [<address>, <labelIndex>, <wrappedAddress>] only present in GCDebuggingSnapshot-type snapshots
187 //      "nodes": [
188 //          <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>
189 //          <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>
190 //          ...
191 //      ],
192 //      "nodeClassNames": [
193 //          "string", "Structure", "Object", ...
194 //      ],
195 //      "edges": [
196 //          <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData>,
197 //          <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData>,
198 //          ...
199 //      ],
200 //      "edgeTypes": [
201 //          "Internal", "Property", "Index", "Variable"
202 //      ],
203 //      "edgeNames": [
204 //          "propertyName", "variableName", ...
205 //      ]
206 //   }
207 //
208 //  GC heap debugger snapshots:
209 //
210 //   {
211 //      "version": 2,
212 //      "type": "GCDebugging",
213 //      "nodes": [
214 //          <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>, <labelIndex>, <cellEddress>, <wrappedAddress>,
215 //          <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>, <labelIndex>, <cellEddress>, <wrappedAddress>,
216 //          ...
217 //      ],
218 //      "nodeClassNames": [
219 //          "string", "Structure", "Object", ...
220 //      ],
221 //      "edges": [
222 //          <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData>,
223 //          <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData>,
224 //          ...
225 //      ],
226 //      "edgeTypes": [
227 //          "Internal", "Property", "Index", "Variable"
228 //      ],
229 //      "edgeNames": [
230 //          "propertyName", "variableName", ...
231 //      ],
232 //      "roots" : [
233 //          <nodeId>, <rootReasonIndex>, <reachabilityReasonIndex>,
234 //          <nodeId>, <rootReasonIndex>, <reachabilityReasonIndex>,
235 //          ... // <nodeId> may be repeated
236 //      ],
237 //      "labels" : [
238 //          "foo", "bar", ...
239 //      ]
240 //   }
241 //
242 // Notes:
243 //
244 //     <nodeClassNameIndex>
245 //       - index into the "nodeClassNames" list.
246 //
247 //     <flags>
248 //       - 0b0000 - no flags
249 //       - 0b0001 - internal instance
250 //       - 0b0010 - Object subclassification
251 //
252 //     <edgeTypeIndex>
253 //       - index into the "edgeTypes" list.
254 //
255 //     <edgeExtraData>
256 //       - for Internal edges this should be ignored (0).
257 //       - for Index edges this is the index value.
258 //       - for Property or Variable edges this is an index into the "edgeNames" list.
259 //
260 //      <rootReasonIndex>
261 //       - index into the "labels" list.
262
263 enum class NodeFlags {
264     Internal      = 1 << 0,
265     ObjectSubtype = 1 << 1,
266 };
267
268 static uint8_t edgeTypeToNumber(EdgeType type)
269 {
270     return static_cast<uint8_t>(type);
271 }
272
273 static const char* edgeTypeToString(EdgeType type)
274 {
275     switch (type) {
276     case EdgeType::Internal:
277         return "Internal";
278     case EdgeType::Property:
279         return "Property";
280     case EdgeType::Index:
281         return "Index";
282     case EdgeType::Variable:
283         return "Variable";
284     }
285     ASSERT_NOT_REACHED();
286     return "Internal";
287 }
288
289 static const char* snapshotTypeToString(HeapSnapshotBuilder::SnapshotType type)
290 {
291     switch (type) {
292     case HeapSnapshotBuilder::SnapshotType::InspectorSnapshot:
293         return "Inspector";
294     case HeapSnapshotBuilder::SnapshotType::GCDebuggingSnapshot:
295         return "GCDebugging";
296     }
297     ASSERT_NOT_REACHED();
298     return "Inspector";
299 }
300
301 static const char* rootTypeToString(SlotVisitor::RootMarkReason type)
302 {
303     switch (type) {
304     case SlotVisitor::RootMarkReason::None:
305         return "None";
306     case SlotVisitor::RootMarkReason::ConservativeScan:
307         return "Conservative scan";
308     case SlotVisitor::RootMarkReason::StrongReferences:
309         return "Strong references";
310     case SlotVisitor::RootMarkReason::ProtectedValues:
311         return "Protected values";
312     case SlotVisitor::RootMarkReason::MarkListSet:
313         return "Mark list set";
314     case SlotVisitor::RootMarkReason::VMExceptions:
315         return "VM exceptions";
316     case SlotVisitor::RootMarkReason::StrongHandles:
317         return "Strong handles";
318     case SlotVisitor::RootMarkReason::Debugger:
319         return "Debugger";
320     case SlotVisitor::RootMarkReason::JITStubRoutines:
321         return "JIT stub routines";
322     case SlotVisitor::RootMarkReason::WeakSets:
323         return "Weak sets";
324     case SlotVisitor::RootMarkReason::Output:
325         return "Output";
326     case SlotVisitor::RootMarkReason::DFGWorkLists:
327         return "DFG work lists";
328     case SlotVisitor::RootMarkReason::CodeBlocks:
329         return "Code blocks";
330     case SlotVisitor::RootMarkReason::DOMGCOutput:
331         return "DOM GC output";
332     }
333     ASSERT_NOT_REACHED();
334     return "None";
335 }
336
337 String HeapSnapshotBuilder::json()
338 {
339     return json([] (const HeapSnapshotNode&) { return true; });
340 }
341
342 void HeapSnapshotBuilder::setLabelForCell(JSCell* cell, const String& label)
343 {
344     m_cellLabels.set(cell, label);
345 }
346
347 String HeapSnapshotBuilder::descriptionForCell(JSCell *cell) const
348 {
349     if (cell->isString())
350         return emptyString(); // FIXME: get part of string.
351
352     VM& vm = m_profiler.vm();
353     Structure* structure = cell->structure(vm);
354
355     if (structure->classInfo()->isSubClassOf(Structure::info())) {
356         Structure* cellAsStructure = jsCast<Structure*>(cell);
357         return cellAsStructure->classInfo()->className;
358     }
359
360     return emptyString();
361 }
362
363 String HeapSnapshotBuilder::json(Function<bool (const HeapSnapshotNode&)> allowNodeCallback)
364 {
365     VM& vm = m_profiler.vm();
366     DeferGCForAWhile deferGC(vm.heap);
367
368     // Build a node to identifier map of allowed nodes to use when serializing edges.
369     HashMap<JSCell*, NodeIdentifier> allowedNodeIdentifiers;
370
371     // Build a list of used class names.
372     HashMap<String, unsigned> classNameIndexes;
373     classNameIndexes.set("<root>"_s, 0);
374     unsigned nextClassNameIndex = 1;
375
376     // Build a list of labels (this is just a string table).
377     HashMap<String, unsigned> labelIndexes;
378     labelIndexes.set(emptyString(), 0);
379     unsigned nextLabelIndex = 1;
380
381     // Build a list of used edge names.
382     HashMap<UniquedStringImpl*, unsigned> edgeNameIndexes;
383     unsigned nextEdgeNameIndex = 0;
384
385     StringBuilder json;
386
387     auto appendNodeJSON = [&] (const HeapSnapshotNode& node) {
388         // Let the client decide if they want to allow or disallow certain nodes.
389         if (!allowNodeCallback(node))
390             return;
391
392         unsigned flags = 0;
393
394         allowedNodeIdentifiers.set(node.cell, node.identifier);
395
396         String className = node.cell->classInfo(vm)->className;
397         if (node.cell->isObject() && className == JSObject::info()->className) {
398             flags |= static_cast<unsigned>(NodeFlags::ObjectSubtype);
399
400             // Skip calculating a class name if this object has a `constructor` own property.
401             // These cases are typically F.prototype objects and we want to treat these as
402             // "Object" in snapshots and not get the name of the prototype's parent.
403             JSObject* object = asObject(node.cell);
404             if (JSGlobalObject* globalObject = object->globalObject(vm)) {
405                 PropertySlot slot(object, PropertySlot::InternalMethodType::VMInquiry);
406                 if (!object->getOwnPropertySlot(object, globalObject, vm.propertyNames->constructor, slot))
407                     className = JSObject::calculatedClassName(object);
408             }
409         }
410
411         auto result = classNameIndexes.add(className, nextClassNameIndex);
412         if (result.isNewEntry)
413             nextClassNameIndex++;
414         unsigned classNameIndex = result.iterator->value;
415
416         void* wrappedAddress = 0;
417         unsigned labelIndex = 0;
418         if (!node.cell->isString() && !node.cell->isBigInt()) {
419             Structure* structure = node.cell->structure(vm);
420             if (!structure || !structure->globalObject())
421                 flags |= static_cast<unsigned>(NodeFlags::Internal);
422
423             if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) {
424                 String nodeLabel;
425                 auto it = m_cellLabels.find(node.cell);
426                 if (it != m_cellLabels.end())
427                     nodeLabel = it->value;
428
429                 if (nodeLabel.isEmpty()) {
430                     if (auto* object = jsDynamicCast<JSObject*>(vm, node.cell)) {
431                         if (auto* function = jsDynamicCast<JSFunction*>(vm, object))
432                             nodeLabel = function->calculatedDisplayName(vm);
433                     }
434                 }
435
436                 String description = descriptionForCell(node.cell);
437                 if (description.length()) {
438                     if (nodeLabel.length())
439                         nodeLabel.append(' ');
440                     nodeLabel.append(description);
441                 }
442
443                 if (!nodeLabel.isEmpty() && m_snapshotType == SnapshotType::GCDebuggingSnapshot) {
444                     auto result = labelIndexes.add(nodeLabel, nextLabelIndex);
445                     if (result.isNewEntry)
446                         nextLabelIndex++;
447                     labelIndex = result.iterator->value;
448                 }
449
450                 wrappedAddress = m_wrappedObjectPointers.get(node.cell);
451             }
452         }
453
454         // <nodeId>, <sizeInBytes>, <nodeClassNameIndex>, <flags>, [<labelIndex>, <cellEddress>, <wrappedAddress>]
455         json.append(',');
456         json.appendNumber(node.identifier);
457         json.append(',');
458         json.appendNumber(node.cell->estimatedSizeInBytes(vm));
459         json.append(',');
460         json.appendNumber(classNameIndex);
461         json.append(',');
462         json.appendNumber(flags);
463         if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) {
464             json.append(',');
465             json.appendNumber(labelIndex);
466             json.appendLiteral(",\"0x");
467             json.append(hex(reinterpret_cast<uintptr_t>(node.cell), Lowercase));
468             json.appendLiteral("\",\"0x");
469             json.append(hex(reinterpret_cast<uintptr_t>(wrappedAddress), Lowercase));
470             json.append('"');
471         }
472     };
473
474     bool firstEdge = true;
475     auto appendEdgeJSON = [&] (const HeapSnapshotEdge& edge) {
476         if (!firstEdge)
477             json.append(',');
478         firstEdge = false;
479
480         // <fromNodeId>, <toNodeId>, <edgeTypeIndex>, <edgeExtraData>
481         json.appendNumber(edge.from.identifier);
482         json.append(',');
483         json.appendNumber(edge.to.identifier);
484         json.append(',');
485         json.appendNumber(edgeTypeToNumber(edge.type));
486         json.append(',');
487         switch (edge.type) {
488         case EdgeType::Property:
489         case EdgeType::Variable: {
490             auto result = edgeNameIndexes.add(edge.u.name, nextEdgeNameIndex);
491             if (result.isNewEntry)
492                 nextEdgeNameIndex++;
493             unsigned edgeNameIndex = result.iterator->value;
494             json.appendNumber(edgeNameIndex);
495             break;
496         }
497         case EdgeType::Index:
498             json.appendNumber(edge.u.index);
499             break;
500         default:
501             // No data for this edge type.
502             json.append('0');
503             break;
504         }
505     };
506
507     json.append('{');
508
509     // version
510     json.appendLiteral("\"version\":2");
511
512     // type
513     json.append(',');
514     json.appendLiteral("\"type\":");
515     json.appendQuotedJSONString(snapshotTypeToString(m_snapshotType));
516
517     // nodes
518     json.append(',');
519     json.appendLiteral("\"nodes\":");
520     json.append('[');
521     // <root>
522     if (m_snapshotType == SnapshotType::GCDebuggingSnapshot)
523         json.appendLiteral("0,0,0,0,0,\"0x0\",\"0x0\"");
524     else
525         json.appendLiteral("0,0,0,0");
526
527     for (HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot(); snapshot; snapshot = snapshot->previous()) {
528         for (auto& node : snapshot->m_nodes)
529             appendNodeJSON(node);
530     }
531     json.append(']');
532
533     // node class names
534     json.append(',');
535     json.appendLiteral("\"nodeClassNames\":");
536     json.append('[');
537     Vector<String> orderedClassNames(classNameIndexes.size());
538     for (auto& entry : classNameIndexes)
539         orderedClassNames[entry.value] = entry.key;
540     classNameIndexes.clear();
541     bool firstClassName = true;
542     for (auto& className : orderedClassNames) {
543         if (!firstClassName)
544             json.append(',');
545         firstClassName = false;
546         json.appendQuotedJSONString(className);
547     }
548     orderedClassNames.clear();
549     json.append(']');
550
551     // Process edges.
552     // Replace pointers with identifiers.
553     // Remove any edges that we won't need.
554     m_edges.removeAllMatching([&] (HeapSnapshotEdge& edge) {
555         // If the from cell is null, this means a <root> edge.
556         if (!edge.from.cell)
557             edge.from.identifier = 0;
558         else {
559             auto fromLookup = allowedNodeIdentifiers.find(edge.from.cell);
560             if (fromLookup == allowedNodeIdentifiers.end()) {
561                 if (m_snapshotType == SnapshotType::GCDebuggingSnapshot)
562                     WTFLogAlways("Failed to find node for from-edge cell %p", edge.from.cell);
563                 return true;
564             }
565             edge.from.identifier = fromLookup->value;
566         }
567
568         if (!edge.to.cell)
569             edge.to.identifier = 0;
570         else {
571             auto toLookup = allowedNodeIdentifiers.find(edge.to.cell);
572             if (toLookup == allowedNodeIdentifiers.end()) {
573                 if (m_snapshotType == SnapshotType::GCDebuggingSnapshot)
574                     WTFLogAlways("Failed to find node for to-edge cell %p", edge.to.cell);
575                 return true;
576             }
577             edge.to.identifier = toLookup->value;
578         }
579
580         return false;
581     });
582
583     allowedNodeIdentifiers.clear();
584     m_edges.shrinkToFit();
585
586     // Sort edges based on from identifier.
587     std::sort(m_edges.begin(), m_edges.end(), [&] (const HeapSnapshotEdge& a, const HeapSnapshotEdge& b) {
588         return a.from.identifier < b.from.identifier;
589     });
590
591     // edges
592     json.append(',');
593     json.appendLiteral("\"edges\":");
594     json.append('[');
595     for (auto& edge : m_edges)
596         appendEdgeJSON(edge);
597     json.append(']');
598
599     // edge types
600     json.append(',');
601     json.appendLiteral("\"edgeTypes\":");
602     json.append('[');
603     json.appendQuotedJSONString(edgeTypeToString(EdgeType::Internal));
604     json.append(',');
605     json.appendQuotedJSONString(edgeTypeToString(EdgeType::Property));
606     json.append(',');
607     json.appendQuotedJSONString(edgeTypeToString(EdgeType::Index));
608     json.append(',');
609     json.appendQuotedJSONString(edgeTypeToString(EdgeType::Variable));
610     json.append(']');
611
612     // edge names
613     json.append(',');
614     json.appendLiteral("\"edgeNames\":");
615     json.append('[');
616     Vector<UniquedStringImpl*> orderedEdgeNames(edgeNameIndexes.size());
617     for (auto& entry : edgeNameIndexes)
618         orderedEdgeNames[entry.value] = entry.key;
619     edgeNameIndexes.clear();
620     bool firstEdgeName = true;
621     for (auto& edgeName : orderedEdgeNames) {
622         if (!firstEdgeName)
623             json.append(',');
624         firstEdgeName = false;
625         json.appendQuotedJSONString(edgeName);
626     }
627     orderedEdgeNames.clear();
628     json.append(']');
629
630     if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) {
631         json.append(',');
632         json.appendLiteral("\"roots\":");
633         json.append('[');
634
635         HeapSnapshot* snapshot = m_profiler.mostRecentSnapshot();
636
637         bool firstNode = true;
638         for (auto it : m_rootData) {
639             auto snapshotNode = snapshot->nodeForCell(it.key);
640             if (!snapshotNode) {
641                 WTFLogAlways("Failed to find snapshot node for cell %p", it.key);
642                 continue;
643             }
644
645             if (!firstNode)
646                 json.append(',');
647
648             firstNode = false;
649             json.appendNumber(snapshotNode.value().identifier);
650
651             // Maybe we should just always encode the root names.
652             const char* rootName = rootTypeToString(it.value.markReason);
653             auto result = labelIndexes.add(rootName, nextLabelIndex);
654             if (result.isNewEntry)
655                 nextLabelIndex++;
656             unsigned labelIndex = result.iterator->value;
657             json.append(',');
658             json.appendNumber(labelIndex);
659
660             unsigned reachabilityReasonIndex = 0;
661             if (it.value.reachabilityFromOpaqueRootReasons) {
662                 auto result = labelIndexes.add(it.value.reachabilityFromOpaqueRootReasons, nextLabelIndex);
663                 if (result.isNewEntry)
664                     nextLabelIndex++;
665                 reachabilityReasonIndex = result.iterator->value;
666             }
667             json.append(',');
668             json.appendNumber(reachabilityReasonIndex);
669         }
670
671         json.append(']');
672     }
673
674     if (m_snapshotType == SnapshotType::GCDebuggingSnapshot) {
675         // internal node descriptions
676         json.append(',');
677         json.appendLiteral("\"labels\":");
678         json.append('[');
679
680         Vector<String> orderedLabels(labelIndexes.size());
681         for (auto& entry : labelIndexes)
682             orderedLabels[entry.value] = entry.key;
683         labelIndexes.clear();
684         bool firstLabel = true;
685         for (auto& label : orderedLabels) {
686             if (!firstLabel)
687                 json.append(',');
688
689             firstLabel = false;
690             json.appendQuotedJSONString(label);
691         }
692         orderedLabels.clear();
693
694         json.append(']');
695     }
696
697     json.append('}');
698     return json.toString();
699 }
700
701 } // namespace JSC