Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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