/* -*- 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 static void TraceArray(JSTracer* trc, void* data) { ArrayT* array = static_cast(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 elements works correctly. */ const size_t ElementCount = 100; const size_t InitialElements = ElementCount / 10; template static void TestGrow(JSContext* cx) { JS_GC(cx); auto array = MakeUnique(); ASSERT_TRUE(array != nullptr); JS_AddExtraGCRootsTracer(cx, TraceArray, 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(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(i), value.toInt32()); } JS_RemoveExtraGCRootsTracer(cx, TraceArray, array.get()); } template static void TestShrink(JSContext* cx) { JS_GC(cx); auto array = MakeUnique(); ASSERT_TRUE(array != nullptr); JS_AddExtraGCRootsTracer(cx, TraceArray, 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(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(i), value.toInt32()); } JS_RemoveExtraGCRootsTracer(cx, TraceArray, array.get()); } template static void TestArrayType(JSContext* cx) { TestGrow(cx); TestShrink(cx); } static void CreateGlobalAndRunTest(JSContext* cx) { static const JSClass GlobalClass = {"global", JSCLASS_GLOBAL_FLAGS, &JS::DefaultGlobalClassOps}; JS::RealmOptions options; 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; TestArrayType>(cx); TestArrayType>(cx); TestArrayType>(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); }