[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 <ulan@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Cr-Commit-Position: refs/heads/master@{#46766}
This commit is contained in:
parent
7cfd0c249e
commit
40c34606a7
@ -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,
|
||||
|
103
src/heap/heap.cc
103
src/heap/heap.cc
@ -407,6 +407,99 @@ void Heap::ReportStatisticsAfterGC() {
|
||||
}
|
||||
}
|
||||
|
||||
void Heap::AddRetainingPathTarget(Handle<HeapObject> object) {
|
||||
if (!FLAG_track_retaining_path) {
|
||||
PrintF("Retaining path tracking requires --trace-retaining-path\n");
|
||||
} else {
|
||||
Handle<WeakFixedArray> 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<HeapObject>()) != 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<void*>(target));
|
||||
HeapObject* object = target;
|
||||
std::vector<HeapObject*> 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<int>(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<HeapObject> 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<HeapObject*, HeapObject*> retainer_;
|
||||
std::map<HeapObject*, Root> retaining_root_;
|
||||
|
||||
// Classes in "heap" can be friends.
|
||||
friend class AlwaysAllocateScope;
|
||||
friend class ConcurrentMarking;
|
||||
friend class GCCallbacksScope;
|
||||
friend class GCTracer;
|
||||
friend class HeapIterator;
|
||||
template <typename ConcreteVisitor>
|
||||
friend class MarkingVisitor;
|
||||
friend class IdleScavengeObserver;
|
||||
friend class IncrementalMarking;
|
||||
friend class IncrementalMarkingJob;
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,23 @@ void MarkCompactCollector::PushBlack(HeapObject* obj) {
|
||||
}
|
||||
}
|
||||
|
||||
void MarkCompactCollector::MarkObject(HeapObject* obj) {
|
||||
void MarkCompactCollector::MarkObject(HeapObject* host, HeapObject* obj) {
|
||||
if (ObjectMarking::WhiteToBlack<AccessMode::NON_ATOMIC>(
|
||||
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<AccessMode::NON_ATOMIC>(
|
||||
obj, MarkingState::Internal(obj))) {
|
||||
PushBlack(obj);
|
||||
if (V8_UNLIKELY(FLAG_track_retaining_path)) {
|
||||
heap_->AddRetainingRoot(Root::kWrapperTracing, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<AccessMode::NON_ATOMIC>(
|
||||
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());
|
||||
|
@ -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);
|
||||
|
@ -321,6 +321,9 @@ void MarkingVisitor<ConcreteVisitor>::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<ConcreteVisitor>::VisitCodeEntry(JSFunction* host,
|
||||
ConcreteVisitor* visitor = static_cast<ConcreteVisitor*>(this);
|
||||
Code* code = Code::cast(Code::GetObjectFromEntryAddress(entry_address));
|
||||
collector_->RecordCodeEntrySlot(host, entry_address, code);
|
||||
visitor->MarkObject(code);
|
||||
visitor->MarkObject(host, code);
|
||||
}
|
||||
|
||||
template <typename ConcreteVisitor>
|
||||
@ -403,7 +406,7 @@ void MarkingVisitor<ConcreteVisitor>::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<ConcreteVisitor>::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<ConcreteVisitor>::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 <typename ConcreteVisitor>
|
||||
@ -437,7 +440,7 @@ void MarkingVisitor<ConcreteVisitor>::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 <typename ConcreteVisitor>
|
||||
@ -448,7 +451,7 @@ void MarkingVisitor<ConcreteVisitor>::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
|
||||
|
@ -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<char[]> 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<char[]> 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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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) \
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user