/* -*- 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 "mozilla/Result.h" #include "mozilla/ResultVariant.h" #include "ds/TraceableFifo.h" #include "gc/Policy.h" #include "js/GCHashTable.h" #include "js/GCVector.h" #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty, JS_SetProperty #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 { int whichConstructor; HeapPtr obj; HeapPtr str; MyContainer() : whichConstructor(1), obj(nullptr), str(nullptr) {} explicit MyContainer(double) : MyContainer() { whichConstructor = 2; } explicit MyContainer(JSContext* cx) : MyContainer() { whichConstructor = 3; } MyContainer(JSContext* cx, JSContext* cx2, JSContext* cx3) : MyContainer() { whichConstructor = 4; } MyContainer(const MyContainer& rhs) : whichConstructor(100 + rhs.whichConstructor), obj(rhs.obj), str(rhs.str) {} void trace(JSTracer* trc) { js::TraceNullableEdge(trc, &obj, "test container obj"); js::TraceNullableEdge(trc, &str, "test container str"); } }; struct MyNonCopyableContainer { int whichConstructor; HeapPtr obj; HeapPtr str; MyNonCopyableContainer() : whichConstructor(1), obj(nullptr), str(nullptr) {} explicit MyNonCopyableContainer(double) : MyNonCopyableContainer() { whichConstructor = 2; } explicit MyNonCopyableContainer(JSContext* cx) : MyNonCopyableContainer() { whichConstructor = 3; } explicit MyNonCopyableContainer(JSContext* cx, JSContext* cx2, JSContext* cx3) : MyNonCopyableContainer() { whichConstructor = 4; } MyNonCopyableContainer(const MyNonCopyableContainer&) = delete; MyNonCopyableContainer& operator=(const MyNonCopyableContainer&) = delete; void trace(JSTracer* trc) { js::TraceNullableEdge(trc, &obj, "test container obj"); js::TraceNullableEdge(trc, &str, "test container str"); } }; namespace js { template struct MutableWrappedPtrOperations { HeapPtr& obj() { return static_cast(this)->get().obj; } HeapPtr& str() { return static_cast(this)->get().str; } int constructor() { return static_cast(this)->get().whichConstructor; } }; template struct MutableWrappedPtrOperations { HeapPtr& obj() { return static_cast(this)->get().obj; } HeapPtr& str() { return static_cast(this)->get().str; } int constructor() { return static_cast(this)->get().whichConstructor; } }; } // namespace js BEGIN_TEST(testGCRootedStaticStructInternalStackStorageAugmented) { // Test Rooted constructors for a copyable type. JS::Rooted r1(cx); JS::Rooted r2(cx, 3.4); JS::Rooted r3(cx, MyContainer(cx)); JS::Rooted r4(cx, cx); JS::Rooted r5(cx, cx, cx, cx); JS::Rooted rv(cx); CHECK_EQUAL(r1.constructor(), 1); // direct SafelyInitialized CHECK_EQUAL(r2.constructor(), 2); // direct MyContainer(3.4) CHECK_EQUAL(r3.constructor(), 103); // copy of MyContainer(cx) CHECK_EQUAL(r4.constructor(), 3); // direct MyContainer(cx) CHECK_EQUAL(r5.constructor(), 4); // direct MyContainer(cx, cx, cx) // Test Rooted constructor forwarding for a non-copyable type. JS::Rooted nc1(cx); JS::Rooted nc2(cx, 3.4); // Compile error: cannot copy // JS::Rooted nc3(cx, MyNonCopyableContainer(cx)); JS::Rooted nc4(cx, cx); JS::Rooted nc5(cx, cx, cx, cx); CHECK_EQUAL(nc1.constructor(), 1); // direct MyNonCopyableContainer() CHECK_EQUAL(nc2.constructor(), 2); // direct MyNonCopyableContainer(3.4) CHECK_EQUAL(nc4.constructor(), 3); // direct MyNonCopyableContainer(cx) CHECK_EQUAL(nc5.constructor(), 4); // direct MyNonCopyableContainer(cx, cx, cx) 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); // Copyable types in place. JS::PersistentRooted cp1(cx); JS::PersistentRooted cp2(cx, 7.8); JS::PersistentRooted cp3(cx, cx); JS::PersistentRooted cp4(cx, cx, cx, cx); CHECK_EQUAL(cp1.constructor(), 1); // direct SafelyInitialized CHECK_EQUAL(cp2.constructor(), 2); // direct MyContainer(double) CHECK_EQUAL(cp3.constructor(), 3); // direct MyContainer(cx) CHECK_EQUAL(cp4.constructor(), 4); // direct MyContainer(cx, cx, cx) // Construct uncopyable type in place. JS::PersistentRooted ncp1(cx); JS::PersistentRooted ncp2(cx, 7.8); // We're not just using a 1-arg constructor, right? JS::PersistentRooted ncp3(cx, cx); JS::PersistentRooted ncp4(cx, cx, cx, cx); CHECK_EQUAL(ncp1.constructor(), 1); // direct SafelyInitialized CHECK_EQUAL(ncp2.constructor(), 2); // direct Ctor(double) CHECK_EQUAL(ncp3.constructor(), 3); // direct Ctor(cx) CHECK_EQUAL(ncp4.constructor(), 4); // direct Ctor(cx, cx, cx) // 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->shape(), obj)); } JS_GC(cx); JS_GC(cx); for (auto r = map.all(); !r.empty(); r.popFront()) { RootedObject obj(cx, r.front().value()); CHECK(obj->shape() == 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->shape(), 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->shape(), 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->shape() != 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, 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().shape())); } 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; ShapePropertyIter iter(shapes[i]); CHECK(JS_StringEqualsAscii(cx, iter->key().toString(), &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().shape())); } 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; ShapePropertyIter iter(shapes.front()); CHECK(JS_StringEqualsAscii(cx, iter->key().toString(), &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().shape())) { 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; ShapePropertyIter iter(shapes[i]); if (!JS_StringEqualsAscii(cx, iter->key().toString(), &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) struct TestErr {}; struct OtherTestErr {}; struct SimpleTraceable { // I'm using plain objects rather than Heap because Heap would get // traced via the store buffer. Heap would be a more realistic example, // but would require compaction to test for tracing. JSObject* obj; JS::Value val; void trace(JSTracer* trc) { TraceRoot(trc, &obj, "obj"); TraceRoot(trc, &val, "val"); } }; namespace JS { template <> struct GCPolicy : public IgnoreGCPolicy {}; } // namespace JS BEGIN_TEST_WITH_ATTRIBUTES(testGCRootedResult, JS_EXPECT_HAZARDS) { AutoLeaveZeal noZeal(cx); JSObject* unrootedObj = JS_NewPlainObject(cx); CHECK(js::gc::IsInsideNursery(unrootedObj)); Value unrootedVal = ObjectValue(*unrootedObj); RootedObject obj(cx, unrootedObj); RootedValue val(cx, unrootedVal); Result unrootedValerr(val); Rooted> valerr(cx, val); Result unrootedOkval(val); Rooted> okval(cx, val); Result simple{mozilla::Ok()}; Result unrootedValobj1(val); Rooted> valobj1(cx, val); Result unrootedValobj2(obj); Rooted> valobj2(cx, obj); // Test nested traceable structures. Result, JSObject*> maybeobj( mozilla::Some(mozilla::Ok())); Rooted, JSObject*>> rooted_maybeobj( cx, mozilla::Some(mozilla::Ok())); // This would fail to compile because Result<> deletes its copy constructor, // which prevents updating after tracing: // // Rooted, JSObject*>> // But this should be fine when no tracing is required. Result, double> dummy(3.4); // One thing I didn't realize initially about Result<>: unwrap() takes // ownership of a value. In the case of Result, that means the // contained Maybe is reset to Nothing. Result, int> confusing(mozilla::Some(7)); CHECK(confusing.unwrap().isSome()); CHECK(!confusing.unwrap().isSome()); Result, JSObject*> maybevalobj( mozilla::Some(val.get())); Rooted, JSObject*>> rooted_maybevalobj( cx, mozilla::Some(val.get())); // Custom types that haven't had GCPolicy explicitly specialized. SimpleTraceable s1{obj, val}; Result custom(s1); SimpleTraceable s2{obj, val}; Rooted> rootedCustom(cx, s2); CHECK(obj == unrootedObj); CHECK(val == unrootedVal); CHECK(simple.isOk()); CHECK(unrootedValerr.inspect() == unrootedVal); CHECK(valerr.get().inspect() == val); CHECK(unrootedOkval.inspectErr() == unrootedVal); CHECK(okval.get().inspectErr() == val); CHECK(unrootedValobj1.inspect() == unrootedVal); CHECK(valobj1.get().inspect() == val); CHECK(unrootedValobj2.inspectErr() == unrootedObj); CHECK(valobj2.get().inspectErr() == obj); CHECK(*maybevalobj.inspect() == unrootedVal); CHECK(*rooted_maybevalobj.get().inspect() == val); CHECK(custom.inspect().obj == unrootedObj); CHECK(custom.inspect().val == unrootedVal); CHECK(rootedCustom.get().inspect().obj == obj); CHECK(rootedCustom.get().inspect().val == val); JS_GC(cx); CHECK(obj != unrootedObj); CHECK(val != unrootedVal); CHECK(unrootedValerr.inspect() == unrootedVal); CHECK(valerr.get().inspect() == val); CHECK(unrootedOkval.inspectErr() == unrootedVal); CHECK(okval.get().inspectErr() == val); CHECK(unrootedValobj1.inspect() == unrootedVal); CHECK(valobj1.get().inspect() == val); CHECK(unrootedValobj2.inspectErr() == unrootedObj); CHECK(valobj2.get().inspectErr() == obj); CHECK(*maybevalobj.inspect() == unrootedVal); CHECK(*rooted_maybevalobj.get().inspect() == val); MOZ_ASSERT(custom.inspect().obj == unrootedObj); CHECK(custom.inspect().obj == unrootedObj); CHECK(custom.inspect().val == unrootedVal); CHECK(rootedCustom.get().inspect().obj == obj); CHECK(rootedCustom.get().inspect().val == val); mozilla::Result r(mozilla::Ok{}); (void)r; return true; } END_TEST(testGCRootedResult) static int copies = 0; struct DontCopyMe_Variant { JSObject* obj; explicit DontCopyMe_Variant(JSObject* objArg) : obj(objArg) {} DontCopyMe_Variant(const DontCopyMe_Variant& other) : obj(other.obj) { copies++; } DontCopyMe_Variant(DontCopyMe_Variant&& other) : obj(std::move(other.obj)) { other.obj = nullptr; } void trace(JSTracer* trc) { TraceRoot(trc, &obj, "obj"); } }; enum struct TestUnusedZeroEnum : int16_t { Ok = 0, NotOk = 1 }; namespace mozilla::detail { template <> struct UnusedZero : UnusedZeroEnum {}; } // namespace mozilla::detail namespace JS { template <> struct GCPolicy : public IgnoreGCPolicy {}; } // namespace JS struct DontCopyMe_NullIsOk { JS::Value val; DontCopyMe_NullIsOk() : val(UndefinedValue()) {} explicit DontCopyMe_NullIsOk(const JS::Value& valArg) : val(valArg) {} DontCopyMe_NullIsOk(const DontCopyMe_NullIsOk& other) = delete; DontCopyMe_NullIsOk(DontCopyMe_NullIsOk&& other) : val(std::move(other.val)) {} DontCopyMe_NullIsOk& operator=(DontCopyMe_NullIsOk&& other) { val = std::move(other.val); other.val = UndefinedValue(); return *this; } void trace(JSTracer* trc) { TraceRoot(trc, &val, "val"); } }; struct Failed {}; namespace mozilla::detail { template <> struct UnusedZero { using StorageType = uintptr_t; static constexpr bool value = true; static constexpr StorageType nullValue = 0; static constexpr StorageType GetDefaultValue() { return 2; } static constexpr void AssertValid(StorageType aValue) {} static constexpr Failed Inspect(const StorageType& aValue) { return Failed{}; } static constexpr Failed Unwrap(StorageType aValue) { return Failed{}; } static constexpr StorageType Store(Failed aValue) { return GetDefaultValue(); } }; } // namespace mozilla::detail namespace JS { template <> struct GCPolicy : public IgnoreGCPolicy {}; } // namespace JS struct TriviallyCopyable_LowBitTagIsError { JSObject* obj; TriviallyCopyable_LowBitTagIsError() : obj(nullptr) {} explicit TriviallyCopyable_LowBitTagIsError(JSObject* objArg) : obj(objArg) {} TriviallyCopyable_LowBitTagIsError( const TriviallyCopyable_LowBitTagIsError& other) = default; void trace(JSTracer* trc) { TraceRoot(trc, &obj, "obj"); } }; namespace mozilla::detail { template <> struct HasFreeLSB : HasFreeLSB { }; } // namespace mozilla::detail BEGIN_TEST_WITH_ATTRIBUTES(testRootedResultCtors, JS_EXPECT_HAZARDS) { JSObject* unrootedObj = JS_NewPlainObject(cx); CHECK(unrootedObj); Rooted obj(cx, unrootedObj); using mozilla::detail::PackingStrategy; static_assert(Result::Strategy == PackingStrategy::Variant); Rooted> vv(cx, DontCopyMe_Variant{obj}); static_assert(Result::Strategy == PackingStrategy::Variant); Rooted> ve(cx, DontCopyMe_Variant{obj}); static_assert(Result::Strategy == PackingStrategy::NullIsOk); Rooted> nv( cx, DontCopyMe_NullIsOk{JS::ObjectValue(*obj)}); static_assert(Result::Strategy == PackingStrategy::LowBitTagIsError); Rooted> lv( cx, TriviallyCopyable_LowBitTagIsError{obj}); CHECK(obj == unrootedObj); CHECK(vv.get().inspect().obj == obj); CHECK(ve.get().inspectErr().obj == obj); CHECK(nv.get().inspect().val.toObjectOrNull() == obj); CHECK(lv.get().inspect().obj == obj); JS_GC(cx); CHECK(obj != unrootedObj); CHECK(vv.get().inspect().obj == obj); CHECK(ve.get().inspectErr().obj == obj); CHECK(nv.get().inspect().val.toObjectOrNull() == obj); CHECK(lv.get().inspect().obj == obj); CHECK(copies == 0); return true; } END_TEST(testRootedResultCtors) #if defined(HAVE_64BIT_BUILD) && !defined(XP_WIN) // This depends on a pointer fitting in 48 bits, leaving space for an empty // struct and a bool in a packed struct. Windows doesn't seem to do this // packing, so we'll skip this test here. We're primarily checking whether // copy constructors get called, which should be cross-platform, and // secondarily making sure that the Rooted/tracing stuff is compiled and // executed properly. There are certainly more clever ways to do this that // would work cross-platform, but it doesn't seem worth the bother right now. struct __attribute__((packed)) DontCopyMe_PackedVariant { uintptr_t obj : 48; static JSObject* Unwrap(uintptr_t packed) { return reinterpret_cast(packed); } static uintptr_t Store(JSObject* obj) { return reinterpret_cast(obj); } DontCopyMe_PackedVariant() : obj(0) {} explicit DontCopyMe_PackedVariant(JSObject* objArg) : obj(reinterpret_cast(objArg)) {} DontCopyMe_PackedVariant(const DontCopyMe_PackedVariant& other) : obj(other.obj) { copies++; } DontCopyMe_PackedVariant(DontCopyMe_PackedVariant&& other) : obj(other.obj) { other.obj = 0; } void trace(JSTracer* trc) { JSObject* realObj = Unwrap(obj); TraceRoot(trc, &realObj, "obj"); obj = Store(realObj); } }; static_assert(std::is_default_constructible_v); static_assert(std::is_default_constructible_v); static_assert(mozilla::detail::IsPackableVariant::value); BEGIN_TEST_WITH_ATTRIBUTES(testResultPackedVariant, JS_EXPECT_HAZARDS) { JSObject* unrootedObj = JS_NewPlainObject(cx); CHECK(unrootedObj); Rooted obj(cx, unrootedObj); using mozilla::detail::PackingStrategy; static_assert(Result::Strategy == PackingStrategy::PackedVariant); Rooted> pv( cx, DontCopyMe_PackedVariant{obj}); static_assert(Result::Strategy == PackingStrategy::PackedVariant); Rooted> pe( cx, DontCopyMe_PackedVariant{obj}); CHECK(obj == unrootedObj); CHECK(DontCopyMe_PackedVariant::Unwrap(pv.get().inspect().obj) == obj); CHECK(DontCopyMe_PackedVariant::Unwrap(pe.get().inspectErr().obj) == obj); JS_GC(cx); CHECK(obj != unrootedObj); CHECK(DontCopyMe_PackedVariant::Unwrap(pv.get().inspect().obj) == obj); CHECK(DontCopyMe_PackedVariant::Unwrap(pe.get().inspectErr().obj) == obj); return true; } END_TEST(testResultPackedVariant) #endif // HAVE_64BIT_BUILD