tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

testGCHeapBarriers.cpp (23943B)


      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 "gc/AllocKind.h"
      9 #include "gc/Cell.h"
     10 #include "gc/GCInternals.h"
     11 #include "gc/GCRuntime.h"
     12 #include "js/ArrayBuffer.h"  // JS::NewArrayBuffer
     13 #include "js/experimental/TypedData.h"
     14 #include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_GetProperty
     15 #include "js/RootingAPI.h"
     16 #include "jsapi-tests/tests.h"
     17 #include "vm/Runtime.h"
     18 #include "vm/TypedArrayObject.h"
     19 
     20 #include "gc/Heap-inl.h"
     21 #include "vm/JSContext-inl.h"
     22 
     23 using namespace js;
     24 
     25 static js::gc::CellColor GetColor(JSObject* obj) { return obj->color(); }
     26 static js::gc::CellColor GetColor(const JS::ArrayBufferOrView& view) {
     27  return view.asObjectUnbarriered()->color();
     28 }
     29 
     30 [[maybe_unused]] static bool IsInsideNursery(gc::Cell* cell) {
     31  return !cell->isTenured();
     32 }
     33 [[maybe_unused]] static bool IsInsideNursery(
     34    const JS::ArrayBufferOrView& view) {
     35  return IsInsideNursery(view.asObjectUnbarriered());
     36 }
     37 
     38 // A heap-allocated structure containing one of our barriered pointer wrappers
     39 // to test.
     40 template <typename W, typename T>
     41 struct TestStruct {
     42  W wrapper;
     43 
     44  void trace(JSTracer* trc) {
     45    TraceNullableEdge(trc, &wrapper, "TestStruct::wrapper");
     46  }
     47 
     48  TestStruct() {}
     49  explicit TestStruct(T init) : wrapper(init) {}
     50 };
     51 
     52 template <typename T>
     53 static T CreateNurseryGCThing(JSContext* cx) = delete;
     54 
     55 template <>
     56 JSObject* CreateNurseryGCThing(JSContext* cx) {
     57  JS::RootedObject obj(cx, JS_NewPlainObject(cx));
     58  if (!obj) {
     59    return nullptr;
     60  }
     61  JS_DefineProperty(cx, obj, "x", 42, 0);
     62  MOZ_ASSERT(IsInsideNursery(obj));
     63  return obj;
     64 }
     65 
     66 template <>
     67 JSFunction* CreateNurseryGCThing(JSContext* cx) {
     68  /*
     69   * We don't actually use the function as a function, so here we cheat and
     70   * cast a JSObject.
     71   */
     72  return static_cast<JSFunction*>(CreateNurseryGCThing<JSObject*>(cx));
     73 }
     74 
     75 template <>
     76 JS::Uint8Array CreateNurseryGCThing(JSContext* cx) {
     77  JS::Rooted<JS::Uint8Array> arr(cx, JS::Uint8Array::create(cx, 100));
     78  JS::RootedObject obj(cx, arr.asObject());
     79  JS_DefineProperty(cx, obj, "x", 42, 0);
     80  MOZ_ASSERT(IsInsideNursery(obj));
     81  return arr;
     82 }
     83 
     84 template <typename T>
     85 static T CreateTenuredGCThing(JSContext* cx) = delete;
     86 
     87 template <>
     88 JSObject* CreateTenuredGCThing(JSContext* cx) {
     89  // Use ArrayBuffers because they have finalizers, which allows using them in
     90  // TenuredHeap<> without awkward conversations about nursery allocatability.
     91  // Note that at some point ArrayBuffers might become nursery allocated at
     92  // which point this test will have to change.
     93  JSObject* obj = JS::NewArrayBuffer(cx, 20);
     94  MOZ_ASSERT(!IsInsideNursery(obj));
     95  MOZ_ASSERT(obj->getClass()->hasFinalize() &&
     96             !(obj->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE));
     97  return obj;
     98 }
     99 
    100 template <>
    101 JS::ArrayBuffer CreateTenuredGCThing(JSContext* cx) {
    102  return JS::ArrayBuffer::fromObject(CreateTenuredGCThing<JSObject*>(cx));
    103 }
    104 
    105 template <>
    106 JS::Uint8Array CreateTenuredGCThing(JSContext* cx) {
    107  // Use internal APIs that lets us specify the InitialHeap so we can ensure
    108  // that this is tenured.
    109  JSObject* obj = js::NewUint8ArrayWithLength(cx, 100, gc::Heap::Tenured);
    110  MOZ_ASSERT(!IsInsideNursery(obj));
    111  return JS::Uint8Array::fromObject(obj);
    112 }
    113 
    114 template <typename T>
    115 void* CreateHiddenTenuredGCThing(JSContext* cx) {
    116  return CreateTenuredGCThing<T>(cx);
    117 }
    118 
    119 template <>
    120 void* CreateHiddenTenuredGCThing<JS::ArrayBuffer>(JSContext* cx) {
    121  return CreateTenuredGCThing<JS::ArrayBuffer>(cx).asObjectUnbarriered();
    122 }
    123 
    124 template <>
    125 void* CreateHiddenTenuredGCThing<JS::Uint8Array>(JSContext* cx) {
    126  return CreateTenuredGCThing<JS::Uint8Array>(cx).asObjectUnbarriered();
    127 }
    128 
    129 static uintptr_t UnbarrieredCastToInt(gc::Cell* cell) {
    130  return reinterpret_cast<uintptr_t>(cell);
    131 }
    132 static uintptr_t UnbarrieredCastToInt(const JS::ArrayBufferOrView& view) {
    133  return UnbarrieredCastToInt(view.asObjectUnbarriered());
    134 }
    135 
    136 template <typename T>
    137 T RecoverHiddenGCThing(void* ptr) {
    138  return reinterpret_cast<T>(ptr);
    139 }
    140 
    141 template <>
    142 JS::ArrayBuffer RecoverHiddenGCThing(void* ptr) {
    143  return JS::ArrayBuffer::fromObject(RecoverHiddenGCThing<JSObject*>(ptr));
    144 }
    145 
    146 template <>
    147 JS::Uint8Array RecoverHiddenGCThing(void* ptr) {
    148  return JS::Uint8Array::fromObject(RecoverHiddenGCThing<JSObject*>(ptr));
    149 }
    150 
    151 static void MakeGray(JSObject* obj) {
    152  gc::TenuredCell* cell = &obj->asTenured();
    153  cell->unmark();
    154  cell->markIfUnmarked(gc::MarkColor::Gray);
    155  MOZ_ASSERT(obj->isMarkedGray());
    156 }
    157 
    158 static void MakeGray(const JS::ArrayBufferOrView& view) {
    159  MakeGray(view.asObjectUnbarriered());
    160 }
    161 
    162 // Test post-barrier implementation on wrapper types. The following wrapper
    163 // types have post barriers:
    164 //  - JS::Heap
    165 //  - GCPtr
    166 //  - HeapPtr
    167 //  - WeakHeapPtr
    168 BEGIN_TEST(testGCHeapPostBarriers) {
    169  AutoLeaveZeal nozeal(cx);
    170  AutoGCParameter disableSemispace(cx, JSGC_SEMISPACE_NURSERY_ENABLED, 0);
    171 
    172  /* Sanity check - objects start in the nursery and then become tenured. */
    173  JS_GC(cx);
    174  JS::RootedObject obj(cx, CreateNurseryGCThing<JSObject*>(cx));
    175  CHECK(IsInsideNursery(obj.get()));
    176  JS_GC(cx);
    177  CHECK(!IsInsideNursery(obj.get()));
    178  JS::RootedObject tenuredObject(cx, obj);
    179 
    180  /* JSObject and JSFunction objects are nursery allocated. */
    181  CHECK(TestHeapPostBarriersForType<JSObject*>());
    182  CHECK(TestHeapPostBarriersForType<JSFunction*>());
    183  CHECK(TestHeapPostBarriersForType<JS::Uint8Array>());
    184  // Bug 1599378: Add string tests.
    185 
    186  return true;
    187 }
    188 
    189 [[nodiscard]] bool TestCanAccessObject(JSObject* obj) {
    190  JS::RootedObject rootedObj(cx, obj);
    191  JS::RootedValue value(cx);
    192  CHECK(JS_GetProperty(cx, rootedObj, "x", &value));
    193  CHECK(value.isInt32());
    194  CHECK(value.toInt32() == 42);
    195  return true;
    196 }
    197 [[nodiscard]] bool TestCanAccessObject(const JS::ArrayBufferOrView& view) {
    198  return TestCanAccessObject(view.asObject());
    199 }
    200 
    201 template <typename T>
    202 [[nodiscard]] bool TestHeapPostBarriersForType() {
    203  CHECK((TestHeapPostBarriersForWrapper<js::GCPtr, T>()));
    204  CHECK((TestHeapPostBarriersForMovableWrapper<JS::Heap, T>()));
    205  CHECK((TestHeapPostBarriersForMovableWrapper<js::HeapPtr, T>()));
    206  CHECK((TestHeapPostBarriersForMovableWrapper<js::WeakHeapPtr, T>()));
    207  return true;
    208 }
    209 
    210 template <template <typename> class W, typename T>
    211 [[nodiscard]] bool TestHeapPostBarriersForMovableWrapper() {
    212  CHECK((TestHeapPostBarriersForWrapper<W, T>()));
    213  CHECK((TestHeapPostBarrierMoveConstruction<W<T>, T>()));
    214  CHECK((TestHeapPostBarrierMoveAssignment<W<T>, T>()));
    215  return true;
    216 }
    217 
    218 template <template <typename> class W, typename T>
    219 [[nodiscard]] bool TestHeapPostBarriersForWrapper() {
    220  CHECK((TestHeapPostBarrierConstruction<W<T>, T>()));
    221  CHECK((TestHeapPostBarrierConstruction<const W<T>, T>()));
    222  CHECK((TestHeapPostBarrierUpdate<W<T>, T>()));
    223  if constexpr (!std::is_same_v<W<T>, GCPtr<T>>) {
    224    // It is not allowed to delete heap memory containing GCPtrs on
    225    // initialization failure like this and doing so will cause an assertion to
    226    // fail in the GCPtr destructor (although we disable this in some places in
    227    // this test).
    228    CHECK((TestHeapPostBarrierInitFailure<W<T>, T>()));
    229    CHECK((TestHeapPostBarrierInitFailure<const W<T>, T>()));
    230  }
    231  return true;
    232 }
    233 
    234 template <typename W, typename T>
    235 [[nodiscard]] bool TestHeapPostBarrierConstruction() {
    236  T initialObj = CreateNurseryGCThing<T>(cx);
    237  CHECK(initialObj);
    238  CHECK(IsInsideNursery(initialObj));
    239  uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
    240 
    241  {
    242    // We don't root our structure because that would end up tracing it on minor
    243    // GC and we're testing that heap post barrier works for things that aren't
    244    // roots.
    245    JS::AutoSuppressGCAnalysis noAnalysis(cx);
    246 
    247    auto* testStruct = js_new<TestStruct<W, T>>(initialObj);
    248    CHECK(testStruct);
    249 
    250    auto& wrapper = testStruct->wrapper;
    251    CHECK(wrapper == initialObj);
    252 
    253    cx->minorGC(JS::GCReason::API);
    254 
    255    CHECK(UnbarrieredCastToInt(wrapper.get()) != initialObjAsInt);
    256    CHECK(!IsInsideNursery(wrapper.get()));
    257    CHECK(TestCanAccessObject(wrapper.get()));
    258 
    259    // Disable the check that GCPtrs are only destroyed by the GC. What happens
    260    // on destruction isn't relevant to the test.
    261    gc::AutoSetThreadIsFinalizing threadIsFinalizing;
    262 
    263    js_delete(testStruct);
    264  }
    265 
    266  cx->minorGC(JS::GCReason::API);
    267 
    268  return true;
    269 }
    270 
    271 template <typename W, typename T>
    272 [[nodiscard]] bool TestHeapPostBarrierUpdate() {
    273  // Normal case - allocate a heap object, write a nursery pointer into it and
    274  // check that it gets updated on minor GC.
    275 
    276  T initialObj = CreateNurseryGCThing<T>(cx);
    277  CHECK(initialObj);
    278  CHECK(IsInsideNursery(initialObj));
    279  uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
    280 
    281  {
    282    // We don't root our structure because that would end up tracing it on minor
    283    // GC and we're testing that heap post barrier works for things that aren't
    284    // roots.
    285    JS::AutoSuppressGCAnalysis noAnalysis(cx);
    286 
    287    auto* testStruct = js_new<TestStruct<W, T>>();
    288    CHECK(testStruct);
    289 
    290    auto& wrapper = testStruct->wrapper;
    291    CHECK(!wrapper.get());
    292 
    293    wrapper = initialObj;
    294    CHECK(wrapper == initialObj);
    295 
    296    cx->minorGC(JS::GCReason::API);
    297 
    298    CHECK(UnbarrieredCastToInt(wrapper.get()) != initialObjAsInt);
    299    CHECK(!IsInsideNursery(wrapper.get()));
    300    CHECK(TestCanAccessObject(wrapper.get()));
    301 
    302    // Disable the check that GCPtrs are only destroyed by the GC. What happens
    303    // on destruction isn't relevant to the test.
    304    gc::AutoSetThreadIsFinalizing threadIsFinalizing;
    305 
    306    js_delete(testStruct);
    307  }
    308 
    309  cx->minorGC(JS::GCReason::API);
    310 
    311  return true;
    312 }
    313 
    314 template <typename W, typename T>
    315 [[nodiscard]] bool TestHeapPostBarrierInitFailure() {
    316  // Failure case - allocate a heap object, write a nursery pointer into it
    317  // and fail to complete initialization.
    318 
    319  T initialObj = CreateNurseryGCThing<T>(cx);
    320  CHECK(initialObj);
    321  CHECK(IsInsideNursery(initialObj));
    322 
    323  {
    324    // We don't root our structure because that would end up tracing it on minor
    325    // GC and we're testing that heap post barrier works for things that aren't
    326    // roots.
    327    JS::AutoSuppressGCAnalysis noAnalysis(cx);
    328 
    329    auto testStruct = cx->make_unique<TestStruct<W, T>>(initialObj);
    330    CHECK(testStruct);
    331 
    332    auto& wrapper = testStruct->wrapper;
    333    CHECK(wrapper == initialObj);
    334 
    335    // testStruct deleted here, as if we left this block due to an error.
    336  }
    337 
    338  cx->minorGC(JS::GCReason::API);
    339 
    340  return true;
    341 }
    342 
    343 template <typename W, typename T>
    344 [[nodiscard]] bool TestHeapPostBarrierMoveConstruction() {
    345  T initialObj = CreateNurseryGCThing<T>(cx);
    346  CHECK(initialObj);
    347  CHECK(IsInsideNursery(initialObj));
    348  uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
    349 
    350  {
    351    // We don't root our structure because that would end up tracing it on minor
    352    // GC and we're testing that heap post barrier works for things that aren't
    353    // roots.
    354    JS::AutoSuppressGCAnalysis noAnalysis(cx);
    355 
    356    W wrapper1(initialObj);
    357    CHECK(wrapper1 == initialObj);
    358 
    359    W wrapper2(std::move(wrapper1));
    360    CHECK(wrapper2 == initialObj);
    361 
    362    cx->minorGC(JS::GCReason::API);
    363 
    364    CHECK(UnbarrieredCastToInt(wrapper1.get()) != initialObjAsInt);
    365    CHECK(UnbarrieredCastToInt(wrapper2.get()) != initialObjAsInt);
    366    CHECK(!IsInsideNursery(wrapper2.get()));
    367    CHECK(TestCanAccessObject(wrapper2.get()));
    368  }
    369 
    370  cx->minorGC(JS::GCReason::API);
    371 
    372  return true;
    373 }
    374 
    375 template <typename W, typename T>
    376 [[nodiscard]] bool TestHeapPostBarrierMoveAssignment() {
    377  T initialObj = CreateNurseryGCThing<T>(cx);
    378  CHECK(initialObj);
    379  CHECK(IsInsideNursery(initialObj));
    380  uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
    381 
    382  {
    383    // We don't root our structure because that would end up tracing it on minor
    384    // GC and we're testing that heap post barrier works for things that aren't
    385    // roots.
    386    JS::AutoSuppressGCAnalysis noAnalysis(cx);
    387 
    388    W wrapper1(initialObj);
    389    CHECK(wrapper1 == initialObj);
    390 
    391    W wrapper2;
    392    wrapper2 = std::move(wrapper1);
    393    CHECK(wrapper2 == initialObj);
    394 
    395    cx->minorGC(JS::GCReason::API);
    396 
    397    CHECK(UnbarrieredCastToInt(wrapper1.get()) != initialObjAsInt);
    398    CHECK(UnbarrieredCastToInt(wrapper2.get()) != initialObjAsInt);
    399    CHECK(!IsInsideNursery(wrapper2.get()));
    400    CHECK(TestCanAccessObject(wrapper2.get()));
    401  }
    402 
    403  cx->minorGC(JS::GCReason::API);
    404 
    405  return true;
    406 }
    407 
    408 END_TEST(testGCHeapPostBarriers)
    409 
    410 // Test read barrier implementation on wrapper types. The following wrapper
    411 // types have read barriers:
    412 //  - JS::Heap
    413 //  - JS::TenuredHeap
    414 //  - WeakHeapPtr
    415 //
    416 // Also check that equality comparisons on wrappers do not trigger the read
    417 // barrier.
    418 BEGIN_TEST(testGCHeapReadBarriers) {
    419  AutoLeaveZeal nozeal(cx);
    420 
    421  CHECK((TestWrapperType<JS::Heap<JSObject*>, JSObject*>()));
    422  CHECK((TestWrapperType<JS::TenuredHeap<JSObject*>, JSObject*>()));
    423  CHECK((TestWrapperType<WeakHeapPtr<JSObject*>, JSObject*>()));
    424  CHECK((TestWrapperType<JS::Heap<JS::ArrayBuffer>, JS::ArrayBuffer>()));
    425  CHECK((TestWrapperType<JS::Heap<JS::Uint8Array>, JS::Uint8Array>()));
    426 
    427  return true;
    428 }
    429 
    430 template <typename WrapperT, typename ObjectT>
    431 [[nodiscard]] bool TestWrapperType() {
    432  // Check that the read barrier normally marks gray things black.
    433  CHECK((TestReadBarrierUnmarksGray<WrapperT, ObjectT>()));
    434 
    435  // Check that the read barrier marks gray and white things black during an
    436  // incremental GC.
    437  CHECK((TestReadBarrierMarksBlack<WrapperT, ObjectT>(true)));
    438  CHECK((TestReadBarrierMarksBlack<WrapperT, ObjectT>(false)));
    439 
    440  // Allocate test objects and make them gray. We will make sure they stay
    441  // gray. (For most reads, the barrier will unmark gray.)
    442  Rooted<ObjectT> obj1(cx, CreateTenuredGCThing<ObjectT>(cx));
    443  Rooted<ObjectT> obj2(cx, CreateTenuredGCThing<ObjectT>(cx));
    444  MakeGray(obj1);
    445  MakeGray(obj2);
    446 
    447  WrapperT wrapper1(obj1);
    448  WrapperT wrapper2(obj2);
    449  const ObjectT constobj1 = obj1;
    450  const ObjectT constobj2 = obj2;
    451  CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(obj1, obj2, wrapper1,
    452                                                      wrapper2)));
    453  CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(constobj1, constobj2,
    454                                                      wrapper1, wrapper2)));
    455 
    456  return true;
    457 }
    458 
    459 template <typename WrapperT, typename ObjectT>
    460 void Access(const WrapperT& wrapper) {
    461  if constexpr (std::is_base_of_v<JS::ArrayBufferOrView, ObjectT>) {
    462    (void)wrapper.asObject();
    463  } else {
    464    (void)*wrapper;
    465  }
    466 }
    467 
    468 template <typename WrapperT, typename ObjectT>
    469 [[nodiscard]] bool TestReadBarrierUnmarksGray() {
    470  Rooted<ObjectT> obj(cx, CreateTenuredGCThing<ObjectT>(cx));
    471  WrapperT wrapper(obj);
    472 
    473  CHECK(GetColor(obj) == gc::CellColor::White);
    474 
    475  Access<WrapperT, ObjectT>(wrapper);
    476 
    477  CHECK(GetColor(obj) == gc::CellColor::White);
    478 
    479  MakeGray(obj);
    480  Access<WrapperT, ObjectT>(wrapper);
    481 
    482  CHECK(GetColor(obj) == gc::CellColor::Black);
    483 
    484  return true;
    485 }
    486 
    487 // Execute thunk |f| between two slices of an incremental GC controlled by zeal
    488 // mode |mode|.
    489 template <typename F>
    490 [[nodiscard]] bool CallDuringIncrementalGC(uint32_t mode, F&& f) {
    491 #ifndef JS_GC_ZEAL
    492  fprintf(stderr, "This test requires building with --enable-gczeal\n");
    493 #else
    494  AutoGCParameter incremental(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
    495 
    496  const int64_t BudgetMS = 10000;  // 10S should be long enough for anyone.
    497 
    498  JS::SetGCZeal(cx, mode, 0);
    499  JS::PrepareZoneForGC(cx, js::GetContextZone(cx));
    500  JS::SliceBudget budget{JS::TimeBudget(BudgetMS)};
    501  JS::StartIncrementalGC(cx, JS::GCOptions(), JS::GCReason::DEBUG_GC, budget);
    502  CHECK(JS::IsIncrementalGCInProgress(cx));
    503 
    504  CHECK(f());
    505 
    506  JS::FinishIncrementalGC(cx, JS::GCReason::DEBUG_GC);
    507 #endif
    508 
    509  return true;
    510 }
    511 
    512 template <typename WrapperT, typename ObjectT>
    513 [[nodiscard]] bool TestReadBarrierMarksBlack(bool fromWhite) {
    514  AutoLeaveZeal noZeal(cx);
    515 
    516  // Create an object and hide it from the hazard analysis.
    517  void* ptr = CreateHiddenTenuredGCThing<ObjectT>(cx);
    518  CHECK(ptr);
    519 
    520  CHECK(CallDuringIncrementalGC(9 /* YieldBeforeSweeping */, [&]() -> bool {
    521    CHECK(JS::IsIncrementalBarrierNeeded(cx));
    522 
    523    auto obj = RecoverHiddenGCThing<ObjectT>(ptr);
    524 
    525    WrapperT wrapper(obj);
    526 
    527    CHECK(GetColor(obj) == gc::CellColor::White);
    528    if (!fromWhite) {
    529      MakeGray(obj);
    530    }
    531 
    532    Access<WrapperT, ObjectT>(wrapper);
    533 
    534    CHECK(GetColor(obj) == gc::CellColor::Black);
    535 
    536    return true;
    537  }));
    538 
    539  return true;
    540 }
    541 
    542 template <typename WrapperT, typename ObjectT>
    543 [[nodiscard]] bool TestUnbarrieredOperations(ObjectT obj, ObjectT obj2,
    544                                             WrapperT& wrapper,
    545                                             WrapperT& wrapper2) {
    546  (void)bool(wrapper);
    547  (void)bool(wrapper2);
    548  CHECK(GetColor(obj) == gc::CellColor::Gray);
    549  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    550 
    551  int x = 0;
    552 
    553  CHECK(GetColor(obj) == gc::CellColor::Gray);
    554  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    555  x += obj == obj2;
    556  CHECK(GetColor(obj) == gc::CellColor::Gray);
    557  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    558  x += obj == wrapper2;
    559  CHECK(GetColor(obj) == gc::CellColor::Gray);
    560  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    561  x += wrapper == obj2;
    562  CHECK(GetColor(obj) == gc::CellColor::Gray);
    563  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    564  x += wrapper == wrapper2;
    565  CHECK(GetColor(obj) == gc::CellColor::Gray);
    566  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    567 
    568  CHECK(x == 0);
    569 
    570  x += obj != obj2;
    571  CHECK(GetColor(obj) == gc::CellColor::Gray);
    572  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    573  x += obj != wrapper2;
    574  CHECK(GetColor(obj) == gc::CellColor::Gray);
    575  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    576  x += wrapper != obj2;
    577  CHECK(GetColor(obj) == gc::CellColor::Gray);
    578  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    579  x += wrapper != wrapper2;
    580  CHECK(GetColor(obj) == gc::CellColor::Gray);
    581  CHECK(GetColor(obj2) == gc::CellColor::Gray);
    582 
    583  CHECK(x == 4);
    584 
    585  return true;
    586 }
    587 
    588 END_TEST(testGCHeapReadBarriers)
    589 
    590 using ObjectVector = Vector<JSObject*, 0, SystemAllocPolicy>;
    591 
    592 // Test pre-barrier implementation on wrapper types. The following wrapper types
    593 // have a pre-barrier:
    594 //  - GCPtr
    595 //  - HeapPtr
    596 //  - PreBarriered
    597 BEGIN_TEST(testGCHeapPreBarriers) {
    598  AutoLeaveZeal nozeal(cx);
    599 
    600  AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
    601 
    602  // Create a bunch of objects. These are unrooted and will be used to test
    603  // whether barriers have fired by checking whether they have been marked
    604  // black.
    605  size_t objectCount = 100;  // Increase this if necessary when adding tests.
    606  ObjectVector testObjects;
    607  for (size_t i = 0; i < objectCount; i++) {
    608    JSObject* obj = CreateTenuredGCThing<JSObject*>(cx);
    609    CHECK(obj);
    610    CHECK(testObjects.append(obj));
    611  }
    612 
    613  // Start an incremental GC so we can detect if we cause barriers to fire, as
    614  // these will mark objects black.
    615  JS::PrepareForFullGC(cx);
    616  JS::SliceBudget budget(JS::WorkBudget(1));
    617  gc::GCRuntime* gc = &cx->runtime()->gc;
    618  gc->startDebugGC(JS::GCOptions::Normal, budget);
    619  while (gc->state() != gc::State::Mark) {
    620    gc->debugGCSlice(budget);
    621  }
    622  MOZ_ASSERT(cx->zone()->needsIncrementalBarrier());
    623 
    624  CHECK(TestWrapper<HeapPtr<JSObject*>>(testObjects));
    625  CHECK(TestWrapper<PreBarriered<JSObject*>>(testObjects));
    626 
    627  // GCPtr is different because 1) it doesn't support move operations as it's
    628  // supposed to be part of a GC thing and 2) it doesn't perform a pre-barrier
    629  // in its destructor because these are only destroyed as part of a GC where
    630  // the barrier is unnecessary.
    631  CHECK(TestGCPtr(testObjects));
    632 
    633  gc::FinishGC(cx, JS::GCReason::API);
    634 
    635  return true;
    636 }
    637 
    638 template <typename Wrapper>
    639 [[nodiscard]] bool TestWrapper(ObjectVector& testObjects) {
    640  CHECK(TestCopyConstruction<Wrapper>(testObjects.popCopy()));
    641  CHECK(TestMoveConstruction<Wrapper>(testObjects.popCopy()));
    642  CHECK(TestAssignment<Wrapper>(testObjects.popCopy(), testObjects.popCopy()));
    643  CHECK(TestMoveAssignment<Wrapper>(testObjects.popCopy(),
    644                                    testObjects.popCopy()));
    645  return true;
    646 }
    647 
    648 template <typename Wrapper>
    649 [[nodiscard]] bool TestCopyConstruction(JSObject* obj) {
    650  CHECK(GetColor(obj) == gc::CellColor::White);
    651 
    652  {
    653    Wrapper wrapper1(obj);
    654    Wrapper wrapper2(wrapper1);
    655    CHECK(wrapper1 == obj);
    656    CHECK(wrapper2 == obj);
    657    CHECK(GetColor(obj) == gc::CellColor::White);
    658  }
    659 
    660  // Check destructor performs pre-barrier.
    661  CHECK(GetColor(obj) == gc::CellColor::Black);
    662 
    663  return true;
    664 }
    665 
    666 template <typename Wrapper>
    667 [[nodiscard]] bool TestMoveConstruction(JSObject* obj) {
    668  CHECK(GetColor(obj) == gc::CellColor::White);
    669 
    670  {
    671    Wrapper wrapper1(obj);
    672    MakeGray(obj);  // Check that we allow move of gray GC thing.
    673    Wrapper wrapper2(std::move(wrapper1));
    674    CHECK(!wrapper1);
    675    CHECK(wrapper2 == obj);
    676    CHECK(GetColor(obj) == gc::CellColor::Gray);
    677  }
    678 
    679  // Check destructor performs pre-barrier.
    680  CHECK(GetColor(obj) == gc::CellColor::Black);
    681 
    682  return true;
    683 }
    684 
    685 template <typename Wrapper>
    686 [[nodiscard]] bool TestAssignment(JSObject* obj1, JSObject* obj2) {
    687  CHECK(GetColor(obj1) == gc::CellColor::White);
    688  CHECK(GetColor(obj2) == gc::CellColor::White);
    689 
    690  {
    691    Wrapper wrapper1(obj1);
    692    Wrapper wrapper2(obj2);
    693 
    694    wrapper2 = wrapper1;
    695 
    696    CHECK(wrapper1 == obj1);
    697    CHECK(wrapper2 == obj1);
    698    CHECK(GetColor(obj1) == gc::CellColor::White);  // No barrier fired.
    699    CHECK(GetColor(obj2) == gc::CellColor::Black);  // Pre barrier fired.
    700  }
    701 
    702  // Check destructor performs pre-barrier.
    703  CHECK(GetColor(obj1) == gc::CellColor::Black);
    704 
    705  return true;
    706 }
    707 
    708 template <typename Wrapper>
    709 [[nodiscard]] bool TestMoveAssignment(JSObject* obj1, JSObject* obj2) {
    710  CHECK(GetColor(obj1) == gc::CellColor::White);
    711  CHECK(GetColor(obj2) == gc::CellColor::White);
    712 
    713  {
    714    Wrapper wrapper1(obj1);
    715    Wrapper wrapper2(obj2);
    716 
    717    MakeGray(obj1);  // Check we allow move of gray thing.
    718    wrapper2 = std::move(wrapper1);
    719 
    720    CHECK(!wrapper1);
    721    CHECK(wrapper2 == obj1);
    722    CHECK(GetColor(obj1) == gc::CellColor::Gray);   // No barrier fired.
    723    CHECK(GetColor(obj2) == gc::CellColor::Black);  // Pre barrier fired.
    724  }
    725 
    726  // Check destructor performs pre-barrier.
    727  CHECK(GetColor(obj1) == gc::CellColor::Black);
    728 
    729  return true;
    730 }
    731 
    732 [[nodiscard]] bool TestGCPtr(ObjectVector& testObjects) {
    733  CHECK(TestGCPtrCopyConstruction(testObjects.popCopy()));
    734  CHECK(TestGCPtrAssignment(testObjects.popCopy(), testObjects.popCopy()));
    735  return true;
    736 }
    737 
    738 [[nodiscard]] bool TestGCPtrCopyConstruction(JSObject* obj) {
    739  CHECK(GetColor(obj) == gc::CellColor::White);
    740 
    741  {
    742    // Let us destroy GCPtrs ourselves for testing purposes.
    743    gc::AutoSetThreadIsFinalizing threadIsFinalizing;
    744 
    745    GCPtr<JSObject*> wrapper1(obj);
    746    GCPtr<JSObject*> wrapper2(wrapper1);
    747    CHECK(wrapper1 == obj);
    748    CHECK(wrapper2 == obj);
    749    CHECK(GetColor(obj) == gc::CellColor::White);
    750  }
    751 
    752  // GCPtr doesn't perform pre-barrier in destructor.
    753  CHECK(GetColor(obj) == gc::CellColor::White);
    754 
    755  return true;
    756 }
    757 
    758 [[nodiscard]] bool TestGCPtrAssignment(JSObject* obj1, JSObject* obj2) {
    759  CHECK(GetColor(obj1) == gc::CellColor::White);
    760  CHECK(GetColor(obj2) == gc::CellColor::White);
    761 
    762  {
    763    // Let us destroy GCPtrs ourselves for testing purposes.
    764    gc::AutoSetThreadIsFinalizing threadIsFinalizing;
    765 
    766    GCPtr<JSObject*> wrapper1(obj1);
    767    GCPtr<JSObject*> wrapper2(obj2);
    768 
    769    wrapper2 = wrapper1;
    770 
    771    CHECK(wrapper1 == obj1);
    772    CHECK(wrapper2 == obj1);
    773    CHECK(GetColor(obj1) == gc::CellColor::White);  // No barrier fired.
    774    CHECK(GetColor(obj2) == gc::CellColor::Black);  // Pre barrier fired.
    775  }
    776 
    777  // GCPtr doesn't perform pre-barrier in destructor.
    778  CHECK(GetColor(obj1) == gc::CellColor::White);
    779 
    780  return true;
    781 }
    782 
    783 END_TEST(testGCHeapPreBarriers)