fc49e77f26
This CL only affects non-production code. In non-production code, test runners may invoke tasks (base::RunLoop()) with an interesting stack. V8 assumes that it can clear certain data structures when running from a non-nested task due to not having any interesting stack on top. During testing this can lead to UAF on stack as data structures are prematurely cleared. With cppgc this failure can be fixed as the information on whether test runners invoke tasks with a non-trivial stack is actually present. Example failure: https://logs.chromium.org/logs/chromium/buildbucket/cr-buildbucket.appspot.com/8847453411432681120/+/steps/webkit_unit_tests__with_patch__on_Ubuntu-18.04/0/logs/Flaky_failure:_WebSocketStreamTest.ConnectWithFailedHandshake__status_CRASH_SUCCESS_/0 Change-Id: Ib9f6fb2d8a1aa43d0b973afeb2d0a740c769e784 Bug: chromium:1056170 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2891574 Reviewed-by: Omer Katz <omerkatz@chromium.org> Commit-Queue: Michael Lippautz <mlippautz@chromium.org> Cr-Commit-Position: refs/heads/master@{#74539}
1334 lines
48 KiB
C++
1334 lines
48 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/api-inl.h"
|
|
#include "src/heap/embedder-tracing.h"
|
|
#include "src/heap/heap-inl.h"
|
|
#include "src/heap/heap.h"
|
|
#include "src/heap/safepoint.h"
|
|
#include "src/objects/module.h"
|
|
#include "src/objects/objects-inl.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::Value>* global) {
|
|
to_register_with_v8_.push_back(global);
|
|
}
|
|
|
|
void AddReferenceForTracing(v8::TracedReference<v8::Value>* ref) {
|
|
to_register_with_v8_references_.push_back(ref);
|
|
}
|
|
|
|
bool AdvanceTracing(double deadline_in_ms) final {
|
|
for (auto global : to_register_with_v8_) {
|
|
RegisterEmbedderReference(global->As<v8::Data>());
|
|
}
|
|
to_register_with_v8_.clear();
|
|
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_.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 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::Value>*> to_register_with_v8_;
|
|
std::vector<v8::TracedReference<v8::Value>*> to_register_with_v8_references_;
|
|
bool consider_traced_global_as_root_ = true;
|
|
TracePrologueBehavior prologue_behavior_ = TracePrologueBehavior::kNoop;
|
|
v8::Global<v8::Array> array_;
|
|
};
|
|
|
|
} // 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;
|
|
heap::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_and_second_field = reinterpret_cast<void*>(0x2);
|
|
v8::Local<v8::Object> api_object = ConstructTraceableJSApiObject(
|
|
context, first_and_second_field, first_and_second_field);
|
|
CHECK(!api_object.IsEmpty());
|
|
CcTest::CollectGarbage(i::OLD_SPACE);
|
|
CHECK(tracer.IsRegisteredFromV8(first_and_second_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;
|
|
heap::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::Value> g;
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<v8::Value> 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;
|
|
heap::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_and_second_field = reinterpret_cast<void*>(0x4);
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<v8::Object> api_object = ConstructTraceableJSApiObject(
|
|
context, first_and_second_field, first_and_second_field);
|
|
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_and_second_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;
|
|
heap::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_and_second_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_and_second_field, first_and_second_field);
|
|
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_and_second_field));
|
|
}
|
|
|
|
TEST(FinalizeTracingIsNoopWhenNotMarking) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Isolate* i_isolate = CcTest::i_isolate();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::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) {
|
|
if (!FLAG_incremental_marking) return;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Isolate* i_isolate = CcTest::i_isolate();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::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();
|
|
{
|
|
SafepointScope scope(i_isolate->heap());
|
|
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;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
int saved_gc_counter = i_isolate->heap()->gc_count();
|
|
tracer.GarbageCollectionForTesting(
|
|
EmbedderHeapTracer::EmbedderStackState::kMayContainHeapPointers);
|
|
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());
|
|
}
|
|
|
|
template <typename T>
|
|
void ConstructJSApiObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
|
|
T* global) {
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(
|
|
ConstructTraceableJSApiObject(context, nullptr, nullptr));
|
|
CHECK(!object.IsEmpty());
|
|
*global = T(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);
|
|
|
|
auto global = std::make_unique<v8::TracedGlobal<v8::Object>>();
|
|
construct_function(isolate, context, global.get());
|
|
CHECK(InCorrectGeneration(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(TracedGlobalCopyWithDestructor) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
|
|
|
|
const size_t initial_count = global_handles->handles_count();
|
|
auto global1 = std::make_unique<v8::TracedGlobal<v8::Object>>();
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
global1->Reset(isolate, v8::Object::New(isolate));
|
|
}
|
|
auto global2 = std::make_unique<v8::TracedGlobal<v8::Object>>(*global1);
|
|
auto global3 = std::make_unique<v8::TracedGlobal<v8::Object>>();
|
|
*global3 = *global2;
|
|
CHECK_EQ(initial_count + 3, global_handles->handles_count());
|
|
CHECK(!global1->IsEmpty());
|
|
CHECK_EQ(*global1, *global2);
|
|
CHECK_EQ(*global2, *global3);
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
auto tmp = v8::Local<v8::Object>::New(isolate, *global3);
|
|
CHECK(!tmp.IsEmpty());
|
|
InvokeMarkSweep();
|
|
}
|
|
CHECK_EQ(initial_count + 3, global_handles->handles_count());
|
|
CHECK(!global1->IsEmpty());
|
|
CHECK_EQ(*global1, *global2);
|
|
CHECK_EQ(*global2, *global3);
|
|
InvokeMarkSweep();
|
|
CHECK_EQ(initial_count, global_handles->handles_count());
|
|
CHECK(global1->IsEmpty());
|
|
CHECK_EQ(*global1, *global2);
|
|
CHECK_EQ(*global2, *global3);
|
|
}
|
|
|
|
TEST(TracedGlobalCopyNoDestructor) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
|
|
|
|
const size_t initial_count = global_handles->handles_count();
|
|
auto global1 = std::make_unique<v8::TracedReference<v8::Value>>();
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
global1->Reset(isolate, v8::Object::New(isolate));
|
|
}
|
|
auto global2 = std::make_unique<v8::TracedReference<v8::Value>>(*global1);
|
|
auto global3 = std::make_unique<v8::TracedReference<v8::Value>>();
|
|
*global3 = *global2;
|
|
CHECK_EQ(initial_count + 3, global_handles->handles_count());
|
|
CHECK(!global1->IsEmpty());
|
|
CHECK_EQ(*global1, *global2);
|
|
CHECK_EQ(*global2, *global3);
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
auto tmp = v8::Local<v8::Value>::New(isolate, *global3);
|
|
CHECK(!tmp.IsEmpty());
|
|
InvokeMarkSweep();
|
|
}
|
|
CHECK_EQ(initial_count + 3, global_handles->handles_count());
|
|
CHECK(!global1->IsEmpty());
|
|
CHECK_EQ(*global1, *global2);
|
|
CHECK_EQ(*global2, *global3);
|
|
InvokeMarkSweep();
|
|
CHECK_EQ(initial_count, global_handles->handles_count());
|
|
}
|
|
|
|
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) {
|
|
if (FLAG_single_generation) return;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSObject,
|
|
[](const TracedGlobal<v8::Object>& global) {}, []() { InvokeScavenge(); },
|
|
SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavengeWhenExcludedFromRoots) {
|
|
if (FLAG_single_generation) return;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
tracer.ConsiderTracedGlobalAsRoot(false);
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSObject,
|
|
[](const TracedGlobal<v8::Object>& global) {}, []() { InvokeScavenge(); },
|
|
SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST(TracedGlobalToUnmodifiedJSApiObjectSurvivesScavengePerDefault) {
|
|
if (FLAG_single_generation) return;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
tracer.ConsiderTracedGlobalAsRoot(true);
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSApiObject<TracedGlobal<v8::Object>>,
|
|
[](const TracedGlobal<v8::Object>& global) {}, []() { InvokeScavenge(); },
|
|
SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST(TracedGlobalToUnmodifiedJSApiObjectDiesOnScavengeWhenExcludedFromRoots) {
|
|
if (FLAG_single_generation) return;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
tracer.ConsiderTracedGlobalAsRoot(false);
|
|
TracedGlobalTest(
|
|
CcTest::isolate(), ConstructJSApiObject<TracedGlobal<v8::Object>>,
|
|
[](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;
|
|
heap::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());
|
|
}
|
|
|
|
TEST(TracedReferenceHandlesMarking) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
auto live = std::make_unique<v8::TracedReference<v8::Value>>();
|
|
auto dead = std::make_unique<v8::TracedReference<v8::Value>>();
|
|
live->Reset(isolate, v8::Undefined(isolate));
|
|
dead->Reset(isolate, v8::Undefined(isolate));
|
|
i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
|
|
{
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
tracer.AddReferenceForTracing(live.get());
|
|
const size_t initial_count = global_handles->handles_count();
|
|
InvokeMarkSweep();
|
|
const size_t final_count = global_handles->handles_count();
|
|
// Handles are black allocated, so the first GC does not collect them.
|
|
CHECK_EQ(initial_count, final_count);
|
|
}
|
|
|
|
{
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
tracer.AddReferenceForTracing(live.get());
|
|
const size_t initial_count = global_handles->handles_count();
|
|
InvokeMarkSweep();
|
|
const size_t final_count = global_handles->handles_count();
|
|
CHECK_EQ(initial_count, final_count + 1);
|
|
}
|
|
}
|
|
|
|
TEST(TracedReferenceHandlesDoNotLeak) {
|
|
// TracedReference handles are not cleared by the destructor of the embedder
|
|
// object. To avoid leaks we need to mark these handles during GC.
|
|
// This test checks that unmarked handles do not leak.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
auto ref = std::make_unique<v8::TracedReference<v8::Value>>();
|
|
ref->Reset(isolate, v8::Undefined(isolate));
|
|
i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
|
|
const size_t initial_count = global_handles->handles_count();
|
|
// We need two GCs because handles are black allocated.
|
|
InvokeMarkSweep();
|
|
InvokeMarkSweep();
|
|
const size_t final_count = global_handles->handles_count();
|
|
CHECK_EQ(initial_count, final_count + 1);
|
|
}
|
|
|
|
TEST(TracedGlobalHandlesAreRetained) {
|
|
// TracedGlobal handles are cleared by the destructor of the embedder object.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TracedGlobal<v8::Value> global;
|
|
global.Reset(isolate, v8::Undefined(isolate));
|
|
i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
|
|
const size_t initial_count = global_handles->handles_count();
|
|
// We need two GCs because handles are black allocated.
|
|
InvokeMarkSweep();
|
|
InvokeMarkSweep();
|
|
const size_t final_count = global_handles->handles_count();
|
|
CHECK_EQ(initial_count, final_count);
|
|
}
|
|
|
|
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;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
auto traced = std::make_unique<v8::TracedGlobal<v8::Object>>();
|
|
ConstructJSObject(isolate, isolate->GetCurrentContext(), traced.get());
|
|
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) {
|
|
if (FLAG_single_generation) return;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
TestEmbedderHeapTracer tracer;
|
|
tracer.ConsiderTracedGlobalAsRoot(false);
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
auto traced = std::make_unique<v8::TracedGlobal<v8::Object>>();
|
|
ConstructJSApiObject(isolate, isolate->GetCurrentContext(), traced.get());
|
|
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.get(), FinalizationCallback);
|
|
heap::InvokeScavenge();
|
|
CHECK(traced->IsEmpty());
|
|
}
|
|
|
|
TEST(TracedGlobalSetFinalizationCallbackMarkSweep) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
auto traced = std::make_unique<v8::TracedGlobal<v8::Object>>();
|
|
ConstructJSApiObject(isolate, isolate->GetCurrentContext(), traced.get());
|
|
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.get(), FinalizationCallback);
|
|
heap::InvokeMarkSweep();
|
|
CHECK(traced->IsEmpty());
|
|
}
|
|
|
|
TEST(TracePrologueCallingIntoV8WriteBarrier) {
|
|
// Regression test: https://crbug.com/940003
|
|
if (!FLAG_incremental_marking) return;
|
|
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));
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
SimulateIncrementalMarking(CcTest::i_isolate()->heap());
|
|
// Finish GC to avoid removing the tracer while GC is running which may end up
|
|
// in an infinite loop because of unprocessed objects.
|
|
heap::InvokeMarkSweep();
|
|
}
|
|
|
|
TEST(TracedGlobalWithDestructor) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
|
|
|
|
const size_t initial_count = global_handles->handles_count();
|
|
auto* traced = new v8::TracedGlobal<v8::Object>();
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
CHECK(traced->IsEmpty());
|
|
*traced = v8::TracedGlobal<v8::Object>(isolate, object);
|
|
CHECK(!traced->IsEmpty());
|
|
CHECK_EQ(initial_count + 1, global_handles->handles_count());
|
|
}
|
|
delete traced;
|
|
CHECK_EQ(initial_count, global_handles->handles_count());
|
|
// GC should not need to clear the handle.
|
|
heap::InvokeMarkSweep();
|
|
CHECK_EQ(initial_count, global_handles->handles_count());
|
|
}
|
|
|
|
TEST(TracedGlobalNoDestructor) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
|
|
|
|
const size_t initial_count = global_handles->handles_count();
|
|
char* memory = new char[sizeof(v8::TracedReference<v8::Value>)];
|
|
auto* traced = new (memory) v8::TracedReference<v8::Value>();
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Value> object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
CHECK(traced->IsEmpty());
|
|
*traced = v8::TracedReference<v8::Value>(isolate, object);
|
|
CHECK(!traced->IsEmpty());
|
|
CHECK_EQ(initial_count + 1, global_handles->handles_count());
|
|
}
|
|
traced->~TracedReference<v8::Value>();
|
|
CHECK_EQ(initial_count + 1, global_handles->handles_count());
|
|
// GC should clear the handle.
|
|
heap::InvokeMarkSweep();
|
|
CHECK_EQ(initial_count, global_handles->handles_count());
|
|
delete[] memory;
|
|
}
|
|
|
|
namespace {
|
|
|
|
class EmptyEmbedderHeapTracer : public v8::EmbedderHeapTracer {
|
|
public:
|
|
void RegisterV8References(
|
|
const std::vector<std::pair<void*, void*>>& embedder_fields) final {}
|
|
|
|
bool AdvanceTracing(double deadline_in_ms) final { return true; }
|
|
bool IsTracingDone() final { return true; }
|
|
void TracePrologue(EmbedderHeapTracer::TraceFlags) final {}
|
|
void TraceEpilogue(TraceSummary*) final {}
|
|
void EnterFinalPause(EmbedderStackState) final {}
|
|
};
|
|
|
|
// EmbedderHeapTracer that can optimize Scavenger handling when used with
|
|
// TraceGlobal handles that have destructors.
|
|
class EmbedderHeapTracerDestructorNonTracingClearing final
|
|
: public EmptyEmbedderHeapTracer {
|
|
public:
|
|
explicit EmbedderHeapTracerDestructorNonTracingClearing(
|
|
uint16_t class_id_to_optimize)
|
|
: class_id_to_optimize_(class_id_to_optimize) {}
|
|
|
|
bool IsRootForNonTracingGC(const v8::TracedGlobal<v8::Value>& handle) final {
|
|
return handle.WrapperClassId() != class_id_to_optimize_;
|
|
}
|
|
|
|
private:
|
|
uint16_t class_id_to_optimize_;
|
|
};
|
|
|
|
// EmbedderHeapTracer that can optimize Scavenger handling when used with
|
|
// TraceGlobal handles without destructors.
|
|
class EmbedderHeapTracerNoDestructorNonTracingClearing final
|
|
: public EmptyEmbedderHeapTracer {
|
|
public:
|
|
explicit EmbedderHeapTracerNoDestructorNonTracingClearing(
|
|
uint16_t class_id_to_optimize)
|
|
: class_id_to_optimize_(class_id_to_optimize) {}
|
|
|
|
bool IsRootForNonTracingGC(
|
|
const v8::TracedReference<v8::Value>& handle) final {
|
|
return handle.WrapperClassId() != class_id_to_optimize_;
|
|
}
|
|
|
|
void ResetHandleInNonTracingGC(
|
|
const v8::TracedReference<v8::Value>& handle) final {
|
|
if (handle.WrapperClassId() != class_id_to_optimize_) return;
|
|
|
|
// Convention (for test): Objects that are optimized have their first field
|
|
// set as a back pointer.
|
|
BasicTracedReference<v8::Value>* original_handle =
|
|
reinterpret_cast<BasicTracedReference<v8::Value>*>(
|
|
v8::Object::GetAlignedPointerFromInternalField(
|
|
handle.As<v8::Object>(), 0));
|
|
original_handle->Reset();
|
|
}
|
|
|
|
private:
|
|
uint16_t class_id_to_optimize_;
|
|
};
|
|
|
|
template <typename T>
|
|
void SetupOptimizedAndNonOptimizedHandle(v8::Isolate* isolate,
|
|
uint16_t optimized_class_id,
|
|
T* optimized_handle,
|
|
T* non_optimized_handle) {
|
|
v8::HandleScope scope(isolate);
|
|
|
|
v8::Local<v8::Object> optimized_object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), optimized_handle, nullptr));
|
|
CHECK(optimized_handle->IsEmpty());
|
|
*optimized_handle = T(isolate, optimized_object);
|
|
CHECK(!optimized_handle->IsEmpty());
|
|
optimized_handle->SetWrapperClassId(optimized_class_id);
|
|
|
|
v8::Local<v8::Object> non_optimized_object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
CHECK(non_optimized_handle->IsEmpty());
|
|
*non_optimized_handle = T(isolate, non_optimized_object);
|
|
CHECK(!non_optimized_handle->IsEmpty());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TracedGlobalDestructorReclaimedOnScavenge) {
|
|
if (FLAG_single_generation) return;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
constexpr uint16_t kClassIdToOptimize = 17;
|
|
EmbedderHeapTracerDestructorNonTracingClearing tracer(kClassIdToOptimize);
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
|
|
|
|
const size_t initial_count = global_handles->handles_count();
|
|
auto* optimized_handle = new v8::TracedGlobal<v8::Object>();
|
|
auto* non_optimized_handle = new v8::TracedGlobal<v8::Object>();
|
|
SetupOptimizedAndNonOptimizedHandle(isolate, kClassIdToOptimize,
|
|
optimized_handle, non_optimized_handle);
|
|
CHECK_EQ(initial_count + 2, global_handles->handles_count());
|
|
heap::InvokeScavenge();
|
|
CHECK_EQ(initial_count + 1, global_handles->handles_count());
|
|
CHECK(optimized_handle->IsEmpty());
|
|
delete optimized_handle;
|
|
CHECK(!non_optimized_handle->IsEmpty());
|
|
delete non_optimized_handle;
|
|
CHECK_EQ(initial_count, global_handles->handles_count());
|
|
}
|
|
|
|
TEST(TracedGlobalNoDestructorReclaimedOnScavenge) {
|
|
if (FLAG_single_generation) return;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
constexpr uint16_t kClassIdToOptimize = 23;
|
|
EmbedderHeapTracerNoDestructorNonTracingClearing tracer(kClassIdToOptimize);
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
|
|
|
|
const size_t initial_count = global_handles->handles_count();
|
|
auto* optimized_handle = new v8::TracedReference<v8::Value>();
|
|
auto* non_optimized_handle = new v8::TracedReference<v8::Value>();
|
|
SetupOptimizedAndNonOptimizedHandle(isolate, kClassIdToOptimize,
|
|
optimized_handle, non_optimized_handle);
|
|
CHECK_EQ(initial_count + 2, global_handles->handles_count());
|
|
heap::InvokeScavenge();
|
|
CHECK_EQ(initial_count + 1, global_handles->handles_count());
|
|
CHECK(optimized_handle->IsEmpty());
|
|
delete optimized_handle;
|
|
CHECK(!non_optimized_handle->IsEmpty());
|
|
non_optimized_handle->Reset();
|
|
delete non_optimized_handle;
|
|
CHECK_EQ(initial_count, global_handles->handles_count());
|
|
}
|
|
|
|
namespace {
|
|
|
|
template <typename T>
|
|
V8_NOINLINE void OnStackTest(TestEmbedderHeapTracer* tracer) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Global<v8::Object> observer;
|
|
T stack_ref;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
stack_ref.Reset(isolate, object);
|
|
observer.Reset(isolate, object);
|
|
observer.SetWeak();
|
|
}
|
|
CHECK(!observer.IsEmpty());
|
|
heap::InvokeMarkSweep();
|
|
CHECK(!observer.IsEmpty());
|
|
}
|
|
|
|
V8_NOINLINE void CreateTracedReferenceInDeepStack(
|
|
v8::Isolate* isolate, v8::Global<v8::Object>* observer) {
|
|
v8::TracedReference<v8::Value> stack_ref;
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
stack_ref.Reset(isolate, object);
|
|
observer->Reset(isolate, object);
|
|
observer->SetWeak();
|
|
}
|
|
|
|
V8_NOINLINE void TracedReferenceNotifyEmptyStackTest(
|
|
TestEmbedderHeapTracer* tracer) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Global<v8::Object> observer;
|
|
CreateTracedReferenceInDeepStack(isolate, &observer);
|
|
CHECK(!observer.IsEmpty());
|
|
reinterpret_cast<i::Isolate*>(isolate)
|
|
->heap()
|
|
->local_embedder_heap_tracer()
|
|
->NotifyEmptyEmbedderStack();
|
|
heap::InvokeMarkSweep();
|
|
CHECK(observer.IsEmpty());
|
|
}
|
|
|
|
enum class Operation {
|
|
kCopy,
|
|
kMove,
|
|
};
|
|
|
|
template <typename T>
|
|
void PerformOperation(Operation op, T* lhs, T* rhs) {
|
|
switch (op) {
|
|
case Operation::kMove:
|
|
*lhs = std::move(*rhs);
|
|
break;
|
|
case Operation::kCopy:
|
|
*lhs = *rhs;
|
|
rhs->Reset();
|
|
break;
|
|
}
|
|
}
|
|
|
|
enum class TargetHandling {
|
|
kNonInitialized,
|
|
kInitializedYoungGen,
|
|
kInitializedOldGen
|
|
};
|
|
|
|
template <typename T>
|
|
V8_NOINLINE void StackToHeapTest(TestEmbedderHeapTracer* tracer, Operation op,
|
|
TargetHandling target_handling) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Global<v8::Object> observer;
|
|
T stack_handle;
|
|
T* heap_handle = new T();
|
|
if (target_handling != TargetHandling::kNonInitialized) {
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
CHECK(InCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
if (!FLAG_single_generation &&
|
|
target_handling == TargetHandling::kInitializedOldGen) {
|
|
heap::InvokeScavenge();
|
|
heap::InvokeScavenge();
|
|
CHECK(!i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
}
|
|
heap_handle->Reset(isolate, to_object);
|
|
}
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
stack_handle.Reset(isolate, object);
|
|
observer.Reset(isolate, object);
|
|
observer.SetWeak();
|
|
}
|
|
CHECK(!observer.IsEmpty());
|
|
tracer->AddReferenceForTracing(heap_handle);
|
|
heap::InvokeMarkSweep();
|
|
CHECK(!observer.IsEmpty());
|
|
tracer->AddReferenceForTracing(heap_handle);
|
|
PerformOperation(op, heap_handle, &stack_handle);
|
|
heap::InvokeMarkSweep();
|
|
CHECK(!observer.IsEmpty());
|
|
heap::InvokeMarkSweep();
|
|
CHECK(observer.IsEmpty());
|
|
delete heap_handle;
|
|
}
|
|
|
|
template <typename T>
|
|
V8_NOINLINE void HeapToStackTest(TestEmbedderHeapTracer* tracer, Operation op,
|
|
TargetHandling target_handling) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Global<v8::Object> observer;
|
|
T stack_handle;
|
|
T* heap_handle = new T();
|
|
if (target_handling != TargetHandling::kNonInitialized) {
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
CHECK(InCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
if (!FLAG_single_generation &&
|
|
target_handling == TargetHandling::kInitializedOldGen) {
|
|
heap::InvokeScavenge();
|
|
heap::InvokeScavenge();
|
|
CHECK(!i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
}
|
|
stack_handle.Reset(isolate, to_object);
|
|
}
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
heap_handle->Reset(isolate, object);
|
|
observer.Reset(isolate, object);
|
|
observer.SetWeak();
|
|
}
|
|
CHECK(!observer.IsEmpty());
|
|
tracer->AddReferenceForTracing(heap_handle);
|
|
heap::InvokeMarkSweep();
|
|
CHECK(!observer.IsEmpty());
|
|
PerformOperation(op, &stack_handle, heap_handle);
|
|
heap::InvokeMarkSweep();
|
|
CHECK(!observer.IsEmpty());
|
|
stack_handle.Reset();
|
|
heap::InvokeMarkSweep();
|
|
CHECK(observer.IsEmpty());
|
|
delete heap_handle;
|
|
}
|
|
|
|
template <typename T>
|
|
V8_NOINLINE void StackToStackTest(TestEmbedderHeapTracer* tracer, Operation op,
|
|
TargetHandling target_handling) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Global<v8::Object> observer;
|
|
T stack_handle1;
|
|
T stack_handle2;
|
|
if (target_handling != TargetHandling::kNonInitialized) {
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
CHECK(InCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
if (!FLAG_single_generation &&
|
|
target_handling == TargetHandling::kInitializedOldGen) {
|
|
heap::InvokeScavenge();
|
|
heap::InvokeScavenge();
|
|
CHECK(!i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
}
|
|
stack_handle2.Reset(isolate, to_object);
|
|
}
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
stack_handle1.Reset(isolate, object);
|
|
observer.Reset(isolate, object);
|
|
observer.SetWeak();
|
|
}
|
|
CHECK(!observer.IsEmpty());
|
|
heap::InvokeMarkSweep();
|
|
CHECK(!observer.IsEmpty());
|
|
PerformOperation(op, &stack_handle2, &stack_handle1);
|
|
heap::InvokeMarkSweep();
|
|
CHECK(!observer.IsEmpty());
|
|
stack_handle2.Reset();
|
|
heap::InvokeMarkSweep();
|
|
CHECK(observer.IsEmpty());
|
|
}
|
|
|
|
template <typename T>
|
|
V8_NOINLINE void TracedReferenceCleanedTest(TestEmbedderHeapTracer* tracer) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
const size_t before =
|
|
CcTest::i_isolate()->global_handles()->NumberOfOnStackHandlesForTesting();
|
|
for (int i = 0; i < 100; i++) {
|
|
T stack_handle;
|
|
stack_handle.Reset(isolate, object);
|
|
}
|
|
CHECK_EQ(before + 1, CcTest::i_isolate()
|
|
->global_handles()
|
|
->NumberOfOnStackHandlesForTesting());
|
|
}
|
|
|
|
V8_NOINLINE void TracedGlobalDestructorTest(TestEmbedderHeapTracer* tracer) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::Global<v8::Object> observer;
|
|
{
|
|
v8::TracedGlobal<v8::Value> stack_handle;
|
|
{
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
stack_handle.Reset(isolate, object);
|
|
observer.Reset(isolate, object);
|
|
observer.SetWeak();
|
|
}
|
|
CHECK(!observer.IsEmpty());
|
|
heap::InvokeMarkSweep();
|
|
CHECK(!observer.IsEmpty());
|
|
}
|
|
heap::InvokeMarkSweep();
|
|
CHECK(observer.IsEmpty());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TracedReferenceOnStack) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
OnStackTest<v8::TracedReference<v8::Value>>(&tracer);
|
|
}
|
|
|
|
TEST(TracedGlobalOnStack) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
OnStackTest<v8::TracedGlobal<v8::Value>>(&tracer);
|
|
}
|
|
|
|
TEST(TracedReferenceCleaned) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
TracedReferenceCleanedTest<v8::TracedReference<v8::Value>>(&tracer);
|
|
}
|
|
|
|
TEST(TracedGlobalCleaned) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
TracedReferenceCleanedTest<v8::TracedGlobal<v8::Value>>(&tracer);
|
|
}
|
|
|
|
TEST(TracedReferenceMove) {
|
|
using ReferenceType = v8::TracedReference<v8::Value>;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kNonInitialized);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedOldGen);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kNonInitialized);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedYoungGen);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedOldGen);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kNonInitialized);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedOldGen);
|
|
}
|
|
|
|
TEST(TracedReferenceCopy) {
|
|
using ReferenceType = v8::TracedReference<v8::Value>;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kNonInitialized);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedOldGen);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kNonInitialized);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedYoungGen);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedOldGen);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kNonInitialized);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedOldGen);
|
|
}
|
|
|
|
TEST(TracedGlobalMove) {
|
|
using ReferenceType = v8::TracedGlobal<v8::Value>;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kNonInitialized);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedOldGen);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kNonInitialized);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedYoungGen);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedOldGen);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kNonInitialized);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
|
|
TargetHandling::kInitializedOldGen);
|
|
}
|
|
|
|
TEST(TracedGlobalCopy) {
|
|
using ReferenceType = v8::TracedGlobal<v8::Value>;
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kNonInitialized);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedOldGen);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kNonInitialized);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedYoungGen);
|
|
HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedOldGen);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kNonInitialized);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedOldGen);
|
|
}
|
|
|
|
TEST(TracedGlobalDestructor) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
TracedGlobalDestructorTest(&tracer);
|
|
}
|
|
|
|
TEST(NotifyEmptyStack) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
|
|
&tracer);
|
|
tracer.SetStackStart(&manual_gc);
|
|
TracedReferenceNotifyEmptyStackTest(&tracer);
|
|
}
|
|
|
|
} // namespace heap
|
|
} // namespace internal
|
|
} // namespace v8
|