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
40namespace JSC {
41
42static const char* rootTypeToString(SlotVisitor::RootMarkReason);
43
44NodeIdentifier HeapSnapshotBuilder::nextAvailableObjectIdentifier = 1;
45NodeIdentifier HeapSnapshotBuilder::getNextObjectIdentifier() { return nextAvailableObjectIdentifier++; }
46void HeapSnapshotBuilder::resetNextAvailableObjectIdentifier() { HeapSnapshotBuilder::nextAvailableObjectIdentifier = 1; }
47
48HeapSnapshotBuilder::HeapSnapshotBuilder(HeapProfiler& profiler, SnapshotType type)
49 : HeapAnalyzer()
50 , m_profiler(profiler)
51 , m_snapshotType(type)
52{
53}
54
55HeapSnapshotBuilder::~HeapSnapshotBuilder()
56{
57 if (m_snapshotType == SnapshotType::GCDebuggingSnapshot)
58 m_profiler.clearSnapshots();
59}
60
61void 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
81void 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 std::lock_guard<Lock> lock(m_buildingNodeMutex);
92 m_snapshot->appendNode(HeapSnapshotNode(cell, getNextObjectIdentifier()));
93}
94
95void 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 std::lock_guard<Lock> lock(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
118void HeapSnapshotBuilder::analyzePropertyNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* propertyName)
119{
120 ASSERT(m_profiler.activeHeapAnalyzer() == this);
121 ASSERT(to);
122
123 std::lock_guard<Lock> lock(m_buildingEdgeMutex);
124
125 m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Property, propertyName));
126}
127
128void HeapSnapshotBuilder::analyzeVariableNameEdge(JSCell* from, JSCell* to, UniquedStringImpl* variableName)
129{
130 ASSERT(m_profiler.activeHeapAnalyzer() == this);
131 ASSERT(to);
132
133 std::lock_guard<Lock> lock(m_buildingEdgeMutex);
134
135 m_edges.append(HeapSnapshotEdge(from, to, EdgeType::Variable, variableName));
136}
137
138void HeapSnapshotBuilder::analyzeIndexEdge(JSCell* from, JSCell* to, uint32_t index)
139{
140 ASSERT(m_profiler.activeHeapAnalyzer() == this);
141 ASSERT(to);
142
143 std::lock_guard<Lock> lock(m_buildingEdgeMutex);
144
145 m_edges.append(HeapSnapshotEdge(from, to, index));
146}
147
148void HeapSnapshotBuilder::setOpaqueRootReachabilityReasonForCell(JSCell* cell, const char* reason)
149{
150 if (!reason || !*reason || m_snapshotType != SnapshotType::GCDebuggingSnapshot)
151 return;
152
153 std::lock_guard<Lock> lock(m_buildingEdgeMutex);
154
155 m_rootData.ensure(cell, [] () -> RootData {
156 return { };
157 }).iterator->value.reachabilityFromOpaqueRootReasons = reason;
158}
159
160void HeapSnapshotBuilder::setWrappedObjectForCell(JSCell* cell, void* wrappedPtr)
161{
162 m_wrappedObjectPointers.set(cell, wrappedPtr);
163}
164
165bool 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
263enum class NodeFlags {
264 Internal = 1 << 0,
265 ObjectSubtype = 1 << 1,
266};
267
268static uint8_t edgeTypeToNumber(EdgeType type)
269{
270 return static_cast<uint8_t>(type);
271}
272
273static 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
289static 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
301static 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
337String HeapSnapshotBuilder::json()
338{
339 return json([] (const HeapSnapshotNode&) { return true; });
340}
341
342void HeapSnapshotBuilder::setLabelForCell(JSCell* cell, const String& label)
343{
344 m_cellLabels.set(cell, label);
345}
346
347String 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
363String 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 appendUnsignedAsHex(reinterpret_cast<uintptr_t>(node.cell), json, Lowercase);
468 json.appendLiteral("\",\"0x");
469 appendUnsignedAsHex(reinterpret_cast<uintptr_t>(wrappedAddress), json, 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
702