/* -*- 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<JSObject*> obj; HeapPtr<JSString*> 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<JSObject*> obj; HeapPtr<JSString*> 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 <typename Wrapper> struct MutableWrappedPtrOperations<MyContainer, Wrapper> { HeapPtr<JSObject*>& obj() { return static_cast<Wrapper*>(this)->get().obj; } HeapPtr<JSString*>& str() { return static_cast<Wrapper*>(this)->get().str; } int constructor() { return static_cast<Wrapper*>(this)->get().whichConstructor; } }; template <typename Wrapper> struct MutableWrappedPtrOperations<MyNonCopyableContainer, Wrapper> { HeapPtr<JSObject*>& obj() { return static_cast<Wrapper*>(this)->get().obj; } HeapPtr<JSString*>& str() { return static_cast<Wrapper*>(this)->get().str; } int constructor() { return static_cast<Wrapper*>(this)->get().whichConstructor; } }; } // namespace js BEGIN_TEST(testGCRootedStaticStructInternalStackStorageAugmented) { // Test Rooted constructors for a copyable type. JS::Rooted<MyContainer> r1(cx); JS::Rooted<MyContainer> r2(cx, 3.4); JS::Rooted<MyContainer> r3(cx, MyContainer(cx)); JS::Rooted<MyContainer> r4(cx, cx); JS::Rooted<MyContainer> r5(cx, cx, cx, cx); JS::Rooted<Value> rv(cx); CHECK_EQUAL(r1.constructor(), 1); // direct SafelyInitialized<T> 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<MyNonCopyableContainer> nc1(cx); JS::Rooted<MyNonCopyableContainer> nc2(cx, 3.4); // Compile error: cannot copy // JS::Rooted<MyNonCopyableContainer> nc3(cx, MyNonCopyableContainer(cx)); JS::Rooted<MyNonCopyableContainer> nc4(cx, cx); JS::Rooted<MyNonCopyableContainer> 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<MyContainer> 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<MyContainer> heap(cx, container); // Copyable types in place. JS::PersistentRooted<MyContainer> cp1(cx); JS::PersistentRooted<MyContainer> cp2(cx, 7.8); JS::PersistentRooted<MyContainer> cp3(cx, cx); JS::PersistentRooted<MyContainer> cp4(cx, cx, cx, cx); CHECK_EQUAL(cp1.constructor(), 1); // direct SafelyInitialized<T> 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<MyNonCopyableContainer> ncp1(cx); JS::PersistentRooted<MyNonCopyableContainer> ncp2(cx, 7.8); // We're not just using a 1-arg constructor, right? JS::PersistentRooted<MyNonCopyableContainer> ncp3(cx, cx); JS::PersistentRooted<MyNonCopyableContainer> ncp4(cx, cx, cx, cx); CHECK_EQUAL(ncp1.constructor(), 1); // direct SafelyInitialized<T> 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<JSObject*> 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<Traceable> 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<MyContainer> sContainer; BEGIN_TEST(testGCPersistentRootedTraceableCannotOutliveRuntime) { JS::Rooted<MyContainer> 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<js::Shape*, JSObject*>; BEGIN_TEST(testGCRootedHashMap) { JS::Rooted<MyHashMap> 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) { AutoLeaveZeal noZeal(cx); 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<uint64_t, uint64_t> map(cx, 15); JS_GC(cx); CHECK(map.putNew(12, 13)); return true; } END_TEST(testSafelyUnrootedGCHashMap) static bool FillMyHashMap(JSContext* cx, MutableHandle<MyHashMap> 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<MyHashMap> 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<MyHashMap> 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<NativeShape*>; BEGIN_TEST(testGCRootedVector) { JS::Rooted<ShapeVec> 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<NativeObject>().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<NoGC> 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<GCVector<NativeShape*>>& rooted) { // Ensure range enumeration works through the reference. for (auto shape : rooted) { CHECK(shape); } return true; } bool receiveHandleToShapeVector(JS::Handle<GCVector<NativeShape*>> handle) { // Ensure range enumeration works through the handle. for (auto shape : handle) { CHECK(shape); } return true; } bool receiveMutableHandleToShapeVector( JS::MutableHandle<GCVector<NativeShape*>> 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<NativeShape*>; JS::Rooted<ShapeFifo> 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<NativeObject>().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<NoGC> 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<NativeShape*>; static bool FillVector(JSContext* cx, MutableHandle<ShapeVec> 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<NativeObject>().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<ShapeVec> 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<NoGC> 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<ShapeVec> 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<Foo>; BEGIN_TEST(testGCVectorEmplaceBack) { JS::Rooted<FooVector> vector(cx, FooVector(cx)); CHECK(vector.emplaceBack(1, 2)); return true; } END_TEST(testGCVectorEmplaceBack) BEGIN_TEST(testRootedMaybeValue) { JS::Rooted<Maybe<Value>> maybeNothing(cx); CHECK(maybeNothing.isNothing()); CHECK(!maybeNothing.isSome()); JS::Rooted<Maybe<Value>> maybe(cx, Some(UndefinedValue())); CHECK(CheckConstOperations<Rooted<Maybe<Value>>&>(maybe)); CHECK(CheckConstOperations<Handle<Maybe<Value>>>(maybe)); CHECK(CheckConstOperations<MutableHandle<Maybe<Value>>>(&maybe)); maybe = Some(JS::TrueValue()); CHECK(CheckMutableOperations<Rooted<Maybe<Value>>&>(maybe)); maybe = Some(JS::TrueValue()); CHECK(CheckMutableOperations<MutableHandle<Maybe<Value>>>(&maybe)); CHECK(JS::NothingHandleValue.isNothing()); return true; } template <typename T> bool CheckConstOperations(T someUndefinedValue) { CHECK(someUndefinedValue.isSome()); CHECK(someUndefinedValue.value().isUndefined()); CHECK(someUndefinedValue->isUndefined()); CHECK((*someUndefinedValue).isUndefined()); return true; } template <typename T> 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<T> because Heap<T> would get // traced via the store buffer. Heap<T> 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<TestErr> : public IgnoreGCPolicy<TestErr> {}; } // 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<Value, TestErr> unrootedValerr(val); Rooted<Result<Value, TestErr>> valerr(cx, val); Result<mozilla::Ok, Value> unrootedOkval(val); Rooted<Result<mozilla::Ok, Value>> okval(cx, val); Result<mozilla::Ok, TestErr> simple{mozilla::Ok()}; Result<Value, JSObject*> unrootedValobj1(val); Rooted<Result<Value, JSObject*>> valobj1(cx, val); Result<Value, JSObject*> unrootedValobj2(obj); Rooted<Result<Value, JSObject*>> valobj2(cx, obj); // Test nested traceable structures. Result<mozilla::Maybe<mozilla::Ok>, JSObject*> maybeobj( mozilla::Some(mozilla::Ok())); Rooted<Result<mozilla::Maybe<mozilla::Ok>, 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<Result<Result<mozilla::Ok, JS::Value>, JSObject*>> // But this should be fine when no tracing is required. Result<Result<mozilla::Ok, int>, double> dummy(3.4); // One thing I didn't realize initially about Result<>: unwrap() takes // ownership of a value. In the case of Result<Maybe>, that means the // contained Maybe is reset to Nothing. Result<mozilla::Maybe<int>, int> confusing(mozilla::Some(7)); CHECK(confusing.unwrap().isSome()); CHECK(!confusing.unwrap().isSome()); Result<mozilla::Maybe<JS::Value>, JSObject*> maybevalobj( mozilla::Some(val.get())); Rooted<Result<mozilla::Maybe<JS::Value>, JSObject*>> rooted_maybevalobj( cx, mozilla::Some(val.get())); // Custom types that haven't had GCPolicy explicitly specialized. SimpleTraceable s1{obj, val}; Result<SimpleTraceable, TestErr> custom(s1); SimpleTraceable s2{obj, val}; Rooted<Result<SimpleTraceable, TestErr>> 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<OtherTestErr, mozilla::Ok> 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<TestUnusedZeroEnum> : UnusedZeroEnum<TestUnusedZeroEnum> {}; } // namespace mozilla::detail namespace JS { template <> struct GCPolicy<TestUnusedZeroEnum> : public IgnoreGCPolicy<TestUnusedZeroEnum> {}; } // 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<Failed> { 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<Failed> : public IgnoreGCPolicy<Failed> {}; } // 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<TriviallyCopyable_LowBitTagIsError> : HasFreeLSB<JSObject*> { }; } // namespace mozilla::detail BEGIN_TEST_WITH_ATTRIBUTES(testRootedResultCtors, JS_EXPECT_HAZARDS) { JSObject* unrootedObj = JS_NewPlainObject(cx); CHECK(unrootedObj); Rooted<JSObject*> obj(cx, unrootedObj); using mozilla::detail::PackingStrategy; static_assert(Result<DontCopyMe_Variant, TestErr>::Strategy == PackingStrategy::Variant); Rooted<Result<DontCopyMe_Variant, TestErr>> vv(cx, DontCopyMe_Variant{obj}); static_assert(Result<mozilla::Ok, DontCopyMe_Variant>::Strategy == PackingStrategy::Variant); Rooted<Result<mozilla::Ok, DontCopyMe_Variant>> ve(cx, DontCopyMe_Variant{obj}); static_assert(Result<DontCopyMe_NullIsOk, TestUnusedZeroEnum>::Strategy == PackingStrategy::NullIsOk); Rooted<Result<DontCopyMe_NullIsOk, TestUnusedZeroEnum>> nv( cx, DontCopyMe_NullIsOk{JS::ObjectValue(*obj)}); static_assert(Result<TriviallyCopyable_LowBitTagIsError, Failed>::Strategy == PackingStrategy::LowBitTagIsError); Rooted<Result<TriviallyCopyable_LowBitTagIsError, Failed>> 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<JSObject*>(packed); } static uintptr_t Store(JSObject* obj) { return reinterpret_cast<uintptr_t>(obj); } DontCopyMe_PackedVariant() : obj(0) {} explicit DontCopyMe_PackedVariant(JSObject* objArg) : obj(reinterpret_cast<uintptr_t>(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<DontCopyMe_PackedVariant>); static_assert(std::is_default_constructible_v<TestErr>); static_assert(mozilla::detail::IsPackableVariant<DontCopyMe_PackedVariant, TestErr>::value); BEGIN_TEST_WITH_ATTRIBUTES(testResultPackedVariant, JS_EXPECT_HAZARDS) { JSObject* unrootedObj = JS_NewPlainObject(cx); CHECK(unrootedObj); Rooted<JSObject*> obj(cx, unrootedObj); using mozilla::detail::PackingStrategy; static_assert(Result<DontCopyMe_PackedVariant, TestErr>::Strategy == PackingStrategy::PackedVariant); Rooted<Result<DontCopyMe_PackedVariant, TestErr>> pv( cx, DontCopyMe_PackedVariant{obj}); static_assert(Result<mozilla::Ok, DontCopyMe_PackedVariant>::Strategy == PackingStrategy::PackedVariant); Rooted<Result<mozilla::Ok, DontCopyMe_PackedVariant>> 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