Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / js / src / jsapi-tests / testGCExactRooting.cpp
blobd53e293f23e1de2563c7e586b3683fd95d7344c0
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:
3 */
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"
21 using namespace js;
23 using mozilla::Maybe;
24 using mozilla::Some;
26 BEGIN_TEST(testGCExactRooting) {
27 JS::RootedObject rootCx(cx, JS_NewPlainObject(cx));
29 JS_GC(cx);
31 /* Use the objects we just created to ensure that they are still alive. */
32 JS_DefineProperty(cx, rootCx, "foo", JS::UndefinedHandleValue, 0);
34 return true;
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);
47 return true;
49 END_TEST(testGCSuppressions)
51 struct MyContainer {
52 int whichConstructor;
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() {
60 whichConstructor = 4;
62 MyContainer(const MyContainer& rhs)
63 : whichConstructor(100 + rhs.whichConstructor),
64 obj(rhs.obj),
65 str(rhs.str) {}
66 void trace(JSTracer* trc) {
67 js::TraceNullableEdge(trc, &obj, "test container obj");
68 js::TraceNullableEdge(trc, &str, "test container str");
72 struct MyNonCopyableContainer {
73 int whichConstructor;
74 HeapPtr<JSObject*> obj;
75 HeapPtr<JSString*> str;
77 MyNonCopyableContainer() : whichConstructor(1), obj(nullptr), str(nullptr) {}
78 explicit MyNonCopyableContainer(double) : MyNonCopyableContainer() {
79 whichConstructor = 2;
81 explicit MyNonCopyableContainer(JSContext* cx) : MyNonCopyableContainer() {
82 whichConstructor = 3;
84 explicit MyNonCopyableContainer(JSContext* cx, JSContext* cx2, JSContext* cx3)
85 : MyNonCopyableContainer() {
86 whichConstructor = 4;
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");
98 namespace js {
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; }
103 int constructor() {
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; }
112 int constructor() {
113 return static_cast<Wrapper*>(this)->get().whichConstructor;
116 } // namespace js
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");
152 JS_GC(cx);
153 JS_GC(cx);
155 JS::RootedObject obj(cx, container.obj());
156 JS::RootedValue val(cx, StringValue(container.str()));
157 CHECK(JS_SetProperty(cx, obj, "foo", val));
158 obj = nullptr;
159 val = UndefinedValue();
162 JS::RootedString actual(cx);
163 bool same;
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;
196 obj = heap.obj();
197 CHECK(JS_GetProperty(cx, obj, "foo", &val));
198 actual = val.toString();
199 CHECK(JS_StringEqualsLiteral(cx, actual, "Hello", &same));
200 CHECK(same);
201 obj = nullptr;
202 actual = nullptr;
204 JS_GC(cx);
205 JS_GC(cx);
207 obj = heap.obj();
208 CHECK(JS_GetProperty(cx, obj, "foo", &val));
209 actual = val.toString();
210 CHECK(JS_StringEqualsLiteral(cx, actual, "Hello", &same));
211 CHECK(same);
212 obj = nullptr;
213 actual = nullptr;
216 return true;
218 END_TEST(testGCRootedStaticStructInternalStackStorageAugmented)
220 static JS::PersistentRooted<JSObject*> sLongLived;
221 BEGIN_TEST(testGCPersistentRootedOutlivesRuntime) {
222 sLongLived.init(cx, JS_NewObject(cx, nullptr));
223 CHECK(sLongLived);
224 return true;
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.
241 sContainer.reset();
243 return true;
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
256 // new shape.
257 char buffer[2];
258 buffer[0] = 'a' + i;
259 buffer[1] = '\0';
260 CHECK(JS_SetProperty(cx, obj, buffer, val));
261 CHECK(map.putNew(obj->shape(), obj));
264 JS_GC(cx);
265 JS_GC(cx);
267 for (auto r = map.all(); !r.empty(); r.popFront()) {
268 RootedObject obj(cx, r.front().value());
269 CHECK(obj->shape() == r.front().key());
272 return true;
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
286 // new shape.
287 char buffer[2];
288 buffer[0] = 'a' + i;
289 buffer[1] = '\0';
290 CHECK(JS_SetProperty(cx, obj, buffer, val));
291 CHECK(map.putNew(obj->shape(), obj));
294 JS_GC(cx);
296 // Access map to keep it live across the GC.
297 CHECK(map.count() == 10);
299 return true;
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
305 // it's ok.
306 js::GCHashMap<uint64_t, uint64_t> map(cx, 15);
308 JS_GC(cx);
309 CHECK(map.putNew(12, 13));
311 return true;
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
320 // new shape.
321 char buffer[2];
322 buffer[0] = 'a' + i;
323 buffer[1] = '\0';
324 if (!JS_SetProperty(cx, obj, buffer, val)) {
325 return false;
327 if (!map.putNew(obj->shape(), obj)) {
328 return false;
331 return true;
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()) {
338 return false;
341 return true;
344 BEGIN_TEST(testGCHandleHashMap) {
345 JS::Rooted<MyHashMap> map(cx, MyHashMap(cx, 15));
347 CHECK(FillMyHashMap(cx, &map));
349 JS_GC(cx);
350 JS_GC(cx);
352 CHECK(CheckMyHashMap(cx, map));
354 return true;
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
367 // new shape.
368 char buffer[2];
369 buffer[0] = 'a' + i;
370 buffer[1] = '\0';
371 CHECK(JS_SetProperty(cx, obj, buffer, val));
372 CHECK(shapes.append(obj->as<NativeObject>().shape()));
375 JS_GC(cx);
376 JS_GC(cx);
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;
381 bool match;
382 ShapePropertyIter<NoGC> iter(shapes[i]);
383 CHECK(JS_StringEqualsAscii(cx, iter->key().toString(), &letter, 1, &match));
384 CHECK(match);
387 // Ensure iterator enumeration works through the rooted.
388 for (auto shape : shapes) {
389 CHECK(shape);
392 CHECK(receiveConstRefToShapeVector(shapes));
394 // Ensure rooted converts to handles.
395 CHECK(receiveHandleToShapeVector(shapes));
396 CHECK(receiveMutableHandleToShapeVector(&shapes));
398 return true;
401 bool receiveConstRefToShapeVector(
402 const JS::Rooted<GCVector<NativeShape*>>& rooted) {
403 // Ensure range enumeration works through the reference.
404 for (auto shape : rooted) {
405 CHECK(shape);
407 return true;
410 bool receiveHandleToShapeVector(JS::Handle<GCVector<NativeShape*>> handle) {
411 // Ensure range enumeration works through the handle.
412 for (auto shape : handle) {
413 CHECK(shape);
415 return true;
418 bool receiveMutableHandleToShapeVector(
419 JS::MutableHandle<GCVector<NativeShape*>> handle) {
420 // Ensure range enumeration works through the handle.
421 for (auto shape : handle) {
422 CHECK(shape);
424 return true;
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
437 // new shape.
438 char buffer[2];
439 buffer[0] = 'a' + i;
440 buffer[1] = '\0';
441 CHECK(JS_SetProperty(cx, obj, buffer, val));
442 CHECK(shapes.pushBack(obj->as<NativeObject>().shape()));
445 CHECK(shapes.length() == 10);
447 JS_GC(cx);
448 JS_GC(cx);
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;
453 bool match;
454 ShapePropertyIter<NoGC> iter(shapes.front());
455 CHECK(JS_StringEqualsAscii(cx, iter->key().toString(), &letter, 1, &match));
456 CHECK(match);
457 shapes.popFront();
460 CHECK(shapes.empty());
461 return true;
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
472 // new shape.
473 char buffer[2];
474 buffer[0] = 'a' + i;
475 buffer[1] = '\0';
476 if (!JS_SetProperty(cx, obj, buffer, val)) {
477 return false;
479 if (!shapes.append(obj->as<NativeObject>().shape())) {
480 return false;
484 // Ensure iterator enumeration works through the mutable handle.
485 for (auto shape : shapes) {
486 if (!shape) {
487 return false;
491 return true;
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;
498 bool match;
499 ShapePropertyIter<NoGC> iter(shapes[i]);
500 if (!JS_StringEqualsAscii(cx, iter->key().toString(), &letter, 1, &match)) {
501 return false;
503 if (!match) {
504 return false;
508 // Ensure iterator enumeration works through the handle.
509 for (auto shape : shapes) {
510 if (!shape) {
511 return false;
515 return true;
518 BEGIN_TEST(testGCHandleVector) {
519 JS::Rooted<ShapeVec> vec(cx, ShapeVec(cx));
521 CHECK(FillVector(cx, &vec));
523 JS_GC(cx);
524 JS_GC(cx);
526 CHECK(CheckVector(cx, vec));
528 return true;
530 END_TEST(testGCHandleVector)
532 class Foo {
533 public:
534 Foo(int, int) {}
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));
545 return true;
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());
567 return true;
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());
576 return true;
579 template <typename T>
580 bool CheckMutableOperations(T maybe) {
581 CHECK(maybe->isTrue());
583 *maybe = JS::FalseValue();
584 CHECK(maybe->isFalse());
586 maybe.reset();
587 CHECK(maybe.isNothing());
589 return true;
592 END_TEST(testRootedMaybeValue)
594 struct TestErr {};
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.
601 JSObject* obj;
602 JS::Value val;
604 void trace(JSTracer* trc) {
605 TraceRoot(trc, &obj, "obj");
606 TraceRoot(trc, &val, "val");
610 namespace JS {
611 template <>
612 struct GCPolicy<TestErr> : public IgnoreGCPolicy<TestErr> {};
613 } // namespace JS
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);
688 JS_GC(cx);
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{});
709 (void)r;
711 return true;
713 END_TEST(testGCRootedResult)
715 static int copies = 0;
717 struct DontCopyMe_Variant {
718 JSObject* obj;
719 explicit DontCopyMe_Variant(JSObject* objArg) : obj(objArg) {}
720 DontCopyMe_Variant(const DontCopyMe_Variant& other) : obj(other.obj) {
721 copies++;
723 DontCopyMe_Variant(DontCopyMe_Variant&& other) : obj(std::move(other.obj)) {
724 other.obj = nullptr;
726 void trace(JSTracer* trc) { TraceRoot(trc, &obj, "obj"); }
729 enum struct TestUnusedZeroEnum : int16_t { Ok = 0, NotOk = 1 };
731 namespace mozilla::detail {
732 template <>
733 struct UnusedZero<TestUnusedZeroEnum> : UnusedZeroEnum<TestUnusedZeroEnum> {};
734 } // namespace mozilla::detail
736 namespace JS {
737 template <>
738 struct GCPolicy<TestUnusedZeroEnum>
739 : public IgnoreGCPolicy<TestUnusedZeroEnum> {};
740 } // namespace JS
742 struct DontCopyMe_NullIsOk {
743 JS::Value val;
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();
752 return *this;
754 void trace(JSTracer* trc) { TraceRoot(trc, &val, "val"); }
757 struct Failed {};
759 namespace mozilla::detail {
760 template <>
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) {
770 return Failed{};
772 static constexpr Failed Unwrap(StorageType aValue) { return Failed{}; }
773 static constexpr StorageType Store(Failed aValue) {
774 return GetDefaultValue();
777 } // namespace mozilla::detail
779 namespace JS {
780 template <>
781 struct GCPolicy<Failed> : public IgnoreGCPolicy<Failed> {};
782 } // namespace JS
784 struct TriviallyCopyable_LowBitTagIsError {
785 JSObject* obj;
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 {
794 template <>
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);
801 CHECK(unrootedObj);
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);
831 JS_GC(cx);
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);
838 CHECK(copies == 0);
839 return true;
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 {
854 uintptr_t obj : 48;
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)
866 : obj(other.obj) {
867 copies++;
869 DontCopyMe_PackedVariant(DontCopyMe_PackedVariant&& other) : obj(other.obj) {
870 other.obj = 0;
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,
882 TestErr>::value);
884 BEGIN_TEST_WITH_ATTRIBUTES(testResultPackedVariant, JS_EXPECT_HAZARDS) {
885 JSObject* unrootedObj = JS_NewPlainObject(cx);
886 CHECK(unrootedObj);
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);
905 JS_GC(cx);
906 CHECK(obj != unrootedObj);
908 CHECK(DontCopyMe_PackedVariant::Unwrap(pv.get().inspect().obj) == obj);
909 CHECK(DontCopyMe_PackedVariant::Unwrap(pe.get().inspectErr().obj) == obj);
911 return true;
913 END_TEST(testResultPackedVariant)
915 #endif // HAVE_64BIT_BUILD