Commit 3176bfd4 authored by Anna Henningsen's avatar Anna Henningsen Committed by Commit Bot

[heap-profiler] Fix crash when a snapshot deleted while taking one

Fix a crash/hang that occurred when deleting a snapshot during the
GC that is part of taking another one.

Specifically, when deleting the only other snapshot in such
a situation, the `v8::HeapSnapshot::Delete()` method sees that there
is only one (complete) snapshot at that point, and decides that it is
okay to perform “delete all snapshots” instead of just deleting
the requested one. That resets the internal string lookup table
of the heap profiler, but the new snapshot that is currently in
progress still holds references to the old string lookup table,
leading to a use-after-free segfault or infinite loop.

Fix this by guarding against resetting the string table while
another heap snapshot is being taken, and add a test that would
crash before this fix.

This can be triggered in Node.js by repeatedly calling
`v8.getHeapSnapshot()`, which provides heap snapshots as weakly
held host objects.

Change-Id: If9ac3728bf79114000982f1e7bb05e8034299e3c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2464823Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70445}
parent d76abfed
...@@ -10896,7 +10896,8 @@ static i::HeapSnapshot* ToInternal(const HeapSnapshot* snapshot) { ...@@ -10896,7 +10896,8 @@ static i::HeapSnapshot* ToInternal(const HeapSnapshot* snapshot) {
void HeapSnapshot::Delete() { void HeapSnapshot::Delete() {
i::Isolate* isolate = ToInternal(this)->profiler()->isolate(); i::Isolate* isolate = ToInternal(this)->profiler()->isolate();
if (isolate->heap_profiler()->GetSnapshotsCount() > 1) { if (isolate->heap_profiler()->GetSnapshotsCount() > 1 ||
isolate->heap_profiler()->IsTakingSnapshot()) {
ToInternal(this)->Delete(); ToInternal(this)->Delete();
} else { } else {
// If this is the last snapshot, clean up all accessory data as well. // If this is the last snapshot, clean up all accessory data as well.
......
...@@ -18,7 +18,8 @@ namespace internal { ...@@ -18,7 +18,8 @@ namespace internal {
HeapProfiler::HeapProfiler(Heap* heap) HeapProfiler::HeapProfiler(Heap* heap)
: ids_(new HeapObjectsMap(heap)), : ids_(new HeapObjectsMap(heap)),
names_(new StringsStorage()), names_(new StringsStorage()),
is_tracking_object_moves_(false) {} is_tracking_object_moves_(false),
is_taking_snapshot_(false) {}
HeapProfiler::~HeapProfiler() = default; HeapProfiler::~HeapProfiler() = default;
...@@ -28,7 +29,8 @@ void HeapProfiler::DeleteAllSnapshots() { ...@@ -28,7 +29,8 @@ void HeapProfiler::DeleteAllSnapshots() {
} }
void HeapProfiler::MaybeClearStringsStorage() { void HeapProfiler::MaybeClearStringsStorage() {
if (snapshots_.empty() && !sampling_heap_profiler_ && !allocation_tracker_) { if (snapshots_.empty() && !sampling_heap_profiler_ && !allocation_tracker_ &&
!is_taking_snapshot_) {
names_.reset(new StringsStorage()); names_.reset(new StringsStorage());
} }
} }
...@@ -66,6 +68,7 @@ HeapSnapshot* HeapProfiler::TakeSnapshot( ...@@ -66,6 +68,7 @@ HeapSnapshot* HeapProfiler::TakeSnapshot(
v8::ActivityControl* control, v8::ActivityControl* control,
v8::HeapProfiler::ObjectNameResolver* resolver, v8::HeapProfiler::ObjectNameResolver* resolver,
bool treat_global_objects_as_roots) { bool treat_global_objects_as_roots) {
is_taking_snapshot_ = true;
HeapSnapshot* result = new HeapSnapshot(this, treat_global_objects_as_roots); HeapSnapshot* result = new HeapSnapshot(this, treat_global_objects_as_roots);
{ {
HeapSnapshotGenerator generator(result, control, resolver, heap()); HeapSnapshotGenerator generator(result, control, resolver, heap());
...@@ -78,6 +81,7 @@ HeapSnapshot* HeapProfiler::TakeSnapshot( ...@@ -78,6 +81,7 @@ HeapSnapshot* HeapProfiler::TakeSnapshot(
} }
ids_->RemoveDeadEntries(); ids_->RemoveDeadEntries();
is_tracking_object_moves_ = true; is_tracking_object_moves_ = true;
is_taking_snapshot_ = false;
heap()->isolate()->debug()->feature_tracker()->Track( heap()->isolate()->debug()->feature_tracker()->Track(
DebugFeatureTracker::kHeapSnapshot); DebugFeatureTracker::kHeapSnapshot);
...@@ -138,10 +142,12 @@ void HeapProfiler::StopHeapObjectsTracking() { ...@@ -138,10 +142,12 @@ void HeapProfiler::StopHeapObjectsTracking() {
} }
} }
int HeapProfiler::GetSnapshotsCount() { int HeapProfiler::GetSnapshotsCount() const {
return static_cast<int>(snapshots_.size()); return static_cast<int>(snapshots_.size());
} }
bool HeapProfiler::IsTakingSnapshot() const { return is_taking_snapshot_; }
HeapSnapshot* HeapProfiler::GetSnapshot(int index) { HeapSnapshot* HeapProfiler::GetSnapshot(int index) {
return snapshots_.at(index).get(); return snapshots_.at(index).get();
} }
......
...@@ -49,7 +49,8 @@ class HeapProfiler : public HeapObjectAllocationTracker { ...@@ -49,7 +49,8 @@ class HeapProfiler : public HeapObjectAllocationTracker {
SnapshotObjectId PushHeapObjectsStats(OutputStream* stream, SnapshotObjectId PushHeapObjectsStats(OutputStream* stream,
int64_t* timestamp_us); int64_t* timestamp_us);
int GetSnapshotsCount(); int GetSnapshotsCount() const;
bool IsTakingSnapshot() const;
HeapSnapshot* GetSnapshot(int index); HeapSnapshot* GetSnapshot(int index);
SnapshotObjectId GetSnapshotObjectId(Handle<Object> obj); SnapshotObjectId GetSnapshotObjectId(Handle<Object> obj);
SnapshotObjectId GetSnapshotObjectId(NativeObject obj); SnapshotObjectId GetSnapshotObjectId(NativeObject obj);
...@@ -93,6 +94,7 @@ class HeapProfiler : public HeapObjectAllocationTracker { ...@@ -93,6 +94,7 @@ class HeapProfiler : public HeapObjectAllocationTracker {
std::unique_ptr<StringsStorage> names_; std::unique_ptr<StringsStorage> names_;
std::unique_ptr<AllocationTracker> allocation_tracker_; std::unique_ptr<AllocationTracker> allocation_tracker_;
bool is_tracking_object_moves_; bool is_tracking_object_moves_;
bool is_taking_snapshot_;
base::Mutex profiler_mutex_; base::Mutex profiler_mutex_;
std::unique_ptr<SamplingHeapProfiler> sampling_heap_profiler_; std::unique_ptr<SamplingHeapProfiler> sampling_heap_profiler_;
std::vector<std::pair<v8::HeapProfiler::BuildEmbedderGraphCallback, void*>> std::vector<std::pair<v8::HeapProfiler::BuildEmbedderGraphCallback, void*>>
......
...@@ -4121,3 +4121,40 @@ TEST(Bug8373_2) { ...@@ -4121,3 +4121,40 @@ TEST(Bug8373_2) {
heap_profiler->StopTrackingHeapObjects(); heap_profiler->StopTrackingHeapObjects();
} }
TEST(HeapSnapshotDeleteDuringTakeSnapshot) {
// Check that a heap snapshot can be deleted during GC while another one
// is being taken.
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
int gc_calls = 0;
v8::Global<v8::Object> handle;
{
struct WeakData {
const v8::HeapSnapshot* snapshot;
int* gc_calls;
v8::Global<v8::Object>* handle;
};
WeakData* data =
new WeakData{heap_profiler->TakeHeapSnapshot(), &gc_calls, &handle};
v8::HandleScope scope(env->GetIsolate());
handle.Reset(env->GetIsolate(), v8::Object::New(env->GetIsolate()));
handle.SetWeak(
data,
[](const v8::WeakCallbackInfo<WeakData>& data) {
std::unique_ptr<WeakData> weakdata{data.GetParameter()};
const_cast<v8::HeapSnapshot*>(weakdata->snapshot)->Delete();
++*weakdata->gc_calls;
weakdata->handle->Reset();
},
v8::WeakCallbackType::kParameter);
}
CHECK_EQ(gc_calls, 0);
CHECK(ValidateSnapshot(heap_profiler->TakeHeapSnapshot()));
CHECK_EQ(gc_calls, 1);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment