blob: d1e5bd7a0fa5f6c6b605d5ccb414136e3aa91ff0 [file] [log] [blame]
// Copyright 2016 The Chromium 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 "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
#include "third_party/blink/renderer/core/testing/death_aware_script_wrappable.h"
#include "third_party/blink/renderer/platform/bindings/name_client.h"
#include "third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h"
#include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
namespace blink {
namespace {
// Temporarily swaps out the underlying ScriptWrappableMarkingVisitor from a
// given v8::Isolate. Gracefully finalized potentially running garbage
// collections.
class TemporaryScriptWrappableVisitorScope {
WTF_MAKE_NONCOPYABLE(TemporaryScriptWrappableVisitorScope);
STACK_ALLOCATED();
public:
TemporaryScriptWrappableVisitorScope(
v8::Isolate* isolate,
std::unique_ptr<ScriptWrappableMarkingVisitor> controller)
: isolate_(isolate), saved_controller_(std::move(controller)) {
SwapWithV8PerIsolateDataVisitor();
}
~TemporaryScriptWrappableVisitorScope() { SwapWithV8PerIsolateDataVisitor(); }
ScriptWrappableMarkingVisitor* CurrentVisitor() {
return V8PerIsolateData::From(isolate_)->GetScriptWrappableMarkingVisitor();
}
private:
void SwapWithV8PerIsolateDataVisitor() {
ScriptWrappableMarkingVisitor* current = CurrentVisitor();
if (current)
ScriptWrappableMarkingVisitor::PerformCleanup(isolate_);
V8PerIsolateData::From(isolate_)->SwapScriptWrappableMarkingVisitor(
saved_controller_);
isolate_->SetEmbedderHeapTracer(CurrentVisitor());
}
v8::Isolate* const isolate_;
std::unique_ptr<ScriptWrappableMarkingVisitor> saved_controller_;
};
class InterceptingScriptWrappableMarkingVisitor
: public blink::ScriptWrappableMarkingVisitor {
public:
InterceptingScriptWrappableMarkingVisitor()
: ScriptWrappableMarkingVisitor(ThreadState::Current()),
marked_wrappers_(new size_t(0)) {}
~InterceptingScriptWrappableMarkingVisitor() override {
delete marked_wrappers_;
}
void Visit(const TraceWrapperV8Reference<v8::Value>&) override {
*marked_wrappers_ += 1;
// Do not actually mark this visitor, as this would call into v8, which
// would require executing an actual GC.
}
size_t NumberOfMarkedWrappers() const { return *marked_wrappers_; }
void Start() { TracePrologue(); }
void end() {
// Gracefully terminate tracing.
AdvanceTracing(std::numeric_limits<double>::infinity());
AbortTracingForTermination();
}
private:
size_t* marked_wrappers_; // Indirection required because of const override.
};
class InterceptingScriptWrappableMarkingVisitorScope
: public TemporaryScriptWrappableVisitorScope {
WTF_MAKE_NONCOPYABLE(InterceptingScriptWrappableMarkingVisitorScope);
STACK_ALLOCATED();
public:
InterceptingScriptWrappableMarkingVisitorScope(v8::Isolate* isolate)
: TemporaryScriptWrappableVisitorScope(
isolate,
std::unique_ptr<InterceptingScriptWrappableMarkingVisitor>(
new InterceptingScriptWrappableMarkingVisitor())) {
Visitor()->Start();
}
virtual ~InterceptingScriptWrappableMarkingVisitorScope() {
Visitor()->end();
}
InterceptingScriptWrappableMarkingVisitor* Visitor() {
return reinterpret_cast<InterceptingScriptWrappableMarkingVisitor*>(
CurrentVisitor());
}
};
void PreciselyCollectGarbage() {
ThreadState::Current()->CollectAllGarbage();
}
} // namespace
// =============================================================================
// Wrapper tracing tests for Blink handling. ===================================
// =============================================================================
TEST(ScriptWrappableMarkingVisitorTest,
ScriptWrappableMarkingVisitorTracesWrappers) {
V8TestingScope scope;
DeathAwareScriptWrappable* target = DeathAwareScriptWrappable::Create();
DeathAwareScriptWrappable* dependency = DeathAwareScriptWrappable::Create();
target->SetWrappedDependency(dependency);
// The graph needs to be set up before starting tracing as otherwise the
// conservative write barrier would trigger.
InterceptingScriptWrappableMarkingVisitorScope intercepting_scope(
scope.GetIsolate());
ScriptWrappableMarkingVisitor* visitor = intercepting_scope.Visitor();
HeapObjectHeader* target_header = HeapObjectHeader::FromPayload(target);
HeapObjectHeader* dependency_header =
HeapObjectHeader::FromPayload(dependency);
EXPECT_TRUE(visitor->MarkingDeque()->IsEmpty());
EXPECT_FALSE(target_header->IsWrapperHeaderMarked());
EXPECT_FALSE(dependency_header->IsWrapperHeaderMarked());
std::pair<void*, void*> pair = std::make_pair(
const_cast<WrapperTypeInfo*>(target->GetWrapperTypeInfo()), target);
visitor->RegisterV8Reference(pair);
EXPECT_EQ(visitor->MarkingDeque()->size(), 1ul);
visitor->AdvanceTracing(std::numeric_limits<double>::infinity());
EXPECT_EQ(visitor->MarkingDeque()->size(), 0ul);
EXPECT_TRUE(target_header->IsWrapperHeaderMarked());
EXPECT_TRUE(dependency_header->IsWrapperHeaderMarked());
}
TEST(ScriptWrappableMarkingVisitorTest,
OilpanClearsMarkingDequeWhenObjectDied) {
V8TestingScope scope;
DeathAwareScriptWrappable* object = DeathAwareScriptWrappable::Create();
InterceptingScriptWrappableMarkingVisitorScope intercepting_scope(
scope.GetIsolate());
ScriptWrappableMarkingVisitor* visitor = intercepting_scope.Visitor();
visitor->TraceWithWrappers(object);
EXPECT_EQ(visitor->MarkingDeque()->front().RawObjectPointer(), object);
PreciselyCollectGarbage();
EXPECT_EQ(visitor->MarkingDeque()->front().RawObjectPointer(), nullptr);
}
TEST(ScriptWrappableMarkingVisitorTest,
MarkedObjectDoesNothingOnWriteBarrierHitWhenDependencyIsMarkedToo) {
V8TestingScope scope;
InterceptingScriptWrappableMarkingVisitorScope intercepting_scope(
scope.GetIsolate());
ScriptWrappableMarkingVisitor* visitor = intercepting_scope.Visitor();
DeathAwareScriptWrappable* target = DeathAwareScriptWrappable::Create();
DeathAwareScriptWrappable* dependencies[] = {
DeathAwareScriptWrappable::Create(), DeathAwareScriptWrappable::Create(),
DeathAwareScriptWrappable::Create(), DeathAwareScriptWrappable::Create()};
HeapObjectHeader::FromPayload(target)->MarkWrapperHeader();
for (int i = 0; i < 4; i++) {
HeapObjectHeader::FromPayload(dependencies[i])->MarkWrapperHeader();
}
EXPECT_TRUE(visitor->MarkingDeque()->IsEmpty());
target->SetWrappedDependency(dependencies[0]);
target->AddWrappedVectorDependency(dependencies[1]);
target->AddWrappedHashMapDependency(dependencies[2], dependencies[3]);
EXPECT_TRUE(visitor->MarkingDeque()->IsEmpty());
}
TEST(ScriptWrappableMarkingVisitorTest,
MarkedObjectMarksDependencyOnWriteBarrierHitWhenNotMarked) {
V8TestingScope scope;
InterceptingScriptWrappableMarkingVisitorScope intercepting_scope(
scope.GetIsolate());
ScriptWrappableMarkingVisitor* visitor = intercepting_scope.Visitor();
DeathAwareScriptWrappable* target = DeathAwareScriptWrappable::Create();
DeathAwareScriptWrappable* dependencies[] = {
DeathAwareScriptWrappable::Create(), DeathAwareScriptWrappable::Create(),
DeathAwareScriptWrappable::Create(), DeathAwareScriptWrappable::Create()};
HeapObjectHeader::FromPayload(target)->MarkWrapperHeader();
EXPECT_TRUE(visitor->MarkingDeque()->IsEmpty());
target->SetWrappedDependency(dependencies[0]);
target->AddWrappedVectorDependency(dependencies[1]);
target->AddWrappedHashMapDependency(dependencies[2], dependencies[3]);
for (int i = 0; i < 4; i++) {
EXPECT_TRUE(visitor->MarkingDequeContains(dependencies[i]));
}
}
namespace {
class HandleContainer
: public blink::GarbageCollectedFinalized<HandleContainer> {
public:
static HandleContainer* Create() { return new HandleContainer(); }
virtual ~HandleContainer() = default;
void Trace(blink::Visitor* visitor) {
visitor->Trace(handle_.Cast<v8::Value>());
}
void SetValue(v8::Isolate* isolate, v8::Local<v8::String> string) {
handle_.Set(isolate, string);
}
private:
HandleContainer() = default;
TraceWrapperV8Reference<v8::String> handle_;
};
} // namespace
TEST(ScriptWrappableMarkingVisitorTest, WriteBarrierOnUnmarkedContainer) {
V8TestingScope scope;
InterceptingScriptWrappableMarkingVisitorScope visitor_scope(
scope.GetIsolate());
auto* raw_visitor = visitor_scope.Visitor();
v8::Local<v8::String> str =
v8::String::NewFromUtf8(scope.GetIsolate(), "teststring",
v8::NewStringType::kNormal, sizeof("teststring"))
.ToLocalChecked();
HandleContainer* container = HandleContainer::Create();
CHECK_EQ(0u, raw_visitor->NumberOfMarkedWrappers());
container->SetValue(scope.GetIsolate(), str);
// The write barrier is conservative and does not check the mark bits of the
// source container.
CHECK_EQ(1u, raw_visitor->NumberOfMarkedWrappers());
}
TEST(ScriptWrappableMarkingVisitorTest, WriteBarrierTriggersOnMarkedContainer) {
V8TestingScope scope;
InterceptingScriptWrappableMarkingVisitorScope visitor_scope(
scope.GetIsolate());
auto* raw_visitor = visitor_scope.Visitor();
v8::Local<v8::String> str =
v8::String::NewFromUtf8(scope.GetIsolate(), "teststring",
v8::NewStringType::kNormal, sizeof("teststring"))
.ToLocalChecked();
HandleContainer* container = HandleContainer::Create();
HeapObjectHeader::FromPayload(container)->MarkWrapperHeader();
CHECK_EQ(0u, raw_visitor->NumberOfMarkedWrappers());
container->SetValue(scope.GetIsolate(), str);
CHECK_EQ(1u, raw_visitor->NumberOfMarkedWrappers());
}
TEST(ScriptWrappableMarkingVisitorTest, VtableAtObjectStart) {
// This test makes sure that the subobject v8::EmbedderHeapTracer is placed
// at the start of a ScriptWrappableMarkingVisitor object. We do this to
// mitigate potential problems that could be caused by LTO when passing
// v8::EmbedderHeapTracer across the API boundary.
std::unique_ptr<blink::ScriptWrappableMarkingVisitor> visitor(
new ScriptWrappableMarkingVisitor(ThreadState::Current()));
CHECK_EQ(
static_cast<void*>(visitor.get()),
static_cast<void*>(dynamic_cast<v8::EmbedderHeapTracer*>(visitor.get())));
}
TEST(ScriptWrappableMarkingVisitor, WriteBarrierForScriptWrappable) {
// Regression test for crbug.com/702490.
V8TestingScope scope;
InterceptingScriptWrappableMarkingVisitorScope visitor_scope(
scope.GetIsolate());
auto* raw_visitor = visitor_scope.Visitor();
// Mark the ScriptWrappable.
DeathAwareScriptWrappable* target = DeathAwareScriptWrappable::Create();
HeapObjectHeader::FromPayload(target)->MarkWrapperHeader();
// Create a 'wrapper' object.
v8::Local<v8::Object> wrapper = V8DOMWrapper::CreateWrapper(
scope.GetIsolate(), scope.GetContext()->Global(),
target->GetWrapperTypeInfo());
// Upon setting the wrapper we should have executed the write barrier.
CHECK_EQ(0u, raw_visitor->NumberOfMarkedWrappers());
v8::Local<v8::Object> final_wrapper =
V8DOMWrapper::AssociateObjectWithWrapper(
scope.GetIsolate(), target, target->GetWrapperTypeInfo(), wrapper);
CHECK(!final_wrapper.IsEmpty());
CHECK_EQ(1u, raw_visitor->NumberOfMarkedWrappers());
}
TEST(ScriptWrappableMarkingVisitorTest, WriteBarrierOnHeapVectorSwap1) {
V8TestingScope scope;
HeapVector<DeathAwareScriptWrappable::Wrapper> vector1;
DeathAwareScriptWrappable* entry1 = DeathAwareScriptWrappable::Create();
vector1.push_back(entry1);
HeapVector<DeathAwareScriptWrappable::Wrapper> vector2;
DeathAwareScriptWrappable* entry2 = DeathAwareScriptWrappable::Create();
vector2.push_back(entry2);
InterceptingScriptWrappableMarkingVisitorScope intercepting_scope(
scope.GetIsolate());
ScriptWrappableMarkingVisitor* visitor = intercepting_scope.Visitor();
EXPECT_TRUE(visitor->MarkingDeque()->IsEmpty());
swap(vector1, vector2);
EXPECT_TRUE(visitor->MarkingDequeContains(entry1));
EXPECT_TRUE(visitor->MarkingDequeContains(entry2));
}
TEST(ScriptWrappableMarkingVisitorTest, WriteBarrierOnHeapVectorSwap2) {
V8TestingScope scope;
HeapVector<DeathAwareScriptWrappable::Wrapper> vector1;
DeathAwareScriptWrappable* entry1 = DeathAwareScriptWrappable::Create();
vector1.push_back(entry1);
HeapVector<Member<DeathAwareScriptWrappable>> vector2;
DeathAwareScriptWrappable* entry2 = DeathAwareScriptWrappable::Create();
vector2.push_back(entry2);
InterceptingScriptWrappableMarkingVisitorScope intercepting_scope(
scope.GetIsolate());
ScriptWrappableMarkingVisitor* visitor = intercepting_scope.Visitor();
EXPECT_TRUE(visitor->MarkingDeque()->IsEmpty());
swap(vector1, vector2);
// Only entry2 is held alive by TraceWrapperMember, so we only expect this
// barrier to fire.
EXPECT_TRUE(visitor->MarkingDequeContains(entry2));
}
namespace {
class Mixin : public GarbageCollectedMixin {
public:
explicit Mixin(DeathAwareScriptWrappable* wrapper_in_mixin)
: wrapper_in_mixin_(wrapper_in_mixin) {}
void Trace(Visitor* visitor) override { visitor->Trace(wrapper_in_mixin_); }
protected:
DeathAwareScriptWrappable::Wrapper wrapper_in_mixin_;
};
class ClassWithField {
protected:
int field_;
};
class Base : public blink::GarbageCollected<Base>,
public NameClient, // Force vtable on Base.
public ClassWithField,
public Mixin {
USING_GARBAGE_COLLECTED_MIXIN(Base);
public:
static Base* Create(DeathAwareScriptWrappable* wrapper_in_base,
DeathAwareScriptWrappable* wrapper_in_mixin) {
return new Base(wrapper_in_base, wrapper_in_mixin);
}
void Trace(Visitor* visitor) override {
visitor->Trace(wrapper_in_base_);
Mixin::Trace(visitor);
}
const char* NameInHeapSnapshot() const override { return "HandleContainer"; }
protected:
Base(DeathAwareScriptWrappable* wrapper_in_base,
DeathAwareScriptWrappable* wrapper_in_mixin)
: Mixin(wrapper_in_mixin), wrapper_in_base_(wrapper_in_base) {
// Use field_;
field_ = 0;
}
DeathAwareScriptWrappable::Wrapper wrapper_in_base_;
};
} // namespace
TEST(ScriptWrappableMarkingVisitorTest, MixinTracing) {
V8TestingScope scope;
DeathAwareScriptWrappable* base_wrapper = DeathAwareScriptWrappable::Create();
DeathAwareScriptWrappable* mixin_wrapper =
DeathAwareScriptWrappable::Create();
Base* base = Base::Create(base_wrapper, mixin_wrapper);
Mixin* mixin = static_cast<Mixin*>(base);
HeapObjectHeader* base_header = HeapObjectHeader::FromPayload(base);
EXPECT_FALSE(base_header->IsWrapperHeaderMarked());
// Make sure that mixin does not point to the object header.
EXPECT_NE(static_cast<void*>(base), static_cast<void*>(mixin));
InterceptingScriptWrappableMarkingVisitorScope intercepting_scope(
scope.GetIsolate());
ScriptWrappableMarkingVisitor* visitor = intercepting_scope.Visitor();
EXPECT_TRUE(visitor->MarkingDeque()->IsEmpty());
// TraceWrapperMember itself is not required to live in an Oilpan object.
TraceWrapperMember<Mixin> mixin_handle = mixin;
EXPECT_TRUE(base_header->IsWrapperHeaderMarked());
EXPECT_FALSE(visitor->MarkingDeque()->IsEmpty());
EXPECT_TRUE(visitor->MarkingDequeContains(base));
visitor->AdvanceTracing(std::numeric_limits<double>::infinity());
EXPECT_EQ(visitor->MarkingDeque()->size(), 0ul);
EXPECT_TRUE(base_header->IsWrapperHeaderMarked());
EXPECT_TRUE(
HeapObjectHeader::FromPayload(base_wrapper)->IsWrapperHeaderMarked());
EXPECT_TRUE(
HeapObjectHeader::FromPayload(mixin_wrapper)->IsWrapperHeaderMarked());
mixin_handle = nullptr;
}
TEST(ScriptWrappableMarkingVisitorTest, OilpanClearsHeadersWhenObjectDied) {
V8TestingScope scope;
DeathAwareScriptWrappable* object = DeathAwareScriptWrappable::Create();
InterceptingScriptWrappableMarkingVisitorScope intercepting_scope(
scope.GetIsolate());
ScriptWrappableMarkingVisitor* visitor = intercepting_scope.Visitor();
auto* header = HeapObjectHeader::FromPayload(object);
visitor->headers_to_unmark_.push_back(header);
object = nullptr;
PreciselyCollectGarbage();
EXPECT_FALSE(visitor->headers_to_unmark_.Contains(header));
}
} // namespace blink