8c2c22fa9c
GetName() is very generic and arleady exists as virtual method in Blink. Bug: chromium:1056170 Change-Id: I7aa6e869a06c048e7baea45939894717c872d89e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2947404 Auto-Submit: Michael Lippautz <mlippautz@chromium.org> Commit-Queue: Dominik Inführ <dinfuehr@chromium.org> Reviewed-by: Dominik Inführ <dinfuehr@chromium.org> Cr-Commit-Position: refs/heads/master@{#75024}
486 lines
18 KiB
C++
486 lines
18 KiB
C++
// Copyright 2020 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <cstring>
|
|
|
|
#include "include/cppgc/allocation.h"
|
|
#include "include/cppgc/cross-thread-persistent.h"
|
|
#include "include/cppgc/garbage-collected.h"
|
|
#include "include/cppgc/name-provider.h"
|
|
#include "include/cppgc/persistent.h"
|
|
#include "include/cppgc/platform.h"
|
|
#include "include/v8-cppgc.h"
|
|
#include "include/v8-profiler.h"
|
|
#include "src/api/api-inl.h"
|
|
#include "src/heap/cppgc-js/cpp-heap.h"
|
|
#include "src/heap/cppgc/heap-object-header.h"
|
|
#include "src/heap/cppgc/object-allocator.h"
|
|
#include "src/objects/heap-object.h"
|
|
#include "src/objects/objects-inl.h"
|
|
#include "src/profiler/heap-snapshot-generator-inl.h"
|
|
#include "src/profiler/heap-snapshot-generator.h"
|
|
#include "test/unittests/heap/heap-utils.h"
|
|
#include "test/unittests/heap/unified-heap-utils.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
namespace {
|
|
|
|
class UnifiedHeapSnapshotTest : public UnifiedHeapTest {
|
|
public:
|
|
const v8::HeapSnapshot* TakeHeapSnapshot() {
|
|
v8::HeapProfiler* heap_profiler = v8_isolate()->GetHeapProfiler();
|
|
return heap_profiler->TakeHeapSnapshot();
|
|
}
|
|
};
|
|
|
|
bool IsValidSnapshot(const v8::HeapSnapshot* snapshot, int depth = 3) {
|
|
const HeapSnapshot* heap_snapshot =
|
|
reinterpret_cast<const HeapSnapshot*>(snapshot);
|
|
std::unordered_set<const HeapEntry*> visited;
|
|
for (const HeapGraphEdge& edge : heap_snapshot->edges()) {
|
|
visited.insert(edge.to());
|
|
}
|
|
size_t unretained_entries_count = 0;
|
|
for (const HeapEntry& entry : heap_snapshot->entries()) {
|
|
if (visited.find(&entry) == visited.end() && entry.id() != 1) {
|
|
entry.Print("entry with no retainer", "", depth, 0);
|
|
++unretained_entries_count;
|
|
}
|
|
}
|
|
return unretained_entries_count == 0;
|
|
}
|
|
|
|
bool ContainsRetainingPath(const v8::HeapSnapshot& snapshot,
|
|
const std::vector<std::string> retaining_path,
|
|
bool debug_retaining_path = false) {
|
|
const HeapSnapshot& heap_snapshot =
|
|
reinterpret_cast<const HeapSnapshot&>(snapshot);
|
|
std::vector<HeapEntry*> haystack = {heap_snapshot.root()};
|
|
for (size_t i = 0; i < retaining_path.size(); ++i) {
|
|
const std::string& needle = retaining_path[i];
|
|
std::vector<HeapEntry*> new_haystack;
|
|
for (HeapEntry* parent : haystack) {
|
|
for (int j = 0; j < parent->children_count(); j++) {
|
|
HeapEntry* child = parent->child(j)->to();
|
|
if (0 == strcmp(child->name(), needle.c_str())) {
|
|
new_haystack.push_back(child);
|
|
}
|
|
}
|
|
}
|
|
if (new_haystack.empty()) {
|
|
if (debug_retaining_path) {
|
|
fprintf(stderr,
|
|
"#\n# Could not find object with name '%s'\n#\n# Path:\n",
|
|
needle.c_str());
|
|
for (size_t j = 0; j < retaining_path.size(); ++j) {
|
|
fprintf(stderr, "# - '%s'%s\n", retaining_path[j].c_str(),
|
|
i == j ? "\t<--- not found" : "");
|
|
}
|
|
fprintf(stderr, "#\n");
|
|
}
|
|
return false;
|
|
}
|
|
std::swap(haystack, new_haystack);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class BaseWithoutName : public cppgc::GarbageCollected<BaseWithoutName> {
|
|
public:
|
|
static constexpr const char kExpectedName[] =
|
|
"v8::internal::(anonymous namespace)::BaseWithoutName";
|
|
|
|
virtual void Trace(cppgc::Visitor* v) const {
|
|
v->Trace(next);
|
|
v->Trace(next2);
|
|
}
|
|
cppgc::Member<BaseWithoutName> next;
|
|
cppgc::Member<BaseWithoutName> next2;
|
|
};
|
|
// static
|
|
constexpr const char BaseWithoutName::kExpectedName[];
|
|
|
|
class GCed final : public BaseWithoutName, public cppgc::NameProvider {
|
|
public:
|
|
static constexpr const char kExpectedName[] = "GCed";
|
|
|
|
void Trace(cppgc::Visitor* v) const final { BaseWithoutName::Trace(v); }
|
|
const char* GetHumanReadableName() const final { return "GCed"; }
|
|
};
|
|
// static
|
|
constexpr const char GCed::kExpectedName[];
|
|
|
|
constexpr const char kExpectedCppRootsName[] = "C++ roots";
|
|
constexpr const char kExpectedCppCrossThreadRootsName[] =
|
|
"C++ cross-thread roots";
|
|
|
|
template <typename T>
|
|
constexpr const char* GetExpectedName() {
|
|
if (std::is_base_of<cppgc::NameProvider, T>::value ||
|
|
!cppgc::NameProvider::HideInternalNames()) {
|
|
return T::kExpectedName;
|
|
} else {
|
|
return cppgc::NameProvider::kHiddenName;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, EmptySnapshot) {
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
}
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, RetainedByCppRoot) {
|
|
cppgc::Persistent<GCed> gced =
|
|
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(ContainsRetainingPath(
|
|
*snapshot, {kExpectedCppRootsName, GetExpectedName<GCed>()}));
|
|
}
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, RetainedByCppCrossThreadRoot) {
|
|
cppgc::subtle::CrossThreadPersistent<GCed> gced =
|
|
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(ContainsRetainingPath(
|
|
*snapshot, {kExpectedCppCrossThreadRootsName, GetExpectedName<GCed>()}));
|
|
}
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, RetainingUnnamedType) {
|
|
cppgc::Persistent<BaseWithoutName> base_without_name =
|
|
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
if (cppgc::NameProvider::HideInternalNames()) {
|
|
EXPECT_FALSE(ContainsRetainingPath(
|
|
*snapshot, {kExpectedCppRootsName, cppgc::NameProvider::kHiddenName}));
|
|
} else {
|
|
EXPECT_TRUE(ContainsRetainingPath(
|
|
*snapshot,
|
|
{kExpectedCppRootsName, GetExpectedName<BaseWithoutName>()}));
|
|
}
|
|
}
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, RetainingNamedThroughUnnamed) {
|
|
cppgc::Persistent<BaseWithoutName> base_without_name =
|
|
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
|
|
base_without_name->next =
|
|
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(ContainsRetainingPath(
|
|
*snapshot, {kExpectedCppRootsName, GetExpectedName<BaseWithoutName>(),
|
|
GetExpectedName<GCed>()}));
|
|
}
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, PendingCallStack) {
|
|
// Test ensures that the algorithm handles references into the current call
|
|
// stack.
|
|
//
|
|
// Graph:
|
|
// Persistent -> BaseWithoutName (2) <-> BaseWithoutName (1) -> GCed (3)
|
|
//
|
|
// Visitation order is (1)->(2)->(3) which is a corner case, as when following
|
|
// back from (2)->(1) the object in (1) is already visited and will only later
|
|
// be marked as visible.
|
|
auto* first =
|
|
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
|
|
auto* second =
|
|
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
|
|
first->next = second;
|
|
first->next->next = first;
|
|
auto* third = cppgc::MakeGarbageCollected<GCed>(allocation_handle());
|
|
first->next2 = third;
|
|
|
|
cppgc::Persistent<BaseWithoutName> holder(second);
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(ContainsRetainingPath(
|
|
*snapshot,
|
|
{kExpectedCppRootsName, GetExpectedName<BaseWithoutName>(),
|
|
GetExpectedName<BaseWithoutName>(), GetExpectedName<GCed>()}));
|
|
}
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, ReferenceToFinishedSCC) {
|
|
// Test ensures that the algorithm handles reference into an already finished
|
|
// SCC that is marked as hidden whereas the current SCC would resolve to
|
|
// visible.
|
|
//
|
|
// Graph:
|
|
// Persistent -> BaseWithoutName (1)
|
|
// Persistent -> BaseWithoutName (2)
|
|
// + <-> BaseWithoutName (3) -> BaseWithoutName (1)
|
|
// + -> GCed (4)
|
|
//
|
|
// Visitation order (1)->(2)->(3)->(1) which is a corner case as (3) would set
|
|
// a dependency on (1) which is hidden. Instead (3) should set a dependency on
|
|
// (2) as (1) resolves to hidden whereas (2) resolves to visible. The test
|
|
// ensures that resolved hidden dependencies are ignored.
|
|
cppgc::Persistent<BaseWithoutName> hidden_holder(
|
|
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle()));
|
|
auto* first =
|
|
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
|
|
auto* second =
|
|
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
|
|
first->next = second;
|
|
second->next = *hidden_holder;
|
|
second->next2 = first;
|
|
first->next2 = cppgc::MakeGarbageCollected<GCed>(allocation_handle());
|
|
cppgc::Persistent<BaseWithoutName> holder(first);
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(ContainsRetainingPath(
|
|
*snapshot,
|
|
{kExpectedCppRootsName, GetExpectedName<BaseWithoutName>(),
|
|
GetExpectedName<BaseWithoutName>(), GetExpectedName<BaseWithoutName>(),
|
|
GetExpectedName<GCed>()}));
|
|
}
|
|
|
|
namespace {
|
|
|
|
class GCedWithJSRef : public cppgc::GarbageCollected<GCedWithJSRef> {
|
|
public:
|
|
static uint16_t kWrappableType;
|
|
static constexpr const char kExpectedName[] =
|
|
"v8::internal::(anonymous namespace)::GCedWithJSRef";
|
|
|
|
virtual void Trace(cppgc::Visitor* v) const { v->Trace(v8_object_); }
|
|
|
|
void SetV8Object(v8::Isolate* isolate, v8::Local<v8::Object> object) {
|
|
v8_object_.Reset(isolate, object);
|
|
}
|
|
|
|
void SetWrapperClassId(uint16_t class_id) {
|
|
v8_object_.SetWrapperClassId(class_id);
|
|
}
|
|
|
|
uint16_t WrapperClassId() const { return v8_object_.WrapperClassId(); }
|
|
|
|
TracedReference<v8::Object>& wrapper() { return v8_object_; }
|
|
|
|
private:
|
|
TracedReference<v8::Object> v8_object_;
|
|
};
|
|
|
|
constexpr const char GCedWithJSRef::kExpectedName[];
|
|
|
|
// static
|
|
uint16_t GCedWithJSRef::kWrappableType = WrapperHelper::kTracedEmbedderId;
|
|
|
|
class V8_NODISCARD JsTestingScope {
|
|
public:
|
|
explicit JsTestingScope(v8::Isolate* isolate)
|
|
: isolate_(isolate),
|
|
handle_scope_(isolate),
|
|
context_(v8::Context::New(isolate)),
|
|
context_scope_(context_) {}
|
|
|
|
v8::Isolate* isolate() const { return isolate_; }
|
|
v8::Local<v8::Context> context() const { return context_; }
|
|
|
|
private:
|
|
v8::Isolate* isolate_;
|
|
v8::HandleScope handle_scope_;
|
|
v8::Local<v8::Context> context_;
|
|
v8::Context::Scope context_scope_;
|
|
};
|
|
|
|
cppgc::Persistent<GCedWithJSRef> SetupWrapperWrappablePair(
|
|
JsTestingScope& testing_scope, cppgc::AllocationHandle& allocation_handle,
|
|
const char* name) {
|
|
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref =
|
|
cppgc::MakeGarbageCollected<GCedWithJSRef>(allocation_handle);
|
|
v8::Local<v8::Object> wrapper_object = WrapperHelper::CreateWrapper(
|
|
testing_scope.context(), &GCedWithJSRef::kWrappableType,
|
|
gc_w_js_ref.Get(), name);
|
|
gc_w_js_ref->SetV8Object(testing_scope.isolate(), wrapper_object);
|
|
return std::move(gc_w_js_ref);
|
|
}
|
|
|
|
template <typename Callback>
|
|
void ForEachEntryWithName(const v8::HeapSnapshot* snapshot, const char* needle,
|
|
Callback callback) {
|
|
const HeapSnapshot* heap_snapshot =
|
|
reinterpret_cast<const HeapSnapshot*>(snapshot);
|
|
for (const HeapEntry& entry : heap_snapshot->entries()) {
|
|
if (strcmp(entry.name(), needle) == 0) {
|
|
callback(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, JSReferenceForcesVisibleObject) {
|
|
// Test ensures that a C++->JS reference forces an object to be visible in the
|
|
// snapshot.
|
|
JsTestingScope testing_scope(v8_isolate());
|
|
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
|
|
testing_scope, allocation_handle(), "LeafJSObject");
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(ContainsRetainingPath(
|
|
*snapshot, {kExpectedCppRootsName, GetExpectedName<GCedWithJSRef>(),
|
|
"LeafJSObject"}));
|
|
}
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, MergedWrapperNode) {
|
|
// Test ensures that the snapshot sets a wrapper node for C++->JS references
|
|
// that have a class id set and that object nodes are merged. In practice, the
|
|
// C++ node is merged into the existing JS node.
|
|
JsTestingScope testing_scope(v8_isolate());
|
|
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
|
|
testing_scope, allocation_handle(), "MergedObject");
|
|
gc_w_js_ref->SetWrapperClassId(1); // Any class id will do.
|
|
v8::Local<v8::Object> next_object = WrapperHelper::CreateWrapper(
|
|
testing_scope.context(), nullptr, nullptr, "NextObject");
|
|
v8::Local<v8::Object> wrapper_object =
|
|
gc_w_js_ref->wrapper().Get(v8_isolate());
|
|
// Chain another object to `wrapper_object`. Since `wrapper_object` should be
|
|
// merged into `GCedWithJSRef`, the additional object must show up as direct
|
|
// child from `GCedWithJSRef`.
|
|
wrapper_object
|
|
->Set(testing_scope.context(),
|
|
v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), "link")
|
|
.ToLocalChecked(),
|
|
next_object)
|
|
.ToChecked();
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(ContainsRetainingPath(
|
|
*snapshot,
|
|
{kExpectedCppRootsName, GetExpectedName<GCedWithJSRef>(),
|
|
// GCedWithJSRef is merged into MergedObject, replacing its name.
|
|
"NextObject"}));
|
|
const size_t js_size = Utils::OpenHandle(*wrapper_object)->Size();
|
|
#if CPPGC_SUPPORTS_OBJECT_NAMES
|
|
const size_t cpp_size =
|
|
cppgc::internal::HeapObjectHeader::FromObject(gc_w_js_ref.Get())
|
|
.AllocatedSize();
|
|
ForEachEntryWithName(snapshot, GetExpectedName<GCedWithJSRef>(),
|
|
[cpp_size, js_size](const HeapEntry& entry) {
|
|
EXPECT_EQ(cpp_size + js_size, entry.self_size());
|
|
});
|
|
#else // !CPPGC_SUPPORTS_OBJECT_NAMES
|
|
ForEachEntryWithName(snapshot, GetExpectedName<GCedWithJSRef>(),
|
|
[js_size](const HeapEntry& entry) {
|
|
EXPECT_EQ(js_size, entry.self_size());
|
|
});
|
|
#endif // !CPPGC_SUPPORTS_OBJECT_NAMES
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr uint16_t kClassIdForAttachedState = 0xAAAA;
|
|
|
|
class DetachednessHandler {
|
|
public:
|
|
static size_t callback_count;
|
|
|
|
static v8::EmbedderGraph::Node::Detachedness GetDetachedness(
|
|
v8::Isolate* isolate, const v8::Local<v8::Value>& v8_value,
|
|
uint16_t class_id, void* data) {
|
|
callback_count++;
|
|
return class_id == kClassIdForAttachedState
|
|
? v8::EmbedderGraph::Node::Detachedness::kAttached
|
|
: v8::EmbedderGraph::Node::Detachedness::kDetached;
|
|
}
|
|
|
|
static void Reset() { callback_count = 0; }
|
|
};
|
|
// static
|
|
size_t DetachednessHandler::callback_count = 0;
|
|
|
|
constexpr uint8_t kExpectedDetachedValueForUnknown =
|
|
static_cast<uint8_t>(v8::EmbedderGraph::Node::Detachedness::kUnknown);
|
|
constexpr uint8_t kExpectedDetachedValueForAttached =
|
|
static_cast<uint8_t>(v8::EmbedderGraph::Node::Detachedness::kAttached);
|
|
constexpr uint8_t kExpectedDetachedValueForDetached =
|
|
static_cast<uint8_t>(v8::EmbedderGraph::Node::Detachedness::kDetached);
|
|
|
|
} // namespace
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, NoTriggerForClassIdZero) {
|
|
// Test ensures that objects with JS references that have no class id set do
|
|
// not have their detachedness state queried.
|
|
JsTestingScope testing_scope(v8_isolate());
|
|
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
|
|
testing_scope, allocation_handle(), "MergedObject");
|
|
DetachednessHandler::Reset();
|
|
v8_isolate()->GetHeapProfiler()->SetGetDetachednessCallback(
|
|
DetachednessHandler::GetDetachedness, nullptr);
|
|
gc_w_js_ref->SetWrapperClassId(0);
|
|
EXPECT_EQ(0u, gc_w_js_ref->WrapperClassId());
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_EQ(0u, DetachednessHandler::callback_count);
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(
|
|
ContainsRetainingPath(*snapshot, {
|
|
kExpectedCppRootsName,
|
|
GetExpectedName<GCedWithJSRef>(),
|
|
}));
|
|
ForEachEntryWithName(
|
|
snapshot, GetExpectedName<GCedWithJSRef>(), [](const HeapEntry& entry) {
|
|
EXPECT_EQ(kExpectedDetachedValueForUnknown, entry.detachedness());
|
|
});
|
|
}
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, TriggerDetachednessCallbackSettingAttached) {
|
|
// Test ensures that objects with JS references that have a non-zero class id
|
|
// set do have their detachedness state queried and set (attached version).
|
|
JsTestingScope testing_scope(v8_isolate());
|
|
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
|
|
testing_scope, allocation_handle(), "MergedObject");
|
|
DetachednessHandler::Reset();
|
|
v8_isolate()->GetHeapProfiler()->SetGetDetachednessCallback(
|
|
DetachednessHandler::GetDetachedness, nullptr);
|
|
gc_w_js_ref->SetWrapperClassId(kClassIdForAttachedState);
|
|
EXPECT_NE(0u, gc_w_js_ref->WrapperClassId());
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_EQ(1u, DetachednessHandler::callback_count);
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(
|
|
ContainsRetainingPath(*snapshot, {
|
|
kExpectedCppRootsName,
|
|
GetExpectedName<GCedWithJSRef>(),
|
|
}));
|
|
ForEachEntryWithName(
|
|
snapshot, GetExpectedName<GCedWithJSRef>(), [](const HeapEntry& entry) {
|
|
EXPECT_EQ(kExpectedDetachedValueForAttached, entry.detachedness());
|
|
});
|
|
}
|
|
|
|
TEST_F(UnifiedHeapSnapshotTest, TriggerDetachednessCallbackSettingDetached) {
|
|
// Test ensures that objects with JS references that have a non-zero class id
|
|
// set do have their detachedness state queried and set (detached version).
|
|
JsTestingScope testing_scope(v8_isolate());
|
|
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref = SetupWrapperWrappablePair(
|
|
testing_scope, allocation_handle(), "MergedObject");
|
|
DetachednessHandler::Reset();
|
|
v8_isolate()->GetHeapProfiler()->SetGetDetachednessCallback(
|
|
DetachednessHandler::GetDetachedness, nullptr);
|
|
gc_w_js_ref->SetWrapperClassId(kClassIdForAttachedState - 1);
|
|
EXPECT_NE(0u, gc_w_js_ref->WrapperClassId());
|
|
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
|
|
EXPECT_EQ(1u, DetachednessHandler::callback_count);
|
|
EXPECT_TRUE(IsValidSnapshot(snapshot));
|
|
EXPECT_TRUE(
|
|
ContainsRetainingPath(*snapshot, {
|
|
kExpectedCppRootsName,
|
|
GetExpectedName<GCedWithJSRef>(),
|
|
}));
|
|
ForEachEntryWithName(
|
|
snapshot, GetExpectedName<GCedWithJSRef>(), [](const HeapEntry& entry) {
|
|
EXPECT_EQ(kExpectedDetachedValueForDetached, entry.detachedness());
|
|
});
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|