From 40c34606a7d02287e05f78bf4fcb1081a9cbc33b Mon Sep 17 00:00:00 2001 From: Ulan Degenbaev Date: Wed, 19 Jul 2017 15:27:45 +0200 Subject: [PATCH] [heap] Instrument mark-compact to track retaining paths for debugging. This patch adds a new intrinsic: %DebugTrackRetainingPath(object). Calling the intrinsic in JS code saves a weak reference to the given object in GC internal table of tracked objects. Each subsequent full GC prints to stdout the retaining path for each tracked object (if it is still alive). The retaining path is the real path that the marker took from the root set to the tracked object. This is useful for investigating of memory leaks: 1) Add %DebugTrackRetainingPath(leaking_object) in JS code. For example: function foo() { let x = { bar: "bar"}; %DebugTrackRetainingPath(x); return () => { return x; } } let closure = foo(); gc(); 2) Run d8 with --allow-natives-syntax --track-retaining-path --expose-gc. 3) Check the retaining path in stdout. For more detailed inspection, run d8 in gdb and set breakpoint in v8: :internal::Heap::PrintRetainingPath. Change-Id: I01a0faac1e009bc6c321fa75613900b49d2b036f Reviewed-on: https://chromium-review.googlesource.com/575972 Commit-Queue: Ulan Degenbaev Reviewed-by: Michael Lippautz Cr-Commit-Position: refs/heads/master@{#46766} --- src/flag-definitions.h | 4 ++ src/heap/heap.cc | 103 ++++++++++++++++++++++++++++- src/heap/heap.h | 25 ++++++- src/heap/incremental-marking.cc | 6 +- src/heap/mark-compact-inl.h | 15 ++++- src/heap/mark-compact.cc | 34 ++++++---- src/heap/mark-compact.h | 5 +- src/heap/objects-visiting-inl.h | 15 +++-- src/objects-printer.cc | 24 ++++--- src/objects/shared-function-info.h | 3 + src/runtime/runtime-test.cc | 11 +++ src/runtime/runtime.h | 1 + src/visitors.h | 4 +- 13 files changed, 213 insertions(+), 37 deletions(-) diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 1e80dd4cf6..dae42235ad 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -650,11 +650,15 @@ DEFINE_BOOL(track_gc_object_stats, false, "track object counts and memory usage") DEFINE_BOOL(trace_gc_object_stats, false, "trace object counts and memory usage") +DEFINE_BOOL(track_retaining_path, false, + "enable support for tracking retaining path") DEFINE_INT(gc_stats, 0, "Used by tracing internally to enable gc statistics") DEFINE_IMPLICATION(trace_gc_object_stats, track_gc_object_stats) DEFINE_VALUE_IMPLICATION(track_gc_object_stats, gc_stats, 1) DEFINE_VALUE_IMPLICATION(trace_gc_object_stats, gc_stats, 1) DEFINE_NEG_IMPLICATION(trace_gc_object_stats, incremental_marking) +DEFINE_NEG_IMPLICATION(track_retaining_path, incremental_marking) +DEFINE_NEG_IMPLICATION(track_retaining_path, concurrent_marking) DEFINE_BOOL(track_detached_contexts, true, "track native contexts that are expected to be garbage collected") DEFINE_BOOL(trace_detached_contexts, false, diff --git a/src/heap/heap.cc b/src/heap/heap.cc index 6d264879a5..56895e1a1d 100644 --- a/src/heap/heap.cc +++ b/src/heap/heap.cc @@ -407,6 +407,99 @@ void Heap::ReportStatisticsAfterGC() { } } +void Heap::AddRetainingPathTarget(Handle object) { + if (!FLAG_track_retaining_path) { + PrintF("Retaining path tracking requires --trace-retaining-path\n"); + } else { + Handle array = WeakFixedArray::Add( + handle(retaining_path_targets(), isolate()), object); + set_retaining_path_targets(*array); + } +} + +bool Heap::IsRetainingPathTarget(HeapObject* object) { + WeakFixedArray::Iterator it(retaining_path_targets()); + HeapObject* target; + while ((target = it.Next()) != nullptr) { + if (target == object) return true; + } + return false; +} + +namespace { +const char* RootToString(Root root) { + switch (root) { +#define ROOT_CASE(root_id, ignore, description) \ + case Root::root_id: \ + return description; + ROOT_ID_LIST(ROOT_CASE) +#undef ROOT_CASE + case Root::kCodeFlusher: + return "(Code flusher)"; + case Root::kPartialSnapshotCache: + return "(Partial snapshot cache)"; + case Root::kWeakCollections: + return "(Weak collections)"; + case Root::kWrapperTracing: + return "(Wrapper tracing)"; + case Root::kUnknown: + return "(Unknown)"; + } + UNREACHABLE(); + return nullptr; +} +} // namespace + +void Heap::PrintRetainingPath(HeapObject* target) { + PrintF("\n\n\n"); + PrintF("#################################################\n"); + PrintF("Retaining path for %p:\n", static_cast(target)); + HeapObject* object = target; + std::vector retaining_path; + Root root = Root::kUnknown; + while (true) { + retaining_path.push_back(object); + if (retainer_.count(object)) { + object = retainer_[object]; + } else { + if (retaining_root_.count(object)) { + root = retaining_root_[object]; + } + break; + } + } + int distance = static_cast(retaining_path.size()); + for (auto object : retaining_path) { + PrintF("\n"); + PrintF("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + PrintF("Distance from root %d: ", distance); + object->ShortPrint(); + PrintF("\n"); +#ifdef OBJECT_PRINT + object->Print(); + PrintF("\n"); +#endif + --distance; + } + PrintF("\n"); + PrintF("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + PrintF("Root: %s\n", RootToString(root)); + PrintF("-------------------------------------------------\n"); +} + +void Heap::AddRetainer(HeapObject* retainer, HeapObject* object) { + retainer_[object] = retainer; + if (IsRetainingPathTarget(object)) { + PrintRetainingPath(object); + } +} + +void Heap::AddRetainingRoot(Root root, HeapObject* object) { + retaining_root_[object] = root; + if (IsRetainingPathTarget(object)) { + PrintRetainingPath(object); + } +} void Heap::IncrementDeferredCount(v8::Isolate::UseCounterFeature feature) { deferred_counters_[feature]++; @@ -452,6 +545,10 @@ void Heap::GarbageCollectionPrologue() { } CheckNewSpaceExpansionCriteria(); UpdateNewSpaceAllocationCounter(); + if (FLAG_track_retaining_path) { + retainer_.clear(); + retaining_root_.clear(); + } } size_t Heap::SizeOfObjects() { @@ -966,6 +1063,7 @@ void Heap::CollectAllAvailableGarbage(GarbageCollectionReason gc_reason) { break; } } + set_current_gc_flags(kNoGCFlags); new_space_->Shrink(); UncommitFromSpace(); @@ -2859,6 +2957,7 @@ void Heap::CreateInitialObjects() { set_detached_contexts(empty_fixed_array()); set_retained_maps(ArrayList::cast(empty_fixed_array())); + set_retaining_path_targets(undefined_value()); set_weak_object_to_code_table(*WeakHashTable::New(isolate(), 16, TENURED)); @@ -2994,6 +3093,7 @@ bool Heap::RootCanBeWrittenAfterInitialization(Heap::RootListIndex root_index) { case kWeakObjectToCodeTableRootIndex: case kWeakNewSpaceObjectToCodeListRootIndex: case kRetainedMapsRootIndex: + case kRetainingPathTargetsRootIndex: case kCodeCoverageListRootIndex: case kNoScriptSharedFunctionInfosRootIndex: case kWeakStackTraceListRootIndex: @@ -4508,7 +4608,6 @@ bool Heap::PerformIdleTimeAction(GCIdleTimeAction action, return result; } - void Heap::IdleNotificationEpilogue(GCIdleTimeAction action, GCIdleTimeHeapState heap_state, double start_ms, double deadline_in_ms) { @@ -5908,7 +6007,7 @@ void Heap::RegisterExternallyReferencedObject(Object** object) { incremental_marking()->WhiteToGreyAndPush(heap_object); } else { DCHECK(mark_compact_collector()->in_use()); - mark_compact_collector()->MarkObject(heap_object); + mark_compact_collector()->MarkExternallyReferencedObject(heap_object); } } diff --git a/src/heap/heap.h b/src/heap/heap.h index 0c2e55d669..fd77d46483 100644 --- a/src/heap/heap.h +++ b/src/heap/heap.h @@ -203,6 +203,7 @@ using v8::MemoryPressureLevel; V(FixedArray, materialized_objects, MaterializedObjects) \ V(FixedArray, microtask_queue, MicrotaskQueue) \ V(FixedArray, detached_contexts, DetachedContexts) \ + V(HeapObject, retaining_path_targets, RetainingPathTargets) \ V(ArrayList, retained_maps, RetainedMaps) \ V(WeakHashTable, weak_object_to_code_table, WeakObjectToCodeTable) \ /* weak_new_space_object_to_code_list is an array of weak cells, where */ \ @@ -1480,8 +1481,16 @@ class Heap { void MergeAllocationSitePretenuringFeedback( const base::HashMap& local_pretenuring_feedback); -// ============================================================================= + // =========================================================================== + // Retaining path tracking. ================================================== + // =========================================================================== + // Adds the given object to the weak table of retaining path targets. + // On each GC if the marker discovers the object, it will print the retaining + // path. This requires --track-retaining-path flag. + void AddRetainingPathTarget(Handle object); + +// ============================================================================= #ifdef VERIFY_HEAP // Verify the heap is in its normal state before or after a GC. void Verify(); @@ -2142,9 +2151,16 @@ class Heap { MUST_USE_RESULT AllocationResult AllocateCode(int object_size, bool immovable); + void set_force_oom(bool value) { force_oom_ = value; } + + // =========================================================================== + // Retaining path tracing ==================================================== // =========================================================================== - void set_force_oom(bool value) { force_oom_ = value; } + void AddRetainer(HeapObject* retainer, HeapObject* object); + void AddRetainingRoot(Root root, HeapObject* object); + bool IsRetainingPathTarget(HeapObject* object); + void PrintRetainingPath(HeapObject* object); // The amount of external memory registered through the API. int64_t external_memory_; @@ -2382,12 +2398,17 @@ class Heap { HeapObject* pending_layout_change_object_; + std::map retainer_; + std::map retaining_root_; + // Classes in "heap" can be friends. friend class AlwaysAllocateScope; friend class ConcurrentMarking; friend class GCCallbacksScope; friend class GCTracer; friend class HeapIterator; + template + friend class MarkingVisitor; friend class IdleScavengeObserver; friend class IncrementalMarking; friend class IncrementalMarkingJob; diff --git a/src/heap/incremental-marking.cc b/src/heap/incremental-marking.cc index cdc8881f88..da82078043 100644 --- a/src/heap/incremental-marking.cc +++ b/src/heap/incremental-marking.cc @@ -294,7 +294,7 @@ class IncrementalMarkingMarkingVisitor final Object* target = *p; if (target->IsHeapObject()) { collector_->RecordSlot(host, p, target); - MarkObject(target); + MarkObject(host, target); } } @@ -304,13 +304,13 @@ class IncrementalMarkingMarkingVisitor final Object* target = *p; if (target->IsHeapObject()) { collector_->RecordSlot(host, p, target); - MarkObject(target); + MarkObject(host, target); } } } // Marks the object grey and pushes it on the marking stack. - V8_INLINE void MarkObject(Object* obj) { + V8_INLINE void MarkObject(HeapObject* host, Object* obj) { incremental_marking_->WhiteToGreyAndPush(HeapObject::cast(obj)); } diff --git a/src/heap/mark-compact-inl.h b/src/heap/mark-compact-inl.h index 8873d213c2..df3d173185 100644 --- a/src/heap/mark-compact-inl.h +++ b/src/heap/mark-compact-inl.h @@ -21,10 +21,23 @@ void MarkCompactCollector::PushBlack(HeapObject* obj) { } } -void MarkCompactCollector::MarkObject(HeapObject* obj) { +void MarkCompactCollector::MarkObject(HeapObject* host, HeapObject* obj) { if (ObjectMarking::WhiteToBlack( obj, MarkingState::Internal(obj))) { PushBlack(obj); + if (V8_UNLIKELY(FLAG_track_retaining_path)) { + heap_->AddRetainer(host, obj); + } + } +} + +void MarkCompactCollector::MarkExternallyReferencedObject(HeapObject* obj) { + if (ObjectMarking::WhiteToBlack( + obj, MarkingState::Internal(obj))) { + PushBlack(obj); + if (V8_UNLIKELY(FLAG_track_retaining_path)) { + heap_->AddRetainingRoot(Root::kWrapperTracing, obj); + } } } diff --git a/src/heap/mark-compact.cc b/src/heap/mark-compact.cc index 055b11082f..a11dc69752 100644 --- a/src/heap/mark-compact.cc +++ b/src/heap/mark-compact.cc @@ -1052,7 +1052,8 @@ class MarkCompactMarkingVisitor final Object** end) final { // Mark all objects pointed to in [start, end). const int kMinRangeForMarkingRecursion = 64; - if (end - start >= kMinRangeForMarkingRecursion) { + if (end - start >= kMinRangeForMarkingRecursion && + V8_LIKELY(!FLAG_track_retaining_path)) { if (VisitUnmarkedObjects(host, start, end)) return; // We are close to a stack overflow, so just mark the objects. } @@ -1062,8 +1063,8 @@ class MarkCompactMarkingVisitor final } // Marks the object black and pushes it on the marking stack. - V8_INLINE void MarkObject(HeapObject* object) { - collector_->MarkObject(object); + V8_INLINE void MarkObject(HeapObject* host, HeapObject* object) { + collector_->MarkObject(host, object); } // Marks the object black without pushing it on the marking stack. Returns @@ -1076,7 +1077,7 @@ class MarkCompactMarkingVisitor final if (!(*p)->IsHeapObject()) return; HeapObject* target_object = HeapObject::cast(*p); collector_->RecordSlot(host, p, target_object); - collector_->MarkObject(target_object); + collector_->MarkObject(host, target_object); } protected: @@ -1106,7 +1107,7 @@ class MarkCompactMarkingVisitor final Map* map = obj->map(); ObjectMarking::WhiteToBlack(obj, MarkingState::Internal(obj)); // Mark the map pointer and the body. - collector_->MarkObject(map); + collector_->MarkObject(obj, map); Visit(map, obj); } } @@ -1132,19 +1133,20 @@ class MarkCompactCollector::RootMarkingVisitor : public ObjectVisitor, : collector_(heap->mark_compact_collector()), visitor_(collector_) {} void VisitPointer(HeapObject* host, Object** p) override { - MarkObjectByPointer(p); + MarkObjectByPointer(host, p); } void VisitPointers(HeapObject* host, Object** start, Object** end) override { - for (Object** p = start; p < end; p++) MarkObjectByPointer(p); + for (Object** p = start; p < end; p++) MarkObjectByPointer(host, p); } void VisitRootPointer(Root root, Object** p) override { - MarkObjectByPointer(p); + MarkObjectByPointer(nullptr, p, root); } void VisitRootPointers(Root root, Object** start, Object** end) override { - for (Object** p = start; p < end; p++) MarkObjectByPointer(p); + for (Object** p = start; p < end; p++) + MarkObjectByPointer(nullptr, p, root); } // Skip the weak next code link in a code object, which is visited in @@ -1152,16 +1154,24 @@ class MarkCompactCollector::RootMarkingVisitor : public ObjectVisitor, void VisitNextCodeLink(Code* host, Object** p) override {} private: - void MarkObjectByPointer(Object** p) { + void MarkObjectByPointer(HeapObject* host, Object** p, + Root root = Root::kUnknown) { if (!(*p)->IsHeapObject()) return; HeapObject* object = HeapObject::cast(*p); if (ObjectMarking::WhiteToBlack( object, MarkingState::Internal(object))) { + if (V8_UNLIKELY(FLAG_track_retaining_path)) { + if (host) { + object->GetHeap()->AddRetainer(host, object); + } else { + object->GetHeap()->AddRetainingRoot(root, object); + } + } Map* map = object->map(); // Mark the map pointer and body, and push them on the marking stack. - collector_->MarkObject(map); + collector_->MarkObject(object, map); visitor_.Visit(map, object); // Mark all the objects reachable from the map and body. May leave // overflowed objects in the heap. @@ -1961,7 +1971,7 @@ void MarkCompactCollector::EmptyMarkingWorklist() { object, MarkingState::Internal(object)))); Map* map = object->map(); - MarkObject(map); + MarkObject(object, map); visitor.Visit(map, object); } DCHECK(marking_worklist()->IsEmpty()); diff --git a/src/heap/mark-compact.h b/src/heap/mark-compact.h index 937dad1a91..8894a0ead8 100644 --- a/src/heap/mark-compact.h +++ b/src/heap/mark-compact.h @@ -681,7 +681,10 @@ class MarkCompactCollector final : public MarkCompactCollectorBase { // Marks the object black and pushes it on the marking stack. // This is for non-incremental marking only. - V8_INLINE void MarkObject(HeapObject* obj); + V8_INLINE void MarkObject(HeapObject* host, HeapObject* obj); + + // Used by wrapper tracing. + V8_INLINE void MarkExternallyReferencedObject(HeapObject* obj); // Mark the heap roots and all objects reachable from them. void MarkRoots(RootMarkingVisitor* visitor); diff --git a/src/heap/objects-visiting-inl.h b/src/heap/objects-visiting-inl.h index ad3ddbc52c..097c8579a3 100644 --- a/src/heap/objects-visiting-inl.h +++ b/src/heap/objects-visiting-inl.h @@ -321,6 +321,9 @@ void MarkingVisitor::MarkMapContents(Map* map) { // just mark the entire descriptor array. if (!map->is_prototype_map()) { DescriptorArray* descriptors = map->instance_descriptors(); + if (V8_UNLIKELY(FLAG_track_retaining_path)) { + heap_->AddRetainer(map, descriptors); + } if (visitor->MarkObjectWithoutPush(descriptors) && descriptors->length() > 0) { visitor->VisitPointers(descriptors, descriptors->GetFirstElementAddress(), @@ -392,7 +395,7 @@ void MarkingVisitor::VisitCodeEntry(JSFunction* host, ConcreteVisitor* visitor = static_cast(this); Code* code = Code::cast(Code::GetObjectFromEntryAddress(entry_address)); collector_->RecordCodeEntrySlot(host, entry_address, code); - visitor->MarkObject(code); + visitor->MarkObject(host, code); } template @@ -403,7 +406,7 @@ void MarkingVisitor::VisitEmbeddedPointer(Code* host, HeapObject* object = HeapObject::cast(rinfo->target_object()); collector_->RecordRelocSlot(host, rinfo, object); if (!host->IsWeakObject(object)) { - visitor->MarkObject(object); + visitor->MarkObject(host, object); } } @@ -415,7 +418,7 @@ void MarkingVisitor::VisitCellPointer(Code* host, Cell* cell = rinfo->target_cell(); collector_->RecordRelocSlot(host, rinfo, cell); if (!host->IsWeakObject(cell)) { - visitor->MarkObject(cell); + visitor->MarkObject(host, cell); } } @@ -427,7 +430,7 @@ void MarkingVisitor::VisitDebugTarget(Code* host, rinfo->IsPatchedDebugBreakSlotSequence()); Code* target = Code::GetCodeFromTargetAddress(rinfo->debug_call_address()); collector_->RecordRelocSlot(host, rinfo, target); - visitor->MarkObject(target); + visitor->MarkObject(host, target); } template @@ -437,7 +440,7 @@ void MarkingVisitor::VisitCodeTarget(Code* host, DCHECK(RelocInfo::IsCodeTarget(rinfo->rmode())); Code* target = Code::GetCodeFromTargetAddress(rinfo->target_address()); collector_->RecordRelocSlot(host, rinfo, target); - visitor->MarkObject(target); + visitor->MarkObject(host, target); } template @@ -448,7 +451,7 @@ void MarkingVisitor::VisitCodeAgeSequence(Code* host, Code* target = rinfo->code_age_stub(); DCHECK_NOT_NULL(target); collector_->RecordRelocSlot(host, rinfo, target); - visitor->MarkObject(target); + visitor->MarkObject(host, target); } } // namespace internal diff --git a/src/objects-printer.cc b/src/objects-printer.cc index c4f8291f81..832cef30f2 100644 --- a/src/objects-printer.cc +++ b/src/objects-printer.cc @@ -269,6 +269,7 @@ void ByteArray::ByteArrayPrint(std::ostream& os) { // NOLINT void BytecodeArray::BytecodeArrayPrint(std::ostream& os) { // NOLINT + HeapObject::PrintHeader(os, "BytecodeArray"); Disassemble(os); } @@ -1090,9 +1091,22 @@ void JSFunction::JSFunctionPrint(std::ostream& os) { // NOLINT os << "\n - bytecode = " << shared()->bytecode_array(); } } + shared()->PrintSourceCode(os); JSObjectPrintBody(os, this); } +void SharedFunctionInfo::PrintSourceCode(std::ostream& os) { + if (HasSourceCode()) { + os << "\n - source code = "; + String* source = String::cast(Script::cast(script())->source()); + int start = start_position(); + int length = end_position() - start; + std::unique_ptr source_string = source->ToCString( + DISALLOW_NULLS, FAST_STRING_TRAVERSAL, start, length, NULL); + os << source_string.get(); + } +} + void SharedFunctionInfo::SharedFunctionInfoPrint(std::ostream& os) { // NOLINT HeapObject::PrintHeader(os, "SharedFunctionInfo"); os << "\n - name = "; @@ -1113,15 +1127,7 @@ void SharedFunctionInfo::SharedFunctionInfoPrint(std::ostream& os) { // NOLINT if (HasBytecodeArray()) { os << "\n - bytecode_array = " << bytecode_array(); } - if (HasSourceCode()) { - os << "\n - source code = "; - String* source = String::cast(Script::cast(script())->source()); - int start = start_position(); - int length = end_position() - start; - std::unique_ptr source_string = source->ToCString( - DISALLOW_NULLS, FAST_STRING_TRAVERSAL, start, length, NULL); - os << source_string.get(); - } + PrintSourceCode(os); // Script files are often large, hard to read. // os << "\n - script ="; // script()->Print(os); diff --git a/src/objects/shared-function-info.h b/src/objects/shared-function-info.h index 03cae0aede..74aee72ad7 100644 --- a/src/objects/shared-function-info.h +++ b/src/objects/shared-function-info.h @@ -391,6 +391,9 @@ class SharedFunctionInfo : public HeapObject { // Dispatched behavior. DECL_PRINTER(SharedFunctionInfo) DECL_VERIFIER(SharedFunctionInfo) +#ifdef OBJECT_PRINT + void PrintSourceCode(std::ostream& os); +#endif void ResetForNewContext(int new_ic_age); diff --git a/src/runtime/runtime-test.cc b/src/runtime/runtime-test.cc index 10deb67216..474adf8b95 100644 --- a/src/runtime/runtime-test.cc +++ b/src/runtime/runtime-test.cc @@ -612,6 +612,17 @@ RUNTIME_FUNCTION(Runtime_DebugTrace) { return isolate->heap()->undefined_value(); } +RUNTIME_FUNCTION(Runtime_DebugTrackRetainingPath) { + HandleScope scope(isolate); + DCHECK_EQ(1, args.length()); + if (!FLAG_track_retaining_path) { + PrintF("DebugTrackRetainingPath requires --track-retaining-path flag.\n"); + } else { + CONVERT_ARG_HANDLE_CHECKED(HeapObject, object, 0); + isolate->heap()->AddRetainingPathTarget(object); + } + return isolate->heap()->undefined_value(); +} // This will not allocate (flatten the string), but it may run // very slowly for very deeply nested ConsStrings. For debugging use only. diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 9d852b3e1f..9b2db8a54d 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -569,6 +569,7 @@ namespace internal { F(SetAllocationTimeout, -1 /* 2 || 3 */, 1) \ F(DebugPrint, 1, 1) \ F(DebugTrace, 0, 1) \ + F(DebugTrackRetainingPath, 1, 1) \ F(GetExceptionDetails, 1, 1) \ F(GlobalPrint, 1, 1) \ F(SystemBreak, 0, 1) \ diff --git a/src/visitors.h b/src/visitors.h index 0822d91690..af150fcee7 100644 --- a/src/visitors.h +++ b/src/visitors.h @@ -48,7 +48,9 @@ enum class Root { // TODO(ulan): Merge with the ROOT_ID_LIST. kCodeFlusher, kPartialSnapshotCache, - kWeakCollections + kWeakCollections, + kWrapperTracing, + kUnknown }; // Abstract base class for visiting, and optionally modifying, the