Commit b73835ef authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

cppgc: Make tests use idiomatic allocation

Neither Member, nor GarbageCollected objects (and friends) should be
allocated on the stack. Create a special test fixture that allows for
writing idiomatic unit tests that depend on allocation but do not pull
in garbage collection.

Bug: chromium:1056170
Change-Id: I4118201a51658f7247412434a867d35c91299439
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2139583Reviewed-by: 's avatarOmer Katz <omerkatz@chromium.org>
Reviewed-by: 's avatarAnton Bikineev <bikineev@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67046}
parent 350e0f79
...@@ -49,7 +49,17 @@ class StackMarker final : public StackVisitor { ...@@ -49,7 +49,17 @@ class StackMarker final : public StackVisitor {
Heap::Heap() Heap::Heap()
: stack_(std::make_unique<Stack>(v8::base::Stack::GetStackStart())) {} : stack_(std::make_unique<Stack>(v8::base::Stack::GetStackStart())) {}
Heap::~Heap() {
for (HeapObjectHeader* header : objects_) {
header->Finalize();
free(header);
}
}
void Heap::CollectGarbage(GCConfig config) { void Heap::CollectGarbage(GCConfig config) {
// No GC calls when in NoGCScope.
CHECK(!in_no_gc_scope());
// TODO(chromium:1056170): Replace with proper mark-sweep algorithm. // TODO(chromium:1056170): Replace with proper mark-sweep algorithm.
// "Marking". // "Marking".
if (NeedsConservativeStackScan(config)) { if (NeedsConservativeStackScan(config)) {
...@@ -70,5 +80,9 @@ void Heap::CollectGarbage(GCConfig config) { ...@@ -70,5 +80,9 @@ void Heap::CollectGarbage(GCConfig config) {
} }
} }
Heap::NoGCScope::NoGCScope(Heap* heap) : heap_(heap) { heap_->no_gc_scope_++; }
Heap::NoGCScope::~NoGCScope() { heap_->no_gc_scope_--; }
} // namespace internal } // namespace internal
} // namespace cppgc } // namespace cppgc
...@@ -19,6 +19,15 @@ class Stack; ...@@ -19,6 +19,15 @@ class Stack;
class V8_EXPORT_PRIVATE Heap final : public cppgc::Heap { class V8_EXPORT_PRIVATE Heap final : public cppgc::Heap {
public: public:
class V8_EXPORT_PRIVATE NoGCScope final {
public:
explicit NoGCScope(Heap* heap);
~NoGCScope();
private:
Heap* heap_;
};
struct GCConfig { struct GCConfig {
enum class StackState : uint8_t { enum class StackState : uint8_t {
kEmpty, kEmpty,
...@@ -33,15 +42,19 @@ class V8_EXPORT_PRIVATE Heap final : public cppgc::Heap { ...@@ -33,15 +42,19 @@ class V8_EXPORT_PRIVATE Heap final : public cppgc::Heap {
static Heap* From(cppgc::Heap* heap) { return static_cast<Heap*>(heap); } static Heap* From(cppgc::Heap* heap) { return static_cast<Heap*>(heap); }
Heap(); Heap();
~Heap() final = default; ~Heap() final;
inline void* Allocate(size_t size, GCInfoIndex index); inline void* Allocate(size_t size, GCInfoIndex index);
void CollectGarbage(GCConfig config = GCConfig::Default()); void CollectGarbage(GCConfig config = GCConfig::Default());
private: private:
bool in_no_gc_scope() { return no_gc_scope_ > 0; }
std::unique_ptr<Stack> stack_; std::unique_ptr<Stack> stack_;
std::vector<HeapObjectHeader*> objects_; std::vector<HeapObjectHeader*> objects_;
size_t no_gc_scope_ = 0;
}; };
} // namespace internal } // namespace internal
......
...@@ -37,12 +37,8 @@ class GCWithMergedMixins : public GCed, public MergedMixins { ...@@ -37,12 +37,8 @@ class GCWithMergedMixins : public GCed, public MergedMixins {
void Trace(cppgc::Visitor* visitor) override { MergedMixins::Trace(visitor); } void Trace(cppgc::Visitor* visitor) override { MergedMixins::Trace(visitor); }
}; };
class GarbageCollectedTestWithHeap : public testing::TestWithHeap { class GarbageCollectedTestWithHeap
void TearDown() override { : public testing::TestSupportingAllocationOnly {};
internal::Heap::From(GetHeap())->CollectGarbage();
TestWithHeap::TearDown();
}
};
} // namespace } // namespace
...@@ -66,8 +62,6 @@ TEST(GarbageCollectedTest, GarbageCollectedMixinTrait) { ...@@ -66,8 +62,6 @@ TEST(GarbageCollectedTest, GarbageCollectedMixinTrait) {
STATIC_ASSERT(IsGarbageCollectedMixinType<GCWithMergedMixins>::value); STATIC_ASSERT(IsGarbageCollectedMixinType<GCWithMergedMixins>::value);
} }
#ifdef CPPGC_SUPPORTS_CONSERVATIVE_STACK_SCAN
TEST_F(GarbageCollectedTestWithHeap, GetObjectStartReturnsCorrentAddress) { TEST_F(GarbageCollectedTestWithHeap, GetObjectStartReturnsCorrentAddress) {
GCed* gced = MakeGarbageCollected<GCed>(GetHeap()); GCed* gced = MakeGarbageCollected<GCed>(GetHeap());
GCedWithMixin* gced_with_mixin = GCedWithMixin* gced_with_mixin =
...@@ -80,7 +74,5 @@ TEST_F(GarbageCollectedTestWithHeap, GetObjectStartReturnsCorrentAddress) { ...@@ -80,7 +74,5 @@ TEST_F(GarbageCollectedTestWithHeap, GetObjectStartReturnsCorrentAddress) {
.base_object_payload); .base_object_payload);
} }
#endif // CPPGC_SUPPORTS_CONSERVATIVE_STACK_SCAN
} // namespace internal } // namespace internal
} // namespace cppgc } // namespace cppgc
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "test/unittests/heap/cppgc/tests.h" #include "test/unittests/heap/cppgc/tests.h"
#include <memory>
namespace cppgc { namespace cppgc {
namespace testing { namespace testing {
...@@ -12,7 +14,7 @@ std::unique_ptr<cppgc::PageAllocator> TestWithPlatform::page_allocator_; ...@@ -12,7 +14,7 @@ std::unique_ptr<cppgc::PageAllocator> TestWithPlatform::page_allocator_;
// static // static
void TestWithPlatform::SetUpTestSuite() { void TestWithPlatform::SetUpTestSuite() {
page_allocator_.reset(new v8::base::PageAllocator()); page_allocator_ = std::make_unique<v8::base::PageAllocator>();
cppgc::InitializePlatform(page_allocator_.get()); cppgc::InitializePlatform(page_allocator_.get());
} }
...@@ -22,15 +24,11 @@ void TestWithPlatform::TearDownTestSuite() { ...@@ -22,15 +24,11 @@ void TestWithPlatform::TearDownTestSuite() {
page_allocator_.reset(); page_allocator_.reset();
} }
void TestWithHeap::SetUp() { TestWithHeap::TestWithHeap() : heap_(Heap::Create()) {}
heap_ = Heap::Create();
TestWithPlatform::SetUp();
}
void TestWithHeap::TearDown() { TestSupportingAllocationOnly::TestSupportingAllocationOnly()
heap_.reset(); : no_gc_scope_(std::make_unique<internal::Heap::NoGCScope>(
TestWithPlatform::TearDown(); internal::Heap::From(GetHeap()))) {}
}
} // namespace testing } // namespace testing
} // namespace cppgc } // namespace cppgc
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "include/cppgc/heap.h" #include "include/cppgc/heap.h"
#include "include/cppgc/platform.h" #include "include/cppgc/platform.h"
#include "src/base/page-allocator.h" #include "src/base/page-allocator.h"
#include "src/heap/cppgc/heap.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace cppgc { namespace cppgc {
...@@ -24,8 +25,7 @@ class TestWithPlatform : public ::testing::Test { ...@@ -24,8 +25,7 @@ class TestWithPlatform : public ::testing::Test {
class TestWithHeap : public TestWithPlatform { class TestWithHeap : public TestWithPlatform {
protected: protected:
void SetUp() override; TestWithHeap();
void TearDown() override;
Heap* GetHeap() const { return heap_.get(); } Heap* GetHeap() const { return heap_.get(); }
...@@ -33,6 +33,18 @@ class TestWithHeap : public TestWithPlatform { ...@@ -33,6 +33,18 @@ class TestWithHeap : public TestWithPlatform {
std::unique_ptr<cppgc::Heap> heap_; std::unique_ptr<cppgc::Heap> heap_;
}; };
// Restrictive test fixture that supports allocation but will make sure no
// garbage collection is triggered. This is useful for writing idiomatic
// tests where object are allocated on the managed heap while still avoiding
// far reaching test consquences of full garbage collection calls.
class TestSupportingAllocationOnly : public TestWithHeap {
protected:
TestSupportingAllocationOnly();
private:
std::unique_ptr<cppgc::internal::Heap::NoGCScope> no_gc_scope_;
};
} // namespace testing } // namespace testing
} // namespace cppgc } // namespace cppgc
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "src/heap/cppgc/visitor.h" #include "src/heap/cppgc/visitor.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h" #include "include/cppgc/garbage-collected.h"
#include "include/cppgc/trace-trait.h" #include "include/cppgc/trace-trait.h"
#include "src/base/macros.h" #include "src/base/macros.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace cppgc { namespace cppgc {
...@@ -13,6 +15,9 @@ namespace internal { ...@@ -13,6 +15,9 @@ namespace internal {
namespace { namespace {
class TraceTraitTest : public testing::TestSupportingAllocationOnly {};
class VisitorTest : public testing::TestSupportingAllocationOnly {};
class GCed : public GarbageCollected<GCed> { class GCed : public GarbageCollected<GCed> {
public: public:
static size_t trace_callcount; static size_t trace_callcount;
...@@ -62,71 +67,83 @@ class DispatchingVisitor final : public VisitorBase { ...@@ -62,71 +67,83 @@ class DispatchingVisitor final : public VisitorBase {
} // namespace } // namespace
TEST(TraceTraitTest, GetObjectStartGCed) { TEST_F(TraceTraitTest, GetObjectStartGCed) {
GCed gced; auto* gced = MakeGarbageCollected<GCed>(GetHeap());
EXPECT_EQ(&gced, EXPECT_EQ(gced,
TraceTrait<GCed>::GetTraceDescriptor(&gced).base_object_payload); TraceTrait<GCed>::GetTraceDescriptor(gced).base_object_payload);
} }
TEST(TraceTraitTest, GetObjectStartGCedMixin) { TEST_F(TraceTraitTest, GetObjectStartGCedMixin) {
GCedMixinApplication gced_mixin_app; auto* gced_mixin_app = MakeGarbageCollected<GCedMixinApplication>(GetHeap());
GCedMixin* gced_mixin = static_cast<GCedMixin*>(&gced_mixin_app); auto* gced_mixin = static_cast<GCedMixin*>(gced_mixin_app);
EXPECT_EQ(&gced_mixin_app, EXPECT_EQ(gced_mixin_app,
TraceTrait<GCedMixin>::GetTraceDescriptor(gced_mixin) TraceTrait<GCedMixin>::GetTraceDescriptor(gced_mixin)
.base_object_payload); .base_object_payload);
} }
TEST(TraceTraitTest, TraceGCed) { TEST_F(TraceTraitTest, TraceGCed) {
GCed gced; auto* gced = MakeGarbageCollected<GCed>(GetHeap());
EXPECT_EQ(0u, GCed::trace_callcount); EXPECT_EQ(0u, GCed::trace_callcount);
TraceTrait<GCed>::Trace(nullptr, &gced); TraceTrait<GCed>::Trace(nullptr, gced);
EXPECT_EQ(1u, GCed::trace_callcount); EXPECT_EQ(1u, GCed::trace_callcount);
} }
TEST(TraceTraitTest, TraceGCedMixin) { TEST_F(TraceTraitTest, TraceGCedMixin) {
GCedMixinApplication gced_mixin_app; auto* gced_mixin_app = MakeGarbageCollected<GCedMixinApplication>(GetHeap());
GCedMixin* gced_mixin = static_cast<GCedMixin*>(&gced_mixin_app); auto* gced_mixin = static_cast<GCedMixin*>(gced_mixin_app);
EXPECT_EQ(0u, GCed::trace_callcount); EXPECT_EQ(0u, GCed::trace_callcount);
TraceTrait<GCedMixin>::Trace(nullptr, gced_mixin); TraceTrait<GCedMixin>::Trace(nullptr, gced_mixin);
EXPECT_EQ(1u, GCed::trace_callcount); EXPECT_EQ(1u, GCed::trace_callcount);
} }
TEST(TraceTraitTest, TraceGCedThroughTraceDescriptor) { TEST_F(TraceTraitTest, TraceGCedThroughTraceDescriptor) {
GCed gced; auto* gced = MakeGarbageCollected<GCed>(GetHeap());
EXPECT_EQ(0u, GCed::trace_callcount); EXPECT_EQ(0u, GCed::trace_callcount);
TraceDescriptor desc = TraceTrait<GCed>::GetTraceDescriptor(&gced); TraceDescriptor desc = TraceTrait<GCed>::GetTraceDescriptor(gced);
desc.callback(nullptr, desc.base_object_payload); desc.callback(nullptr, desc.base_object_payload);
EXPECT_EQ(1u, GCed::trace_callcount); EXPECT_EQ(1u, GCed::trace_callcount);
} }
TEST(TraceTraitTest, TraceGCedMixinThroughTraceDescriptor) { TEST_F(TraceTraitTest, TraceGCedMixinThroughTraceDescriptor) {
GCedMixinApplication gced_mixin_app; auto* gced_mixin_app = MakeGarbageCollected<GCedMixinApplication>(GetHeap());
GCedMixin* gced_mixin = static_cast<GCedMixin*>(&gced_mixin_app); auto* gced_mixin = static_cast<GCedMixin*>(gced_mixin_app);
EXPECT_EQ(0u, GCed::trace_callcount); EXPECT_EQ(0u, GCed::trace_callcount);
TraceDescriptor desc = TraceTrait<GCedMixin>::GetTraceDescriptor(gced_mixin); TraceDescriptor desc = TraceTrait<GCedMixin>::GetTraceDescriptor(gced_mixin);
desc.callback(nullptr, desc.base_object_payload); desc.callback(nullptr, desc.base_object_payload);
EXPECT_EQ(1u, GCed::trace_callcount); EXPECT_EQ(1u, GCed::trace_callcount);
} }
TEST(VisitorTest, DispatchTraceGCed) { namespace {
GCed gced;
Member<GCed> ref(&gced); template <typename T>
DispatchingVisitor visitor(&gced, &gced); class MemberHolder final : public GarbageCollected<MemberHolder<T>> {
public:
void Trace(Visitor* visitor) { visitor->Trace(ref); }
Member<T> ref;
};
} // namespace
TEST_F(VisitorTest, DispatchTraceGCed) {
auto* gced = MakeGarbageCollected<GCed>(GetHeap());
auto* holder = MakeGarbageCollected<MemberHolder<GCed>>(GetHeap());
holder->ref = gced;
DispatchingVisitor visitor(gced, gced);
EXPECT_EQ(0u, GCed::trace_callcount); EXPECT_EQ(0u, GCed::trace_callcount);
visitor.Trace(ref); visitor.Trace(holder->ref);
EXPECT_EQ(1u, GCed::trace_callcount); EXPECT_EQ(1u, GCed::trace_callcount);
} }
TEST(VisitorTest, DispatchTraceGCedMixin) { TEST_F(VisitorTest, DispatchTraceGCedMixin) {
GCedMixinApplication gced_mixin_app; auto* gced_mixin_app = MakeGarbageCollected<GCedMixinApplication>(GetHeap());
GCedMixin* gced_mixin = static_cast<GCedMixin*>(&gced_mixin_app); auto* gced_mixin = static_cast<GCedMixin*>(gced_mixin_app);
// Ensure that we indeed test dispatching an inner object. // Ensure that we indeed test dispatching an inner object.
EXPECT_NE(static_cast<void*>(&gced_mixin_app), EXPECT_NE(static_cast<void*>(gced_mixin_app), static_cast<void*>(gced_mixin));
static_cast<void*>(gced_mixin)); auto* holder = MakeGarbageCollected<MemberHolder<GCedMixin>>(GetHeap());
Member<GCedMixin> ref(gced_mixin); holder->ref = gced_mixin;
DispatchingVisitor visitor(gced_mixin, &gced_mixin_app); DispatchingVisitor visitor(gced_mixin, gced_mixin_app);
EXPECT_EQ(0u, GCed::trace_callcount); EXPECT_EQ(0u, GCed::trace_callcount);
visitor.Trace(ref); visitor.Trace(holder->ref);
EXPECT_EQ(1u, GCed::trace_callcount); EXPECT_EQ(1u, GCed::trace_callcount);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment