testGCExactRooting.cpp (32504B)
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/. */ 7 8 #include "mozilla/Maybe.h" 9 #include "mozilla/Result.h" 10 #include "mozilla/ResultVariant.h" 11 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" 18 19 #include "jsapi-tests/tests.h" 20 21 using namespace js; 22 23 using mozilla::Maybe; 24 using mozilla::Some; 25 26 BEGIN_TEST(testGCExactRooting) { 27 JS::RootedObject rootCx(cx, JS_NewPlainObject(cx)); 28 29 JS_GC(cx); 30 31 /* Use the objects we just created to ensure that they are still alive. */ 32 JS_DefineProperty(cx, rootCx, "foo", JS::UndefinedHandleValue, 0); 33 34 return true; 35 } 36 END_TEST(testGCExactRooting) 37 38 BEGIN_TEST(testGCSuppressions) { 39 JS::AutoAssertNoGC nogc; 40 JS::AutoCheckCannotGC checkgc; 41 JS::AutoSuppressGCAnalysis noanalysis; 42 43 JS::AutoAssertNoGC nogcCx(cx); 44 JS::AutoCheckCannotGC checkgcCx(cx); 45 JS::AutoSuppressGCAnalysis noanalysisCx(cx); 46 47 return true; 48 } 49 END_TEST(testGCSuppressions) 50 51 struct MyContainer { 52 int whichConstructor; 53 HeapPtr<JSObject*> obj; 54 HeapPtr<JSString*> str; 55 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; 61 } 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"); 69 } 70 }; 71 72 struct MyNonCopyableContainer { 73 int whichConstructor; 74 HeapPtr<JSObject*> obj; 75 HeapPtr<JSString*> str; 76 77 MyNonCopyableContainer() : whichConstructor(1), obj(nullptr), str(nullptr) {} 78 explicit MyNonCopyableContainer(double) : MyNonCopyableContainer() { 79 whichConstructor = 2; 80 } 81 explicit MyNonCopyableContainer(JSContext* cx) : MyNonCopyableContainer() { 82 whichConstructor = 3; 83 } 84 explicit MyNonCopyableContainer(JSContext* cx, JSContext* cx2, JSContext* cx3) 85 : MyNonCopyableContainer() { 86 whichConstructor = 4; 87 } 88 89 MyNonCopyableContainer(const MyNonCopyableContainer&) = delete; 90 MyNonCopyableContainer& operator=(const MyNonCopyableContainer&) = delete; 91 92 void trace(JSTracer* trc) { 93 js::TraceNullableEdge(trc, &obj, "test container obj"); 94 js::TraceNullableEdge(trc, &str, "test container str"); 95 } 96 }; 97 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; 105 } 106 }; 107 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; 114 } 115 }; 116 } // namespace js 117 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); 125 126 JS::Rooted<Value> rv(cx); 127 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) 133 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); 141 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) 147 148 JS::Rooted<MyContainer> container(cx); 149 container.obj() = JS_NewObject(cx, nullptr); 150 container.str() = JS_NewStringCopyZ(cx, "Hello"); 151 152 JS_GC(cx); 153 JS_GC(cx); 154 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(); 160 161 { 162 JS::RootedString actual(cx); 163 bool same; 164 165 // Automatic move from stack to heap. 166 JS::PersistentRooted<MyContainer> heap(cx, container); 167 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); 173 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) 178 179 // Construct uncopyable type in place. 180 JS::PersistentRooted<MyNonCopyableContainer> ncp1(cx); 181 JS::PersistentRooted<MyNonCopyableContainer> ncp2(cx, 7.8); 182 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); 186 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) 191 192 // clear prior rooting. 193 container.obj() = nullptr; 194 container.str() = nullptr; 195 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; 203 204 JS_GC(cx); 205 JS_GC(cx); 206 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; 214 } 215 216 return true; 217 } 218 END_TEST(testGCRootedStaticStructInternalStackStorageAugmented) 219 220 MOZ_RUNINIT static JS::PersistentRooted<JSObject*> sLongLived; 221 BEGIN_TEST(testGCPersistentRootedOutlivesRuntime) { 222 sLongLived.init(cx, JS_NewObject(cx, nullptr)); 223 CHECK(sLongLived); 224 return true; 225 } 226 END_TEST(testGCPersistentRootedOutlivesRuntime) 227 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 MOZ_RUNINIT 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); 238 239 // Commenting the following line will trigger an assertion that the 240 // PersistentRooted outlives the runtime it is attached to. 241 sContainer.reset(); 242 243 return true; 244 } 245 END_TEST(testGCPersistentRootedTraceableCannotOutliveRuntime) 246 247 using MyHashMap = js::GCHashMap<js::Shape*, JSObject*>; 248 249 BEGIN_TEST(testGCRootedHashMap) { 250 JS::Rooted<MyHashMap> map(cx, MyHashMap(cx, 15)); 251 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)); 262 } 263 264 JS_GC(cx); 265 JS_GC(cx); 266 267 for (auto r = map.all(); !r.empty(); r.popFront()) { 268 RootedObject obj(cx, r.front().value()); 269 CHECK(obj->shape() == r.front().key()); 270 } 271 272 return true; 273 } 274 END_TEST(testGCRootedHashMap) 275 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 AutoLeaveZeal noZeal(cx); 281 282 MyHashMap map(cx, 15); 283 284 for (size_t i = 0; i < 10; ++i) { 285 RootedObject obj(cx, JS_NewObject(cx, nullptr)); 286 RootedValue val(cx, UndefinedValue()); 287 // Construct a unique property name to ensure that the object creates a 288 // new shape. 289 char buffer[2]; 290 buffer[0] = 'a' + i; 291 buffer[1] = '\0'; 292 CHECK(JS_SetProperty(cx, obj, buffer, val)); 293 CHECK(map.putNew(obj->shape(), obj)); 294 } 295 296 JS_GC(cx); 297 298 // Access map to keep it live across the GC. 299 CHECK(map.count() == 10); 300 301 return true; 302 } 303 END_TEST(testUnrootedGCHashMap) 304 305 BEGIN_TEST(testSafelyUnrootedGCHashMap) { 306 // This is not rooted, but it doesn't use GC pointers as keys or values so 307 // it's ok. 308 js::GCHashMap<uint64_t, uint64_t> map(cx, 15); 309 310 JS_GC(cx); 311 CHECK(map.putNew(12, 13)); 312 313 return true; 314 } 315 END_TEST(testSafelyUnrootedGCHashMap) 316 317 static bool FillMyHashMap(JSContext* cx, MutableHandle<MyHashMap> map) { 318 for (size_t i = 0; i < 10; ++i) { 319 RootedObject obj(cx, JS_NewObject(cx, nullptr)); 320 RootedValue val(cx, UndefinedValue()); 321 // Construct a unique property name to ensure that the object creates a 322 // new shape. 323 char buffer[2]; 324 buffer[0] = 'a' + i; 325 buffer[1] = '\0'; 326 if (!JS_SetProperty(cx, obj, buffer, val)) { 327 return false; 328 } 329 if (!map.putNew(obj->shape(), obj)) { 330 return false; 331 } 332 } 333 return true; 334 } 335 336 static bool CheckMyHashMap(JSContext* cx, Handle<MyHashMap> map) { 337 for (auto r = map.all(); !r.empty(); r.popFront()) { 338 RootedObject obj(cx, r.front().value()); 339 if (obj->shape() != r.front().key()) { 340 return false; 341 } 342 } 343 return true; 344 } 345 346 BEGIN_TEST(testGCHandleHashMap) { 347 JS::Rooted<MyHashMap> map(cx, MyHashMap(cx, 15)); 348 349 CHECK(FillMyHashMap(cx, &map)); 350 351 JS_GC(cx); 352 JS_GC(cx); 353 354 CHECK(CheckMyHashMap(cx, map)); 355 356 return true; 357 } 358 END_TEST(testGCHandleHashMap) 359 360 using ShapeVec = GCVector<NativeShape*>; 361 362 BEGIN_TEST(testGCRootedVector) { 363 JS::Rooted<ShapeVec> shapes(cx, cx); 364 365 for (size_t i = 0; i < 10; ++i) { 366 RootedObject obj(cx, JS_NewObject(cx, nullptr)); 367 RootedValue val(cx, UndefinedValue()); 368 // Construct a unique property name to ensure that the object creates a 369 // new shape. 370 char buffer[2]; 371 buffer[0] = 'a' + i; 372 buffer[1] = '\0'; 373 CHECK(JS_SetProperty(cx, obj, buffer, val)); 374 CHECK(shapes.append(obj->as<NativeObject>().shape())); 375 } 376 377 JS_GC(cx); 378 JS_GC(cx); 379 380 for (size_t i = 0; i < 10; ++i) { 381 // Check the shape to ensure it did not get collected. 382 char letter = 'a' + i; 383 bool match; 384 ShapePropertyIter<NoGC> iter(shapes[i]); 385 CHECK(JS_StringEqualsAscii(cx, iter->key().toString(), &letter, 1, &match)); 386 CHECK(match); 387 } 388 389 // Ensure iterator enumeration works through the rooted. 390 for (auto shape : shapes) { 391 CHECK(shape); 392 } 393 394 CHECK(receiveConstRefToShapeVector(shapes)); 395 396 // Ensure rooted converts to handles. 397 CHECK(receiveHandleToShapeVector(shapes)); 398 CHECK(receiveMutableHandleToShapeVector(&shapes)); 399 400 return true; 401 } 402 403 bool receiveConstRefToShapeVector( 404 const JS::Rooted<GCVector<NativeShape*>>& rooted) { 405 // Ensure range enumeration works through the reference. 406 for (auto shape : rooted) { 407 CHECK(shape); 408 } 409 return true; 410 } 411 412 bool receiveHandleToShapeVector(JS::Handle<GCVector<NativeShape*>> handle) { 413 // Ensure range enumeration works through the handle. 414 for (auto shape : handle) { 415 CHECK(shape); 416 } 417 return true; 418 } 419 420 bool receiveMutableHandleToShapeVector( 421 JS::MutableHandle<GCVector<NativeShape*>> handle) { 422 // Ensure range enumeration works through the handle. 423 for (auto shape : handle) { 424 CHECK(shape); 425 } 426 return true; 427 } 428 END_TEST(testGCRootedVector) 429 430 BEGIN_TEST(testTraceableFifo) { 431 using ShapeFifo = TraceableFifo<NativeShape*>; 432 JS::Rooted<ShapeFifo> shapes(cx, ShapeFifo(cx)); 433 CHECK(shapes.empty()); 434 435 for (size_t i = 0; i < 10; ++i) { 436 RootedObject obj(cx, JS_NewObject(cx, nullptr)); 437 RootedValue val(cx, UndefinedValue()); 438 // Construct a unique property name to ensure that the object creates a 439 // new shape. 440 char buffer[2]; 441 buffer[0] = 'a' + i; 442 buffer[1] = '\0'; 443 CHECK(JS_SetProperty(cx, obj, buffer, val)); 444 CHECK(shapes.pushBack(obj->as<NativeObject>().shape())); 445 } 446 447 CHECK(shapes.length() == 10); 448 449 JS_GC(cx); 450 JS_GC(cx); 451 452 for (size_t i = 0; i < 10; ++i) { 453 // Check the shape to ensure it did not get collected. 454 char letter = 'a' + i; 455 bool match; 456 ShapePropertyIter<NoGC> iter(shapes.front()); 457 CHECK(JS_StringEqualsAscii(cx, iter->key().toString(), &letter, 1, &match)); 458 CHECK(match); 459 shapes.popFront(); 460 } 461 462 CHECK(shapes.empty()); 463 return true; 464 } 465 END_TEST(testTraceableFifo) 466 467 using ShapeVec = GCVector<NativeShape*>; 468 469 static bool FillVector(JSContext* cx, MutableHandle<ShapeVec> shapes) { 470 for (size_t i = 0; i < 10; ++i) { 471 RootedObject obj(cx, JS_NewObject(cx, nullptr)); 472 RootedValue val(cx, UndefinedValue()); 473 // Construct a unique property name to ensure that the object creates a 474 // new shape. 475 char buffer[2]; 476 buffer[0] = 'a' + i; 477 buffer[1] = '\0'; 478 if (!JS_SetProperty(cx, obj, buffer, val)) { 479 return false; 480 } 481 if (!shapes.append(obj->as<NativeObject>().shape())) { 482 return false; 483 } 484 } 485 486 // Ensure iterator enumeration works through the mutable handle. 487 for (auto shape : shapes) { 488 if (!shape) { 489 return false; 490 } 491 } 492 493 return true; 494 } 495 496 static bool CheckVector(JSContext* cx, Handle<ShapeVec> shapes) { 497 for (size_t i = 0; i < 10; ++i) { 498 // Check the shape to ensure it did not get collected. 499 char letter = 'a' + i; 500 bool match; 501 ShapePropertyIter<NoGC> iter(shapes[i]); 502 if (!JS_StringEqualsAscii(cx, iter->key().toString(), &letter, 1, &match)) { 503 return false; 504 } 505 if (!match) { 506 return false; 507 } 508 } 509 510 // Ensure iterator enumeration works through the handle. 511 for (auto shape : shapes) { 512 if (!shape) { 513 return false; 514 } 515 } 516 517 return true; 518 } 519 520 BEGIN_TEST(testGCHandleVector) { 521 JS::Rooted<ShapeVec> vec(cx, ShapeVec(cx)); 522 523 CHECK(FillVector(cx, &vec)); 524 525 JS_GC(cx); 526 JS_GC(cx); 527 528 CHECK(CheckVector(cx, vec)); 529 530 return true; 531 } 532 END_TEST(testGCHandleVector) 533 534 class Foo { 535 public: 536 Foo(int, int) {} 537 void trace(JSTracer*) {} 538 }; 539 540 using FooVector = JS::GCVector<Foo>; 541 542 BEGIN_TEST(testGCVectorEmplaceBack) { 543 JS::Rooted<FooVector> vector(cx, FooVector(cx)); 544 545 CHECK(vector.emplaceBack(1, 2)); 546 547 return true; 548 } 549 END_TEST(testGCVectorEmplaceBack) 550 551 BEGIN_TEST(testRootedMaybeValue) { 552 JS::Rooted<Maybe<Value>> maybeNothing(cx); 553 CHECK(maybeNothing.isNothing()); 554 CHECK(!maybeNothing.isSome()); 555 556 JS::Rooted<Maybe<Value>> maybe(cx, Some(UndefinedValue())); 557 CHECK(CheckConstOperations<Rooted<Maybe<Value>>&>(maybe)); 558 CHECK(CheckConstOperations<Handle<Maybe<Value>>>(maybe)); 559 CHECK(CheckConstOperations<MutableHandle<Maybe<Value>>>(&maybe)); 560 561 maybe = Some(JS::TrueValue()); 562 CHECK(CheckMutableOperations<Rooted<Maybe<Value>>&>(maybe)); 563 564 maybe = Some(JS::TrueValue()); 565 CHECK(CheckMutableOperations<MutableHandle<Maybe<Value>>>(&maybe)); 566 567 CHECK(JS::NothingHandleValue.isNothing()); 568 569 return true; 570 } 571 572 template <typename T> 573 bool CheckConstOperations(T someUndefinedValue) { 574 CHECK(someUndefinedValue.isSome()); 575 CHECK(someUndefinedValue.value().isUndefined()); 576 CHECK(someUndefinedValue->isUndefined()); 577 CHECK((*someUndefinedValue).isUndefined()); 578 return true; 579 } 580 581 template <typename T> 582 bool CheckMutableOperations(T maybe) { 583 CHECK(maybe->isTrue()); 584 585 *maybe = JS::FalseValue(); 586 CHECK(maybe->isFalse()); 587 588 maybe.reset(); 589 CHECK(maybe.isNothing()); 590 591 return true; 592 } 593 594 END_TEST(testRootedMaybeValue) 595 596 struct TestErr {}; 597 struct OtherTestErr {}; 598 599 struct SimpleTraceable { 600 // I'm using plain objects rather than Heap<T> because Heap<T> would get 601 // traced via the store buffer. Heap<T> would be a more realistic example, 602 // but would require compaction to test for tracing. 603 JSObject* obj; 604 JS::Value val; 605 606 void trace(JSTracer* trc) { 607 TraceRoot(trc, &obj, "obj"); 608 TraceRoot(trc, &val, "val"); 609 } 610 }; 611 612 namespace JS { 613 template <> 614 struct GCPolicy<TestErr> : public IgnoreGCPolicy<TestErr> {}; 615 } // namespace JS 616 617 BEGIN_TEST_WITH_ATTRIBUTES(testGCRootedResult, JS_EXPECT_HAZARDS) { 618 AutoLeaveZeal noZeal(cx); 619 620 JSObject* unrootedObj = JS_NewPlainObject(cx); 621 CHECK(js::gc::IsInsideNursery(unrootedObj)); 622 Value unrootedVal = ObjectValue(*unrootedObj); 623 624 RootedObject obj(cx, unrootedObj); 625 RootedValue val(cx, unrootedVal); 626 627 Result<Value, TestErr> unrootedValerr(val); 628 Rooted<Result<Value, TestErr>> valerr(cx, val); 629 630 Result<mozilla::Ok, Value> unrootedOkval(val); 631 Rooted<Result<mozilla::Ok, Value>> okval(cx, val); 632 633 Result<mozilla::Ok, TestErr> simple{mozilla::Ok()}; 634 635 Result<Value, JSObject*> unrootedValobj1(val); 636 Rooted<Result<Value, JSObject*>> valobj1(cx, val); 637 Result<Value, JSObject*> unrootedValobj2(obj); 638 Rooted<Result<Value, JSObject*>> valobj2(cx, obj); 639 640 // Test nested traceable structures. 641 Result<mozilla::Maybe<mozilla::Ok>, JSObject*> maybeobj( 642 mozilla::Some(mozilla::Ok())); 643 Rooted<Result<mozilla::Maybe<mozilla::Ok>, JSObject*>> rooted_maybeobj( 644 cx, mozilla::Some(mozilla::Ok())); 645 646 // This would fail to compile because Result<> deletes its copy constructor, 647 // which prevents updating after tracing: 648 // 649 // Rooted<Result<Result<mozilla::Ok, JS::Value>, JSObject*>> 650 651 // But this should be fine when no tracing is required. 652 Result<Result<mozilla::Ok, int>, double> dummy(3.4); 653 654 // One thing I didn't realize initially about Result<>: unwrap() takes 655 // ownership of a value. In the case of Result<Maybe>, that means the 656 // contained Maybe is reset to Nothing. 657 Result<mozilla::Maybe<int>, int> confusing(mozilla::Some(7)); 658 CHECK(confusing.unwrap().isSome()); 659 CHECK(!confusing.unwrap().isSome()); 660 661 Result<mozilla::Maybe<JS::Value>, JSObject*> maybevalobj( 662 mozilla::Some(val.get())); 663 Rooted<Result<mozilla::Maybe<JS::Value>, JSObject*>> rooted_maybevalobj( 664 cx, mozilla::Some(val.get())); 665 666 // Custom types that haven't had GCPolicy explicitly specialized. 667 SimpleTraceable s1{obj, val}; 668 Result<SimpleTraceable, TestErr> custom(s1); 669 SimpleTraceable s2{obj, val}; 670 Rooted<Result<SimpleTraceable, TestErr>> rootedCustom(cx, s2); 671 672 CHECK(obj == unrootedObj); 673 CHECK(val == unrootedVal); 674 CHECK(simple.isOk()); 675 CHECK(unrootedValerr.inspect() == unrootedVal); 676 CHECK(valerr.get().inspect() == val); 677 CHECK(unrootedOkval.inspectErr() == unrootedVal); 678 CHECK(okval.get().inspectErr() == val); 679 CHECK(unrootedValobj1.inspect() == unrootedVal); 680 CHECK(valobj1.get().inspect() == val); 681 CHECK(unrootedValobj2.inspectErr() == unrootedObj); 682 CHECK(valobj2.get().inspectErr() == obj); 683 CHECK(*maybevalobj.inspect() == unrootedVal); 684 CHECK(*rooted_maybevalobj.get().inspect() == val); 685 CHECK(custom.inspect().obj == unrootedObj); 686 CHECK(custom.inspect().val == unrootedVal); 687 CHECK(rootedCustom.get().inspect().obj == obj); 688 CHECK(rootedCustom.get().inspect().val == val); 689 690 JS_GC(cx); 691 692 CHECK(obj != unrootedObj); 693 CHECK(val != unrootedVal); 694 CHECK(unrootedValerr.inspect() == unrootedVal); 695 CHECK(valerr.get().inspect() == val); 696 CHECK(unrootedOkval.inspectErr() == unrootedVal); 697 CHECK(okval.get().inspectErr() == val); 698 CHECK(unrootedValobj1.inspect() == unrootedVal); 699 CHECK(valobj1.get().inspect() == val); 700 CHECK(unrootedValobj2.inspectErr() == unrootedObj); 701 CHECK(valobj2.get().inspectErr() == obj); 702 CHECK(*maybevalobj.inspect() == unrootedVal); 703 CHECK(*rooted_maybevalobj.get().inspect() == val); 704 MOZ_ASSERT(custom.inspect().obj == unrootedObj); 705 CHECK(custom.inspect().obj == unrootedObj); 706 CHECK(custom.inspect().val == unrootedVal); 707 CHECK(rootedCustom.get().inspect().obj == obj); 708 CHECK(rootedCustom.get().inspect().val == val); 709 710 mozilla::Result<OtherTestErr, mozilla::Ok> r(mozilla::Ok{}); 711 (void)r; 712 713 return true; 714 } 715 END_TEST(testGCRootedResult) 716 717 static int copies = 0; 718 719 struct DontCopyMe_Variant { 720 JSObject* obj; 721 explicit DontCopyMe_Variant(JSObject* objArg) : obj(objArg) {} 722 DontCopyMe_Variant(const DontCopyMe_Variant& other) : obj(other.obj) { 723 copies++; 724 } 725 DontCopyMe_Variant(DontCopyMe_Variant&& other) : obj(std::move(other.obj)) { 726 other.obj = nullptr; 727 } 728 void trace(JSTracer* trc) { TraceRoot(trc, &obj, "obj"); } 729 }; 730 731 enum struct TestUnusedZeroEnum : int16_t { Ok = 0, NotOk = 1 }; 732 733 namespace mozilla::detail { 734 template <> 735 struct UnusedZero<TestUnusedZeroEnum> : UnusedZeroEnum<TestUnusedZeroEnum> {}; 736 } // namespace mozilla::detail 737 738 namespace JS { 739 template <> 740 struct GCPolicy<TestUnusedZeroEnum> 741 : public IgnoreGCPolicy<TestUnusedZeroEnum> {}; 742 } // namespace JS 743 744 struct DontCopyMe_NullIsOk { 745 JS::Value val; 746 DontCopyMe_NullIsOk() : val(UndefinedValue()) {} 747 explicit DontCopyMe_NullIsOk(const JS::Value& valArg) : val(valArg) {} 748 DontCopyMe_NullIsOk(const DontCopyMe_NullIsOk& other) = delete; 749 DontCopyMe_NullIsOk(DontCopyMe_NullIsOk&& other) 750 : val(std::move(other.val)) {} 751 DontCopyMe_NullIsOk& operator=(DontCopyMe_NullIsOk&& other) { 752 val = std::move(other.val); 753 other.val = UndefinedValue(); 754 return *this; 755 } 756 void trace(JSTracer* trc) { TraceRoot(trc, &val, "val"); } 757 }; 758 759 struct Failed {}; 760 761 namespace mozilla::detail { 762 template <> 763 struct UnusedZero<Failed> { 764 using StorageType = uintptr_t; 765 766 static constexpr bool value = true; 767 static constexpr StorageType nullValue = 0; 768 static constexpr StorageType GetDefaultValue() { return 2; } 769 770 static constexpr void AssertValid(StorageType aValue) {} 771 static constexpr Failed Inspect(const StorageType& aValue) { 772 return Failed{}; 773 } 774 static constexpr Failed Unwrap(StorageType aValue) { return Failed{}; } 775 static constexpr StorageType Store(Failed aValue) { 776 return GetDefaultValue(); 777 } 778 }; 779 } // namespace mozilla::detail 780 781 namespace JS { 782 template <> 783 struct GCPolicy<Failed> : public IgnoreGCPolicy<Failed> {}; 784 } // namespace JS 785 786 struct TriviallyCopyable_LowBitTagIsError { 787 JSObject* obj; 788 TriviallyCopyable_LowBitTagIsError() : obj(nullptr) {} 789 explicit TriviallyCopyable_LowBitTagIsError(JSObject* objArg) : obj(objArg) {} 790 TriviallyCopyable_LowBitTagIsError( 791 const TriviallyCopyable_LowBitTagIsError& other) = default; 792 void trace(JSTracer* trc) { TraceRoot(trc, &obj, "obj"); } 793 }; 794 795 namespace mozilla::detail { 796 template <> 797 struct HasFreeLSB<TriviallyCopyable_LowBitTagIsError> : HasFreeLSB<JSObject*> { 798 }; 799 } // namespace mozilla::detail 800 801 BEGIN_TEST_WITH_ATTRIBUTES(testRootedResultCtors, JS_EXPECT_HAZARDS) { 802 JSObject* unrootedObj = JS_NewPlainObject(cx); 803 CHECK(unrootedObj); 804 Rooted<JSObject*> obj(cx, unrootedObj); 805 806 using mozilla::detail::PackingStrategy; 807 808 static_assert(Result<DontCopyMe_Variant, TestErr>::Strategy == 809 PackingStrategy::Variant); 810 Rooted<Result<DontCopyMe_Variant, TestErr>> vv(cx, DontCopyMe_Variant{obj}); 811 static_assert(Result<mozilla::Ok, DontCopyMe_Variant>::Strategy == 812 PackingStrategy::Variant); 813 Rooted<Result<mozilla::Ok, DontCopyMe_Variant>> ve(cx, 814 DontCopyMe_Variant{obj}); 815 816 static_assert(Result<DontCopyMe_NullIsOk, TestUnusedZeroEnum>::Strategy == 817 PackingStrategy::NullIsOk); 818 Rooted<Result<DontCopyMe_NullIsOk, TestUnusedZeroEnum>> nv( 819 cx, DontCopyMe_NullIsOk{JS::ObjectValue(*obj)}); 820 821 static_assert(Result<TriviallyCopyable_LowBitTagIsError, Failed>::Strategy == 822 PackingStrategy::LowBitTagIsError); 823 Rooted<Result<TriviallyCopyable_LowBitTagIsError, Failed>> lv( 824 cx, TriviallyCopyable_LowBitTagIsError{obj}); 825 826 CHECK(obj == unrootedObj); 827 828 CHECK(vv.get().inspect().obj == obj); 829 CHECK(ve.get().inspectErr().obj == obj); 830 CHECK(nv.get().inspect().val.toObjectOrNull() == obj); 831 CHECK(lv.get().inspect().obj == obj); 832 833 JS_GC(cx); 834 CHECK(obj != unrootedObj); 835 836 CHECK(vv.get().inspect().obj == obj); 837 CHECK(ve.get().inspectErr().obj == obj); 838 CHECK(nv.get().inspect().val.toObjectOrNull() == obj); 839 CHECK(lv.get().inspect().obj == obj); 840 CHECK(copies == 0); 841 return true; 842 } 843 END_TEST(testRootedResultCtors) 844 845 #if defined(HAVE_64BIT_BUILD) && !defined(XP_WIN) 846 847 // This depends on a pointer fitting in 48 bits, leaving space for an empty 848 // struct and a bool in a packed struct. Windows doesn't seem to do this 849 // packing, so we'll skip this test here. We're primarily checking whether 850 // copy constructors get called, which should be cross-platform, and 851 // secondarily making sure that the Rooted/tracing stuff is compiled and 852 // executed properly. There are certainly more clever ways to do this that 853 // would work cross-platform, but it doesn't seem worth the bother right now. 854 855 struct __attribute__((packed)) DontCopyMe_PackedVariant { 856 uintptr_t obj : 48; 857 static JSObject* Unwrap(uintptr_t packed) { 858 return reinterpret_cast<JSObject*>(packed); 859 } 860 static uintptr_t Store(JSObject* obj) { 861 return reinterpret_cast<uintptr_t>(obj); 862 } 863 864 DontCopyMe_PackedVariant() : obj(0) {} 865 explicit DontCopyMe_PackedVariant(JSObject* objArg) 866 : obj(reinterpret_cast<uintptr_t>(objArg)) {} 867 DontCopyMe_PackedVariant(const DontCopyMe_PackedVariant& other) 868 : obj(other.obj) { 869 copies++; 870 } 871 DontCopyMe_PackedVariant(DontCopyMe_PackedVariant&& other) : obj(other.obj) { 872 other.obj = 0; 873 } 874 void trace(JSTracer* trc) { 875 JSObject* realObj = Unwrap(obj); 876 TraceRoot(trc, &realObj, "obj"); 877 obj = Store(realObj); 878 } 879 }; 880 881 static_assert(std::is_default_constructible_v<DontCopyMe_PackedVariant>); 882 static_assert(std::is_default_constructible_v<TestErr>); 883 static_assert(mozilla::detail::IsPackableVariant<DontCopyMe_PackedVariant, 884 TestErr>::value); 885 886 BEGIN_TEST_WITH_ATTRIBUTES(testResultPackedVariant, JS_EXPECT_HAZARDS) { 887 JSObject* unrootedObj = JS_NewPlainObject(cx); 888 CHECK(unrootedObj); 889 Rooted<JSObject*> obj(cx, unrootedObj); 890 891 using mozilla::detail::PackingStrategy; 892 893 static_assert(Result<DontCopyMe_PackedVariant, TestErr>::Strategy == 894 PackingStrategy::PackedVariant); 895 Rooted<Result<DontCopyMe_PackedVariant, TestErr>> pv( 896 cx, DontCopyMe_PackedVariant{obj}); 897 static_assert(Result<mozilla::Ok, DontCopyMe_PackedVariant>::Strategy == 898 PackingStrategy::PackedVariant); 899 Rooted<Result<mozilla::Ok, DontCopyMe_PackedVariant>> pe( 900 cx, DontCopyMe_PackedVariant{obj}); 901 902 CHECK(obj == unrootedObj); 903 904 CHECK(DontCopyMe_PackedVariant::Unwrap(pv.get().inspect().obj) == obj); 905 CHECK(DontCopyMe_PackedVariant::Unwrap(pe.get().inspectErr().obj) == obj); 906 907 JS_GC(cx); 908 CHECK(obj != unrootedObj); 909 910 CHECK(DontCopyMe_PackedVariant::Unwrap(pv.get().inspect().obj) == obj); 911 CHECK(DontCopyMe_PackedVariant::Unwrap(pe.get().inspectErr().obj) == obj); 912 913 return true; 914 } 915 END_TEST(testResultPackedVariant) 916 917 #endif // HAVE_64BIT_BUILD 918 919 BEGIN_TEST(testIndexOf) { 920 // Test template metaprogramming for finding the index of a type in a 921 // parameter pack. This is used by RootedField. 922 // 923 // Missing types fail to compile. 924 925 CHECK((JS::detail::IndexOfTypeV<int, int> == 0)); 926 CHECK((JS::detail::IndexOfTypeV<int, float, int, float> == 1)); 927 CHECK((JS::detail::IndexOfTypeV<int, float, double, int> == 2)); 928 929 // IndexOfType doesn't check for duplicates. 930 CHECK((JS::detail::IndexOfTypeV<float, float, int, float> == 0)); 931 932 return true; 933 } 934 END_TEST(testIndexOf) 935 936 BEGIN_TEST(testRootedTuple) { 937 // Tuple with a single GC thing field. 938 { 939 RootedTuple<JSObject*> roots(cx); 940 RootedField<JSObject*> x(roots); 941 CHECK(!x); 942 CHECK(x == nullptr); 943 x = JS_NewPlainObject(cx); 944 CHECK(x); 945 CHECK(IsInsideNursery(x)); 946 JS_GC(cx); 947 CHECK(x); 948 CHECK(!IsInsideNursery(x)); 949 } 950 951 // Tuple with multiple fields of different types. 952 { 953 RootedTuple<JSObject*, JSString*> roots(cx); 954 RootedField<JSObject*> x(roots); 955 RootedField<JSString*> y(roots); 956 CHECK(!x); 957 CHECK(!y); 958 x = JS_NewPlainObject(cx); 959 y = JS_NewStringCopyZ(cx, "foobar"); 960 CHECK(x); 961 CHECK(y); 962 CHECK(IsInsideNursery(x)); 963 CHECK(IsInsideNursery(y)); 964 JS_GC(cx); 965 CHECK(x); 966 CHECK(y); 967 CHECK(!IsInsideNursery(x)); 968 CHECK(!IsInsideNursery(y)); 969 } 970 971 // Tuple with multiple fields of the same type. 972 { 973 RootedTuple<JSObject*, JSObject*> roots(cx); 974 RootedField<JSObject*, 0> x(roots); 975 RootedField<JSObject*, 1> y(roots); 976 CHECK(!x); 977 CHECK(!y); 978 x = JS_NewPlainObject(cx); 979 y = JS_NewPlainObject(cx); 980 CHECK(x); 981 CHECK(y); 982 CHECK(x != y); 983 CHECK(IsInsideNursery(x)); 984 CHECK(IsInsideNursery(y)); 985 JS_GC(cx); 986 CHECK(x); 987 CHECK(y); 988 CHECK(x != y); 989 CHECK(!IsInsideNursery(x)); 990 CHECK(!IsInsideNursery(y)); 991 } 992 993 // Test initialization by RootedTuple. 994 { 995 Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx)); 996 CHECK(obj); 997 RootedTuple<JSObject*> roots(cx, obj); 998 RootedField<JSObject*> x(roots); 999 CHECK(x == obj); 1000 } 1001 1002 // Test initialization by RootedField. 1003 { 1004 Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx)); 1005 CHECK(obj); 1006 RootedTuple<JSObject*> roots(cx); 1007 RootedField<JSObject*> x(roots, obj); 1008 CHECK(x == obj); 1009 } 1010 1011 // Test RootedField converts to Handle. 1012 { 1013 RootedTuple<JSObject*> roots(cx); 1014 RootedField<JSObject*> array(roots, JS::NewArrayObject(cx, 1)); 1015 CHECK(array); 1016 uint32_t length = 0; 1017 CHECK(JS::GetArrayLength(cx, array, &length)); 1018 CHECK(length == 1); 1019 } 1020 1021 // Test &RootedField converts to MutableHandle. 1022 { 1023 RootedTuple<JSObject*> roots(cx); 1024 RootedField<JSObject*> obj(roots); 1025 CHECK(!obj); 1026 CHECK(JS_GetClassObject(cx, JSProto_Object, &obj)); 1027 CHECK(obj); 1028 } 1029 1030 // Test RootedField converts to Handle. 1031 { 1032 RootedTuple<JSObject*> roots(cx); 1033 RootedField<JSObject*> array(roots, JS::NewArrayObject(cx, 1)); 1034 CHECK(array); 1035 uint32_t length = 0; 1036 CHECK(JS::GetArrayLength(cx, array, &length)); 1037 CHECK(length == 1); 1038 } 1039 1040 // Test &RootedField converts to MutableHandle. 1041 { 1042 RootedTuple<JSObject*> roots(cx); 1043 RootedField<JSObject*> obj(roots); 1044 CHECK(!obj); 1045 CHECK(JS_GetClassObject(cx, JSProto_Object, &obj)); 1046 CHECK(obj); 1047 } 1048 1049 return true; 1050 } 1051 END_TEST(testRootedTuple) 1052 1053 BEGIN_TEST(testRootedCopying) { 1054 // Make sure that when you write JS::Rooted<T> obj(cx, some_argument) 1055 // some_argument is not copied. 1056 1057 struct NoCopy { 1058 NoCopy() = default; 1059 NoCopy(NoCopy&) = delete; 1060 NoCopy(const NoCopy&) = delete; 1061 NoCopy& operator=(const NoCopy&) = delete; 1062 }; 1063 1064 struct StructWithNoCopyArg { 1065 StructWithNoCopyArg(void* a, NoCopy& nc) {} 1066 void trace(JSTracer* trace) {} 1067 }; 1068 1069 // Two arguments to choose 1070 // Rooted(const RootingContext& cx, CtorArgs... args) 1071 // over 1072 // Rooted(const RootingContext& cx, S&& initial) 1073 void* a = nullptr; 1074 NoCopy nc; 1075 1076 // This compiling is a test pass. 1077 JS::Rooted<StructWithNoCopyArg> rooted(cx, a, nc); 1078 return true; 1079 } 1080 END_TEST(testRootedCopying)