diff options
Diffstat (limited to 'fxjs/gc')
-rw-r--r-- | fxjs/gc/container_trace.h | 66 | ||||
-rw-r--r-- | fxjs/gc/container_trace_unittest.cpp | 89 | ||||
-rw-r--r-- | fxjs/gc/gced_tree_node.h | 47 | ||||
-rw-r--r-- | fxjs/gc/gced_tree_node_mixin.h | 48 | ||||
-rw-r--r-- | fxjs/gc/gced_tree_node_mixin_unittest.cpp | 149 | ||||
-rw-r--r-- | fxjs/gc/gced_tree_node_unittest.cpp | 143 | ||||
-rw-r--r-- | fxjs/gc/heap.cpp | 98 | ||||
-rw-r--r-- | fxjs/gc/heap.h | 36 | ||||
-rw-r--r-- | fxjs/gc/heap_unittest.cpp | 175 | ||||
-rw-r--r-- | fxjs/gc/move_unittest.cpp | 62 |
10 files changed, 913 insertions, 0 deletions
diff --git a/fxjs/gc/container_trace.h b/fxjs/gc/container_trace.h new file mode 100644 index 000000000..dfe4a7465 --- /dev/null +++ b/fxjs/gc/container_trace.h @@ -0,0 +1,66 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FXJS_GC_CONTAINER_TRACE_H_ +#define FXJS_GC_CONTAINER_TRACE_H_ + +#include <list> +#include <map> +#include <set> +#include <vector> + +#include "v8/include/cppgc/member.h" +#include "v8/include/cppgc/visitor.h" + +namespace fxgc { + +template <typename T, typename V = cppgc::Visitor> +void ContainerTrace(V* visitor, const std::list<cppgc::Member<T>>& container) { + for (const auto& item : container) + visitor->Trace(item); +} + +template <typename T, typename U, typename V = cppgc::Visitor> +void ContainerTrace(V* visitor, + const std::map<cppgc::Member<T>, U>& container) { + for (const auto& item : container) { + visitor->Trace(item.first); + } +} + +template <typename T, typename U, typename V = cppgc::Visitor> +void ContainerTrace(V* visitor, + const std::map<U, cppgc::Member<T>>& container) { + for (const auto& item : container) + visitor->Trace(item.second); +} + +template <typename T, typename U, typename V = cppgc::Visitor> +void ContainerTrace( + V* visitor, + const std::map<cppgc::Member<U>, cppgc::Member<T>>& container) { + for (const auto& item : container) { + visitor->Trace(item.first); + visitor->Trace(item.second); + } +} + +template <typename T, typename V = cppgc::Visitor> +void ContainerTrace(V* visitor, const std::set<cppgc::Member<T>>& container) { + for (const auto& item : container) + visitor->Trace(item); +} + +template <typename T, typename V = cppgc::Visitor> +void ContainerTrace(V* visitor, + const std::vector<cppgc::Member<T>>& container) { + for (const auto& item : container) + visitor->Trace(item); +} + +} // namespace fxgc + +using fxgc::ContainerTrace; + +#endif // FXJS_GC_CONTAINER_TRACE_H_ diff --git a/fxjs/gc/container_trace_unittest.cpp b/fxjs/gc/container_trace_unittest.cpp new file mode 100644 index 000000000..9273afe5c --- /dev/null +++ b/fxjs/gc/container_trace_unittest.cpp @@ -0,0 +1,89 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "fxjs/gc/container_trace.h" + +#include <stdint.h> + +#include <list> +#include <map> +#include <set> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "v8/include/cppgc/member.h" + +namespace { + +class Thing : public cppgc::GarbageCollected<Thing> { + public: + void Trace(cppgc::Visitor* visitor) const {} +}; + +class CountingVisitor { + public: + CountingVisitor() = default; + + void Trace(const void* that) { ++call_count_; } + int call_count() const { return call_count_; } + + private: + int call_count_ = 0; +}; + +} // namespace + +TEST(ContainerTrace, ActualListTrace) { + std::list<cppgc::Member<Thing>> thing; + thing.emplace_back(nullptr); + + CountingVisitor cv; + ContainerTrace(&cv, thing); + EXPECT_EQ(1, cv.call_count()); +} + +TEST(ContainerTrace, ActualMapTraceFirst) { + std::map<cppgc::Member<Thing>, int> thing; + thing[nullptr] = 42; + + CountingVisitor cv; + ContainerTrace(&cv, thing); + EXPECT_EQ(1, cv.call_count()); +} + +TEST(ContainerTrace, ActualMapTraceSecond) { + std::map<int, cppgc::Member<Thing>> thing; + thing[42] = nullptr; + + CountingVisitor cv; + ContainerTrace(&cv, thing); + EXPECT_EQ(1, cv.call_count()); +} + +TEST(ContainerTrace, ActualMapTraceBoth) { + std::map<cppgc::Member<Thing>, cppgc::Member<Thing>> thing; + thing[nullptr] = nullptr; + + CountingVisitor cv; + ContainerTrace(&cv, thing); + EXPECT_EQ(2, cv.call_count()); +} + +TEST(ContainerTrace, ActualSetTrace) { + std::set<cppgc::Member<Thing>> thing; + thing.insert(nullptr); + + CountingVisitor cv; + ContainerTrace(&cv, thing); + EXPECT_EQ(1, cv.call_count()); +} + +TEST(ContainerTrace, ActualVectorTrace) { + std::vector<cppgc::Member<Thing>> thing; + thing.emplace_back(nullptr); + + CountingVisitor cv; + ContainerTrace(&cv, thing); + EXPECT_EQ(1, cv.call_count()); +} diff --git a/fxjs/gc/gced_tree_node.h b/fxjs/gc/gced_tree_node.h new file mode 100644 index 000000000..fed5e73ac --- /dev/null +++ b/fxjs/gc/gced_tree_node.h @@ -0,0 +1,47 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FXJS_GC_GCED_TREE_NODE_H_ +#define FXJS_GC_GCED_TREE_NODE_H_ + +#include "core/fxcrt/tree_node.h" +#include "v8/include/cppgc/garbage-collected.h" +#include "v8/include/cppgc/member.h" +#include "v8/include/cppgc/visitor.h" + +namespace fxjs { + +// For DOM/XML-ish trees, where references outside the tree are Persistent<>. +template <typename T> +class GCedTreeNode : public cppgc::GarbageCollected<GCedTreeNode<T>>, + public fxcrt::TreeNodeBase<T> { + public: + virtual void Trace(cppgc::Visitor* visitor) const { + visitor->Trace(m_pParent); + visitor->Trace(m_pFirstChild); + visitor->Trace(m_pLastChild); + visitor->Trace(m_pNextSibling); + visitor->Trace(m_pPrevSibling); + } + + protected: + GCedTreeNode() = default; + GCedTreeNode(const GCedTreeNode& that) = delete; + GCedTreeNode& operator=(const GCedTreeNode& that) = delete; + + private: + friend class fxcrt::TreeNodeBase<T>; + + cppgc::Member<T> m_pParent; + cppgc::Member<T> m_pFirstChild; + cppgc::Member<T> m_pLastChild; + cppgc::Member<T> m_pNextSibling; + cppgc::Member<T> m_pPrevSibling; +}; + +} // namespace fxjs + +using fxjs::GCedTreeNode; + +#endif // FXJS_GC_GCED_TREE_NODE_H_ diff --git a/fxjs/gc/gced_tree_node_mixin.h b/fxjs/gc/gced_tree_node_mixin.h new file mode 100644 index 000000000..2f160e06d --- /dev/null +++ b/fxjs/gc/gced_tree_node_mixin.h @@ -0,0 +1,48 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FXJS_GC_GCED_TREE_NODE_MIXIN_H_ +#define FXJS_GC_GCED_TREE_NODE_MIXIN_H_ + +#include "core/fxcrt/tree_node.h" +#include "v8/include/cppgc/garbage-collected.h" +#include "v8/include/cppgc/member.h" +#include "v8/include/cppgc/visitor.h" + +namespace fxjs { + +// For DOM/XML-ish trees, where references outside the tree are Persistent<>, +// usable by classes that are already garbage collected themselves. +template <typename T> +class GCedTreeNodeMixin : public cppgc::GarbageCollectedMixin, + public fxcrt::TreeNodeBase<T> { + public: + virtual void Trace(cppgc::Visitor* visitor) const { + visitor->Trace(m_pParent); + visitor->Trace(m_pFirstChild); + visitor->Trace(m_pLastChild); + visitor->Trace(m_pNextSibling); + visitor->Trace(m_pPrevSibling); + } + + protected: + GCedTreeNodeMixin() = default; + GCedTreeNodeMixin(const GCedTreeNodeMixin& that) = delete; + GCedTreeNodeMixin& operator=(const GCedTreeNodeMixin& that) = delete; + + private: + friend class fxcrt::TreeNodeBase<T>; + + cppgc::Member<T> m_pParent; + cppgc::Member<T> m_pFirstChild; + cppgc::Member<T> m_pLastChild; + cppgc::Member<T> m_pNextSibling; + cppgc::Member<T> m_pPrevSibling; +}; + +} // namespace fxjs + +using fxjs::GCedTreeNodeMixin; + +#endif // FXJS_GC_GCED_TREE_NODE_MIXIN_H_ diff --git a/fxjs/gc/gced_tree_node_mixin_unittest.cpp b/fxjs/gc/gced_tree_node_mixin_unittest.cpp new file mode 100644 index 000000000..2cd098ed6 --- /dev/null +++ b/fxjs/gc/gced_tree_node_mixin_unittest.cpp @@ -0,0 +1,149 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "fxjs/gc/gced_tree_node_mixin.h" + +#include <map> + +#include "core/fxcrt/observed_ptr.h" +#include "fxjs/gc/heap.h" +#include "testing/fxgc_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/v8_test_environment.h" +#include "v8/include/cppgc/allocation.h" +#include "v8/include/cppgc/persistent.h" + +namespace { + +class ObservableGCedTreeNodeMixinForTest + : public cppgc::GarbageCollected<ObservableGCedTreeNodeMixinForTest>, + public GCedTreeNodeMixin<ObservableGCedTreeNodeMixinForTest>, + public Observable { + public: + CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED; + + // GCedTreeNodeMixin: + void Trace(cppgc::Visitor* visitor) const override { + GCedTreeNodeMixin<ObservableGCedTreeNodeMixinForTest>::Trace(visitor); + } + + private: + ObservableGCedTreeNodeMixinForTest() = default; +}; + +} // namespace + +class GCedTreeNodeMixinUnitTest : public FXGCUnitTest { + public: + GCedTreeNodeMixinUnitTest() = default; + ~GCedTreeNodeMixinUnitTest() override = default; + + // FXGCUnitTest: + void TearDown() override { + root_ = nullptr; // Can't (yet) outlive |FXGCUnitTest::heap_|. + FXGCUnitTest::TearDown(); + } + + ObservableGCedTreeNodeMixinForTest* root() const { return root_; } + void CreateRoot() { root_ = CreateNode(); } + + ObservableGCedTreeNodeMixinForTest* CreateNode() { + return cppgc::MakeGarbageCollected<ObservableGCedTreeNodeMixinForTest>( + heap()->GetAllocationHandle()); + } + + void AddClutterToFront(ObservableGCedTreeNodeMixinForTest* parent) { + for (int i = 0; i < 4; ++i) { + parent->AppendFirstChild( + cppgc::MakeGarbageCollected<ObservableGCedTreeNodeMixinForTest>( + heap()->GetAllocationHandle())); + } + } + + void AddClutterToBack(ObservableGCedTreeNodeMixinForTest* parent) { + for (int i = 0; i < 4; ++i) { + parent->AppendLastChild( + cppgc::MakeGarbageCollected<ObservableGCedTreeNodeMixinForTest>( + heap()->GetAllocationHandle())); + } + } + + private: + cppgc::Persistent<ObservableGCedTreeNodeMixinForTest> root_; +}; + +TEST_F(GCedTreeNodeMixinUnitTest, OneRefence) { + CreateRoot(); + ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(root()); + ForceGCAndPump(); + EXPECT_TRUE(watcher); +} + +TEST_F(GCedTreeNodeMixinUnitTest, NoReferences) { + ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(CreateNode()); + ForceGCAndPump(); + EXPECT_FALSE(watcher); +} + +TEST_F(GCedTreeNodeMixinUnitTest, FirstHasParent) { + CreateRoot(); + ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(CreateNode()); + root()->AppendFirstChild(watcher.Get()); + ForceGCAndPump(); + ASSERT_TRUE(root()); + EXPECT_TRUE(watcher); + root()->RemoveChild(watcher.Get()); + ForceGCAndPump(); + ASSERT_TRUE(root()); + EXPECT_FALSE(watcher); + + // Now add some clutter. + watcher.Reset(CreateNode()); + root()->AppendFirstChild(watcher.Get()); + AddClutterToFront(root()); + AddClutterToBack(root()); + ForceGCAndPump(); + ASSERT_TRUE(root()); + EXPECT_TRUE(watcher); + root()->RemoveChild(watcher.Get()); + ForceGCAndPump(); + EXPECT_TRUE(root()); + EXPECT_FALSE(watcher); +} + +TEST_F(GCedTreeNodeMixinUnitTest, RemoveSelf) { + CreateRoot(); + ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(CreateNode()); + root()->AppendFirstChild(watcher.Get()); + ForceGCAndPump(); + EXPECT_TRUE(root()); + ASSERT_TRUE(watcher); + watcher->RemoveSelfIfParented(); + ForceGCAndPump(); + EXPECT_TRUE(root()); + EXPECT_FALSE(watcher); +} + +TEST_F(GCedTreeNodeMixinUnitTest, InsertBeforeAfter) { + CreateRoot(); + AddClutterToFront(root()); + ObservedPtr<ObservableGCedTreeNodeMixinForTest> watcher(CreateNode()); + root()->AppendFirstChild(watcher.Get()); + root()->InsertBefore(root()->GetFirstChild(), root()->GetLastChild()); + root()->InsertAfter(root()->GetLastChild(), root()->GetFirstChild()); + ForceGCAndPump(); + ASSERT_TRUE(root()); + EXPECT_TRUE(watcher); + root()->RemoveChild(watcher.Get()); + ForceGCAndPump(); + EXPECT_TRUE(root()); + EXPECT_FALSE(watcher); +} + +TEST_F(GCedTreeNodeMixinUnitTest, AsMapKey) { + std::map<cppgc::Persistent<ObservableGCedTreeNodeMixinForTest>, int> score; + ObservableGCedTreeNodeMixinForTest* node = CreateNode(); + score[node] = 100; + EXPECT_EQ(100, score[node]); +} diff --git a/fxjs/gc/gced_tree_node_unittest.cpp b/fxjs/gc/gced_tree_node_unittest.cpp new file mode 100644 index 000000000..a456a313f --- /dev/null +++ b/fxjs/gc/gced_tree_node_unittest.cpp @@ -0,0 +1,143 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "fxjs/gc/gced_tree_node.h" + +#include <map> + +#include "core/fxcrt/observed_ptr.h" +#include "fxjs/gc/heap.h" +#include "testing/fxgc_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/v8_test_environment.h" +#include "v8/include/cppgc/allocation.h" +#include "v8/include/cppgc/persistent.h" + +namespace { + +class ObservableGCedTreeNodeForTest + : public GCedTreeNode<ObservableGCedTreeNodeForTest>, + public Observable { + public: + CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED; + + private: + ObservableGCedTreeNodeForTest() = default; +}; + +} // namespace + +class GCedTreeNodeUnitTest : public FXGCUnitTest { + public: + GCedTreeNodeUnitTest() = default; + ~GCedTreeNodeUnitTest() override = default; + + // FXGCUnitTest: + void TearDown() override { + root_ = nullptr; // Can't (yet) outlive |FXGCUnitTest::heap_|. + FXGCUnitTest::TearDown(); + } + + ObservableGCedTreeNodeForTest* root() const { return root_; } + void CreateRoot() { root_ = CreateNode(); } + + ObservableGCedTreeNodeForTest* CreateNode() { + return cppgc::MakeGarbageCollected<ObservableGCedTreeNodeForTest>( + heap()->GetAllocationHandle()); + } + + void AddClutterToFront(ObservableGCedTreeNodeForTest* parent) { + for (int i = 0; i < 4; ++i) { + parent->AppendFirstChild( + cppgc::MakeGarbageCollected<ObservableGCedTreeNodeForTest>( + heap()->GetAllocationHandle())); + } + } + + void AddClutterToBack(ObservableGCedTreeNodeForTest* parent) { + for (int i = 0; i < 4; ++i) { + parent->AppendLastChild( + cppgc::MakeGarbageCollected<ObservableGCedTreeNodeForTest>( + heap()->GetAllocationHandle())); + } + } + + private: + cppgc::Persistent<ObservableGCedTreeNodeForTest> root_; +}; + +TEST_F(GCedTreeNodeUnitTest, OneRefence) { + CreateRoot(); + ObservedPtr<ObservableGCedTreeNodeForTest> watcher(root()); + ForceGCAndPump(); + EXPECT_TRUE(watcher); +} + +TEST_F(GCedTreeNodeUnitTest, NoReferences) { + ObservedPtr<ObservableGCedTreeNodeForTest> watcher(CreateNode()); + ForceGCAndPump(); + EXPECT_FALSE(watcher); +} + +TEST_F(GCedTreeNodeUnitTest, FirstHasParent) { + CreateRoot(); + ObservedPtr<ObservableGCedTreeNodeForTest> watcher(CreateNode()); + root()->AppendFirstChild(watcher.Get()); + ForceGCAndPump(); + ASSERT_TRUE(root()); + EXPECT_TRUE(watcher); + root()->RemoveChild(watcher.Get()); + ForceGCAndPump(); + ASSERT_TRUE(root()); + EXPECT_FALSE(watcher); + + // Now add some clutter. + watcher.Reset(CreateNode()); + root()->AppendFirstChild(watcher.Get()); + AddClutterToFront(root()); + AddClutterToBack(root()); + ForceGCAndPump(); + ASSERT_TRUE(root()); + EXPECT_TRUE(watcher); + root()->RemoveChild(watcher.Get()); + ForceGCAndPump(); + EXPECT_TRUE(root()); + EXPECT_FALSE(watcher); +} + +TEST_F(GCedTreeNodeUnitTest, RemoveSelf) { + CreateRoot(); + ObservedPtr<ObservableGCedTreeNodeForTest> watcher(CreateNode()); + root()->AppendFirstChild(watcher.Get()); + ForceGCAndPump(); + EXPECT_TRUE(root()); + ASSERT_TRUE(watcher); + watcher->RemoveSelfIfParented(); + ForceGCAndPump(); + EXPECT_TRUE(root()); + EXPECT_FALSE(watcher); +} + +TEST_F(GCedTreeNodeUnitTest, InsertBeforeAfter) { + CreateRoot(); + AddClutterToFront(root()); + ObservedPtr<ObservableGCedTreeNodeForTest> watcher(CreateNode()); + root()->AppendFirstChild(watcher.Get()); + root()->InsertBefore(root()->GetFirstChild(), root()->GetLastChild()); + root()->InsertAfter(root()->GetLastChild(), root()->GetFirstChild()); + ForceGCAndPump(); + ASSERT_TRUE(root()); + EXPECT_TRUE(watcher); + root()->RemoveChild(watcher.Get()); + ForceGCAndPump(); + EXPECT_TRUE(root()); + EXPECT_FALSE(watcher); +} + +TEST_F(GCedTreeNodeUnitTest, AsMapKey) { + std::map<cppgc::Persistent<ObservableGCedTreeNodeForTest>, int> score; + ObservableGCedTreeNodeForTest* node = CreateNode(); + score[node] = 100; + EXPECT_EQ(100, score[node]); +} diff --git a/fxjs/gc/heap.cpp b/fxjs/gc/heap.cpp new file mode 100644 index 000000000..301018fe0 --- /dev/null +++ b/fxjs/gc/heap.cpp @@ -0,0 +1,98 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "fxjs/gc/heap.h" + +#include <utility> + +#include "core/fxcrt/fx_system.h" +#include "third_party/base/check.h" +#include "v8/include/cppgc/heap.h" + +namespace { + +size_t g_platform_ref_count = 0; +v8::Platform* g_platform = nullptr; +v8::Isolate* g_isolate = nullptr; + +} // namespace + +// Taken from v8/samples/cppgc/cppgc-for-v8-embedders.cc. +// Adaptper that makes the global v8::Platform compatible with a +// cppgc::Platform. +class CFXGC_Platform final : public cppgc::Platform { + public: + CFXGC_Platform() = default; + ~CFXGC_Platform() override = default; + + cppgc::PageAllocator* GetPageAllocator() override { + return g_platform->GetPageAllocator(); + } + + double MonotonicallyIncreasingTime() override { + return g_platform->MonotonicallyIncreasingTime(); + } + + std::shared_ptr<cppgc::TaskRunner> GetForegroundTaskRunner() override { + // V8's default platform creates a new task runner when passed the + // v8::Isolate pointer the first time. For non-default platforms this will + // require getting the appropriate task runner. + return g_platform->GetForegroundTaskRunner(g_isolate); + } + + std::unique_ptr<cppgc::JobHandle> PostJob( + cppgc::TaskPriority priority, + std::unique_ptr<cppgc::JobTask> job_task) override { + return g_platform->PostJob(priority, std::move(job_task)); + } +}; + +void FXGC_Initialize(v8::Platform* platform, v8::Isolate* isolate) { + if (platform) { + DCHECK(!g_platform); + g_platform = platform; + g_isolate = isolate; + } +} + +void FXGC_Release() { + if (g_platform && g_platform_ref_count == 0) { + g_platform = nullptr; + g_isolate = nullptr; + } +} + +FXGCScopedHeap FXGC_CreateHeap() { + // If XFA is included at compile-time, but JS is disabled at run-time, + // we may still attempt to build a CPDFXFA_Context which will want a + // heap. But we can't make one because JS is disabled. + // TODO(tsepez): Stop the context from even being created. + if (!g_platform) + return nullptr; + + ++g_platform_ref_count; + auto heap = cppgc::Heap::Create( + std::make_shared<CFXGC_Platform>(), + cppgc::Heap::HeapOptions{ + {}, + cppgc::Heap::StackSupport::kNoConservativeStackScan, + cppgc::Heap::MarkingType::kAtomic, + cppgc::Heap::SweepingType::kIncrementalAndConcurrent, + {}}); + return FXGCScopedHeap(heap.release()); +} + +void FXGC_ForceGarbageCollection(cppgc::Heap* heap) { + heap->ForceGarbageCollectionSlow("FXGC", "ForceGarbageCollection", + cppgc::Heap::StackState::kNoHeapPointers); +} + +void FXGCHeapDeleter::operator()(cppgc::Heap* heap) { + DCHECK(heap); + DCHECK(g_platform_ref_count > 0); + --g_platform_ref_count; + + FXGC_ForceGarbageCollection(heap); + delete heap; +} diff --git a/fxjs/gc/heap.h b/fxjs/gc/heap.h new file mode 100644 index 000000000..1e2bf6159 --- /dev/null +++ b/fxjs/gc/heap.h @@ -0,0 +1,36 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FXJS_GC_HEAP_H_ +#define FXJS_GC_HEAP_H_ + +#include <memory> + +#include "v8/include/cppgc/allocation.h" + +namespace cppgc { +class Heap; +} // namespace cppgc + +namespace v8 { +class Isolate; +class Platform; +} // namespace v8 + +struct FXGCHeapDeleter { + void operator()(cppgc::Heap* heap); +}; + +using FXGCScopedHeap = std::unique_ptr<cppgc::Heap, FXGCHeapDeleter>; + +void FXGC_Initialize(v8::Platform* platform, v8::Isolate* isolate); +void FXGC_Release(); +FXGCScopedHeap FXGC_CreateHeap(); +void FXGC_ForceGarbageCollection(cppgc::Heap* heap); + +#define CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED \ + template <typename T> \ + friend class cppgc::MakeGarbageCollectedTrait + +#endif // FXJS_GC_HEAP_H_ diff --git a/fxjs/gc/heap_unittest.cpp b/fxjs/gc/heap_unittest.cpp new file mode 100644 index 000000000..aa90d0075 --- /dev/null +++ b/fxjs/gc/heap_unittest.cpp @@ -0,0 +1,175 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "fxjs/gc/heap.h" + +#include <memory> +#include <set> + +#include "testing/fxgc_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/v8_test_environment.h" +#include "third_party/base/containers/contains.h" +#include "v8/include/cppgc/allocation.h" +#include "v8/include/cppgc/persistent.h" + +namespace { + +class PseudoCollectible : public cppgc::GarbageCollected<PseudoCollectible> { + public: + static void ClearCounts() { + s_live_.clear(); + s_dead_.clear(); + } + static size_t LiveCount() { return s_live_.size(); } + static size_t DeadCount() { return s_dead_.size(); } + + PseudoCollectible() { s_live_.insert(this); } + virtual ~PseudoCollectible() { + s_live_.erase(this); + s_dead_.insert(this); + } + + bool IsLive() const { return pdfium::Contains(s_live_, this); } + + virtual void Trace(cppgc::Visitor* visitor) const {} + + private: + static std::set<const PseudoCollectible*> s_live_; + static std::set<const PseudoCollectible*> s_dead_; +}; + +std::set<const PseudoCollectible*> PseudoCollectible::s_live_; +std::set<const PseudoCollectible*> PseudoCollectible::s_dead_; + +class CollectibleHolder { + public: + explicit CollectibleHolder(PseudoCollectible* holdee) : holdee_(holdee) {} + ~CollectibleHolder() = default; + + PseudoCollectible* holdee() const { return holdee_; } + + private: + cppgc::Persistent<PseudoCollectible> holdee_; +}; + +class Bloater : public cppgc::GarbageCollected<Bloater> { + public: + void Trace(cppgc::Visitor* visitor) const {} + uint8_t bloat_[65536]; +}; + +} // namespace + +class HeapUnitTest : public FXGCUnitTest { + public: + HeapUnitTest() = default; + ~HeapUnitTest() override = default; + + // FXGCUnitTest: + void TearDown() override { + PseudoCollectible::ClearCounts(); + FXGCUnitTest::TearDown(); + } +}; + +TEST_F(HeapUnitTest, SeveralHeaps) { + FXGCScopedHeap heap1 = FXGC_CreateHeap(); + EXPECT_TRUE(heap1); + + FXGCScopedHeap heap2 = FXGC_CreateHeap(); + EXPECT_TRUE(heap2); + + FXGCScopedHeap heap3 = FXGC_CreateHeap(); + EXPECT_TRUE(heap3); + + // Test manually destroying the heap. + heap3.reset(); + EXPECT_FALSE(heap3); + heap3.reset(); + EXPECT_FALSE(heap3); +} + +TEST_F(HeapUnitTest, NoReferences) { + FXGCScopedHeap heap1 = FXGC_CreateHeap(); + ASSERT_TRUE(heap1); + { + auto holder = std::make_unique<CollectibleHolder>( + cppgc::MakeGarbageCollected<PseudoCollectible>( + heap1->GetAllocationHandle())); + + EXPECT_TRUE(holder->holdee()->IsLive()); + EXPECT_EQ(1u, PseudoCollectible::LiveCount()); + EXPECT_EQ(0u, PseudoCollectible::DeadCount()); + } + FXGC_ForceGarbageCollection(heap1.get()); + EXPECT_EQ(0u, PseudoCollectible::LiveCount()); + EXPECT_EQ(1u, PseudoCollectible::DeadCount()); +} + +TEST_F(HeapUnitTest, HasReferences) { + FXGCScopedHeap heap1 = FXGC_CreateHeap(); + ASSERT_TRUE(heap1); + { + auto holder = std::make_unique<CollectibleHolder>( + cppgc::MakeGarbageCollected<PseudoCollectible>( + heap1->GetAllocationHandle())); + + EXPECT_TRUE(holder->holdee()->IsLive()); + EXPECT_EQ(1u, PseudoCollectible::LiveCount()); + EXPECT_EQ(0u, PseudoCollectible::DeadCount()); + + FXGC_ForceGarbageCollection(heap1.get()); + EXPECT_TRUE(holder->holdee()->IsLive()); + EXPECT_EQ(1u, PseudoCollectible::LiveCount()); + EXPECT_EQ(0u, PseudoCollectible::DeadCount()); + } +} + +// TODO(tsepez): enable when CPPGC fixes this segv. +TEST_F(HeapUnitTest, DISABLED_DeleteHeapHasReferences) { + FXGCScopedHeap heap1 = FXGC_CreateHeap(); + ASSERT_TRUE(heap1); + { + auto holder = std::make_unique<CollectibleHolder>( + cppgc::MakeGarbageCollected<PseudoCollectible>( + heap1->GetAllocationHandle())); + + EXPECT_TRUE(holder->holdee()->IsLive()); + EXPECT_EQ(1u, PseudoCollectible::LiveCount()); + EXPECT_EQ(0u, PseudoCollectible::DeadCount()); + + heap1.reset(); + + // Maybe someday magically nulled by heap destruction. + EXPECT_FALSE(holder->holdee()); + EXPECT_EQ(1u, PseudoCollectible::LiveCount()); + EXPECT_EQ(0u, PseudoCollectible::DeadCount()); + } +} + +TEST_F(HeapUnitTest, DeleteHeapNoReferences) { + FXGCScopedHeap heap1 = FXGC_CreateHeap(); + ASSERT_TRUE(heap1); + { + auto holder = std::make_unique<CollectibleHolder>( + cppgc::MakeGarbageCollected<PseudoCollectible>( + heap1->GetAllocationHandle())); + + EXPECT_TRUE(holder->holdee()->IsLive()); + EXPECT_EQ(1u, PseudoCollectible::LiveCount()); + EXPECT_EQ(0u, PseudoCollectible::DeadCount()); + } + heap1.reset(); + EXPECT_EQ(0u, PseudoCollectible::LiveCount()); + EXPECT_EQ(1u, PseudoCollectible::DeadCount()); +} + +TEST_F(HeapUnitTest, Bloat) { + ASSERT_TRUE(heap()); + for (int i = 0; i < 100000; ++i) { + cppgc::MakeGarbageCollected<Bloater>(heap()->GetAllocationHandle()); + Pump(); // Do not force GC, must happen implicitly when space required. + } +} diff --git a/fxjs/gc/move_unittest.cpp b/fxjs/gc/move_unittest.cpp new file mode 100644 index 000000000..3d5a80959 --- /dev/null +++ b/fxjs/gc/move_unittest.cpp @@ -0,0 +1,62 @@ +// Copyright 2020 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <utility> + +#include "fxjs/gc/heap.h" +#include "testing/fxgc_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "v8/include/cppgc/member.h" +#include "v8/include/cppgc/persistent.h" + +namespace { + +class HeapObject : public cppgc::GarbageCollected<HeapObject> { + public: + CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED; + + void Trace(cppgc::Visitor* visitor) const { + visitor->Trace(frick_); + visitor->Trace(frack_); + } + + cppgc::Member<HeapObject> frick_; + cppgc::Member<HeapObject> frack_; + + private: + HeapObject() = default; +}; + +class CppObject { + public: + CppObject() = default; + + cppgc::Persistent<HeapObject> click_; + cppgc::Persistent<HeapObject> clack_; +}; + +} // namespace + +class MoveUnitTest : public FXGCUnitTest {}; + +TEST_F(MoveUnitTest, Member) { + // Moving a Member<> leaves the moved-from object as null. + auto* obj = + cppgc::MakeGarbageCollected<HeapObject>(heap()->GetAllocationHandle()); + obj->frick_ = obj; + obj->frack_ = std::move(obj->frick_); + EXPECT_FALSE(obj->frick_); + EXPECT_EQ(obj, obj->frack_); +} + +TEST_F(MoveUnitTest, Persistent) { + // Moving a Persistent<> leaves the moved-from object as null. + auto* obj = + cppgc::MakeGarbageCollected<HeapObject>(heap()->GetAllocationHandle()); + CppObject outsider; + outsider.click_ = obj; + outsider.clack_ = std::move(outsider.click_); + EXPECT_FALSE(outsider.click_); + EXPECT_EQ(obj, outsider.clack_); +} |