summaryrefslogtreecommitdiffstats
path: root/xpcom/tests/gtest/TestGCPostBarriers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/tests/gtest/TestGCPostBarriers.cpp')
-rw-r--r--xpcom/tests/gtest/TestGCPostBarriers.cpp165
1 files changed, 165 insertions, 0 deletions
diff --git a/xpcom/tests/gtest/TestGCPostBarriers.cpp b/xpcom/tests/gtest/TestGCPostBarriers.cpp
new file mode 100644
index 0000000000..c32cd48c86
--- /dev/null
+++ b/xpcom/tests/gtest/TestGCPostBarriers.cpp
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests that generational garbage collection post-barriers are correctly
+ * implemented for nsTArrays that contain JavaScript Values.
+ */
+
+#include "mozilla/UniquePtr.h"
+
+#include "jsapi.h"
+#include "nsTArray.h"
+
+#include "gtest/gtest.h"
+
+#include "js/PropertyAndElement.h" // JS_GetProperty, JS_SetProperty
+#include "js/TracingAPI.h"
+#include "js/HeapAPI.h"
+
+#include "mozilla/CycleCollectedJSContext.h"
+
+using namespace mozilla;
+
+template <class ArrayT>
+static void TraceArray(JSTracer* trc, void* data) {
+ ArrayT* array = static_cast<ArrayT*>(data);
+ for (unsigned i = 0; i < array->Length(); ++i) {
+ JS::TraceEdge(trc, &array->ElementAt(i), "array-element");
+ }
+}
+
+/*
+ * Use arrays with initial size much smaller than the final number of elements
+ * to test that moving Heap<T> elements works correctly.
+ */
+const size_t ElementCount = 100;
+const size_t InitialElements = ElementCount / 10;
+
+template <class ArrayT>
+static void TestGrow(JSContext* cx) {
+ JS_GC(cx);
+
+ auto array = MakeUnique<ArrayT>();
+ ASSERT_TRUE(array != nullptr);
+
+ JS_AddExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get());
+
+ /*
+ * Create the array and fill it with new JS objects. With GGC these will be
+ * allocated in the nursery.
+ */
+ JS::RootedValue value(cx);
+ const char* property = "foo";
+ for (size_t i = 0; i < ElementCount; ++i) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ ASSERT_FALSE(JS::ObjectIsTenured(obj));
+ value = JS::Int32Value(static_cast<int32_t>(i));
+ ASSERT_TRUE(JS_SetProperty(cx, obj, property, value));
+ ASSERT_TRUE(array->AppendElement(obj, fallible));
+ }
+
+ /*
+ * If postbarriers are not working, we will crash here when we try to mark
+ * objects that have been moved to the tenured heap.
+ */
+ JS_GC(cx);
+
+ /*
+ * Sanity check that our array contains what we expect.
+ */
+ ASSERT_EQ(array->Length(), ElementCount);
+ for (size_t i = 0; i < array->Length(); i++) {
+ JS::RootedObject obj(cx, array->ElementAt(i));
+ ASSERT_TRUE(JS::ObjectIsTenured(obj));
+ ASSERT_TRUE(JS_GetProperty(cx, obj, property, &value));
+ ASSERT_TRUE(value.isInt32());
+ ASSERT_EQ(static_cast<int32_t>(i), value.toInt32());
+ }
+
+ JS_RemoveExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get());
+}
+
+template <class ArrayT>
+static void TestShrink(JSContext* cx) {
+ JS_GC(cx);
+
+ auto array = MakeUnique<ArrayT>();
+ ASSERT_TRUE(array != nullptr);
+
+ JS_AddExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get());
+
+ /*
+ * Create the array and fill it with new JS objects. With GGC these will be
+ * allocated in the nursery.
+ */
+ JS::RootedValue value(cx);
+ const char* property = "foo";
+ for (size_t i = 0; i < ElementCount; ++i) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ ASSERT_FALSE(JS::ObjectIsTenured(obj));
+ value = JS::Int32Value(static_cast<int32_t>(i));
+ ASSERT_TRUE(JS_SetProperty(cx, obj, property, value));
+ ASSERT_TRUE(array->AppendElement(obj, fallible));
+ }
+
+ /* Shrink and compact the array */
+ array->RemoveElementsAt(InitialElements, ElementCount - InitialElements);
+ array->Compact();
+
+ JS_GC(cx);
+
+ ASSERT_EQ(array->Length(), InitialElements);
+ for (size_t i = 0; i < array->Length(); i++) {
+ JS::RootedObject obj(cx, array->ElementAt(i));
+ ASSERT_TRUE(JS::ObjectIsTenured(obj));
+ ASSERT_TRUE(JS_GetProperty(cx, obj, property, &value));
+ ASSERT_TRUE(value.isInt32());
+ ASSERT_EQ(static_cast<int32_t>(i), value.toInt32());
+ }
+
+ JS_RemoveExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get());
+}
+
+template <class ArrayT>
+static void TestArrayType(JSContext* cx) {
+ TestGrow<ArrayT>(cx);
+ TestShrink<ArrayT>(cx);
+}
+
+static void CreateGlobalAndRunTest(JSContext* cx) {
+ static const JSClass GlobalClass = {"global", JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+
+ JS::RealmOptions options;
+ // dummy
+ options.behaviors().setReduceTimerPrecisionCallerType(
+ JS::RTPCallerTypeToken{0});
+ JS::PersistentRootedObject global(cx);
+ global = JS_NewGlobalObject(cx, &GlobalClass, nullptr,
+ JS::FireOnNewGlobalHook, options);
+ ASSERT_TRUE(global != nullptr);
+
+ JS::Realm* oldRealm = JS::EnterRealm(cx, global);
+
+ using ElementT = JS::Heap<JSObject*>;
+
+ TestArrayType<nsTArray<ElementT>>(cx);
+ TestArrayType<FallibleTArray<ElementT>>(cx);
+ TestArrayType<AutoTArray<ElementT, 1>>(cx);
+
+ JS::LeaveRealm(cx, oldRealm);
+}
+
+TEST(GCPostBarriers, nsTArray)
+{
+ CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
+ ASSERT_TRUE(ccjscx != nullptr);
+ JSContext* cx = ccjscx->Context();
+ ASSERT_TRUE(cx != nullptr);
+
+ CreateGlobalAndRunTest(cx);
+}