aboutsummaryrefslogtreecommitdiff
path: root/fxjs/gc
diff options
context:
space:
mode:
Diffstat (limited to 'fxjs/gc')
-rw-r--r--fxjs/gc/container_trace.h66
-rw-r--r--fxjs/gc/container_trace_unittest.cpp89
-rw-r--r--fxjs/gc/gced_tree_node.h47
-rw-r--r--fxjs/gc/gced_tree_node_mixin.h48
-rw-r--r--fxjs/gc/gced_tree_node_mixin_unittest.cpp149
-rw-r--r--fxjs/gc/gced_tree_node_unittest.cpp143
-rw-r--r--fxjs/gc/heap.cpp98
-rw-r--r--fxjs/gc/heap.h36
-rw-r--r--fxjs/gc/heap_unittest.cpp175
-rw-r--r--fxjs/gc/move_unittest.cpp62
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_);
+}