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