1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "mozilla/Maybe.h"
9 #include "mozilla/Result.h"
10 #include "mozilla/ResultVariant.h"
12 #include "ds/TraceableFifo.h"
13 #include "gc/Policy.h"
14 #include "js/GCHashTable.h"
15 #include "js/GCVector.h"
16 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty, JS_SetProperty
17 #include "js/RootingAPI.h"
19 #include "jsapi-tests/tests.h"
26 BEGIN_TEST(testGCExactRooting
) {
27 JS::RootedObject
rootCx(cx
, JS_NewPlainObject(cx
));
31 /* Use the objects we just created to ensure that they are still alive. */
32 JS_DefineProperty(cx
, rootCx
, "foo", JS::UndefinedHandleValue
, 0);
36 END_TEST(testGCExactRooting
)
38 BEGIN_TEST(testGCSuppressions
) {
39 JS::AutoAssertNoGC nogc
;
40 JS::AutoCheckCannotGC checkgc
;
41 JS::AutoSuppressGCAnalysis noanalysis
;
43 JS::AutoAssertNoGC
nogcCx(cx
);
44 JS::AutoCheckCannotGC
checkgcCx(cx
);
45 JS::AutoSuppressGCAnalysis
noanalysisCx(cx
);
49 END_TEST(testGCSuppressions
)
53 HeapPtr
<JSObject
*> obj
;
54 HeapPtr
<JSString
*> str
;
56 MyContainer() : whichConstructor(1), obj(nullptr), str(nullptr) {}
57 explicit MyContainer(double) : MyContainer() { whichConstructor
= 2; }
58 explicit MyContainer(JSContext
* cx
) : MyContainer() { whichConstructor
= 3; }
59 MyContainer(JSContext
* cx
, JSContext
* cx2
, JSContext
* cx3
) : MyContainer() {
62 MyContainer(const MyContainer
& rhs
)
63 : whichConstructor(100 + rhs
.whichConstructor
),
66 void trace(JSTracer
* trc
) {
67 js::TraceNullableEdge(trc
, &obj
, "test container obj");
68 js::TraceNullableEdge(trc
, &str
, "test container str");
72 struct MyNonCopyableContainer
{
74 HeapPtr
<JSObject
*> obj
;
75 HeapPtr
<JSString
*> str
;
77 MyNonCopyableContainer() : whichConstructor(1), obj(nullptr), str(nullptr) {}
78 explicit MyNonCopyableContainer(double) : MyNonCopyableContainer() {
81 explicit MyNonCopyableContainer(JSContext
* cx
) : MyNonCopyableContainer() {
84 explicit MyNonCopyableContainer(JSContext
* cx
, JSContext
* cx2
, JSContext
* cx3
)
85 : MyNonCopyableContainer() {
89 MyNonCopyableContainer(const MyNonCopyableContainer
&) = delete;
90 MyNonCopyableContainer
& operator=(const MyNonCopyableContainer
&) = delete;
92 void trace(JSTracer
* trc
) {
93 js::TraceNullableEdge(trc
, &obj
, "test container obj");
94 js::TraceNullableEdge(trc
, &str
, "test container str");
99 template <typename Wrapper
>
100 struct MutableWrappedPtrOperations
<MyContainer
, Wrapper
> {
101 HeapPtr
<JSObject
*>& obj() { return static_cast<Wrapper
*>(this)->get().obj
; }
102 HeapPtr
<JSString
*>& str() { return static_cast<Wrapper
*>(this)->get().str
; }
104 return static_cast<Wrapper
*>(this)->get().whichConstructor
;
108 template <typename Wrapper
>
109 struct MutableWrappedPtrOperations
<MyNonCopyableContainer
, Wrapper
> {
110 HeapPtr
<JSObject
*>& obj() { return static_cast<Wrapper
*>(this)->get().obj
; }
111 HeapPtr
<JSString
*>& str() { return static_cast<Wrapper
*>(this)->get().str
; }
113 return static_cast<Wrapper
*>(this)->get().whichConstructor
;
118 BEGIN_TEST(testGCRootedStaticStructInternalStackStorageAugmented
) {
119 // Test Rooted constructors for a copyable type.
120 JS::Rooted
<MyContainer
> r1(cx
);
121 JS::Rooted
<MyContainer
> r2(cx
, 3.4);
122 JS::Rooted
<MyContainer
> r3(cx
, MyContainer(cx
));
123 JS::Rooted
<MyContainer
> r4(cx
, cx
);
124 JS::Rooted
<MyContainer
> r5(cx
, cx
, cx
, cx
);
126 JS::Rooted
<Value
> rv(cx
);
128 CHECK_EQUAL(r1
.constructor(), 1); // direct SafelyInitialized<T>
129 CHECK_EQUAL(r2
.constructor(), 2); // direct MyContainer(3.4)
130 CHECK_EQUAL(r3
.constructor(), 103); // copy of MyContainer(cx)
131 CHECK_EQUAL(r4
.constructor(), 3); // direct MyContainer(cx)
132 CHECK_EQUAL(r5
.constructor(), 4); // direct MyContainer(cx, cx, cx)
134 // Test Rooted constructor forwarding for a non-copyable type.
135 JS::Rooted
<MyNonCopyableContainer
> nc1(cx
);
136 JS::Rooted
<MyNonCopyableContainer
> nc2(cx
, 3.4);
137 // Compile error: cannot copy
138 // JS::Rooted<MyNonCopyableContainer> nc3(cx, MyNonCopyableContainer(cx));
139 JS::Rooted
<MyNonCopyableContainer
> nc4(cx
, cx
);
140 JS::Rooted
<MyNonCopyableContainer
> nc5(cx
, cx
, cx
, cx
);
142 CHECK_EQUAL(nc1
.constructor(), 1); // direct MyNonCopyableContainer()
143 CHECK_EQUAL(nc2
.constructor(), 2); // direct MyNonCopyableContainer(3.4)
144 CHECK_EQUAL(nc4
.constructor(), 3); // direct MyNonCopyableContainer(cx)
145 CHECK_EQUAL(nc5
.constructor(),
146 4); // direct MyNonCopyableContainer(cx, cx, cx)
148 JS::Rooted
<MyContainer
> container(cx
);
149 container
.obj() = JS_NewObject(cx
, nullptr);
150 container
.str() = JS_NewStringCopyZ(cx
, "Hello");
155 JS::RootedObject
obj(cx
, container
.obj());
156 JS::RootedValue
val(cx
, StringValue(container
.str()));
157 CHECK(JS_SetProperty(cx
, obj
, "foo", val
));
159 val
= UndefinedValue();
162 JS::RootedString
actual(cx
);
165 // Automatic move from stack to heap.
166 JS::PersistentRooted
<MyContainer
> heap(cx
, container
);
168 // Copyable types in place.
169 JS::PersistentRooted
<MyContainer
> cp1(cx
);
170 JS::PersistentRooted
<MyContainer
> cp2(cx
, 7.8);
171 JS::PersistentRooted
<MyContainer
> cp3(cx
, cx
);
172 JS::PersistentRooted
<MyContainer
> cp4(cx
, cx
, cx
, cx
);
174 CHECK_EQUAL(cp1
.constructor(), 1); // direct SafelyInitialized<T>
175 CHECK_EQUAL(cp2
.constructor(), 2); // direct MyContainer(double)
176 CHECK_EQUAL(cp3
.constructor(), 3); // direct MyContainer(cx)
177 CHECK_EQUAL(cp4
.constructor(), 4); // direct MyContainer(cx, cx, cx)
179 // Construct uncopyable type in place.
180 JS::PersistentRooted
<MyNonCopyableContainer
> ncp1(cx
);
181 JS::PersistentRooted
<MyNonCopyableContainer
> ncp2(cx
, 7.8);
183 // We're not just using a 1-arg constructor, right?
184 JS::PersistentRooted
<MyNonCopyableContainer
> ncp3(cx
, cx
);
185 JS::PersistentRooted
<MyNonCopyableContainer
> ncp4(cx
, cx
, cx
, cx
);
187 CHECK_EQUAL(ncp1
.constructor(), 1); // direct SafelyInitialized<T>
188 CHECK_EQUAL(ncp2
.constructor(), 2); // direct Ctor(double)
189 CHECK_EQUAL(ncp3
.constructor(), 3); // direct Ctor(cx)
190 CHECK_EQUAL(ncp4
.constructor(), 4); // direct Ctor(cx, cx, cx)
192 // clear prior rooting.
193 container
.obj() = nullptr;
194 container
.str() = nullptr;
197 CHECK(JS_GetProperty(cx
, obj
, "foo", &val
));
198 actual
= val
.toString();
199 CHECK(JS_StringEqualsLiteral(cx
, actual
, "Hello", &same
));
208 CHECK(JS_GetProperty(cx
, obj
, "foo", &val
));
209 actual
= val
.toString();
210 CHECK(JS_StringEqualsLiteral(cx
, actual
, "Hello", &same
));
218 END_TEST(testGCRootedStaticStructInternalStackStorageAugmented
)
220 static JS::PersistentRooted
<JSObject
*> sLongLived
;
221 BEGIN_TEST(testGCPersistentRootedOutlivesRuntime
) {
222 sLongLived
.init(cx
, JS_NewObject(cx
, nullptr));
226 END_TEST(testGCPersistentRootedOutlivesRuntime
)
228 // Unlike the above, the following test is an example of an invalid usage: for
229 // performance and simplicity reasons, PersistentRooted<Traceable> is not
230 // allowed to outlive the container it belongs to. The following commented out
231 // test can be used to verify that the relevant assertion fires as expected.
232 static JS::PersistentRooted
<MyContainer
> sContainer
;
233 BEGIN_TEST(testGCPersistentRootedTraceableCannotOutliveRuntime
) {
234 JS::Rooted
<MyContainer
> container(cx
);
235 container
.obj() = JS_NewObject(cx
, nullptr);
236 container
.str() = JS_NewStringCopyZ(cx
, "Hello");
237 sContainer
.init(cx
, container
);
239 // Commenting the following line will trigger an assertion that the
240 // PersistentRooted outlives the runtime it is attached to.
245 END_TEST(testGCPersistentRootedTraceableCannotOutliveRuntime
)
247 using MyHashMap
= js::GCHashMap
<js::Shape
*, JSObject
*>;
249 BEGIN_TEST(testGCRootedHashMap
) {
250 JS::Rooted
<MyHashMap
> map(cx
, MyHashMap(cx
, 15));
252 for (size_t i
= 0; i
< 10; ++i
) {
253 RootedObject
obj(cx
, JS_NewObject(cx
, nullptr));
254 RootedValue
val(cx
, UndefinedValue());
255 // Construct a unique property name to ensure that the object creates a
260 CHECK(JS_SetProperty(cx
, obj
, buffer
, val
));
261 CHECK(map
.putNew(obj
->shape(), obj
));
267 for (auto r
= map
.all(); !r
.empty(); r
.popFront()) {
268 RootedObject
obj(cx
, r
.front().value());
269 CHECK(obj
->shape() == r
.front().key());
274 END_TEST(testGCRootedHashMap
)
276 // Repeat of the test above, but without rooting. This is a rooting hazard. The
277 // JS_EXPECT_HAZARDS annotation will cause the hazard taskcluster job to fail
278 // if the hazard below is *not* detected.
279 BEGIN_TEST_WITH_ATTRIBUTES(testUnrootedGCHashMap
, JS_EXPECT_HAZARDS
) {
280 MyHashMap
map(cx
, 15);
282 for (size_t i
= 0; i
< 10; ++i
) {
283 RootedObject
obj(cx
, JS_NewObject(cx
, nullptr));
284 RootedValue
val(cx
, UndefinedValue());
285 // Construct a unique property name to ensure that the object creates a
290 CHECK(JS_SetProperty(cx
, obj
, buffer
, val
));
291 CHECK(map
.putNew(obj
->shape(), obj
));
296 // Access map to keep it live across the GC.
297 CHECK(map
.count() == 10);
301 END_TEST(testUnrootedGCHashMap
)
303 BEGIN_TEST(testSafelyUnrootedGCHashMap
) {
304 // This is not rooted, but it doesn't use GC pointers as keys or values so
306 js::GCHashMap
<uint64_t, uint64_t> map(cx
, 15);
309 CHECK(map
.putNew(12, 13));
313 END_TEST(testSafelyUnrootedGCHashMap
)
315 static bool FillMyHashMap(JSContext
* cx
, MutableHandle
<MyHashMap
> map
) {
316 for (size_t i
= 0; i
< 10; ++i
) {
317 RootedObject
obj(cx
, JS_NewObject(cx
, nullptr));
318 RootedValue
val(cx
, UndefinedValue());
319 // Construct a unique property name to ensure that the object creates a
324 if (!JS_SetProperty(cx
, obj
, buffer
, val
)) {
327 if (!map
.putNew(obj
->shape(), obj
)) {
334 static bool CheckMyHashMap(JSContext
* cx
, Handle
<MyHashMap
> map
) {
335 for (auto r
= map
.all(); !r
.empty(); r
.popFront()) {
336 RootedObject
obj(cx
, r
.front().value());
337 if (obj
->shape() != r
.front().key()) {
344 BEGIN_TEST(testGCHandleHashMap
) {
345 JS::Rooted
<MyHashMap
> map(cx
, MyHashMap(cx
, 15));
347 CHECK(FillMyHashMap(cx
, &map
));
352 CHECK(CheckMyHashMap(cx
, map
));
356 END_TEST(testGCHandleHashMap
)
358 using ShapeVec
= GCVector
<NativeShape
*>;
360 BEGIN_TEST(testGCRootedVector
) {
361 JS::Rooted
<ShapeVec
> shapes(cx
, cx
);
363 for (size_t i
= 0; i
< 10; ++i
) {
364 RootedObject
obj(cx
, JS_NewObject(cx
, nullptr));
365 RootedValue
val(cx
, UndefinedValue());
366 // Construct a unique property name to ensure that the object creates a
371 CHECK(JS_SetProperty(cx
, obj
, buffer
, val
));
372 CHECK(shapes
.append(obj
->as
<NativeObject
>().shape()));
378 for (size_t i
= 0; i
< 10; ++i
) {
379 // Check the shape to ensure it did not get collected.
380 char letter
= 'a' + i
;
382 ShapePropertyIter
<NoGC
> iter(shapes
[i
]);
383 CHECK(JS_StringEqualsAscii(cx
, iter
->key().toString(), &letter
, 1, &match
));
387 // Ensure iterator enumeration works through the rooted.
388 for (auto shape
: shapes
) {
392 CHECK(receiveConstRefToShapeVector(shapes
));
394 // Ensure rooted converts to handles.
395 CHECK(receiveHandleToShapeVector(shapes
));
396 CHECK(receiveMutableHandleToShapeVector(&shapes
));
401 bool receiveConstRefToShapeVector(
402 const JS::Rooted
<GCVector
<NativeShape
*>>& rooted
) {
403 // Ensure range enumeration works through the reference.
404 for (auto shape
: rooted
) {
410 bool receiveHandleToShapeVector(JS::Handle
<GCVector
<NativeShape
*>> handle
) {
411 // Ensure range enumeration works through the handle.
412 for (auto shape
: handle
) {
418 bool receiveMutableHandleToShapeVector(
419 JS::MutableHandle
<GCVector
<NativeShape
*>> handle
) {
420 // Ensure range enumeration works through the handle.
421 for (auto shape
: handle
) {
426 END_TEST(testGCRootedVector
)
428 BEGIN_TEST(testTraceableFifo
) {
429 using ShapeFifo
= TraceableFifo
<NativeShape
*>;
430 JS::Rooted
<ShapeFifo
> shapes(cx
, ShapeFifo(cx
));
431 CHECK(shapes
.empty());
433 for (size_t i
= 0; i
< 10; ++i
) {
434 RootedObject
obj(cx
, JS_NewObject(cx
, nullptr));
435 RootedValue
val(cx
, UndefinedValue());
436 // Construct a unique property name to ensure that the object creates a
441 CHECK(JS_SetProperty(cx
, obj
, buffer
, val
));
442 CHECK(shapes
.pushBack(obj
->as
<NativeObject
>().shape()));
445 CHECK(shapes
.length() == 10);
450 for (size_t i
= 0; i
< 10; ++i
) {
451 // Check the shape to ensure it did not get collected.
452 char letter
= 'a' + i
;
454 ShapePropertyIter
<NoGC
> iter(shapes
.front());
455 CHECK(JS_StringEqualsAscii(cx
, iter
->key().toString(), &letter
, 1, &match
));
460 CHECK(shapes
.empty());
463 END_TEST(testTraceableFifo
)
465 using ShapeVec
= GCVector
<NativeShape
*>;
467 static bool FillVector(JSContext
* cx
, MutableHandle
<ShapeVec
> shapes
) {
468 for (size_t i
= 0; i
< 10; ++i
) {
469 RootedObject
obj(cx
, JS_NewObject(cx
, nullptr));
470 RootedValue
val(cx
, UndefinedValue());
471 // Construct a unique property name to ensure that the object creates a
476 if (!JS_SetProperty(cx
, obj
, buffer
, val
)) {
479 if (!shapes
.append(obj
->as
<NativeObject
>().shape())) {
484 // Ensure iterator enumeration works through the mutable handle.
485 for (auto shape
: shapes
) {
494 static bool CheckVector(JSContext
* cx
, Handle
<ShapeVec
> shapes
) {
495 for (size_t i
= 0; i
< 10; ++i
) {
496 // Check the shape to ensure it did not get collected.
497 char letter
= 'a' + i
;
499 ShapePropertyIter
<NoGC
> iter(shapes
[i
]);
500 if (!JS_StringEqualsAscii(cx
, iter
->key().toString(), &letter
, 1, &match
)) {
508 // Ensure iterator enumeration works through the handle.
509 for (auto shape
: shapes
) {
518 BEGIN_TEST(testGCHandleVector
) {
519 JS::Rooted
<ShapeVec
> vec(cx
, ShapeVec(cx
));
521 CHECK(FillVector(cx
, &vec
));
526 CHECK(CheckVector(cx
, vec
));
530 END_TEST(testGCHandleVector
)
535 void trace(JSTracer
*) {}
538 using FooVector
= JS::GCVector
<Foo
>;
540 BEGIN_TEST(testGCVectorEmplaceBack
) {
541 JS::Rooted
<FooVector
> vector(cx
, FooVector(cx
));
543 CHECK(vector
.emplaceBack(1, 2));
547 END_TEST(testGCVectorEmplaceBack
)
549 BEGIN_TEST(testRootedMaybeValue
) {
550 JS::Rooted
<Maybe
<Value
>> maybeNothing(cx
);
551 CHECK(maybeNothing
.isNothing());
552 CHECK(!maybeNothing
.isSome());
554 JS::Rooted
<Maybe
<Value
>> maybe(cx
, Some(UndefinedValue()));
555 CHECK(CheckConstOperations
<Rooted
<Maybe
<Value
>>&>(maybe
));
556 CHECK(CheckConstOperations
<Handle
<Maybe
<Value
>>>(maybe
));
557 CHECK(CheckConstOperations
<MutableHandle
<Maybe
<Value
>>>(&maybe
));
559 maybe
= Some(JS::TrueValue());
560 CHECK(CheckMutableOperations
<Rooted
<Maybe
<Value
>>&>(maybe
));
562 maybe
= Some(JS::TrueValue());
563 CHECK(CheckMutableOperations
<MutableHandle
<Maybe
<Value
>>>(&maybe
));
565 CHECK(JS::NothingHandleValue
.isNothing());
570 template <typename T
>
571 bool CheckConstOperations(T someUndefinedValue
) {
572 CHECK(someUndefinedValue
.isSome());
573 CHECK(someUndefinedValue
.value().isUndefined());
574 CHECK(someUndefinedValue
->isUndefined());
575 CHECK((*someUndefinedValue
).isUndefined());
579 template <typename T
>
580 bool CheckMutableOperations(T maybe
) {
581 CHECK(maybe
->isTrue());
583 *maybe
= JS::FalseValue();
584 CHECK(maybe
->isFalse());
587 CHECK(maybe
.isNothing());
592 END_TEST(testRootedMaybeValue
)
595 struct OtherTestErr
{};
597 struct SimpleTraceable
{
598 // I'm using plain objects rather than Heap<T> because Heap<T> would get
599 // traced via the store buffer. Heap<T> would be a more realistic example,
600 // but would require compaction to test for tracing.
604 void trace(JSTracer
* trc
) {
605 TraceRoot(trc
, &obj
, "obj");
606 TraceRoot(trc
, &val
, "val");
612 struct GCPolicy
<TestErr
> : public IgnoreGCPolicy
<TestErr
> {};
615 BEGIN_TEST_WITH_ATTRIBUTES(testGCRootedResult
, JS_EXPECT_HAZARDS
) {
616 AutoLeaveZeal
noZeal(cx
);
618 JSObject
* unrootedObj
= JS_NewPlainObject(cx
);
619 CHECK(js::gc::IsInsideNursery(unrootedObj
));
620 Value unrootedVal
= ObjectValue(*unrootedObj
);
622 RootedObject
obj(cx
, unrootedObj
);
623 RootedValue
val(cx
, unrootedVal
);
625 Result
<Value
, TestErr
> unrootedValerr(val
);
626 Rooted
<Result
<Value
, TestErr
>> valerr(cx
, val
);
628 Result
<mozilla::Ok
, Value
> unrootedOkval(val
);
629 Rooted
<Result
<mozilla::Ok
, Value
>> okval(cx
, val
);
631 Result
<mozilla::Ok
, TestErr
> simple
{mozilla::Ok()};
633 Result
<Value
, JSObject
*> unrootedValobj1(val
);
634 Rooted
<Result
<Value
, JSObject
*>> valobj1(cx
, val
);
635 Result
<Value
, JSObject
*> unrootedValobj2(obj
);
636 Rooted
<Result
<Value
, JSObject
*>> valobj2(cx
, obj
);
638 // Test nested traceable structures.
639 Result
<mozilla::Maybe
<mozilla::Ok
>, JSObject
*> maybeobj(
640 mozilla::Some(mozilla::Ok()));
641 Rooted
<Result
<mozilla::Maybe
<mozilla::Ok
>, JSObject
*>> rooted_maybeobj(
642 cx
, mozilla::Some(mozilla::Ok()));
644 // This would fail to compile because Result<> deletes its copy constructor,
645 // which prevents updating after tracing:
647 // Rooted<Result<Result<mozilla::Ok, JS::Value>, JSObject*>>
649 // But this should be fine when no tracing is required.
650 Result
<Result
<mozilla::Ok
, int>, double> dummy(3.4);
652 // One thing I didn't realize initially about Result<>: unwrap() takes
653 // ownership of a value. In the case of Result<Maybe>, that means the
654 // contained Maybe is reset to Nothing.
655 Result
<mozilla::Maybe
<int>, int> confusing(mozilla::Some(7));
656 CHECK(confusing
.unwrap().isSome());
657 CHECK(!confusing
.unwrap().isSome());
659 Result
<mozilla::Maybe
<JS::Value
>, JSObject
*> maybevalobj(
660 mozilla::Some(val
.get()));
661 Rooted
<Result
<mozilla::Maybe
<JS::Value
>, JSObject
*>> rooted_maybevalobj(
662 cx
, mozilla::Some(val
.get()));
664 // Custom types that haven't had GCPolicy explicitly specialized.
665 SimpleTraceable s1
{obj
, val
};
666 Result
<SimpleTraceable
, TestErr
> custom(s1
);
667 SimpleTraceable s2
{obj
, val
};
668 Rooted
<Result
<SimpleTraceable
, TestErr
>> rootedCustom(cx
, s2
);
670 CHECK(obj
== unrootedObj
);
671 CHECK(val
== unrootedVal
);
672 CHECK(simple
.isOk());
673 CHECK(unrootedValerr
.inspect() == unrootedVal
);
674 CHECK(valerr
.get().inspect() == val
);
675 CHECK(unrootedOkval
.inspectErr() == unrootedVal
);
676 CHECK(okval
.get().inspectErr() == val
);
677 CHECK(unrootedValobj1
.inspect() == unrootedVal
);
678 CHECK(valobj1
.get().inspect() == val
);
679 CHECK(unrootedValobj2
.inspectErr() == unrootedObj
);
680 CHECK(valobj2
.get().inspectErr() == obj
);
681 CHECK(*maybevalobj
.inspect() == unrootedVal
);
682 CHECK(*rooted_maybevalobj
.get().inspect() == val
);
683 CHECK(custom
.inspect().obj
== unrootedObj
);
684 CHECK(custom
.inspect().val
== unrootedVal
);
685 CHECK(rootedCustom
.get().inspect().obj
== obj
);
686 CHECK(rootedCustom
.get().inspect().val
== val
);
690 CHECK(obj
!= unrootedObj
);
691 CHECK(val
!= unrootedVal
);
692 CHECK(unrootedValerr
.inspect() == unrootedVal
);
693 CHECK(valerr
.get().inspect() == val
);
694 CHECK(unrootedOkval
.inspectErr() == unrootedVal
);
695 CHECK(okval
.get().inspectErr() == val
);
696 CHECK(unrootedValobj1
.inspect() == unrootedVal
);
697 CHECK(valobj1
.get().inspect() == val
);
698 CHECK(unrootedValobj2
.inspectErr() == unrootedObj
);
699 CHECK(valobj2
.get().inspectErr() == obj
);
700 CHECK(*maybevalobj
.inspect() == unrootedVal
);
701 CHECK(*rooted_maybevalobj
.get().inspect() == val
);
702 MOZ_ASSERT(custom
.inspect().obj
== unrootedObj
);
703 CHECK(custom
.inspect().obj
== unrootedObj
);
704 CHECK(custom
.inspect().val
== unrootedVal
);
705 CHECK(rootedCustom
.get().inspect().obj
== obj
);
706 CHECK(rootedCustom
.get().inspect().val
== val
);
708 mozilla::Result
<OtherTestErr
, mozilla::Ok
> r(mozilla::Ok
{});
713 END_TEST(testGCRootedResult
)
715 static int copies
= 0;
717 struct DontCopyMe_Variant
{
719 explicit DontCopyMe_Variant(JSObject
* objArg
) : obj(objArg
) {}
720 DontCopyMe_Variant(const DontCopyMe_Variant
& other
) : obj(other
.obj
) {
723 DontCopyMe_Variant(DontCopyMe_Variant
&& other
) : obj(std::move(other
.obj
)) {
726 void trace(JSTracer
* trc
) { TraceRoot(trc
, &obj
, "obj"); }
729 enum struct TestUnusedZeroEnum
: int16_t { Ok
= 0, NotOk
= 1 };
731 namespace mozilla::detail
{
733 struct UnusedZero
<TestUnusedZeroEnum
> : UnusedZeroEnum
<TestUnusedZeroEnum
> {};
734 } // namespace mozilla::detail
738 struct GCPolicy
<TestUnusedZeroEnum
>
739 : public IgnoreGCPolicy
<TestUnusedZeroEnum
> {};
742 struct DontCopyMe_NullIsOk
{
744 DontCopyMe_NullIsOk() : val(UndefinedValue()) {}
745 explicit DontCopyMe_NullIsOk(const JS::Value
& valArg
) : val(valArg
) {}
746 DontCopyMe_NullIsOk(const DontCopyMe_NullIsOk
& other
) = delete;
747 DontCopyMe_NullIsOk(DontCopyMe_NullIsOk
&& other
)
748 : val(std::move(other
.val
)) {}
749 DontCopyMe_NullIsOk
& operator=(DontCopyMe_NullIsOk
&& other
) {
750 val
= std::move(other
.val
);
751 other
.val
= UndefinedValue();
754 void trace(JSTracer
* trc
) { TraceRoot(trc
, &val
, "val"); }
759 namespace mozilla::detail
{
761 struct UnusedZero
<Failed
> {
762 using StorageType
= uintptr_t;
764 static constexpr bool value
= true;
765 static constexpr StorageType nullValue
= 0;
766 static constexpr StorageType
GetDefaultValue() { return 2; }
768 static constexpr void AssertValid(StorageType aValue
) {}
769 static constexpr Failed
Inspect(const StorageType
& aValue
) {
772 static constexpr Failed
Unwrap(StorageType aValue
) { return Failed
{}; }
773 static constexpr StorageType
Store(Failed aValue
) {
774 return GetDefaultValue();
777 } // namespace mozilla::detail
781 struct GCPolicy
<Failed
> : public IgnoreGCPolicy
<Failed
> {};
784 struct TriviallyCopyable_LowBitTagIsError
{
786 TriviallyCopyable_LowBitTagIsError() : obj(nullptr) {}
787 explicit TriviallyCopyable_LowBitTagIsError(JSObject
* objArg
) : obj(objArg
) {}
788 TriviallyCopyable_LowBitTagIsError(
789 const TriviallyCopyable_LowBitTagIsError
& other
) = default;
790 void trace(JSTracer
* trc
) { TraceRoot(trc
, &obj
, "obj"); }
793 namespace mozilla::detail
{
795 struct HasFreeLSB
<TriviallyCopyable_LowBitTagIsError
> : HasFreeLSB
<JSObject
*> {
797 } // namespace mozilla::detail
799 BEGIN_TEST_WITH_ATTRIBUTES(testRootedResultCtors
, JS_EXPECT_HAZARDS
) {
800 JSObject
* unrootedObj
= JS_NewPlainObject(cx
);
802 Rooted
<JSObject
*> obj(cx
, unrootedObj
);
804 using mozilla::detail::PackingStrategy
;
806 static_assert(Result
<DontCopyMe_Variant
, TestErr
>::Strategy
==
807 PackingStrategy::Variant
);
808 Rooted
<Result
<DontCopyMe_Variant
, TestErr
>> vv(cx
, DontCopyMe_Variant
{obj
});
809 static_assert(Result
<mozilla::Ok
, DontCopyMe_Variant
>::Strategy
==
810 PackingStrategy::Variant
);
811 Rooted
<Result
<mozilla::Ok
, DontCopyMe_Variant
>> ve(cx
,
812 DontCopyMe_Variant
{obj
});
814 static_assert(Result
<DontCopyMe_NullIsOk
, TestUnusedZeroEnum
>::Strategy
==
815 PackingStrategy::NullIsOk
);
816 Rooted
<Result
<DontCopyMe_NullIsOk
, TestUnusedZeroEnum
>> nv(
817 cx
, DontCopyMe_NullIsOk
{JS::ObjectValue(*obj
)});
819 static_assert(Result
<TriviallyCopyable_LowBitTagIsError
, Failed
>::Strategy
==
820 PackingStrategy::LowBitTagIsError
);
821 Rooted
<Result
<TriviallyCopyable_LowBitTagIsError
, Failed
>> lv(
822 cx
, TriviallyCopyable_LowBitTagIsError
{obj
});
824 CHECK(obj
== unrootedObj
);
826 CHECK(vv
.get().inspect().obj
== obj
);
827 CHECK(ve
.get().inspectErr().obj
== obj
);
828 CHECK(nv
.get().inspect().val
.toObjectOrNull() == obj
);
829 CHECK(lv
.get().inspect().obj
== obj
);
832 CHECK(obj
!= unrootedObj
);
834 CHECK(vv
.get().inspect().obj
== obj
);
835 CHECK(ve
.get().inspectErr().obj
== obj
);
836 CHECK(nv
.get().inspect().val
.toObjectOrNull() == obj
);
837 CHECK(lv
.get().inspect().obj
== obj
);
841 END_TEST(testRootedResultCtors
)
843 #if defined(HAVE_64BIT_BUILD) && !defined(XP_WIN)
845 // This depends on a pointer fitting in 48 bits, leaving space for an empty
846 // struct and a bool in a packed struct. Windows doesn't seem to do this
847 // packing, so we'll skip this test here. We're primarily checking whether
848 // copy constructors get called, which should be cross-platform, and
849 // secondarily making sure that the Rooted/tracing stuff is compiled and
850 // executed properly. There are certainly more clever ways to do this that
851 // would work cross-platform, but it doesn't seem worth the bother right now.
853 struct __attribute__((packed
)) DontCopyMe_PackedVariant
{
855 static JSObject
* Unwrap(uintptr_t packed
) {
856 return reinterpret_cast<JSObject
*>(packed
);
858 static uintptr_t Store(JSObject
* obj
) {
859 return reinterpret_cast<uintptr_t>(obj
);
862 DontCopyMe_PackedVariant() : obj(0) {}
863 explicit DontCopyMe_PackedVariant(JSObject
* objArg
)
864 : obj(reinterpret_cast<uintptr_t>(objArg
)) {}
865 DontCopyMe_PackedVariant(const DontCopyMe_PackedVariant
& other
)
869 DontCopyMe_PackedVariant(DontCopyMe_PackedVariant
&& other
) : obj(other
.obj
) {
872 void trace(JSTracer
* trc
) {
873 JSObject
* realObj
= Unwrap(obj
);
874 TraceRoot(trc
, &realObj
, "obj");
875 obj
= Store(realObj
);
879 static_assert(std::is_default_constructible_v
<DontCopyMe_PackedVariant
>);
880 static_assert(std::is_default_constructible_v
<TestErr
>);
881 static_assert(mozilla::detail::IsPackableVariant
<DontCopyMe_PackedVariant
,
884 BEGIN_TEST_WITH_ATTRIBUTES(testResultPackedVariant
, JS_EXPECT_HAZARDS
) {
885 JSObject
* unrootedObj
= JS_NewPlainObject(cx
);
887 Rooted
<JSObject
*> obj(cx
, unrootedObj
);
889 using mozilla::detail::PackingStrategy
;
891 static_assert(Result
<DontCopyMe_PackedVariant
, TestErr
>::Strategy
==
892 PackingStrategy::PackedVariant
);
893 Rooted
<Result
<DontCopyMe_PackedVariant
, TestErr
>> pv(
894 cx
, DontCopyMe_PackedVariant
{obj
});
895 static_assert(Result
<mozilla::Ok
, DontCopyMe_PackedVariant
>::Strategy
==
896 PackingStrategy::PackedVariant
);
897 Rooted
<Result
<mozilla::Ok
, DontCopyMe_PackedVariant
>> pe(
898 cx
, DontCopyMe_PackedVariant
{obj
});
900 CHECK(obj
== unrootedObj
);
902 CHECK(DontCopyMe_PackedVariant::Unwrap(pv
.get().inspect().obj
) == obj
);
903 CHECK(DontCopyMe_PackedVariant::Unwrap(pe
.get().inspectErr().obj
) == obj
);
906 CHECK(obj
!= unrootedObj
);
908 CHECK(DontCopyMe_PackedVariant::Unwrap(pv
.get().inspect().obj
) == obj
);
909 CHECK(DontCopyMe_PackedVariant::Unwrap(pe
.get().inspectErr().obj
) == obj
);
913 END_TEST(testResultPackedVariant
)
915 #endif // HAVE_64BIT_BUILD