f81430caa9
Since its introduction (behind a compile-time flag), conservative stack scanning was disabled by default on tests. This CL inverts this logic, enabling CSS by default for all tests that do not define an explicit scope to disable it. Bug: v8:13257 Change-Id: I5ea4249d02f69b0b1e195415c2562daf5d8c0ea9 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4100912 Reviewed-by: Dominik Inführ <dinfuehr@chromium.org> Commit-Queue: Nikolaos Papaspyrou <nikolaos@chromium.org> Cr-Commit-Position: refs/heads/main@{#84848}
1223 lines
45 KiB
C++
1223 lines
45 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-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, TracingInEphemerons) {
|
|
// Tests that wrappers that are part of ephemerons are traced.
|
|
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);
|
|
|
|
v8::Local<v8::Object> key =
|
|
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
|
|
void* first_and_second_field = reinterpret_cast<void*>(0x8);
|
|
Handle<JSWeakMap> weak_map = i_isolate()->factory()->NewJSWeakMap();
|
|
{
|
|
v8::HandleScope inner_scope(v8_isolate());
|
|
v8::Local<v8::Object> api_object = ConstructTraceableJSApiObject(
|
|
context, first_and_second_field, first_and_second_field);
|
|
EXPECT_FALSE(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);
|
|
}
|
|
CollectGarbage(i::OLD_SPACE);
|
|
EXPECT_TRUE(tracer.IsRegisteredFromV8(first_and_second_field));
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
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));
|
|
EXPECT_FALSE(object.IsEmpty());
|
|
*global = T(isolate, object);
|
|
EXPECT_FALSE(global->IsEmpty());
|
|
}
|
|
|
|
enum class SurvivalMode { kSurvives, kDies };
|
|
|
|
template <typename ModifierFunction, typename ConstructTracedReferenceFunction,
|
|
typename GCFunction>
|
|
void TracedReferenceTest(v8::Isolate* isolate,
|
|
ConstructTracedReferenceFunction construct_function,
|
|
ModifierFunction modifier_function,
|
|
GCFunction 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_handles =
|
|
reinterpret_cast<i::Isolate*>(isolate)->global_handles();
|
|
|
|
const size_t initial_count = global_handles->handles_count();
|
|
auto handle = std::make_unique<v8::TracedReference<v8::Object>>();
|
|
construct_function(isolate, context, handle.get());
|
|
ASSERT_TRUE(IsNewObjectInCorrectGeneration(isolate, *handle));
|
|
modifier_function(*handle);
|
|
const size_t after_modification_count = global_handles->handles_count();
|
|
gc_function();
|
|
// Cannot check the handle as it is not explicitly cleared by the GC. Instead
|
|
// check the handles count.
|
|
CHECK_IMPLIES(survives == SurvivalMode::kSurvives,
|
|
after_modification_count == global_handles->handles_count());
|
|
CHECK_IMPLIES(survives == SurvivalMode::kDies,
|
|
initial_count == global_handles->handles_count());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(EmbedderTracingTest, TracedReferenceReset) {
|
|
v8::HandleScope scope(v8_isolate());
|
|
v8::TracedReference<v8::Object> handle;
|
|
ConstructJSObject(v8_isolate(), v8_isolate()->GetCurrentContext(), &handle);
|
|
EXPECT_FALSE(handle.IsEmpty());
|
|
handle.Reset();
|
|
EXPECT_TRUE(handle.IsEmpty());
|
|
}
|
|
|
|
TEST_F(EmbedderTracingTest, TracedReferenceCopyReferences) {
|
|
ManualGCScope manual_gc(i_isolate());
|
|
v8::HandleScope outer_scope(v8_isolate());
|
|
auto* traced_handles = i_isolate()->traced_handles();
|
|
|
|
const size_t initial_count = traced_handles->used_node_count();
|
|
auto handle1 = std::make_unique<v8::TracedReference<v8::Value>>();
|
|
{
|
|
v8::HandleScope scope(v8_isolate());
|
|
handle1->Reset(v8_isolate(), v8::Object::New(v8_isolate()));
|
|
}
|
|
auto handle2 = std::make_unique<v8::TracedReference<v8::Value>>(*handle1);
|
|
auto handle3 = std::make_unique<v8::TracedReference<v8::Value>>();
|
|
*handle3 = *handle2;
|
|
EXPECT_EQ(initial_count + 3, traced_handles->used_node_count());
|
|
EXPECT_FALSE(handle1->IsEmpty());
|
|
EXPECT_EQ(*handle1, *handle2);
|
|
EXPECT_EQ(*handle2, *handle3);
|
|
{
|
|
v8::HandleScope scope(v8_isolate());
|
|
auto tmp = v8::Local<v8::Value>::New(v8_isolate(), *handle3);
|
|
EXPECT_FALSE(tmp.IsEmpty());
|
|
// Conservative scanning may find stale pointers to on-stack handles.
|
|
// Disable scanning, assuming the slots are overwritten.
|
|
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
|
|
i_isolate()->heap());
|
|
EmbedderStackStateScope stack_scope =
|
|
EmbedderStackStateScope::ExplicitScopeForTesting(
|
|
reinterpret_cast<i::Isolate*>(v8_isolate())
|
|
->heap()
|
|
->local_embedder_heap_tracer(),
|
|
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
|
|
FullGC();
|
|
}
|
|
EXPECT_EQ(initial_count, traced_handles->used_node_count());
|
|
}
|
|
|
|
TEST_F(EmbedderTracingTest, TracedReferenceToUnmodifiedJSObjectDiesOnFullGC) {
|
|
// When stressing incremental marking, a write barrier may keep the object
|
|
// alive.
|
|
if (v8_flags.stress_incremental_marking) return;
|
|
|
|
TracedReferenceTest(
|
|
v8_isolate(), ConstructJSObject,
|
|
[](const TracedReference<v8::Object>&) {}, [this]() { FullGC(); },
|
|
SurvivalMode::kDies);
|
|
}
|
|
|
|
TEST_F(
|
|
EmbedderTracingTest,
|
|
TracedReferenceToUnmodifiedJSObjectDiesOnFullGCEvenWhenPointeeIsHeldAlive) {
|
|
ManualGCScope manual_gcs(i_isolate());
|
|
// The TracedReference itself will die as it's not found by the full GC. The
|
|
// pointee will be kept alive through other means.
|
|
v8::Global<v8::Object> strong_global;
|
|
TracedReferenceTest(
|
|
v8_isolate(), ConstructJSObject,
|
|
[this, &strong_global](const TracedReference<v8::Object>& handle) {
|
|
v8::HandleScope scope(v8_isolate());
|
|
strong_global =
|
|
v8::Global<v8::Object>(v8_isolate(), handle.Get(v8_isolate()));
|
|
},
|
|
[this, &strong_global]() {
|
|
FullGC();
|
|
strong_global.Reset();
|
|
},
|
|
SurvivalMode::kDies);
|
|
}
|
|
|
|
TEST_F(EmbedderTracingTest,
|
|
TracedReferenceToUnmodifiedJSObjectSurvivesYoungGC) {
|
|
if (v8_flags.single_generation) return;
|
|
ManualGCScope manual_gc(i_isolate());
|
|
TracedReferenceTest(
|
|
v8_isolate(), ConstructJSObject,
|
|
[](const TracedReference<v8::Object>&) {}, [this]() { YoungGC(); },
|
|
SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST_F(
|
|
EmbedderTracingTest,
|
|
TracedReferenceToUnmodifiedJSObjectSurvivesYoungGCWhenExcludedFromRoots) {
|
|
if (v8_flags.single_generation) return;
|
|
ManualGCScope manual_gc(i_isolate());
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
|
|
TracedReferenceTest(
|
|
v8_isolate(), ConstructJSObject,
|
|
[&tracer](const TracedReference<v8::Object>& handle) {
|
|
tracer.DoNotConsiderAsRootForScavenge(&handle.As<v8::Value>());
|
|
},
|
|
[this]() { YoungGC(); }, SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST_F(EmbedderTracingTest,
|
|
TracedReferenceToUnmodifiedJSApiObjectSurvivesScavengePerDefault) {
|
|
if (v8_flags.single_generation) return;
|
|
ManualGCScope manual_gc(i_isolate());
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
|
|
TracedReferenceTest(
|
|
v8_isolate(), ConstructJSApiObject<TracedReference<v8::Object>>,
|
|
[](const TracedReference<v8::Object>&) {}, [this]() { YoungGC(); },
|
|
SurvivalMode::kSurvives);
|
|
}
|
|
|
|
TEST_F(
|
|
EmbedderTracingTest,
|
|
TracedReferenceToUnmodifiedJSApiObjectDiesOnScavengeWhenExcludedFromRoots) {
|
|
if (v8_flags.single_generation) return;
|
|
ManualGCScope manual_gc(i_isolate());
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
|
|
TracedReferenceTest(
|
|
v8_isolate(), ConstructJSApiObject<TracedReference<v8::Object>>,
|
|
[&tracer](const TracedReference<v8::Object>& handle) {
|
|
tracer.DoNotConsiderAsRootForScavenge(&handle.As<v8::Value>());
|
|
},
|
|
[this]() { YoungGC(); }, SurvivalMode::kDies);
|
|
}
|
|
|
|
TEST_F(EmbedderTracingTest, TracedReferenceWrapperClassId) {
|
|
ManualGCScope manual_gc(i_isolate());
|
|
v8::HandleScope scope(v8_isolate());
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
|
|
|
|
v8::TracedReference<v8::Object> traced;
|
|
ConstructJSObject(v8_isolate(), v8_isolate()->GetCurrentContext(), &traced);
|
|
EXPECT_EQ(0, traced.WrapperClassId());
|
|
traced.SetWrapperClassId(17);
|
|
EXPECT_EQ(17, traced.WrapperClassId());
|
|
}
|
|
|
|
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());
|
|
EmbedderStackStateScope scope =
|
|
EmbedderStackStateScope::ExplicitScopeForTesting(
|
|
reinterpret_cast<i::Isolate*>(v8_isolate())
|
|
->heap()
|
|
->local_embedder_heap_tracer(),
|
|
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
|
|
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);
|
|
}
|
|
}
|
|
|
|
TEST_F(EmbedderTracingTest, 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(i_isolate());
|
|
v8::HandleScope scope(v8_isolate());
|
|
auto ref = std::make_unique<v8::TracedReference<v8::Value>>();
|
|
ref->Reset(v8_isolate(), v8::Undefined(v8_isolate()));
|
|
auto* traced_handles = i_isolate()->traced_handles();
|
|
const size_t initial_count = traced_handles->used_node_count();
|
|
// We need two GCs because handles are black allocated.
|
|
FullGC();
|
|
FullGC();
|
|
const size_t final_count = traced_handles->used_node_count();
|
|
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());
|
|
EmbedderStackStateScope scope =
|
|
EmbedderStackStateScope::ExplicitScopeForTesting(
|
|
reinterpret_cast<i::Isolate*>(v8_isolate())
|
|
->heap()
|
|
->local_embedder_heap_tracer(),
|
|
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
|
|
FullGC();
|
|
}
|
|
EXPECT_EQ(initial_count, traced_handles->used_node_count());
|
|
delete[] memory;
|
|
}
|
|
|
|
namespace {
|
|
|
|
START_ALLOW_USE_DEPRECATED()
|
|
|
|
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 {}
|
|
};
|
|
|
|
END_ALLOW_USE_DEPRECATED()
|
|
|
|
// EmbedderHeapTracer that can optimize Scavenger handling when used with
|
|
// TracedReference.
|
|
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));
|
|
EXPECT_TRUE(optimized_handle->IsEmpty());
|
|
*optimized_handle = T(isolate, optimized_object);
|
|
EXPECT_FALSE(optimized_handle->IsEmpty());
|
|
optimized_handle->SetWrapperClassId(optimized_class_id);
|
|
|
|
v8::Local<v8::Object> non_optimized_object(ConstructTraceableJSApiObject(
|
|
isolate->GetCurrentContext(), nullptr, nullptr));
|
|
EXPECT_TRUE(non_optimized_handle->IsEmpty());
|
|
*non_optimized_handle = T(isolate, non_optimized_object);
|
|
EXPECT_FALSE(non_optimized_handle->IsEmpty());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(EmbedderTracingTest, TracedReferenceNoDestructorReclaimedOnScavenge) {
|
|
if (v8_flags.single_generation) return;
|
|
ManualGCScope manual_gc(i_isolate());
|
|
v8::HandleScope scope(v8_isolate());
|
|
constexpr uint16_t kClassIdToOptimize = 23;
|
|
EmbedderHeapTracerNoDestructorNonTracingClearing tracer(kClassIdToOptimize);
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
|
|
auto* traced_handles = i_isolate()->traced_handles();
|
|
|
|
const size_t initial_count = traced_handles->used_node_count();
|
|
auto* optimized_handle = new v8::TracedReference<v8::Value>();
|
|
auto* non_optimized_handle = new v8::TracedReference<v8::Value>();
|
|
SetupOptimizedAndNonOptimizedHandle(v8_isolate(), kClassIdToOptimize,
|
|
optimized_handle, non_optimized_handle);
|
|
EXPECT_EQ(initial_count + 2, traced_handles->used_node_count());
|
|
YoungGC();
|
|
EXPECT_EQ(initial_count + 1, traced_handles->used_node_count());
|
|
EXPECT_TRUE(optimized_handle->IsEmpty());
|
|
delete optimized_handle;
|
|
EXPECT_FALSE(non_optimized_handle->IsEmpty());
|
|
non_optimized_handle->Reset();
|
|
delete non_optimized_handle;
|
|
EXPECT_EQ(initial_count, traced_handles->used_node_count());
|
|
}
|
|
|
|
namespace {
|
|
|
|
template <typename T>
|
|
V8_NOINLINE void OnStackTest(v8::Isolate* v8_isolate,
|
|
TestEmbedderHeapTracer* tracer) {
|
|
v8::Global<v8::Object> observer;
|
|
T stack_ref;
|
|
{
|
|
v8::HandleScope scope(v8_isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
v8_isolate->GetCurrentContext(), nullptr, nullptr));
|
|
stack_ref.Reset(v8_isolate, object);
|
|
observer.Reset(v8_isolate, object);
|
|
observer.SetWeak();
|
|
}
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(EmbedderTracingTest, TracedReferenceOnStack) {
|
|
ManualGCScope manual_gc(i_isolate());
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
|
|
tracer.SetStackStart(
|
|
static_cast<void*>(base::Stack::GetCurrentFrameAddress()));
|
|
OnStackTest<v8::TracedReference<v8::Value>>(v8_isolate(), &tracer);
|
|
}
|
|
|
|
namespace {
|
|
|
|
enum class Operation {
|
|
kCopy,
|
|
kMove,
|
|
};
|
|
|
|
template <typename T>
|
|
V8_NOINLINE void PerformOperation(Operation op, T* target, T* source) {
|
|
switch (op) {
|
|
case Operation::kMove:
|
|
*target = std::move(*source);
|
|
break;
|
|
case Operation::kCopy:
|
|
*target = *source;
|
|
source->Reset();
|
|
break;
|
|
}
|
|
}
|
|
|
|
enum class TargetHandling {
|
|
kNonInitialized,
|
|
kInitializedYoungGen,
|
|
kInitializedOldGen
|
|
};
|
|
|
|
V8_NOINLINE void StackToHeapTest(v8::Isolate* v8_isolate,
|
|
TestEmbedderHeapTracer* tracer, Operation op,
|
|
TargetHandling target_handling) {
|
|
v8::Global<v8::Object> observer;
|
|
v8::TracedReference<v8::Value> stack_handle;
|
|
v8::TracedReference<v8::Value>* heap_handle =
|
|
new v8::TracedReference<v8::Value>();
|
|
if (target_handling != TargetHandling::kNonInitialized) {
|
|
v8::HandleScope scope(v8_isolate);
|
|
v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
|
|
v8_isolate->GetCurrentContext(), nullptr, nullptr));
|
|
EXPECT_TRUE(
|
|
IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
if (!v8_flags.single_generation &&
|
|
target_handling == TargetHandling::kInitializedOldGen) {
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(
|
|
i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
}
|
|
heap_handle->Reset(v8_isolate, to_object);
|
|
}
|
|
{
|
|
v8::HandleScope scope(v8_isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
v8_isolate->GetCurrentContext(), nullptr, nullptr));
|
|
stack_handle.Reset(v8_isolate, object);
|
|
observer.Reset(v8_isolate, object);
|
|
observer.SetWeak();
|
|
}
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
tracer->AddReferenceForTracing(heap_handle);
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
PerformOperation(op, heap_handle, &stack_handle);
|
|
tracer->AddReferenceForTracing(heap_handle);
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
{
|
|
// Conservative scanning may find stale pointers to on-stack handles.
|
|
// Disable scanning, assuming the slots are overwritten.
|
|
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
|
|
reinterpret_cast<i::Isolate*>(v8_isolate)->heap());
|
|
EmbedderStackStateScope scope =
|
|
EmbedderStackStateScope::ExplicitScopeForTesting(
|
|
reinterpret_cast<i::Isolate*>(v8_isolate)
|
|
->heap()
|
|
->local_embedder_heap_tracer(),
|
|
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
|
|
FullGC(v8_isolate);
|
|
}
|
|
ASSERT_TRUE(observer.IsEmpty());
|
|
delete heap_handle;
|
|
}
|
|
|
|
V8_NOINLINE void HeapToStackTest(v8::Isolate* v8_isolate,
|
|
TestEmbedderHeapTracer* tracer, Operation op,
|
|
TargetHandling target_handling) {
|
|
v8::Global<v8::Object> observer;
|
|
v8::TracedReference<v8::Value> stack_handle;
|
|
v8::TracedReference<v8::Value>* heap_handle =
|
|
new v8::TracedReference<v8::Value>();
|
|
if (target_handling != TargetHandling::kNonInitialized) {
|
|
v8::HandleScope scope(v8_isolate);
|
|
v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
|
|
v8_isolate->GetCurrentContext(), nullptr, nullptr));
|
|
EXPECT_TRUE(
|
|
IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
if (!v8_flags.single_generation &&
|
|
target_handling == TargetHandling::kInitializedOldGen) {
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(
|
|
i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
}
|
|
stack_handle.Reset(v8_isolate, to_object);
|
|
}
|
|
{
|
|
v8::HandleScope scope(v8_isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
v8_isolate->GetCurrentContext(), nullptr, nullptr));
|
|
heap_handle->Reset(v8_isolate, object);
|
|
observer.Reset(v8_isolate, object);
|
|
observer.SetWeak();
|
|
}
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
tracer->AddReferenceForTracing(heap_handle);
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
PerformOperation(op, &stack_handle, heap_handle);
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
stack_handle.Reset();
|
|
FullGC(v8_isolate);
|
|
EXPECT_TRUE(observer.IsEmpty());
|
|
delete heap_handle;
|
|
}
|
|
|
|
V8_NOINLINE void StackToStackTest(v8::Isolate* v8_isolate,
|
|
TestEmbedderHeapTracer* tracer, Operation op,
|
|
TargetHandling target_handling) {
|
|
v8::Global<v8::Object> observer;
|
|
v8::TracedReference<v8::Value> stack_handle1;
|
|
v8::TracedReference<v8::Value> stack_handle2;
|
|
if (target_handling != TargetHandling::kNonInitialized) {
|
|
v8::HandleScope scope(v8_isolate);
|
|
v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
|
|
v8_isolate->GetCurrentContext(), nullptr, nullptr));
|
|
EXPECT_TRUE(
|
|
IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
if (!v8_flags.single_generation &&
|
|
target_handling == TargetHandling::kInitializedOldGen) {
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(
|
|
i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
|
|
}
|
|
stack_handle2.Reset(v8_isolate, to_object);
|
|
}
|
|
{
|
|
v8::HandleScope scope(v8_isolate);
|
|
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
|
|
v8_isolate->GetCurrentContext(), nullptr, nullptr));
|
|
stack_handle1.Reset(v8_isolate, object);
|
|
observer.Reset(v8_isolate, object);
|
|
observer.SetWeak();
|
|
}
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
PerformOperation(op, &stack_handle2, &stack_handle1);
|
|
FullGC(v8_isolate);
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
stack_handle2.Reset();
|
|
FullGC(v8_isolate);
|
|
EXPECT_TRUE(observer.IsEmpty());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(EmbedderTracingTest, TracedReferenceMove) {
|
|
ManualGCScope manual_gc(i_isolate());
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
|
|
tracer.SetStackStart(
|
|
static_cast<void*>(base::Stack::GetCurrentFrameAddress()));
|
|
StackToHeapTest(v8_isolate(), &tracer, Operation::kMove,
|
|
TargetHandling::kNonInitialized);
|
|
StackToHeapTest(v8_isolate(), &tracer, Operation::kMove,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToHeapTest(v8_isolate(), &tracer, Operation::kMove,
|
|
TargetHandling::kInitializedOldGen);
|
|
HeapToStackTest(v8_isolate(), &tracer, Operation::kMove,
|
|
TargetHandling::kNonInitialized);
|
|
HeapToStackTest(v8_isolate(), &tracer, Operation::kMove,
|
|
TargetHandling::kInitializedYoungGen);
|
|
HeapToStackTest(v8_isolate(), &tracer, Operation::kMove,
|
|
TargetHandling::kInitializedOldGen);
|
|
StackToStackTest(v8_isolate(), &tracer, Operation::kMove,
|
|
TargetHandling::kNonInitialized);
|
|
StackToStackTest(v8_isolate(), &tracer, Operation::kMove,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToStackTest(v8_isolate(), &tracer, Operation::kMove,
|
|
TargetHandling::kInitializedOldGen);
|
|
}
|
|
|
|
TEST_F(EmbedderTracingTest, TracedReferenceCopy) {
|
|
ManualGCScope manual_gc(i_isolate());
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
|
|
tracer.SetStackStart(
|
|
static_cast<void*>(base::Stack::GetCurrentFrameAddress()));
|
|
StackToHeapTest(v8_isolate(), &tracer, Operation::kCopy,
|
|
TargetHandling::kNonInitialized);
|
|
StackToHeapTest(v8_isolate(), &tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToHeapTest(v8_isolate(), &tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedOldGen);
|
|
HeapToStackTest(v8_isolate(), &tracer, Operation::kCopy,
|
|
TargetHandling::kNonInitialized);
|
|
HeapToStackTest(v8_isolate(), &tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedYoungGen);
|
|
HeapToStackTest(v8_isolate(), &tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedOldGen);
|
|
StackToStackTest(v8_isolate(), &tracer, Operation::kCopy,
|
|
TargetHandling::kNonInitialized);
|
|
StackToStackTest(v8_isolate(), &tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedYoungGen);
|
|
StackToStackTest(v8_isolate(), &tracer, Operation::kCopy,
|
|
TargetHandling::kInitializedOldGen);
|
|
}
|
|
|
|
namespace {
|
|
|
|
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 TracedReferenceOnStackReferencesAreTemporaryTest(
|
|
v8::Isolate* v8_isolate, TestEmbedderHeapTracer* tracer) {
|
|
v8::Global<v8::Object> observer;
|
|
CreateTracedReferenceInDeepStack(v8_isolate, &observer);
|
|
EXPECT_FALSE(observer.IsEmpty());
|
|
{
|
|
// Conservative scanning may find stale pointers to on-stack handles.
|
|
// Disable scanning, assuming the slots are overwritten.
|
|
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
|
|
reinterpret_cast<Isolate*>(v8_isolate)->heap());
|
|
EmbedderStackStateScope scope =
|
|
EmbedderStackStateScope::ExplicitScopeForTesting(
|
|
reinterpret_cast<i::Isolate*>(v8_isolate)
|
|
->heap()
|
|
->local_embedder_heap_tracer(),
|
|
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
|
|
FullGC(v8_isolate);
|
|
}
|
|
EXPECT_TRUE(observer.IsEmpty());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(EmbedderTracingTest, OnStackReferencesAreTemporary) {
|
|
ManualGCScope manual_gc(i_isolate());
|
|
TestEmbedderHeapTracer tracer;
|
|
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
|
|
tracer.SetStackStart(
|
|
static_cast<void*>(base::Stack::GetCurrentFrameAddress()));
|
|
TracedReferenceOnStackReferencesAreTemporaryTest(v8_isolate(), &tracer);
|
|
}
|
|
|
|
} // namespace heap
|
|
} // namespace internal
|
|
} // namespace v8
|