v8/test/unittests/heap/embedder-tracing-unittest.cc
Nikolaos Papaspyrou f51e0bb1db [heap] Merge mechanisms for disabling CSS
EmbedderStackStateScope is used to disable conservative stack scanning
for cppgc when the stack is known to not contain heap pointers. Also,
DisableConservativeStackScanningScopeForTesting is used to disable CSS
for the V8 heap in tests that assume a precise GC. Until now, these two
have used two different mechanisms for disabling CSS. This CL merges
the two mechanisms and implements the latter scope via the former.

Bug: v8:13257
Change-Id: Ieca082657854fe2eff9eb5d95a30d48bb8eab44f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4111954
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Nikolaos Papaspyrou <nikolaos@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84964}
2022-12-20 20:16:24 +00:00

596 lines
21 KiB
C++

// Copyright 2016 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 "src/heap/embedder-tracing.h"
#include "include/v8-embedder-heap.h"
#include "include/v8-function.h"
#include "include/v8-template.h"
#include "src/handles/global-handles.h"
#include "src/heap/gc-tracer.h"
#include "src/heap/heap.h"
#include "test/unittests/heap/heap-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
using LocalEmbedderHeapTracerWithIsolate = TestWithHeapInternals;
namespace heap {
using testing::StrictMock;
using testing::_;
using testing::Return;
using v8::EmbedderHeapTracer;
using v8::internal::LocalEmbedderHeapTracer;
namespace {
LocalEmbedderHeapTracer::WrapperInfo CreateWrapperInfo() {
return LocalEmbedderHeapTracer::WrapperInfo(nullptr, nullptr);
}
} // namespace
START_ALLOW_USE_DEPRECATED()
class MockEmbedderHeapTracer : public EmbedderHeapTracer {
public:
MOCK_METHOD(void, TracePrologue, (EmbedderHeapTracer::TraceFlags),
(override));
MOCK_METHOD(void, TraceEpilogue, (EmbedderHeapTracer::TraceSummary*),
(override));
MOCK_METHOD(void, EnterFinalPause, (EmbedderHeapTracer::EmbedderStackState),
(override));
MOCK_METHOD(bool, IsTracingDone, (), (override));
MOCK_METHOD(void, RegisterV8References,
((const std::vector<std::pair<void*, void*> >&)), (override));
MOCK_METHOD(bool, AdvanceTracing, (double deadline_in_ms), (override));
};
END_ALLOW_USE_DEPRECATED()
TEST(LocalEmbedderHeapTracer, InUse) {
MockEmbedderHeapTracer mock_remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr);
local_tracer.SetRemoteTracer(&mock_remote_tracer);
EXPECT_TRUE(local_tracer.InUse());
}
TEST(LocalEmbedderHeapTracer, NoRemoteTracer) {
LocalEmbedderHeapTracer local_tracer(nullptr);
// We should be able to call all functions without a remote tracer being
// attached.
EXPECT_FALSE(local_tracer.InUse());
local_tracer.TracePrologue(EmbedderHeapTracer::TraceFlags::kNoFlags);
local_tracer.EnterFinalPause();
bool done = local_tracer.Trace(std::numeric_limits<double>::infinity());
EXPECT_TRUE(done);
local_tracer.TraceEpilogue();
}
TEST(LocalEmbedderHeapTracer, TracePrologueForwards) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr);
local_tracer.SetRemoteTracer(&remote_tracer);
EXPECT_CALL(remote_tracer, TracePrologue(_));
local_tracer.TracePrologue(EmbedderHeapTracer::TraceFlags::kNoFlags);
}
TEST(LocalEmbedderHeapTracer, TracePrologueForwardsMemoryReducingFlag) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr);
local_tracer.SetRemoteTracer(&remote_tracer);
EXPECT_CALL(remote_tracer,
TracePrologue(EmbedderHeapTracer::TraceFlags::kReduceMemory));
local_tracer.TracePrologue(EmbedderHeapTracer::TraceFlags::kReduceMemory);
}
TEST(LocalEmbedderHeapTracer, TraceEpilogueForwards) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr);
local_tracer.SetRemoteTracer(&remote_tracer);
EXPECT_CALL(remote_tracer, TraceEpilogue(_));
local_tracer.TraceEpilogue();
}
TEST(LocalEmbedderHeapTracer, EnterFinalPauseForwards) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr);
local_tracer.SetRemoteTracer(&remote_tracer);
EXPECT_CALL(remote_tracer, EnterFinalPause(_));
local_tracer.EnterFinalPause();
}
TEST(LocalEmbedderHeapTracer, IsRemoteTracingDoneForwards) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr);
local_tracer.SetRemoteTracer(&remote_tracer);
EXPECT_CALL(remote_tracer, IsTracingDone());
local_tracer.IsRemoteTracingDone();
}
TEST(LocalEmbedderHeapTracer, EnterFinalPauseDefaultStackStateUnkown) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr);
local_tracer.SetRemoteTracer(&remote_tracer);
// The default stack state is expected to be unkown.
EXPECT_CALL(
remote_tracer,
EnterFinalPause(
EmbedderHeapTracer::EmbedderStackState::kMayContainHeapPointers));
local_tracer.EnterFinalPause();
}
TEST_F(LocalEmbedderHeapTracerWithIsolate,
EnterFinalPauseStackStateIsForwarded) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer);
EmbedderStackStateScope scope =
EmbedderStackStateScope::ExplicitScopeForTesting(
&local_tracer,
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
EXPECT_CALL(
remote_tracer,
EnterFinalPause(EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers));
local_tracer.EnterFinalPause();
}
TEST_F(LocalEmbedderHeapTracerWithIsolate, TemporaryEmbedderStackState) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer);
// Default is unknown, see above.
{
EmbedderStackStateScope scope =
EmbedderStackStateScope::ExplicitScopeForTesting(
&local_tracer,
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
EXPECT_CALL(remote_tracer,
EnterFinalPause(
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers));
local_tracer.EnterFinalPause();
}
}
TEST_F(LocalEmbedderHeapTracerWithIsolate,
TemporaryEmbedderStackStateRestores) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer);
// Default is unknown, see above.
{
EmbedderStackStateScope scope =
EmbedderStackStateScope::ExplicitScopeForTesting(
&local_tracer,
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
{
EmbedderStackStateScope nested_scope =
EmbedderStackStateScope::ExplicitScopeForTesting(
&local_tracer,
EmbedderHeapTracer::EmbedderStackState::kMayContainHeapPointers);
EXPECT_CALL(
remote_tracer,
EnterFinalPause(
EmbedderHeapTracer::EmbedderStackState::kMayContainHeapPointers));
local_tracer.EnterFinalPause();
}
EXPECT_CALL(remote_tracer,
EnterFinalPause(
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers));
local_tracer.EnterFinalPause();
}
}
TEST_F(LocalEmbedderHeapTracerWithIsolate, TraceEpilogueStackStateResets) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer);
EmbedderStackStateScope scope =
EmbedderStackStateScope::ExplicitScopeForTesting(
&local_tracer,
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
EXPECT_CALL(
remote_tracer,
EnterFinalPause(EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers));
local_tracer.EnterFinalPause();
EXPECT_CALL(remote_tracer, TraceEpilogue(_));
local_tracer.TraceEpilogue();
EXPECT_CALL(
remote_tracer,
EnterFinalPause(
EmbedderHeapTracer::EmbedderStackState::kMayContainHeapPointers));
local_tracer.EnterFinalPause();
}
TEST(LocalEmbedderHeapTracer, IsRemoteTracingDoneIncludesRemote) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr);
local_tracer.SetRemoteTracer(&remote_tracer);
EXPECT_CALL(remote_tracer, IsTracingDone());
local_tracer.IsRemoteTracingDone();
}
TEST(LocalEmbedderHeapTracer, RegisterV8ReferencesWithRemoteTracer) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr);
local_tracer.SetRemoteTracer(&remote_tracer);
{
LocalEmbedderHeapTracer::ProcessingScope scope(&local_tracer);
scope.AddWrapperInfoForTesting(CreateWrapperInfo());
EXPECT_CALL(remote_tracer, RegisterV8References(_));
}
EXPECT_CALL(remote_tracer, IsTracingDone()).WillOnce(Return(false));
EXPECT_FALSE(local_tracer.IsRemoteTracingDone());
}
TEST_F(LocalEmbedderHeapTracerWithIsolate, SetRemoteTracerSetsIsolate) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer);
EXPECT_EQ(isolate(), reinterpret_cast<Isolate*>(remote_tracer.isolate()));
}
TEST_F(LocalEmbedderHeapTracerWithIsolate, DestructorClearsIsolate) {
StrictMock<MockEmbedderHeapTracer> remote_tracer;
{
LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer);
EXPECT_EQ(isolate(), reinterpret_cast<Isolate*>(remote_tracer.isolate()));
}
EXPECT_EQ(nullptr, remote_tracer.isolate());
}
namespace {
v8::Local<v8::Object> ConstructTraceableJSApiObject(
v8::Local<v8::Context> context, void* first_field, void* second_field) {
v8::EscapableHandleScope scope(context->GetIsolate());
v8::Local<v8::FunctionTemplate> function_t =
v8::FunctionTemplate::New(context->GetIsolate());
v8::Local<v8::ObjectTemplate> instance_t = function_t->InstanceTemplate();
instance_t->SetInternalFieldCount(2);
v8::Local<v8::Function> function =
function_t->GetFunction(context).ToLocalChecked();
v8::Local<v8::Object> instance =
function->NewInstance(context).ToLocalChecked();
instance->SetAlignedPointerInInternalField(0, first_field);
instance->SetAlignedPointerInInternalField(1, second_field);
EXPECT_FALSE(instance.IsEmpty());
i::Handle<i::JSReceiver> js_obj = v8::Utils::OpenHandle(*instance);
EXPECT_EQ(i::JS_API_OBJECT_TYPE, js_obj->map().instance_type());
return scope.Escape(instance);
}
enum class TracePrologueBehavior { kNoop, kCallV8WriteBarrier };
START_ALLOW_USE_DEPRECATED()
class TestEmbedderHeapTracer final : public v8::EmbedderHeapTracer {
public:
TestEmbedderHeapTracer() = default;
TestEmbedderHeapTracer(TracePrologueBehavior prologue_behavior,
v8::Global<v8::Array> array)
: prologue_behavior_(prologue_behavior), array_(std::move(array)) {}
void RegisterV8References(
const std::vector<std::pair<void*, void*>>& embedder_fields) final {
registered_from_v8_.insert(registered_from_v8_.end(),
embedder_fields.begin(), embedder_fields.end());
}
void AddReferenceForTracing(v8::TracedReference<v8::Value>* ref) {
to_register_with_v8_references_.push_back(ref);
}
bool AdvanceTracing(double deadline_in_ms) final {
for (auto ref : to_register_with_v8_references_) {
RegisterEmbedderReference(ref->As<v8::Data>());
}
to_register_with_v8_references_.clear();
return true;
}
bool IsTracingDone() final { return to_register_with_v8_references_.empty(); }
void TracePrologue(EmbedderHeapTracer::TraceFlags) final {
if (prologue_behavior_ == TracePrologueBehavior::kCallV8WriteBarrier) {
auto local = array_.Get(isolate());
local
->Set(local->GetCreationContext().ToLocalChecked(), 0,
v8::Object::New(isolate()))
.Check();
}
}
void TraceEpilogue(TraceSummary*) final {}
void EnterFinalPause(EmbedderStackState) final {}
bool IsRegisteredFromV8(void* first_field) const {
for (auto pair : registered_from_v8_) {
if (pair.first == first_field) return true;
}
return false;
}
void DoNotConsiderAsRootForScavenge(v8::TracedReference<v8::Value>* handle) {
handle->SetWrapperClassId(17);
non_root_handles_.push_back(handle);
}
bool IsRootForNonTracingGC(
const v8::TracedReference<v8::Value>& handle) final {
return handle.WrapperClassId() != 17;
}
void ResetHandleInNonTracingGC(
const v8::TracedReference<v8::Value>& handle) final {
for (auto* non_root_handle : non_root_handles_) {
if (*non_root_handle == handle) {
non_root_handle->Reset();
}
}
}
private:
std::vector<std::pair<void*, void*>> registered_from_v8_;
std::vector<v8::TracedReference<v8::Value>*> to_register_with_v8_references_;
TracePrologueBehavior prologue_behavior_ = TracePrologueBehavior::kNoop;
v8::Global<v8::Array> array_;
std::vector<v8::TracedReference<v8::Value>*> non_root_handles_;
};
class V8_NODISCARD TemporaryEmbedderHeapTracerScope final {
public:
TemporaryEmbedderHeapTracerScope(v8::Isolate* isolate,
v8::EmbedderHeapTracer* tracer)
: isolate_(isolate) {
isolate_->SetEmbedderHeapTracer(tracer);
}
~TemporaryEmbedderHeapTracerScope() {
isolate_->SetEmbedderHeapTracer(nullptr);
}
private:
v8::Isolate* const isolate_;
};
END_ALLOW_USE_DEPRECATED()
} // namespace
using EmbedderTracingTest = TestWithHeapInternalsAndContext;
TEST_F(EmbedderTracingTest, V8RegisterEmbedderReference) {
// Tests that wrappers are properly registered with the embedder heap
// tracer.
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
void* first_and_second_field = reinterpret_cast<void*>(0x2);
v8::Local<v8::Object> api_object = ConstructTraceableJSApiObject(
context, first_and_second_field, first_and_second_field);
ASSERT_FALSE(api_object.IsEmpty());
CollectGarbage(i::OLD_SPACE);
EXPECT_TRUE(tracer.IsRegisteredFromV8(first_and_second_field));
}
TEST_F(EmbedderTracingTest, EmbedderRegisteringV8Reference) {
// Tests that references that are registered by the embedder heap tracer are
// considered live by V8.
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
auto handle = std::make_unique<v8::TracedReference<v8::Value>>();
{
v8::HandleScope inner_scope(v8_isolate());
v8::Local<v8::Value> o =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
handle->Reset(v8_isolate(), o);
}
tracer.AddReferenceForTracing(handle.get());
CollectGarbage(i::OLD_SPACE);
EXPECT_FALSE(handle->IsEmpty());
}
TEST_F(EmbedderTracingTest, FinalizeTracingIsNoopWhenNotMarking) {
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
// Finalize a potentially running garbage collection.
CollectGarbage(OLD_SPACE);
EXPECT_TRUE(i_isolate()->heap()->incremental_marking()->IsStopped());
int gc_counter = i_isolate()->heap()->gc_count();
tracer.FinalizeTracing();
EXPECT_TRUE(i_isolate()->heap()->incremental_marking()->IsStopped());
EXPECT_EQ(gc_counter, i_isolate()->heap()->gc_count());
}
TEST_F(EmbedderTracingTest, FinalizeTracingWhenMarking) {
if (!v8_flags.incremental_marking) return;
ManualGCScope manual_gc(i_isolate());
Heap* heap = i_isolate()->heap();
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
// Finalize a potentially running garbage collection.
CollectGarbage(OLD_SPACE);
if (heap->sweeping_in_progress()) {
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kV8Only);
}
heap->tracer()->StopFullCycleIfNeeded();
EXPECT_TRUE(heap->incremental_marking()->IsStopped());
i::IncrementalMarking* marking = heap->incremental_marking();
{
IsolateSafepointScope scope(heap);
heap->tracer()->StartCycle(
GarbageCollector::MARK_COMPACTOR, GarbageCollectionReason::kTesting,
"collector cctest", GCTracer::MarkingType::kIncremental);
marking->Start(GarbageCollector::MARK_COMPACTOR,
GarbageCollectionReason::kTesting);
}
// Sweeping is not runing so we should immediately start marking.
EXPECT_TRUE(marking->IsMarking());
tracer.FinalizeTracing();
EXPECT_TRUE(marking->IsStopped());
}
namespace {
void ConstructJSObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
v8::TracedReference<v8::Object>* handle) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(v8::Object::New(isolate));
EXPECT_FALSE(object.IsEmpty());
*handle = v8::TracedReference<v8::Object>(isolate, object);
EXPECT_FALSE(handle->IsEmpty());
}
} // namespace
TEST_F(EmbedderTracingTest, TracedReferenceHandlesMarking) {
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
auto live = std::make_unique<v8::TracedReference<v8::Value>>();
auto dead = std::make_unique<v8::TracedReference<v8::Value>>();
live->Reset(v8_isolate(), v8::Undefined(v8_isolate()));
dead->Reset(v8_isolate(), v8::Undefined(v8_isolate()));
auto* traced_handles = i_isolate()->traced_handles();
{
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
tracer.AddReferenceForTracing(live.get());
const size_t initial_count = traced_handles->used_node_count();
{
// Conservative scanning may find stale pointers to on-stack handles.
// Disable scanning, assuming the slots are overwritten.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
i_isolate()->heap());
FullGC();
}
const size_t final_count = traced_handles->used_node_count();
// Handles are not black allocated, so `dead` is immediately reclaimed.
EXPECT_EQ(initial_count, final_count + 1);
}
}
namespace {
START_ALLOW_USE_DEPRECATED()
class TracedReferenceVisitor final
: public v8::EmbedderHeapTracer::TracedGlobalHandleVisitor {
public:
~TracedReferenceVisitor() override = default;
void VisitTracedReference(const TracedReference<Value>& value) final {
if (value.WrapperClassId() == 57) {
count_++;
}
}
size_t count() const { return count_; }
private:
size_t count_ = 0;
};
END_ALLOW_USE_DEPRECATED()
} // namespace
TEST_F(EmbedderTracingTest, TracedReferenceIteration) {
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
auto handle = std::make_unique<v8::TracedReference<v8::Object>>();
ConstructJSObject(v8_isolate(), v8_isolate()->GetCurrentContext(),
handle.get());
EXPECT_FALSE(handle->IsEmpty());
handle->SetWrapperClassId(57);
TracedReferenceVisitor visitor;
{
v8::HandleScope new_scope(v8_isolate());
tracer.IterateTracedGlobalHandles(&visitor);
}
EXPECT_EQ(1u, visitor.count());
}
TEST_F(EmbedderTracingTest, TracePrologueCallingIntoV8WriteBarrier) {
// Regression test: https://crbug.com/940003
if (!v8_flags.incremental_marking) return;
ManualGCScope manual_gc(isolate());
v8::HandleScope scope(v8_isolate());
v8::Global<v8::Array> global;
{
v8::HandleScope new_scope(v8_isolate());
auto local = v8::Array::New(v8_isolate(), 10);
global.Reset(v8_isolate(), local);
}
TestEmbedderHeapTracer tracer(TracePrologueBehavior::kCallV8WriteBarrier,
std::move(global));
TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
SimulateIncrementalMarking();
// Finish GC to avoid removing the tracer while GC is running which may end up
// in an infinite loop because of unprocessed objects.
FullGC();
}
TEST_F(EmbedderTracingTest, BasicTracedReference) {
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
tracer.SetStackStart(
static_cast<void*>(base::Stack::GetCurrentFrameAddress()));
auto* traced_handles = i_isolate()->traced_handles();
const size_t initial_count = traced_handles->used_node_count();
char* memory = new char[sizeof(v8::TracedReference<v8::Value>)];
auto* traced = new (memory) v8::TracedReference<v8::Value>();
{
v8::HandleScope new_scope(v8_isolate());
v8::Local<v8::Value> object(ConstructTraceableJSApiObject(
v8_isolate()->GetCurrentContext(), nullptr, nullptr));
EXPECT_TRUE(traced->IsEmpty());
*traced = v8::TracedReference<v8::Value>(v8_isolate(), object);
EXPECT_FALSE(traced->IsEmpty());
EXPECT_EQ(initial_count + 1, traced_handles->used_node_count());
}
traced->~TracedReference<v8::Value>();
EXPECT_EQ(initial_count + 1, traced_handles->used_node_count());
{
// Conservative scanning may find stale pointers to on-stack handles.
// Disable scanning, assuming the slots are overwritten.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
i_isolate()->heap());
FullGC();
}
EXPECT_EQ(initial_count, traced_handles->used_node_count());
delete[] memory;
}
} // namespace heap
} // namespace internal
} // namespace v8