4c7cabb1d8
v8::EmbedderHeapTracer::TracePrologue may call back into V8 during StartMarking. In this case we expect that the write barriers are set up and consistent, i.e., global flag matches page flag. Blink calls back into V8 in a corner case where sweeping is finalized on incremental marking start which may trigger resettting a V8 Value which may trigger DescriptorArray re-shuffling. Bug: chromium:940003 Change-Id: Ia15c798d0faaab802df1c3b569b5b6a323a4fe59 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1514492 Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Hannes Payer <hpayer@chromium.org> Commit-Queue: Michael Lippautz <mlippautz@chromium.org> Cr-Commit-Position: refs/heads/master@{#60145}
584 lines
19 KiB
C++
584 lines
19 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/heap/heap-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);
|
|
}
|
|
|
|
enum class TracePrologueBehavior { kNoop, kCallV8WriteBarrier };
|
|
|
|
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::TracedGlobal<v8::Object>* global) {
|
|
to_register_with_v8_.push_back(global);
|
|
}
|
|
|
|
bool AdvanceTracing(double deadline_in_ms) final {
|
|
for (auto global : to_register_with_v8_) {
|
|
RegisterEmbedderReference(global->As<v8::Value>());
|
|
}
|
|
to_register_with_v8_.clear();
|
|
return true;
|
|
}
|
|
|
|
bool IsTracingDone() final { return to_register_with_v8_.empty(); }
|
|
|
|
void TracePrologue() final {
|
|
if (prologue_behavior_ == TracePrologueBehavior::kCallV8WriteBarrier) {
|
|
auto local = array_.Get(isolate());
|
|
local->Set(local->CreationContext(), 0, v8::Object::New(isolate()))
|
|
.Check();
|
|
}
|
|
}
|
|
|
|
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:
|
|
std::vector<std::pair<void*, void*>> registered_from_v8_;
|
|
std::vector<v8::TracedGlobal<v8::Object>*> to_register_with_v8_;
|
|
bool consider_traced_global_as_root_ = true;
|
|
TracePrologueBehavior prologue_behavior_ = TracePrologueBehavior::kNoop;
|
|
v8::Global<v8::Array> array_;
|
|
};
|
|
|
|
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;
|
|
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;
|
|
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::TracedGlobal<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);
|
|
}
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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(InYoungGeneration(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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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 {
|
|
|
|
void FinalizationCallback(const WeakCallbackInfo<void>& data) {
|
|
v8::TracedGlobal<v8::Object>* traced =
|
|
reinterpret_cast<v8::TracedGlobal<v8::Object>*>(data.GetParameter());
|
|
CHECK_EQ(reinterpret_cast<void*>(0x4), data.GetInternalField(0));
|
|
CHECK_EQ(reinterpret_cast<void*>(0x8), data.GetInternalField(1));
|
|
traced->Reset();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TracedGlobalSetFinalizationCallbackScavenge) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
TestEmbedderHeapTracer tracer;
|
|
tracer.ConsiderTracedGlobalAsRoot(false);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
v8::TracedGlobal<v8::Object> traced;
|
|
ConstructJSApiObject(isolate, isolate->GetCurrentContext(), &traced);
|
|
CHECK(!traced.IsEmpty());
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
auto local = traced.Get(isolate);
|
|
local->SetAlignedPointerInInternalField(0, reinterpret_cast<void*>(0x4));
|
|
local->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(0x8));
|
|
}
|
|
traced.SetFinalizationCallback(&traced, FinalizationCallback);
|
|
heap::InvokeScavenge();
|
|
CHECK(traced.IsEmpty());
|
|
}
|
|
|
|
TEST(TracedGlobalSetFinalizationCallbackMarkSweep) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
TestEmbedderHeapTracer tracer;
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
v8::TracedGlobal<v8::Object> traced;
|
|
ConstructJSApiObject(isolate, isolate->GetCurrentContext(), &traced);
|
|
CHECK(!traced.IsEmpty());
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
auto local = traced.Get(isolate);
|
|
local->SetAlignedPointerInInternalField(0, reinterpret_cast<void*>(0x4));
|
|
local->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(0x8));
|
|
}
|
|
traced.SetFinalizationCallback(&traced, FinalizationCallback);
|
|
heap::InvokeMarkSweep();
|
|
CHECK(traced.IsEmpty());
|
|
}
|
|
|
|
TEST(TracePrologueCallingIntoV8WriteBarrier) {
|
|
// Regression test: https://crbug.com/940003
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Global<v8::Array> global;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
auto local = v8::Array::New(isolate, 10);
|
|
global.Reset(isolate, local);
|
|
}
|
|
TestEmbedderHeapTracer tracer(TracePrologueBehavior::kCallV8WriteBarrier,
|
|
std::move(global));
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
SimulateIncrementalMarking(CcTest::i_isolate()->heap());
|
|
}
|
|
|
|
} // namespace heap
|
|
} // namespace internal
|
|
} // namespace v8
|