tor-browser

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

testGCGrayMarking.cpp (22612B)


      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 <algorithm>
      9 
     10 #include "gc/GCInternals.h"
     11 #include "gc/WeakMap.h"
     12 #include "gc/Zone.h"
     13 #include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_DefinePropertyById
     14 #include "js/Proxy.h"
     15 #include "js/WeakMap.h"
     16 #include "jsapi-tests/tests.h"
     17 
     18 using namespace js;
     19 using namespace js::gc;
     20 
     21 static constexpr CellColor AllCellColors[] = {CellColor::White, CellColor::Gray,
     22                                              CellColor::Black};
     23 
     24 static constexpr CellColor MarkedCellColors[] = {CellColor::Gray,
     25                                                 CellColor::Black};
     26 
     27 namespace js {
     28 
     29 struct GCManagedObjectWeakMap
     30    : public WeakMap<JSObject*, JSObject*, ZoneAllocPolicy> {
     31  using Base = WeakMap<JSObject*, JSObject*, ZoneAllocPolicy>;
     32  using Base::Base;
     33 };
     34 
     35 }  // namespace js
     36 
     37 namespace JS {
     38 
     39 template <>
     40 struct MapTypeToRootKind<js::GCManagedObjectWeakMap*> {
     41  static const JS::RootKind kind = JS::RootKind::Traceable;
     42 };
     43 
     44 template <>
     45 struct GCPolicy<js::GCManagedObjectWeakMap*>
     46    : public NonGCPointerPolicy<js::GCManagedObjectWeakMap*> {};
     47 
     48 }  // namespace JS
     49 
     50 class AutoNoAnalysisForTest {
     51 public:
     52  AutoNoAnalysisForTest() {}
     53 } JS_HAZ_GC_SUPPRESSED;
     54 
     55 BEGIN_TEST(testGCGrayMarking) {
     56  AutoNoAnalysisForTest disableAnalysis;
     57  AutoDisableCompactingGC disableCompactingGC(cx);
     58  AutoLeaveZeal nozeal(cx);
     59 
     60  CHECK(InitGlobals());
     61  JSAutoRealm ar(cx, global1);
     62 
     63  InitGrayRootTracer();
     64 
     65  // Enable incremental GC.
     66  AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
     67  AutoGCParameter param2(cx, JSGC_PER_ZONE_GC_ENABLED, true);
     68 
     69  bool ok = TestMarking() && TestJSWeakMaps() && TestInternalWeakMaps() &&
     70            TestCCWs() && TestGrayUnmarking();
     71 
     72  global1 = nullptr;
     73  global2 = nullptr;
     74  RemoveGrayRootTracer();
     75 
     76  return ok;
     77 }
     78 
     79 bool TestMarking() {
     80  JSObject* sameTarget = AllocPlainObject();
     81  CHECK(sameTarget);
     82 
     83  JSObject* sameSource = AllocSameCompartmentSourceObject(sameTarget);
     84  CHECK(sameSource);
     85 
     86  JSObject* crossTarget = AllocPlainObject();
     87  CHECK(crossTarget);
     88 
     89  JSObject* crossSource = GetCrossCompartmentWrapper(crossTarget);
     90  CHECK(crossSource);
     91 
     92  // Test GC with black roots marks objects black.
     93 
     94  JS::RootedObject blackRoot1(cx, sameSource);
     95  JS::RootedObject blackRoot2(cx, crossSource);
     96 
     97  JS_GC(cx);
     98 
     99  CHECK(IsMarkedBlack(sameSource));
    100  CHECK(IsMarkedBlack(crossSource));
    101  CHECK(IsMarkedBlack(sameTarget));
    102  CHECK(IsMarkedBlack(crossTarget));
    103 
    104  // Test GC with black and gray roots marks objects black.
    105 
    106  grayRoots.grayRoot1 = sameSource;
    107  grayRoots.grayRoot2 = crossSource;
    108 
    109  JS_GC(cx);
    110 
    111  CHECK(IsMarkedBlack(sameSource));
    112  CHECK(IsMarkedBlack(crossSource));
    113  CHECK(IsMarkedBlack(sameTarget));
    114  CHECK(IsMarkedBlack(crossTarget));
    115 
    116  CHECK(!JS::ObjectIsMarkedGray(sameSource));
    117 
    118  // Test GC with gray roots marks object gray.
    119 
    120  blackRoot1 = nullptr;
    121  blackRoot2 = nullptr;
    122 
    123  JS_GC(cx);
    124 
    125  CHECK(IsMarkedGray(sameSource));
    126  CHECK(IsMarkedGray(crossSource));
    127  CHECK(IsMarkedGray(sameTarget));
    128  CHECK(IsMarkedGray(crossTarget));
    129 
    130  CHECK(JS::ObjectIsMarkedGray(sameSource));
    131 
    132  // Test ExposeToActiveJS marks gray objects black.
    133 
    134  JS::ExposeObjectToActiveJS(sameSource);
    135  JS::ExposeObjectToActiveJS(crossSource);
    136  CHECK(IsMarkedBlack(sameSource));
    137  CHECK(IsMarkedBlack(crossSource));
    138  CHECK(IsMarkedBlack(sameTarget));
    139  CHECK(IsMarkedBlack(crossTarget));
    140 
    141  // Test a zone GC with black roots marks gray object in other zone black.
    142 
    143  JS_GC(cx);
    144 
    145  CHECK(IsMarkedGray(crossSource));
    146  CHECK(IsMarkedGray(crossTarget));
    147 
    148  blackRoot1 = crossSource;
    149  CHECK(ZoneGC(crossSource->zone()));
    150 
    151  CHECK(IsMarkedBlack(crossSource));
    152  CHECK(IsMarkedBlack(crossTarget));
    153 
    154  blackRoot1 = nullptr;
    155  blackRoot2 = nullptr;
    156  grayRoots.grayRoot1 = nullptr;
    157  grayRoots.grayRoot2 = nullptr;
    158 
    159  return true;
    160 }
    161 
    162 static constexpr CellColor DontMark = CellColor::White;
    163 
    164 enum MarkKeyOrDelegate : bool { MarkKey = true, MarkDelegate = false };
    165 
    166 bool TestJSWeakMaps() {
    167  for (auto keyOrDelegateColor : MarkedCellColors) {
    168    for (auto mapColor : MarkedCellColors) {
    169      for (auto markKeyOrDelegate : {MarkKey, MarkDelegate}) {
    170        CellColor expected = std::min(keyOrDelegateColor, mapColor);
    171        CHECK(TestJSWeakMap(markKeyOrDelegate, keyOrDelegateColor, mapColor,
    172                            expected));
    173 #ifdef JS_GC_ZEAL
    174        CHECK(TestJSWeakMapWithGrayUnmarking(
    175            markKeyOrDelegate, keyOrDelegateColor, mapColor, expected));
    176 #endif
    177      }
    178    }
    179  }
    180 
    181  return true;
    182 }
    183 
    184 bool TestInternalWeakMaps() {
    185  for (auto keyMarkColor : AllCellColors) {
    186    for (auto delegateMarkColor : AllCellColors) {
    187      if (keyMarkColor == CellColor::White &&
    188          delegateMarkColor == CellColor::White) {
    189        continue;
    190      }
    191 
    192      // The map is black. The delegate marks its key via wrapper preservation.
    193      // The key maps its delegate and the value. Thus, all three end up the
    194      // maximum of the key and delegate colors.
    195      CellColor expected = std::max(keyMarkColor, delegateMarkColor);
    196      CHECK(TestInternalWeakMap(keyMarkColor, delegateMarkColor, expected));
    197 
    198 #ifdef JS_GC_ZEAL
    199      CHECK(TestInternalWeakMapWithGrayUnmarking(keyMarkColor,
    200                                                 delegateMarkColor, expected));
    201 #endif
    202    }
    203  }
    204 
    205  return true;
    206 }
    207 
    208 bool TestJSWeakMap(MarkKeyOrDelegate markKey, CellColor weakMapMarkColor,
    209                   CellColor keyOrDelegateMarkColor,
    210                   CellColor expectedValueColor) {
    211  using std::swap;
    212 
    213  // Test marking a JS WeakMap object.
    214  //
    215  // This marks the map and one of the key or delegate. The key/delegate and the
    216  // value can end up different colors depending on the color of the map.
    217 
    218  JSObject* weakMap;
    219  JSObject* key;
    220  JSObject* value;
    221 
    222  // If both map and key are marked the same color, test both possible
    223  // orderings.
    224  unsigned markOrderings = weakMapMarkColor == keyOrDelegateMarkColor ? 2 : 1;
    225 
    226  for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
    227    CHECK(CreateJSWeakMapObjects(&weakMap, &key, &value));
    228 
    229    JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
    230    JSObject* keyOrDelegate = markKey ? key : delegate;
    231 
    232    RootedObject blackRoot1(cx);
    233    RootedObject blackRoot2(cx);
    234 
    235    RootObject(weakMap, weakMapMarkColor, blackRoot1, grayRoots.grayRoot1);
    236    RootObject(keyOrDelegate, keyOrDelegateMarkColor, blackRoot2,
    237               grayRoots.grayRoot2);
    238 
    239    if (markOrder != 0) {
    240      swap(blackRoot1.get(), blackRoot2.get());
    241      swap(grayRoots.grayRoot1, grayRoots.grayRoot2);
    242    }
    243 
    244    JS_GC(cx);
    245 
    246    ClearGrayRoots();
    247 
    248    CHECK(weakMap->color() == weakMapMarkColor);
    249    CHECK(keyOrDelegate->color() == keyOrDelegateMarkColor);
    250    CHECK(value->color() == expectedValueColor);
    251  }
    252 
    253  return true;
    254 }
    255 
    256 #ifdef JS_GC_ZEAL
    257 
    258 bool TestJSWeakMapWithGrayUnmarking(MarkKeyOrDelegate markKey,
    259                                    CellColor weakMapMarkColor,
    260                                    CellColor keyOrDelegateMarkColor,
    261                                    CellColor expectedValueColor) {
    262  // This is like the previous test, but things are marked black by gray
    263  // unmarking during incremental GC.
    264 
    265  JSObject* weakMap;
    266  JSObject* key;
    267  JSObject* value;
    268 
    269  // If both map and key are marked the same color, test both possible
    270  // orderings.
    271  unsigned markOrderings = weakMapMarkColor == keyOrDelegateMarkColor ? 2 : 1;
    272 
    273  JS::SetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking), 0);
    274 
    275  for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
    276    CHECK(CreateJSWeakMapObjects(&weakMap, &key, &value));
    277 
    278    JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
    279    JSObject* keyOrDelegate = markKey ? key : delegate;
    280 
    281    grayRoots.grayRoot1 = keyOrDelegate;
    282    grayRoots.grayRoot2 = weakMap;
    283 
    284    // Start an incremental GC and run until gray roots have been pushed onto
    285    // the mark stack.
    286    JS::PrepareForFullGC(cx);
    287    JS::SliceBudget budget(JS::TimeBudget(1000000));
    288    JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::DEBUG_GC,
    289                           budget);
    290    MOZ_ASSERT(cx->runtime()->gc.state() == gc::State::Sweep);
    291    MOZ_ASSERT(cx->zone()->gcState() == Zone::MarkBlackAndGray);
    292 
    293    // Unmark gray things as specified.
    294    if (markOrder != 0) {
    295      MaybeExposeObject(weakMap, weakMapMarkColor);
    296      MaybeExposeObject(keyOrDelegate, keyOrDelegateMarkColor);
    297    } else {
    298      MaybeExposeObject(keyOrDelegate, keyOrDelegateMarkColor);
    299      MaybeExposeObject(weakMap, weakMapMarkColor);
    300    }
    301 
    302    JS::FinishIncrementalGC(cx, JS::GCReason::API);
    303 
    304    ClearGrayRoots();
    305 
    306    CHECK(weakMap->color() == weakMapMarkColor);
    307    CHECK(keyOrDelegate->color() == keyOrDelegateMarkColor);
    308    CHECK(value->color() == expectedValueColor);
    309  }
    310 
    311  JS::UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking));
    312 
    313  return true;
    314 }
    315 
    316 static void MaybeExposeObject(JSObject* object, CellColor color) {
    317  if (color == CellColor::Black) {
    318    JS::ExposeObjectToActiveJS(object);
    319  }
    320 }
    321 
    322 #endif  // JS_GC_ZEAL
    323 
    324 bool CreateJSWeakMapObjects(JSObject** weakMapOut, JSObject** keyOut,
    325                            JSObject** valueOut) {
    326  RootedObject key(cx, AllocWeakmapKeyObject());
    327  CHECK(key);
    328 
    329  RootedObject value(cx, AllocPlainObject());
    330  CHECK(value);
    331 
    332  RootedObject weakMap(cx, JS::NewWeakMapObject(cx));
    333  CHECK(weakMap);
    334 
    335  JS::RootedValue keyValue(cx, ObjectValue(*key));
    336  JS::RootedValue valueValue(cx, ObjectValue(*value));
    337  CHECK(SetWeakMapEntry(cx, weakMap, keyValue, valueValue));
    338 
    339  *weakMapOut = weakMap;
    340  *keyOut = key;
    341  *valueOut = value;
    342  return true;
    343 }
    344 
    345 bool TestInternalWeakMap(CellColor keyMarkColor, CellColor delegateMarkColor,
    346                         CellColor expectedColor) {
    347  using std::swap;
    348 
    349  // Test marking for internal weakmaps (without an owning JSObject).
    350  //
    351  // All of the key, delegate and value are expected to end up the same color.
    352 
    353  UniquePtr<GCManagedObjectWeakMap> weakMap;
    354  JSObject* key;
    355  JSObject* value;
    356 
    357  // If both key and delegate are marked the same color, test both possible
    358  // orderings.
    359  unsigned markOrderings = keyMarkColor == delegateMarkColor ? 2 : 1;
    360 
    361  for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
    362    CHECK(CreateInternalWeakMapObjects(&weakMap, &key, &value));
    363 
    364    JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
    365 
    366    RootedObject blackRoot1(cx);
    367    RootedObject blackRoot2(cx);
    368 
    369    Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get());
    370    RootObject(key, keyMarkColor, blackRoot1, grayRoots.grayRoot1);
    371    RootObject(delegate, delegateMarkColor, blackRoot2, grayRoots.grayRoot2);
    372 
    373    if (markOrder != 0) {
    374      swap(blackRoot1.get(), blackRoot2.get());
    375      swap(grayRoots.grayRoot1, grayRoots.grayRoot2);
    376    }
    377 
    378    JS_GC(cx);
    379 
    380    ClearGrayRoots();
    381 
    382    CHECK(key->color() == expectedColor);
    383    CHECK(delegate->color() == expectedColor);
    384    CHECK(value->color() == expectedColor);
    385 
    386    AutoSetThreadIsFinalizing setFinalizing;
    387    js_delete(weakMap.release());
    388  }
    389 
    390  return true;
    391 }
    392 
    393 #ifdef JS_GC_ZEAL
    394 
    395 bool TestInternalWeakMapWithGrayUnmarking(CellColor keyMarkColor,
    396                                          CellColor delegateMarkColor,
    397                                          CellColor expectedColor) {
    398  UniquePtr<GCManagedObjectWeakMap> weakMap;
    399  JSObject* key;
    400  JSObject* value;
    401 
    402  // If both key and delegate are marked the same color, test both possible
    403  // orderings.
    404  unsigned markOrderings = keyMarkColor == delegateMarkColor ? 2 : 1;
    405 
    406  JS::SetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking), 0);
    407 
    408  for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
    409    CHECK(CreateInternalWeakMapObjects(&weakMap, &key, &value));
    410 
    411    JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
    412 
    413    Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get());
    414    grayRoots.grayRoot1 = key;
    415    grayRoots.grayRoot2 = delegate;
    416 
    417    // Start an incremental GC and run until gray roots have been pushed onto
    418    // the mark stack.
    419    JS::PrepareForFullGC(cx);
    420    JS::SliceBudget budget(JS::TimeBudget(1000000));
    421    JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::DEBUG_GC,
    422                           budget);
    423    MOZ_ASSERT(cx->runtime()->gc.state() == gc::State::Sweep);
    424    MOZ_ASSERT(cx->zone()->gcState() == Zone::MarkBlackAndGray);
    425 
    426    // Unmark gray things as specified.
    427    if (markOrder != 0) {
    428      MaybeExposeObject(key, keyMarkColor);
    429      MaybeExposeObject(delegate, delegateMarkColor);
    430    } else {
    431      MaybeExposeObject(key, keyMarkColor);
    432      MaybeExposeObject(delegate, delegateMarkColor);
    433    }
    434 
    435    JS::FinishIncrementalGC(cx, JS::GCReason::API);
    436 
    437    ClearGrayRoots();
    438 
    439    CHECK(key->color() == expectedColor);
    440    CHECK(delegate->color() == expectedColor);
    441    CHECK(value->color() == expectedColor);
    442 
    443    AutoSetThreadIsFinalizing setFinalizing;
    444    js_delete(weakMap.release());
    445  }
    446 
    447  JS::UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking));
    448 
    449  return true;
    450 }
    451 
    452 #endif  // JS_GC_ZEAL
    453 
    454 bool CreateInternalWeakMapObjects(UniquePtr<GCManagedObjectWeakMap>* weakMapOut,
    455                                  JSObject** keyOut, JSObject** valueOut) {
    456  RootedObject key(cx, AllocWeakmapKeyObject());
    457  CHECK(key);
    458 
    459  RootedObject value(cx, AllocPlainObject());
    460  CHECK(value);
    461 
    462  auto weakMap = cx->make_unique<GCManagedObjectWeakMap>(cx);
    463  CHECK(weakMap);
    464 
    465  CHECK(weakMap->put(key, value));
    466 
    467  *weakMapOut = std::move(weakMap);
    468  *keyOut = key;
    469  *valueOut = value;
    470  return true;
    471 }
    472 
    473 void RootObject(JSObject* object, CellColor color, RootedObject& blackRoot,
    474                JS::Heap<JSObject*>& grayRoot) {
    475  if (color == CellColor::Black) {
    476    blackRoot = object;
    477  } else if (color == CellColor::Gray) {
    478    grayRoot = object;
    479  } else {
    480    MOZ_RELEASE_ASSERT(color == CellColor::White);
    481  }
    482 }
    483 
    484 bool TestCCWs() {
    485  JSObject* target = AllocPlainObject();
    486  CHECK(target);
    487 
    488  // Test getting a new wrapper doesn't return a gray wrapper.
    489 
    490  RootedObject blackRoot(cx, target);
    491  JSObject* wrapper = GetCrossCompartmentWrapper(target);
    492  CHECK(wrapper);
    493  CHECK(!IsMarkedGray(wrapper));
    494 
    495  // Test getting an existing wrapper doesn't return a gray wrapper.
    496 
    497  grayRoots.grayRoot1 = wrapper;
    498  grayRoots.grayRoot2 = nullptr;
    499  JS_GC(cx);
    500  CHECK(IsMarkedGray(wrapper));
    501  CHECK(IsMarkedBlack(target));
    502 
    503  CHECK(GetCrossCompartmentWrapper(target) == wrapper);
    504  CHECK(!IsMarkedGray(wrapper));
    505 
    506  // Test getting an existing wrapper doesn't return a gray wrapper
    507  // during incremental GC.
    508 
    509  JS_GC(cx);
    510  CHECK(IsMarkedGray(wrapper));
    511  CHECK(IsMarkedBlack(target));
    512 
    513  JSRuntime* rt = cx->runtime();
    514  JS::PrepareForFullGC(cx);
    515  JS::SliceBudget budget(JS::WorkBudget(1));
    516  rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
    517  while (rt->gc.state() == gc::State::Prepare) {
    518    rt->gc.debugGCSlice(budget);
    519  }
    520  CHECK(JS::IsIncrementalGCInProgress(cx));
    521 
    522  CHECK(!IsMarkedBlack(wrapper));
    523  CHECK(wrapper->zone()->isGCMarkingBlackOnly());
    524 
    525  CHECK(GetCrossCompartmentWrapper(target) == wrapper);
    526  CHECK(IsMarkedBlack(wrapper));
    527 
    528  JS::FinishIncrementalGC(cx, JS::GCReason::API);
    529 
    530  // Test behaviour of gray CCWs marked black by a barrier during incremental
    531  // GC.
    532 
    533  // Initial state: source and target are gray.
    534  blackRoot = nullptr;
    535  grayRoots.grayRoot1 = wrapper;
    536  grayRoots.grayRoot2 = nullptr;
    537  JS_GC(cx);
    538  CHECK(IsMarkedGray(wrapper));
    539  CHECK(IsMarkedGray(target));
    540 
    541  // Incremental zone GC started: the source is now unmarked.
    542  JS::PrepareZoneForGC(cx, wrapper->zone());
    543  budget = JS::SliceBudget(JS::WorkBudget(1));
    544  rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
    545  while (rt->gc.state() == gc::State::Prepare) {
    546    rt->gc.debugGCSlice(budget);
    547  }
    548  CHECK(JS::IsIncrementalGCInProgress(cx));
    549  CHECK(wrapper->zone()->isGCMarkingBlackOnly());
    550  CHECK(!target->zone()->wasGCStarted());
    551  CHECK(!IsMarkedBlack(wrapper));
    552  CHECK(!IsMarkedGray(wrapper));
    553  CHECK(IsMarkedGray(target));
    554 
    555  // Betweeen GC slices: source marked black by barrier, target is
    556  // still gray. Target will be marked gray
    557  // eventually. ObjectIsMarkedGray() is conservative and reports
    558  // that target is not marked gray; AssertObjectIsNotGray() will
    559  // assert.
    560  grayRoots.grayRoot1.get();
    561  CHECK(IsMarkedBlack(wrapper));
    562  CHECK(IsMarkedGray(target));
    563  CHECK(!JS::ObjectIsMarkedGray(target));
    564 
    565  // Final state: source and target are black.
    566  JS::FinishIncrementalGC(cx, JS::GCReason::API);
    567  CHECK(IsMarkedBlack(wrapper));
    568  CHECK(IsMarkedBlack(target));
    569 
    570  grayRoots.grayRoot1 = nullptr;
    571  grayRoots.grayRoot2 = nullptr;
    572 
    573  return true;
    574 }
    575 
    576 bool TestGrayUnmarking() {
    577  const size_t length = 2000;
    578 
    579  JSObject* chain = AllocObjectChain(length);
    580  CHECK(chain);
    581 
    582  RootedObject blackRoot(cx, chain);
    583  JS_GC(cx);
    584  size_t count;
    585  CHECK(IterateObjectChain(chain, ColorCheckFunctor(MarkColor::Black, &count)));
    586  CHECK(count == length);
    587 
    588  blackRoot = nullptr;
    589  grayRoots.grayRoot1 = chain;
    590  JS_GC(cx);
    591  CHECK(cx->runtime()->gc.areGrayBitsValid());
    592  CHECK(IterateObjectChain(chain, ColorCheckFunctor(MarkColor::Gray, &count)));
    593  CHECK(count == length);
    594 
    595  JS::ExposeObjectToActiveJS(chain);
    596  CHECK(cx->runtime()->gc.areGrayBitsValid());
    597  CHECK(IterateObjectChain(chain, ColorCheckFunctor(MarkColor::Black, &count)));
    598  CHECK(count == length);
    599 
    600  grayRoots.grayRoot1 = nullptr;
    601 
    602  return true;
    603 }
    604 
    605 struct ColorCheckFunctor {
    606  MarkColor color;
    607  size_t& count;
    608 
    609  ColorCheckFunctor(MarkColor colorArg, size_t* countArg)
    610      : color(colorArg), count(*countArg) {
    611    count = 0;
    612  }
    613 
    614  bool operator()(JSObject* obj) {
    615    if (!CheckCellColor(obj, color)) {
    616      return false;
    617    }
    618 
    619    NativeObject& nobj = obj->as<NativeObject>();
    620    if (!CheckCellColor(nobj.shape(), color)) {
    621      return false;
    622    }
    623 
    624    NativeShape* shape = nobj.shape();
    625    if (!CheckCellColor(shape, color)) {
    626      return false;
    627    }
    628 
    629    // Shapes and symbols are never marked gray.
    630    ShapePropertyIter<NoGC> iter(shape);
    631    jsid id = iter->key();
    632    if (id.isGCThing() &&
    633        !CheckCellColor(id.toGCCellPtr().asCell(), MarkColor::Black)) {
    634      return false;
    635    }
    636 
    637    count++;
    638    return true;
    639  }
    640 };
    641 
    642 JS::PersistentRootedObject global1;
    643 JS::PersistentRootedObject global2;
    644 
    645 struct GrayRoots {
    646  JS::Heap<JSObject*> grayRoot1;
    647  JS::Heap<JSObject*> grayRoot2;
    648 };
    649 
    650 GrayRoots grayRoots;
    651 
    652 bool InitGlobals() {
    653  global1.init(cx, global);
    654  if (!createGlobal()) {
    655    return false;
    656  }
    657  global2.init(cx, global);
    658  return global2 != nullptr;
    659 }
    660 
    661 void ClearGrayRoots() {
    662  grayRoots.grayRoot1 = nullptr;
    663  grayRoots.grayRoot2 = nullptr;
    664 }
    665 
    666 void InitGrayRootTracer() {
    667  ClearGrayRoots();
    668  JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, &grayRoots);
    669 }
    670 
    671 void RemoveGrayRootTracer() {
    672  ClearGrayRoots();
    673  JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
    674 }
    675 
    676 static bool TraceGrayRoots(JSTracer* trc, JS::SliceBudget& budget, void* data) {
    677  auto grayRoots = static_cast<GrayRoots*>(data);
    678  TraceEdge(trc, &grayRoots->grayRoot1, "gray root 1");
    679  TraceEdge(trc, &grayRoots->grayRoot2, "gray root 2");
    680  return true;
    681 }
    682 
    683 JSObject* AllocPlainObject() {
    684  JS::RootedObject obj(cx, JS_NewPlainObject(cx));
    685  EvictNursery();
    686 
    687  MOZ_ASSERT(obj->compartment() == global1->compartment());
    688  return obj;
    689 }
    690 
    691 JSObject* AllocSameCompartmentSourceObject(JSObject* target) {
    692  JS::RootedObject source(cx, JS_NewPlainObject(cx));
    693  if (!source) {
    694    return nullptr;
    695  }
    696 
    697  JS::RootedObject obj(cx, target);
    698  if (!JS_DefineProperty(cx, source, "ptr", obj, 0)) {
    699    return nullptr;
    700  }
    701 
    702  EvictNursery();
    703 
    704  MOZ_ASSERT(source->compartment() == global1->compartment());
    705  return source;
    706 }
    707 
    708 JSObject* GetCrossCompartmentWrapper(JSObject* target) {
    709  MOZ_ASSERT(target->compartment() == global1->compartment());
    710  JS::RootedObject obj(cx, target);
    711  JSAutoRealm ar(cx, global2);
    712  if (!JS_WrapObject(cx, &obj)) {
    713    return nullptr;
    714  }
    715 
    716  EvictNursery();
    717 
    718  MOZ_ASSERT(obj->compartment() == global2->compartment());
    719  return obj;
    720 }
    721 
    722 JSObject* AllocWeakmapKeyObject() {
    723  JS::RootedObject delegate(cx, JS_NewPlainObject(cx));
    724  if (!delegate) {
    725    return nullptr;
    726  }
    727 
    728  JS::RootedObject key(cx,
    729                       js::Wrapper::New(cx, delegate, &js::Wrapper::singleton));
    730 
    731  EvictNursery();
    732  return key;
    733 }
    734 
    735 JSObject* AllocObjectChain(size_t length) {
    736  // Allocate a chain of linked JSObjects.
    737 
    738  // Use a unique property name so the shape is not shared with any other
    739  // objects.
    740  RootedString nextPropName(cx, JS_NewStringCopyZ(cx, "unique14142135"));
    741  RootedId nextId(cx);
    742  if (!JS_StringToId(cx, nextPropName, &nextId)) {
    743    return nullptr;
    744  }
    745 
    746  RootedObject head(cx);
    747  for (size_t i = 0; i < length; i++) {
    748    RootedValue next(cx, ObjectOrNullValue(head));
    749    head = AllocPlainObject();
    750    if (!head) {
    751      return nullptr;
    752    }
    753    if (!JS_DefinePropertyById(cx, head, nextId, next, 0)) {
    754      return nullptr;
    755    }
    756  }
    757 
    758  return head;
    759 }
    760 
    761 template <typename F>
    762 bool IterateObjectChain(JSObject* chain, F f) {
    763  RootedObject obj(cx, chain);
    764  while (obj) {
    765    if (!f(obj)) {
    766      return false;
    767    }
    768 
    769    // Access the 'next' property via the object's slots to avoid triggering
    770    // gray marking assertions when calling JS_GetPropertyById.
    771    NativeObject& nobj = obj->as<NativeObject>();
    772    MOZ_ASSERT(nobj.slotSpan() == 1);
    773    obj = nobj.getSlot(0).toObjectOrNull();
    774  }
    775 
    776  return true;
    777 }
    778 
    779 static bool IsMarkedBlack(Cell* cell) {
    780  TenuredCell* tc = &cell->asTenured();
    781  return tc->isMarkedBlack();
    782 }
    783 
    784 static bool IsMarkedGray(Cell* cell) {
    785  TenuredCell* tc = &cell->asTenured();
    786  bool isGray = tc->isMarkedGray();
    787  MOZ_ASSERT_IF(isGray, tc->isMarkedAny());
    788  return isGray;
    789 }
    790 
    791 static bool CheckCellColor(Cell* cell, MarkColor color) {
    792  MOZ_ASSERT(color == MarkColor::Black || color == MarkColor::Gray);
    793  if (color == MarkColor::Black && !IsMarkedBlack(cell)) {
    794    printf("Found non-black cell: %p\n", cell);
    795    return false;
    796  } else if (color == MarkColor::Gray && !IsMarkedGray(cell)) {
    797    printf("Found non-gray cell: %p\n", cell);
    798    return false;
    799  }
    800 
    801  return true;
    802 }
    803 
    804 void EvictNursery() { cx->runtime()->gc.evictNursery(); }
    805 
    806 bool ZoneGC(JS::Zone* zone) {
    807  JS::PrepareZoneForGC(cx, zone);
    808  cx->runtime()->gc.gc(JS::GCOptions::Normal, JS::GCReason::API);
    809  CHECK(!cx->runtime()->gc.isFullGc());
    810  return true;
    811 }
    812 
    813 END_TEST(testGCGrayMarking)