76c9368593
TracedGlobal integrates with the use case of EmbedderHeapTracer and replaces regular weak Global or Persistent nodes for such cases. This allows to simplify the case for regular weak handles in a sense that they follow regular weak semantics (if the underlying object is otherwise unreachable the weak handle will be reset). TracedGlobal requires slightly different semantics in the sense that it can be required to keep them alive on Scavenge garbage collections because there's a transitive path that is only known when using the EmbedderHeapTracer. TracedGlobal accomodates that use case. TracedGlobal follows move semantics and can thus be used in regular std containers without wrapping data structure. The internal state uses 20% less memory and allows for only iterating those nodes when necessary. The design trades the virtual call when iterating interesting persistents in the GC prologue with calling out through the EmbedderHeapTracer for each node which is also a virtual call. There is one less iteration over the set of handles required though and the design is robust against recursive GCs that mutate the embedder state during the prologue callback. Bug: chromium:923361 Change-Id: Idbacfbe4723cd12af9de21058a4792e51dc4df74 Reviewed-on: https://chromium-review.googlesource.com/c/1425523 Commit-Queue: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Cr-Commit-Position: refs/heads/master@{#59183}
496 lines
16 KiB
C++
496 lines
16 KiB
C++
// Copyright 2017 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 <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "include/v8.h"
|
|
#include "src/api-inl.h"
|
|
#include "src/objects-inl.h"
|
|
#include "src/objects/module.h"
|
|
#include "src/objects/script.h"
|
|
#include "src/objects/shared-function-info.h"
|
|
#include "test/cctest/cctest.h"
|
|
#include "test/cctest/heap/heap-utils.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace heap {
|
|
|
|
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);
|
|
CHECK(!instance.IsEmpty());
|
|
i::Handle<i::JSReceiver> js_obj = v8::Utils::OpenHandle(*instance);
|
|
CHECK_EQ(i::JS_API_OBJECT_TYPE, js_obj->map()->instance_type());
|
|
return scope.Escape(instance);
|
|
}
|
|
|
|
class TestEmbedderHeapTracer final : public v8::EmbedderHeapTracer {
|
|
public:
|
|
explicit TestEmbedderHeapTracer(v8::Isolate* isolate) : isolate_(isolate) {}
|
|
|
|
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::Persistent<v8::Object>* persistent) {
|
|
to_register_with_v8_.push_back(persistent);
|
|
}
|
|
|
|
bool AdvanceTracing(double deadline_in_ms) final {
|
|
for (auto persistent : to_register_with_v8_) {
|
|
persistent->RegisterExternalReference(isolate_);
|
|
}
|
|
to_register_with_v8_.clear();
|
|
return true;
|
|
}
|
|
|
|
bool IsTracingDone() final { return to_register_with_v8_.empty(); }
|
|
|
|
void TracePrologue() final {}
|
|
void TraceEpilogue() 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 ConsiderTracedGlobalAsRoot(bool value) {
|
|
consider_traced_global_as_root_ = value;
|
|
}
|
|
|
|
bool IsRootForNonTracingGC(const v8::TracedGlobal<v8::Value>& handle) final {
|
|
return consider_traced_global_as_root_;
|
|
}
|
|
|
|
private:
|
|
v8::Isolate* const isolate_;
|
|
std::vector<std::pair<void*, void*>> registered_from_v8_;
|
|
std::vector<v8::Persistent<v8::Object>*> to_register_with_v8_;
|
|
bool consider_traced_global_as_root_ = true;
|
|
};
|
|
|
|
class TemporaryEmbedderHeapTracerScope {
|
|
public:
|
|
TemporaryEmbedderHeapTracerScope(v8::Isolate* isolate,
|
|
EmbedderHeapTracer* tracer)
|
|
: isolate_(isolate) {
|
|
isolate_->SetEmbedderHeapTracer(tracer);
|
|
}
|
|
|
|
~TemporaryEmbedderHeapTracerScope() {
|
|
isolate_->SetEmbedderHeapTracer(nullptr);
|
|
}
|
|
|
|
private:
|
|
v8::Isolate* const isolate_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(V8RegisteringEmbedderReference) {
|
|
// Tests that wrappers are properly registered with the embedder heap
|
|
// tracer.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
void* first_field = reinterpret_cast<void*>(0x2);
|
|
v8::Local<v8::Object> api_object =
|
|
ConstructTraceableJSApiObject(context, first_field, nullptr);
|
|
CHECK(!api_object.IsEmpty());
|
|
CcTest::CollectGarbage(i::OLD_SPACE);
|
|
CHECK(tracer.IsRegisteredFromV8(first_field));
|
|
}
|
|
|
|
TEST(EmbedderRegisteringV8Reference) {
|
|
// Tests that references that are registered by the embedder heap tracer are
|
|
// considered live by V8.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::Persistent<v8::Object> g;
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<v8::Object> o =
|
|
v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate));
|
|
g.Reset(isolate, o);
|
|
g.SetWeak();
|
|
}
|
|
tracer.AddReferenceForTracing(&g);
|
|
CcTest::CollectGarbage(i::OLD_SPACE);
|
|
CHECK(!g.IsEmpty());
|
|
}
|
|
|
|
namespace {
|
|
|
|
void ResurrectingFinalizer(
|
|
const v8::WeakCallbackInfo<v8::Global<v8::Object>>& data) {
|
|
data.GetParameter()->ClearWeak();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TracingInRevivedSubgraph) {
|
|
// Tests that wrappers are traced when they are contained with in a subgraph
|
|
// that is revived by a finalizer.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::Global<v8::Object> g;
|
|
void* first_field = reinterpret_cast<void*>(0x4);
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<v8::Object> api_object =
|
|
ConstructTraceableJSApiObject(context, first_field, nullptr);
|
|
CHECK(!api_object.IsEmpty());
|
|
v8::Local<v8::Object> o =
|
|
v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate));
|
|
o->Set(context, v8_str("link"), api_object).FromJust();
|
|
g.Reset(isolate, o);
|
|
g.SetWeak(&g, ResurrectingFinalizer, v8::WeakCallbackType::kFinalizer);
|
|
}
|
|
CcTest::CollectGarbage(i::OLD_SPACE);
|
|
CHECK(tracer.IsRegisteredFromV8(first_field));
|
|
}
|
|
|
|
TEST(TracingInEphemerons) {
|
|
// Tests that wrappers that are part of ephemerons are traced.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::Local<v8::Object> key =
|
|
v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate));
|
|
void* first_field = reinterpret_cast<void*>(0x8);
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
Handle<JSWeakMap> weak_map = i_isolate->factory()->NewJSWeakMap();
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<v8::Object> api_object =
|
|
ConstructTraceableJSApiObject(context, first_field, nullptr);
|
|
CHECK(!api_object.IsEmpty());
|
|
Handle<JSObject> js_key =
|
|
handle(JSObject::cast(*v8::Utils::OpenHandle(*key)), i_isolate);
|
|
Handle<JSReceiver> js_api_object = v8::Utils::OpenHandle(*api_object);
|
|
int32_t hash = js_key->GetOrCreateHash(i_isolate)->value();
|
|
JSWeakCollection::Set(weak_map, js_key, js_api_object, hash);
|
|
}
|
|
CcTest::CollectGarbage(i::OLD_SPACE);
|
|
CHECK(tracer.IsRegisteredFromV8(first_field));
|
|
}
|
|
|
|
TEST(FinalizeTracingIsNoopWhenNotMarking) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Isolate* i_isolate = CcTest::i_isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
// Finalize a potentially running garbage collection.
|
|
i_isolate->heap()->CollectGarbage(OLD_SPACE,
|
|
GarbageCollectionReason::kTesting);
|
|
CHECK(i_isolate->heap()->incremental_marking()->IsStopped());
|
|
|
|
int gc_counter = i_isolate->heap()->gc_count();
|
|
tracer.FinalizeTracing();
|
|
CHECK(i_isolate->heap()->incremental_marking()->IsStopped());
|
|
CHECK_EQ(gc_counter, i_isolate->heap()->gc_count());
|
|
}
|
|
|
|
TEST(FinalizeTracingWhenMarking) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Isolate* i_isolate = CcTest::i_isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
// Finalize a potentially running garbage collection.
|
|
i_isolate->heap()->CollectGarbage(OLD_SPACE,
|
|
GarbageCollectionReason::kTesting);
|
|
if (i_isolate->heap()->mark_compact_collector()->sweeping_in_progress()) {
|
|
i_isolate->heap()->mark_compact_collector()->EnsureSweepingCompleted();
|
|
}
|
|
CHECK(i_isolate->heap()->incremental_marking()->IsStopped());
|
|
|
|
i::IncrementalMarking* marking = i_isolate->heap()->incremental_marking();
|
|
marking->Start(i::GarbageCollectionReason::kTesting);
|
|
// Sweeping is not runing so we should immediately start marking.
|
|
CHECK(marking->IsMarking());
|
|
tracer.FinalizeTracing();
|
|
CHECK(marking->IsStopped());
|
|
}
|
|
|
|
TEST(GarbageCollectionForTesting) {
|
|
ManualGCScope manual_gc;
|
|
i::FLAG_expose_gc = true;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Isolate* i_isolate = CcTest::i_isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
int saved_gc_counter = i_isolate->heap()->gc_count();
|
|
tracer.GarbageCollectionForTesting(EmbedderHeapTracer::kUnknown);
|
|
CHECK_GT(i_isolate->heap()->gc_count(), saved_gc_counter);
|
|
}
|
|
|
|
namespace {
|
|
|
|
void ConstructJSObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
|
|
v8::TracedGlobal<v8::Object>* global) {
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(v8::Object::New(isolate));
|
|
CHECK(!object.IsEmpty());
|
|
*global = v8::TracedGlobal<v8::Object>(isolate, object);
|
|
CHECK(!global->IsEmpty());
|
|
}
|
|
|
|
void ConstructJSApiObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
|
|
v8::TracedGlobal<v8::Object>* global) {
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(
|
|
ConstructTraceableJSApiObject(context, nullptr, nullptr));
|
|
CHECK(!object.IsEmpty());
|
|
*global = v8::TracedGlobal<v8::Object>(isolate, object);
|
|
CHECK(!global->IsEmpty());
|
|
}
|
|
|
|
enum class SurvivalMode { kSurvives, kDies };
|
|
|
|
template <typename ModifierFunction, typename ConstructTracedGlobalFunction>
|
|
void TracedGlobalTest(v8::Isolate* isolate,
|
|
ConstructTracedGlobalFunction construct_function,
|
|
ModifierFunction modifier_function, void (*gc_function)(),
|
|
SurvivalMode survives) {
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::TracedGlobal<v8::Object> global;
|
|
construct_function(isolate, context, &global);
|
|
CHECK(InNewSpace(isolate, global));
|
|
modifier_function(global);
|
|
gc_function();
|
|
CHECK_IMPLIES(survives == SurvivalMode::kSurvives, !global.IsEmpty());
|
|
CHECK_IMPLIES(survives == SurvivalMode::kDies, global.IsEmpty());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TracedGlobalReset) {
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::TracedGlobal<v8::Object> traced;
|
|
ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced);
|
|
CHECK(!traced.IsEmpty());
|
|
traced.Reset();
|
|
CHECK(traced.IsEmpty());
|
|
}
|
|
|
|
TEST(TracedGlobalInStdVector) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
std::vector<v8::TracedGlobal<v8::Object>> vec;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
vec.emplace_back(isolate, v8::Object::New(isolate));
|
|
}
|
|
CHECK(!vec[0].IsEmpty());
|
|
InvokeMarkSweep();
|
|
CHECK(vec[0].IsEmpty());
|
|
}
|
|
|
|
TEST(TracedGlobalInStdUnorderedMap) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
std::unordered_map<int, v8::TracedGlobal<v8::Object>> map;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
map.emplace(std::piecewise_construct, std::forward_as_tuple(1),
|
|
std::forward_as_tuple(isolate, v8::Object::New(isolate)));
|
|
}
|
|
CHECK(!map[1].IsEmpty());
|
|
InvokeMarkSweep();
|
|
CHECK(map[1].IsEmpty());
|
|
}
|
|
|
|
TEST(TracedGlobalToUnmodifiedJSObjectDiesOnMarkSweep) {
|
|
CcTest::InitializeVM();
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSObject,
|
|
[](const TracedGlobal<v8::Object>& global) {}, InvokeMarkSweep,
|
|
SurvivalMode::kDies);
|
|
}
|
|
|
|
TEST(TracedGlobalToUnmodifiedJSObjectSurvivesMarkSweepWhenHeldAliveOtherwise) {
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Global<v8::Object> strong_global;
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSObject,
|
|
[isolate, &strong_global](const TracedGlobal<v8::Object>& global) {
|
|
v8::HandleScope scope(isolate);
|
|
strong_global = v8::Global<v8::Object>(isolate, global.Get(isolate));
|
|
},
|
|
InvokeMarkSweep, SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavenge) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSObject,
|
|
[](const TracedGlobal<v8::Object>& global) {}, InvokeScavenge,
|
|
SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavengeWhenExcludedFromRoots) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
tracer.ConsiderTracedGlobalAsRoot(false);
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSObject,
|
|
[](const TracedGlobal<v8::Object>& global) {}, InvokeScavenge,
|
|
SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST(TracedGlobalToUnmodifiedJSApiObjectSurvivesScavengePerDefault) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
tracer.ConsiderTracedGlobalAsRoot(true);
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSApiObject,
|
|
[](const TracedGlobal<v8::Object>& global) {}, InvokeScavenge,
|
|
SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST(TracedGlobalToUnmodifiedJSApiObjectDiesOnScavengeWhenExcludedFromRoots) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
tracer.ConsiderTracedGlobalAsRoot(false);
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSApiObject,
|
|
[](const TracedGlobal<v8::Object>& global) {}, InvokeScavenge,
|
|
SurvivalMode::kDies);
|
|
}
|
|
|
|
TEST(TracedGlobalWrapperClassId) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
v8::TracedGlobal<v8::Object> traced;
|
|
ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced);
|
|
CHECK_EQ(0, traced.WrapperClassId());
|
|
traced.SetWrapperClassId(17);
|
|
CHECK_EQ(17, traced.WrapperClassId());
|
|
}
|
|
|
|
namespace {
|
|
|
|
class TracedGlobalVisitor final
|
|
: public v8::EmbedderHeapTracer::TracedGlobalHandleVisitor {
|
|
public:
|
|
~TracedGlobalVisitor() override = default;
|
|
void VisitTracedGlobalHandle(const TracedGlobal<Value>& value) final {
|
|
if (value.WrapperClassId() == 57) {
|
|
count_++;
|
|
}
|
|
}
|
|
|
|
size_t count() const { return count_; }
|
|
|
|
private:
|
|
size_t count_ = 0;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(TracedGlobalIteration) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
v8::TracedGlobal<v8::Object> traced;
|
|
ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced);
|
|
CHECK(!traced.IsEmpty());
|
|
traced.SetWrapperClassId(57);
|
|
TracedGlobalVisitor visitor;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
tracer.IterateTracedGlobalHandles(&visitor);
|
|
}
|
|
CHECK_EQ(1, visitor.count());
|
|
}
|
|
|
|
} // namespace heap
|
|
} // namespace internal
|
|
} // namespace v8
|