diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jsapi-tests/testGCExactRooting.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | js/src/jsapi-tests/testGCExactRooting.cpp | 915 |
1 files changed, 915 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testGCExactRooting.cpp b/js/src/jsapi-tests/testGCExactRooting.cpp new file mode 100644 index 0000000000..d53e293f23 --- /dev/null +++ b/js/src/jsapi-tests/testGCExactRooting.cpp @@ -0,0 +1,915 @@ +/* -*- 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) { + 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 |