/* -*- 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/. */ #include "mozilla/Maybe.h" #include "ds/TraceableFifo.h" #include "gc/Policy.h" #include "js/GCHashTable.h" #include "js/GCVector.h" #include "js/RootingAPI.h" #include "jsapi-tests/tests.h" using namespace js; using mozilla::Maybe; using mozilla::Some; BEGIN_TEST(testGCExactRooting) { JS::RootedObject rootCx(cx, JS_NewPlainObject(cx)); JS_GC(cx); /* Use the objects we just created to ensure that they are still alive. */ JS_DefineProperty(cx, rootCx, "foo", JS::UndefinedHandleValue, 0); return true; } END_TEST(testGCExactRooting) BEGIN_TEST(testGCSuppressions) { JS::AutoAssertNoGC nogc; JS::AutoCheckCannotGC checkgc; JS::AutoSuppressGCAnalysis noanalysis; JS::AutoAssertNoGC nogcCx(cx); JS::AutoCheckCannotGC checkgcCx(cx); JS::AutoSuppressGCAnalysis noanalysisCx(cx); return true; } END_TEST(testGCSuppressions) struct MyContainer { HeapPtr obj; HeapPtr str; MyContainer() : obj(nullptr), str(nullptr) {} void trace(JSTracer* trc) { js::TraceNullableEdge(trc, &obj, "test container"); js::TraceNullableEdge(trc, &str, "test container"); } }; namespace js { template struct MutableWrappedPtrOperations { HeapPtr& obj() { return static_cast(this)->get().obj; } HeapPtr& str() { return static_cast(this)->get().str; } }; } // namespace js BEGIN_TEST(testGCRootedStaticStructInternalStackStorageAugmented) { JS::Rooted container(cx); container.obj() = JS_NewObject(cx, nullptr); container.str() = JS_NewStringCopyZ(cx, "Hello"); JS_GC(cx); JS_GC(cx); JS::RootedObject obj(cx, container.obj()); JS::RootedValue val(cx, StringValue(container.str())); CHECK(JS_SetProperty(cx, obj, "foo", val)); obj = nullptr; val = UndefinedValue(); { JS::RootedString actual(cx); bool same; // Automatic move from stack to heap. JS::PersistentRooted heap(cx, container); // clear prior rooting. container.obj() = nullptr; container.str() = nullptr; obj = heap.obj(); CHECK(JS_GetProperty(cx, obj, "foo", &val)); actual = val.toString(); CHECK(JS_StringEqualsLiteral(cx, actual, "Hello", &same)); CHECK(same); obj = nullptr; actual = nullptr; JS_GC(cx); JS_GC(cx); obj = heap.obj(); CHECK(JS_GetProperty(cx, obj, "foo", &val)); actual = val.toString(); CHECK(JS_StringEqualsLiteral(cx, actual, "Hello", &same)); CHECK(same); obj = nullptr; actual = nullptr; } return true; } END_TEST(testGCRootedStaticStructInternalStackStorageAugmented) static JS::PersistentRooted sLongLived; BEGIN_TEST(testGCPersistentRootedOutlivesRuntime) { sLongLived.init(cx, JS_NewObject(cx, nullptr)); CHECK(sLongLived); return true; } END_TEST(testGCPersistentRootedOutlivesRuntime) // Unlike the above, the following test is an example of an invalid usage: for // performance and simplicity reasons, PersistentRooted is not // allowed to outlive the container it belongs to. The following commented out // test can be used to verify that the relevant assertion fires as expected. static JS::PersistentRooted sContainer; BEGIN_TEST(testGCPersistentRootedTraceableCannotOutliveRuntime) { JS::Rooted container(cx); container.obj() = JS_NewObject(cx, nullptr); container.str() = JS_NewStringCopyZ(cx, "Hello"); sContainer.init(cx, container); // Commenting the following line will trigger an assertion that the // PersistentRooted outlives the runtime it is attached to. sContainer.reset(); return true; } END_TEST(testGCPersistentRootedTraceableCannotOutliveRuntime) using MyHashMap = js::GCHashMap; BEGIN_TEST(testGCRootedHashMap) { JS::Rooted map(cx, MyHashMap(cx, 15)); for (size_t i = 0; i < 10; ++i) { RootedObject obj(cx, JS_NewObject(cx, nullptr)); RootedValue val(cx, UndefinedValue()); // Construct a unique property name to ensure that the object creates a // new shape. char buffer[2]; buffer[0] = 'a' + i; buffer[1] = '\0'; CHECK(JS_SetProperty(cx, obj, buffer, val)); CHECK(map.putNew(obj->as().lastProperty(), obj)); } JS_GC(cx); JS_GC(cx); for (auto r = map.all(); !r.empty(); r.popFront()) { RootedObject obj(cx, r.front().value()); CHECK(obj->as().lastProperty() == r.front().key()); } return true; } END_TEST(testGCRootedHashMap) // Repeat of the test above, but without rooting. This is a rooting hazard. The // JS_EXPECT_HAZARDS annotation will cause the hazard taskcluster job to fail // if the hazard below is *not* detected. BEGIN_TEST_WITH_ATTRIBUTES(testUnrootedGCHashMap, JS_EXPECT_HAZARDS) { MyHashMap map(cx, 15); for (size_t i = 0; i < 10; ++i) { RootedObject obj(cx, JS_NewObject(cx, nullptr)); RootedValue val(cx, UndefinedValue()); // Construct a unique property name to ensure that the object creates a // new shape. char buffer[2]; buffer[0] = 'a' + i; buffer[1] = '\0'; CHECK(JS_SetProperty(cx, obj, buffer, val)); CHECK(map.putNew(obj->as().lastProperty(), obj)); } JS_GC(cx); // Access map to keep it live across the GC. CHECK(map.count() == 10); return true; } END_TEST(testUnrootedGCHashMap) BEGIN_TEST(testSafelyUnrootedGCHashMap) { // This is not rooted, but it doesn't use GC pointers as keys or values so // it's ok. js::GCHashMap map(cx, 15); JS_GC(cx); CHECK(map.putNew(12, 13)); return true; } END_TEST(testSafelyUnrootedGCHashMap) static bool FillMyHashMap(JSContext* cx, MutableHandle map) { for (size_t i = 0; i < 10; ++i) { RootedObject obj(cx, JS_NewObject(cx, nullptr)); RootedValue val(cx, UndefinedValue()); // Construct a unique property name to ensure that the object creates a // new shape. char buffer[2]; buffer[0] = 'a' + i; buffer[1] = '\0'; if (!JS_SetProperty(cx, obj, buffer, val)) { return false; } if (!map.putNew(obj->as().lastProperty(), obj)) { return false; } } return true; } static bool CheckMyHashMap(JSContext* cx, Handle map) { for (auto r = map.all(); !r.empty(); r.popFront()) { RootedObject obj(cx, r.front().value()); if (obj->as().lastProperty() != r.front().key()) { return false; } } return true; } BEGIN_TEST(testGCHandleHashMap) { JS::Rooted map(cx, MyHashMap(cx, 15)); CHECK(FillMyHashMap(cx, &map)); JS_GC(cx); JS_GC(cx); CHECK(CheckMyHashMap(cx, map)); return true; } END_TEST(testGCHandleHashMap) using ShapeVec = GCVector; BEGIN_TEST(testGCRootedVector) { JS::Rooted shapes(cx); for (size_t i = 0; i < 10; ++i) { RootedObject obj(cx, JS_NewObject(cx, nullptr)); RootedValue val(cx, UndefinedValue()); // Construct a unique property name to ensure that the object creates a // new shape. char buffer[2]; buffer[0] = 'a' + i; buffer[1] = '\0'; CHECK(JS_SetProperty(cx, obj, buffer, val)); CHECK(shapes.append(obj->as().lastProperty())); } JS_GC(cx); JS_GC(cx); for (size_t i = 0; i < 10; ++i) { // Check the shape to ensure it did not get collected. char letter = 'a' + i; bool match; CHECK(JS_StringEqualsAscii(cx, JSID_TO_STRING(shapes[i]->propid()), &letter, 1, &match)); CHECK(match); } // Ensure iterator enumeration works through the rooted. for (auto shape : shapes) { CHECK(shape); } CHECK(receiveConstRefToShapeVector(shapes)); // Ensure rooted converts to handles. CHECK(receiveHandleToShapeVector(shapes)); CHECK(receiveMutableHandleToShapeVector(&shapes)); return true; } bool receiveConstRefToShapeVector(const JS::Rooted>& rooted) { // Ensure range enumeration works through the reference. for (auto shape : rooted) { CHECK(shape); } return true; } bool receiveHandleToShapeVector(JS::Handle> handle) { // Ensure range enumeration works through the handle. for (auto shape : handle) { CHECK(shape); } return true; } bool receiveMutableHandleToShapeVector( JS::MutableHandle> handle) { // Ensure range enumeration works through the handle. for (auto shape : handle) { CHECK(shape); } return true; } END_TEST(testGCRootedVector) BEGIN_TEST(testTraceableFifo) { using ShapeFifo = TraceableFifo; JS::Rooted shapes(cx, ShapeFifo(cx)); CHECK(shapes.empty()); for (size_t i = 0; i < 10; ++i) { RootedObject obj(cx, JS_NewObject(cx, nullptr)); RootedValue val(cx, UndefinedValue()); // Construct a unique property name to ensure that the object creates a // new shape. char buffer[2]; buffer[0] = 'a' + i; buffer[1] = '\0'; CHECK(JS_SetProperty(cx, obj, buffer, val)); CHECK(shapes.pushBack(obj->as().lastProperty())); } CHECK(shapes.length() == 10); JS_GC(cx); JS_GC(cx); for (size_t i = 0; i < 10; ++i) { // Check the shape to ensure it did not get collected. char letter = 'a' + i; bool match; CHECK(JS_StringEqualsAscii(cx, JSID_TO_STRING(shapes.front()->propid()), &letter, 1, &match)); CHECK(match); shapes.popFront(); } CHECK(shapes.empty()); return true; } END_TEST(testTraceableFifo) using ShapeVec = GCVector; static bool FillVector(JSContext* cx, MutableHandle shapes) { for (size_t i = 0; i < 10; ++i) { RootedObject obj(cx, JS_NewObject(cx, nullptr)); RootedValue val(cx, UndefinedValue()); // Construct a unique property name to ensure that the object creates a // new shape. char buffer[2]; buffer[0] = 'a' + i; buffer[1] = '\0'; if (!JS_SetProperty(cx, obj, buffer, val)) { return false; } if (!shapes.append(obj->as().lastProperty())) { return false; } } // Ensure iterator enumeration works through the mutable handle. for (auto shape : shapes) { if (!shape) { return false; } } return true; } static bool CheckVector(JSContext* cx, Handle shapes) { for (size_t i = 0; i < 10; ++i) { // Check the shape to ensure it did not get collected. char letter = 'a' + i; bool match; if (!JS_StringEqualsAscii(cx, JSID_TO_STRING(shapes[i]->propid()), &letter, 1, &match)) { return false; } if (!match) { return false; } } // Ensure iterator enumeration works through the handle. for (auto shape : shapes) { if (!shape) { return false; } } return true; } BEGIN_TEST(testGCHandleVector) { JS::Rooted vec(cx, ShapeVec(cx)); CHECK(FillVector(cx, &vec)); JS_GC(cx); JS_GC(cx); CHECK(CheckVector(cx, vec)); return true; } END_TEST(testGCHandleVector) class Foo { public: Foo(int, int) {} void trace(JSTracer*) {} }; using FooVector = JS::GCVector; BEGIN_TEST(testGCVectorEmplaceBack) { JS::Rooted vector(cx, FooVector(cx)); CHECK(vector.emplaceBack(1, 2)); return true; } END_TEST(testGCVectorEmplaceBack) BEGIN_TEST(testRootedMaybeValue) { JS::Rooted> maybeNothing(cx); CHECK(maybeNothing.isNothing()); CHECK(!maybeNothing.isSome()); JS::Rooted> maybe(cx, Some(UndefinedValue())); CHECK(CheckConstOperations>&>(maybe)); CHECK(CheckConstOperations>>(maybe)); CHECK(CheckConstOperations>>(&maybe)); maybe = Some(JS::TrueValue()); CHECK(CheckMutableOperations>&>(maybe)); maybe = Some(JS::TrueValue()); CHECK(CheckMutableOperations>>(&maybe)); CHECK(JS::NothingHandleValue.isNothing()); return true; } template bool CheckConstOperations(T someUndefinedValue) { CHECK(someUndefinedValue.isSome()); CHECK(someUndefinedValue.value().isUndefined()); CHECK(someUndefinedValue->isUndefined()); CHECK((*someUndefinedValue).isUndefined()); return true; } template bool CheckMutableOperations(T maybe) { CHECK(maybe->isTrue()); *maybe = JS::FalseValue(); CHECK(maybe->isFalse()); maybe.reset(); CHECK(maybe.isNothing()); return true; } END_TEST(testRootedMaybeValue)