tor-browser

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

weak-marking-03.js (20624B)


      1 // |jit-test| allow-unhandlable-oom
      2 
      3 // weakmap marking tests that use the testing mark queue to force an ordering
      4 // of marking.
      5 
      6 // We are carefully controlling the sequence of GC events.
      7 gczeal(0);
      8 
      9 // If a command-line parameter is given, use it as a substring restriction on
     10 // the tests to run.
     11 var testRestriction = scriptArgs[0];
     12 printErr(`testRestriction is ${testRestriction || '(run all tests)'}`);
     13 
     14 function runtest(func) {
     15  if (testRestriction && ! func.name.includes(testRestriction)) {
     16    print("\Skipping " + func.name);
     17  } else {
     18    print("\nRunning " + func.name);
     19    func();
     20  }
     21 }
     22 
     23 function reportMarks(prefix = "") {
     24  const marks = getMarks();
     25  const current = currentgc();
     26  const markstr = marks.join("/");
     27  print(`${prefix}[${current.incrementalState}/${current.sweepGroup}@${current.queuePos}] ${markstr}`);
     28  return markstr;
     29 }
     30 
     31 function startGCMarking(untilQueuePos) {
     32  startgc(100000);
     33  while (gcstate() === "Prepare" || gcstate() === "MarkRoots") {
     34    gcslice(100000);
     35  }
     36  // If running with --no-threads, the GC might decide to suspend early if
     37  // scanning the ephemeron table takes too long. Wait until all of the queue
     38  // entries that the test expects have been processed.
     39  while (currentgc().queuePos < untilQueuePos) {
     40    gcslice(1000);
     41  }
     42 
     43 }
     44 
     45 function purgeKey() {
     46  const m = new WeakMap();
     47  const vals = {};
     48  vals.key = Object.create(null);
     49  vals.val = Object.create(null);
     50  m.set(vals.key, vals.val);
     51 
     52  minorgc();
     53 
     54  addMarkObservers([m, vals.key, vals.val]);
     55 
     56  enqueueMark(m);
     57  enqueueMark("yield");
     58 
     59  enqueueMark(vals.key);
     60  enqueueMark("yield");
     61 
     62  vals.key = vals.val = null;
     63 
     64  startGCMarking(2);
     65  // getMarks() returns map/key/value
     66  assertEq(getMarks().join("/"), "black/unmarked/unmarked",
     67           "marked the map black");
     68 
     69  gcslice(100000);
     70  assertEq(getMarks().join("/"), "black/black/unmarked",
     71           "key is now marked");
     72 
     73  // Trigger purgeWeakKey: the key is in weakkeys (because it was unmarked when
     74  // the map was marked), and now we're removing it.
     75  m.delete(nondeterministicGetWeakMapKeys(m)[0]);
     76 
     77  finishgc(); // Finish the GC
     78  assertEq(getMarks().join("/"), "black/black/black",
     79           "at end, value is marked too");
     80 
     81  clearMarkQueue();
     82  clearMarkObservers();
     83 }
     84 
     85 if (this.enqueueMark)
     86  runtest(purgeKey);
     87 
     88 function removeKey() {
     89  reportMarks("removeKey start: ");
     90 
     91  const m = new WeakMap();
     92  const vals = {};
     93  vals.key = Object.create(null);
     94  vals.val = Object.create(null);
     95  m.set(vals.key, vals.val);
     96 
     97  minorgc();
     98 
     99  addMarkObservers([m, vals.key, vals.val]);
    100 
    101  enqueueMark(m);
    102  enqueueMark("yield");
    103 
    104  startGCMarking(2);
    105  reportMarks("first: ");
    106  var marks = getMarks();
    107  assertEq(marks[0], "black", "map is black");
    108  assertEq(marks[1], "unmarked", "key not marked yet");
    109  assertEq(marks[2], "unmarked", "value not marked yet");
    110  m.delete(vals.key);
    111 
    112  finishgc(); // Finish the GC
    113  reportMarks("done: ");
    114  marks = getMarks();
    115  assertEq(marks[0], "black", "map is black");
    116  assertEq(marks[1], "black", "key is black");
    117  assertEq(marks[2], "black", "value is black");
    118 
    119  // Do it again, but this time, remove all other roots.
    120  m.set(vals.key, vals.val);
    121  vals.key = vals.val = null;
    122  startGCMarking(2);
    123  marks = getMarks();
    124  assertEq(marks[0], "black", "map is black");
    125  assertEq(marks[1], "unmarked", "key not marked yet");
    126  assertEq(marks[2], "unmarked", "value not marked yet");
    127 
    128  // This was meant to test the weakmap deletion barrier, which would remove
    129  // the key from weakkeys. Unfortunately, JS-exposed WeakMaps now have a read
    130  // barrier on lookup that marks the key, and deletion requires a lookup.
    131  m.delete(nondeterministicGetWeakMapKeys(m)[0]);
    132 
    133  finishgc();
    134  marks = getMarks();
    135  assertEq(marks[0], "black", "map is black");
    136  assertEq(marks[1], "black", "key was blackened by lookup read barrier during deletion");
    137  assertEq(marks[2], "black", "value is black because map and key are black");
    138 
    139  clearMarkQueue();
    140  clearMarkObservers();
    141 }
    142 
    143 if (this.enqueueMark)
    144  runtest(removeKey);
    145 
    146 // Test:
    147 //   1. mark the map
    148 //     - that inserts the delegate into weakKeys
    149 //   2. nuke the CCW key
    150 //     - removes the delegate from weakKeys
    151 //   3. mark the key
    152 //   4. enter weak marking mode
    153 //
    154 // The problem it's attempting to recreate is that entering weak marking mode
    155 // will no longer mark the value, because there's no delegate to trigger it,
    156 // and the key was not added to weakKeys (because at the time the map was
    157 // scanned, the key had a delegate, so it was added to weakKeys instead.)
    158 function nukeMarking() {
    159  const g1 = newGlobal({newCompartment: true});
    160 
    161  const vals = {};
    162  vals.map = new WeakMap();
    163  vals.key = g1.eval("Object.create(null)");
    164  vals.val = Object.create(null);
    165  vals.map.set(vals.key, vals.val);
    166  vals.val = null;
    167  gc();
    168 
    169  // Set up the sequence of marking events.
    170  enqueueMark(vals.map);
    171  enqueueMark("yield");
    172  // We will nuke the key's delegate here.
    173  enqueueMark(vals.key);
    174  enqueueMark("enter-weak-marking-mode");
    175 
    176  // Okay, run through the GC now.
    177  startgc(1000000);
    178  while (gcstate() === "Prepare" || gcstate() === "MarkRoots") {
    179    gcslice(100000);
    180  }
    181  assertEq(gcstate(), "Mark", "expected to yield after marking map");
    182  // We should have marked the map and then yielded back here.
    183  nukeCCW(vals.key);
    184  // Finish up the GC.
    185  gcslice();
    186 
    187  clearMarkQueue();
    188 }
    189 
    190 if (this.enqueueMark)
    191  runtest(nukeMarking);
    192 
    193 // Similar to the above, but trying to get a different failure:
    194 //  - start marking
    195 //  - find a map, add its key to ephemeronEdges
    196 //  - nuke the key (and all other CCWs between the key -> delegate zones)
    197 //  - when sweeping, we will no longer have any edges between the key
    198 //    and delegate zones. So they will be placed in separate sweep groups.
    199 //  - for this test, the delegate zone must be swept after the key zone
    200 //  - make sure we don't try to mark back in the key zone (due to an
    201 //    ephemeron edge) while sweeping the delegate zone. In a DEBUG build,
    202 //    this would assert.
    203 function nukeMarkingSweepGroups() {
    204  // Create g1 before host, because that will result in the right zone
    205  // ordering to trigger the bug.
    206  const g1 = newGlobal({newCompartment: true});
    207  const host = newGlobal({newCompartment: true});
    208  host.g1 = g1;
    209  host.eval(`
    210  const vals = {};
    211  vals.map = new WeakMap();
    212  vals.key = g1.eval("Object.create(null)");
    213  vals.val = Object.create(null);
    214  vals.map.set(vals.key, vals.val);
    215  vals.val = null;
    216  gc();
    217 
    218  // Set up the sequence of marking events.
    219  enqueueMark(vals.map);
    220  enqueueMark("yield");
    221  // We will nuke the key's delegate here.
    222  enqueueMark(vals.key);
    223  enqueueMark("enter-weak-marking-mode");
    224 
    225  // Okay, run through the GC now.
    226  startgc(1);
    227  while (gcstate() !== "Mark") {
    228    gcslice(100000);
    229  }
    230  assertEq(gcstate(), "Mark", "expected to yield after marking map");
    231  // We should have marked the map and then yielded back here.
    232  nukeAllCCWs();
    233  // Finish up the GC.
    234  while (gcstate() === "Mark") {
    235    gcslice(1000);
    236  }
    237  gcslice();
    238 
    239  clearMarkQueue();
    240  `);
    241 }
    242 
    243 if (this.enqueueMark)
    244  runtest(nukeMarkingSweepGroups);
    245 
    246 function transplantMarking() {
    247  const g1 = newGlobal({newCompartment: true});
    248 
    249  const vals = {};
    250  vals.map = new WeakMap();
    251  let {object, transplant} = transplantableObject();
    252  vals.key = object;
    253  object = null;
    254  vals.val = Object.create(null);
    255  vals.map.set(vals.key, vals.val);
    256  vals.val = null;
    257  gc();
    258 
    259  // Set up the sequence of marking events.
    260  enqueueMark(vals.map);
    261  enqueueMark("yield");
    262  // We will transplant the key here.
    263  enqueueMark(vals.key);
    264  enqueueMark("enter-weak-marking-mode");
    265 
    266  // Okay, run through the GC now.
    267  startgc(1000000);
    268  while (gcstate() !== "Mark") {
    269    gcslice(100000);
    270  }
    271  assertEq(gcstate(), "Mark", "expected to yield after marking map");
    272  // We should have marked the map and then yielded back here.
    273  transplant(g1);
    274  // Finish up the GC.
    275  gcslice();
    276 
    277  clearMarkQueue();
    278 }
    279 
    280 if (this.enqueueMark)
    281  runtest(transplantMarking);
    282 
    283 // 1. Mark the map
    284 //   => add delegate to weakKeys
    285 // 2. Mark the delegate black
    286 //   => do nothing because we are not in weak marking mode
    287 // 3. Mark the key gray
    288 //   => mark value gray, not that we really care
    289 // 4. Enter weak marking mode
    290 //   => black delegate darkens the key from gray to black
    291 function grayMarkingMapFirst() {
    292  const g = newGlobal({newCompartment: true});
    293  const vals = {};
    294  vals.map = new WeakMap();
    295  vals.key = g.eval("Object.create(null)");
    296  vals.val = Object.create(null);
    297  vals.map.set(vals.key, vals.val);
    298 
    299  g.delegate = vals.key;
    300  g.eval("dummy = Object.create(null)");
    301  g.eval("grayRoot().push(delegate, dummy)");
    302  addMarkObservers([vals.map, vals.key]);
    303  g.addMarkObservers([vals.key, g.dummy]);
    304  addMarkObservers([vals.val]);
    305 
    306  gc();
    307 
    308  enqueueMark(vals.map);
    309  enqueueMark("yield"); // checkpoint 1
    310 
    311  g.enqueueMark(vals.key);
    312  enqueueMark("yield"); // checkpoint 2
    313 
    314  vals.val = null;
    315  vals.key = null;
    316  g.delegate = null;
    317  g.dummy = null;
    318 
    319  const showmarks = () => {
    320    print("[map,key,delegate,graydummy,value] marked " + JSON.stringify(getMarks()));
    321  };
    322 
    323  print("Starting incremental GC");
    324  startGCMarking(2);
    325  // Checkpoint 1, after marking map
    326  showmarks();
    327  var marks = getMarks();
    328  assertEq(marks[0], "black", "map is black");
    329  assertEq(marks[1], "unmarked", "key is not marked yet");
    330  assertEq(marks[2], "unmarked", "delegate is not marked yet");
    331 
    332  gcslice(100000);
    333  // Checkpoint 2, after marking delegate
    334  showmarks();
    335  marks = getMarks();
    336  assertEq(marks[0], "black", "map is black");
    337  assertEq(marks[1], "unmarked", "key is not marked yet");
    338  assertEq(marks[2], "black", "delegate is black");
    339 
    340  gcslice();
    341  // GC complete. Key was marked black (b/c of delegate), then gray marking saw
    342  // it was already black and skipped it.
    343  showmarks();
    344  marks = getMarks();
    345  assertEq(marks[0], "black", "map is black");
    346  assertEq(marks[1], "black", "delegate marked key black because of weakmap");
    347  assertEq(marks[2], "black", "delegate is still black");
    348  assertEq(marks[3], "gray", "basic gray marking is working");
    349  assertEq(marks[4], "black", "black map + black delegate => black value");
    350 
    351  clearMarkQueue();
    352  clearMarkObservers();
    353  grayRoot().length = 0;
    354  g.eval("grayRoot().length = 0");
    355 }
    356 
    357 if (this.enqueueMark)
    358  runtest(grayMarkingMapFirst);
    359 
    360 function grayMarkingMapLast() {
    361  const g = newGlobal({newCompartment: true});
    362  const vals = {};
    363  vals.map = new WeakMap();
    364  vals.key = g.eval("Object.create(null)");
    365  vals.val = Object.create(null);
    366  vals.map.set(vals.key, vals.val);
    367 
    368  vals.map2 = new WeakMap();
    369  vals.key2 = g.eval("Object.create(null)");
    370  vals.val2 = Object.create(null);
    371  vals.map2.set(vals.key2, vals.val2);
    372 
    373  g.delegate = vals.key;
    374  g.eval("grayRoot().push(delegate)");
    375  addMarkObservers([vals.map, vals.key]);
    376  g.addMarkObservers([vals.key]);
    377  addMarkObservers([vals.val]);
    378 
    379  grayRoot().push(vals.key2);
    380  addMarkObservers([vals.map2, vals.key2]);
    381  g.addMarkObservers([vals.key2]);
    382  addMarkObservers([vals.val2]);
    383 
    384  const labels = ["map", "key", "delegate", "value", "map2", "key2", "delegate2", "value2"];
    385 
    386  gc();
    387 
    388  g.enqueueMark(vals.key);
    389  g.enqueueMark(vals.key2);
    390  enqueueMark("yield"); // checkpoint 1
    391 
    392  vals.val = null;
    393  vals.key = null;
    394  g.delegate = null;
    395 
    396  vals.map2 = null; // Important! Second map is never marked, keeps nothing alive.
    397  vals.key2 = null;
    398  vals.val2 = null;
    399  g.delegate2 = null;
    400 
    401  const labeledMarks = () => {
    402    const info = {};
    403    const marks = getMarks();
    404    for (let i = 0; i < labels.length; i++)
    405      info[labels[i]] = marks[i];
    406    return info;
    407  };
    408 
    409  const showmarks = () => {
    410    print("Marks:");
    411    for (const [label, mark] of Object.entries(labeledMarks()))
    412      print(`  ${label}: ${mark}`);
    413  };
    414 
    415  print("Starting incremental GC");
    416  startGCMarking(3);
    417  // Checkpoint 1, after marking key
    418  showmarks();
    419  var marks = labeledMarks();
    420  assertEq(marks.map, "unmarked", "map is unmarked");
    421  assertEq(marks.key, "unmarked", "key is not marked yet");
    422  assertEq(marks.delegate, "black", "delegate is black");
    423  assertEq(marks.map2, "unmarked", "map2 is unmarked");
    424  assertEq(marks.key2, "unmarked", "key2 is not marked yet");
    425  assertEq(marks.delegate2, "black", "delegate2 is black");
    426 
    427  gcslice();
    428  // GC complete. When entering weak marking mode, black delegate propagated to
    429  // key.
    430  showmarks();
    431  marks = labeledMarks();
    432  assertEq(marks.map, "black", "map is black");
    433  assertEq(marks.key, "black", "delegate marked key black because of weakmap");
    434  assertEq(marks.delegate, "black", "delegate is still black");
    435  assertEq(marks.value, "black", "black map + black delegate => black value");
    436  assertEq(marks.map2, "dead", "map2 is dead");
    437  assertEq(marks.key2, "gray", "key2 marked gray, map2 had no effect");
    438  assertEq(marks.delegate2, "black", "delegate artificially marked black via mark queue");
    439  assertEq(marks.value2, "dead", "dead map + black delegate => dead value");
    440 
    441  clearMarkQueue();
    442  clearMarkObservers();
    443  grayRoot().length = 0;
    444  g.eval("grayRoot().length = 0");
    445 
    446  return vals; // To prevent the JIT from optimizing out vals.
    447 }
    448 
    449 if (this.enqueueMark)
    450  runtest(grayMarkingMapLast);
    451 
    452 function grayMapKey() {
    453  const vals = {};
    454  vals.m = new WeakMap();
    455  vals.key = Object.create(null);
    456  vals.val = Object.create(null);
    457  vals.m.set(vals.key, vals.val);
    458 
    459  // Maps are allocated black, so we won't be able to mark it gray during the
    460  // first GC.
    461  gc();
    462 
    463  addMarkObservers([vals.m, vals.key, vals.val]);
    464 
    465  // Wait until we can mark gray (ie, sweeping). Mark the map gray and yield.
    466  // This should happen all in one slice.
    467  enqueueMark("set-color-gray");
    468  enqueueMark(vals.m);
    469  enqueueMark("unset-color");
    470  enqueueMark("yield");
    471 
    472  // Make the weakmap no longer reachable from the roots, so we can mark it
    473  // gray.
    474  vals.m = null;
    475 
    476  enqueueMark(vals.key);
    477  enqueueMark("yield");
    478 
    479  vals.key = vals.val = null;
    480 
    481  startGCMarking(4);
    482  assertEq(getMarks().join("/"), "gray/unmarked/unmarked",
    483           "marked the map gray");
    484 
    485  gcslice(100000);
    486  assertEq(getMarks().join("/"), "gray/black/unmarked",
    487           "key is now marked black");
    488 
    489  finishgc(); // Finish the GC
    490 
    491  assertEq(getMarks().join("/"), "gray/black/gray",
    492           "at end: black/gray => gray");
    493 
    494  clearMarkQueue();
    495  clearMarkObservers();
    496 }
    497 
    498 if (this.enqueueMark)
    499  runtest(grayMapKey);
    500 
    501 function grayKeyMap() {
    502  const vals = {};
    503  vals.m = new WeakMap();
    504  vals.key = Object.create(null);
    505  vals.val = Object.create(null);
    506  vals.m.set(vals.key, vals.val);
    507 
    508  addMarkObservers([vals.m, vals.key, vals.val]);
    509 
    510  enqueueMark(vals.key);
    511  enqueueMark("yield");
    512 
    513  // Wait until we are gray marking.
    514  enqueueMark("set-color-gray");
    515  enqueueMark(vals.m);
    516  enqueueMark("unset-color");
    517  enqueueMark("yield");
    518 
    519  enqueueMark("set-color-black");
    520  enqueueMark(vals.m);
    521  enqueueMark("unset-color");
    522 
    523  // Make the weakmap no longer reachable from the roots, so we can mark it
    524  // gray.
    525  vals.m = null;
    526 
    527  vals.key = vals.val = null;
    528 
    529  // Only mark this zone, to avoid interference from other tests that may have
    530  // created additional zones.
    531  schedulezone(vals);
    532 
    533  startGCMarking();
    534  // getMarks() returns map/key/value
    535  reportMarks("1: ");
    536  assertEq(getMarks().join("/"), "unmarked/black/unmarked",
    537           "marked key black");
    538 
    539  // We always yield before sweeping (in the absence of zeal), so we will see
    540  // the unmarked state another time.
    541  gcslice(100000);
    542  reportMarks("2: ");
    543  assertEq(getMarks().join("/"), "unmarked/black/unmarked",
    544           "marked key black, yield before sweeping");
    545 
    546  gcslice(100000);
    547  reportMarks("3: ");
    548  assertEq(getMarks().join("/"), "gray/black/gray",
    549           "marked the map gray, which marked the value when map scanned");
    550 
    551  finishgc(); // Finish the GC
    552  reportMarks("4: ");
    553  assertEq(getMarks().join("/"), "black/black/black",
    554           "further marked the map black, so value should also be blackened");
    555 
    556  clearMarkQueue();
    557  clearMarkObservers();
    558 }
    559 
    560 if (this.enqueueMark)
    561  runtest(grayKeyMap);
    562 
    563 // Cause a key to be marked black *during gray marking*, by first marking a
    564 // delegate black, then marking the map and key gray. When the key is scanned,
    565 // it should be seen to be a CCW of a black delegate and so should itself be
    566 // marked black.
    567 //
    568 // The bad behavior being prevented is:
    569 //
    570 //  1. You wrap an object in a CCW and use it as a weakmap key to some
    571 //     information.
    572 //  2. You keep a strong reference to the object (in its compartment).
    573 //  3. The only references to the CCW are gray, and are in fact part of a cycle.
    574 //  4. The CC runs and discards the CCW.
    575 //  5. You look up the object in the weakmap again. This creates a new wrapper
    576 //     to use as a key. It is not in the weakmap, so the information you stored
    577 //     before is not found. (It may have even been collected, if you had no
    578 //     other references to it.)
    579 //
    580 function blackDuringGray() {
    581  const g = newGlobal({newCompartment: true});
    582  const vals = {};
    583  vals.map = new WeakMap();
    584  vals.key = g.eval("Object.create(null)");
    585  vals.val = Object.create(null);
    586  vals.map.set(vals.key, vals.val);
    587 
    588  g.delegate = vals.key;
    589  addMarkObservers([vals.map, vals.key]);
    590  g.addMarkObservers([vals.key]);
    591  addMarkObservers([vals.val]);
    592  // Mark observers: map, key, delegate, value
    593 
    594  gc();
    595 
    596  g.enqueueMark(vals.key); // Mark the delegate black
    597  enqueueMark("yield"); // checkpoint 1
    598 
    599  // Mark the map gray. This will scan through all entries, find our key, and
    600  // mark it black because its delegate is black.
    601  enqueueMark("set-color-gray");
    602  enqueueMark(vals.map); // Mark the map gray
    603 
    604  vals.map = null;
    605  vals.val = null;
    606  vals.key = null;
    607  g.delegate = null;
    608 
    609  const showmarks = () => {
    610    print("[map,key,delegate,value] marked " + JSON.stringify(getMarks()));
    611  };
    612 
    613  print("Starting incremental GC");
    614  startGCMarking(2);
    615  // Checkpoint 1, after marking delegate black
    616  showmarks();
    617  var marks = getMarks();
    618  assertEq(marks[0], "unmarked", "map is not marked yet");
    619  assertEq(marks[1], "unmarked", "key is not marked yet");
    620  assertEq(marks[2], "black", "delegate is black");
    621  assertEq(marks[3], "unmarked", "values is not marked yet");
    622 
    623  finishgc();
    624  showmarks();
    625  marks = getMarks();
    626  assertEq(marks[0], "gray", "map is gray");
    627  assertEq(marks[1], "gray", "gray map + black delegate should mark key gray");
    628  assertEq(marks[2], "black", "delegate is still black");
    629  assertEq(marks[3], "gray", "gray map + gray key => gray value");
    630 
    631  clearMarkQueue();
    632  clearMarkObservers();
    633  grayRoot().length = 0;
    634  g.eval("grayRoot().length = 0");
    635 }
    636 
    637 if (this.enqueueMark)
    638  runtest(blackDuringGray);
    639 
    640 // Same as above, except relying on the implicit edge from delegate -> key.
    641 function blackDuringGrayImplicit() {
    642  const g = newGlobal({newCompartment: true});
    643  const vals = {};
    644  vals.map = new WeakMap();
    645  vals.key = g.eval("Object.create(null)");
    646  vals.val = Object.create(null);
    647  vals.map.set(vals.key, vals.val);
    648 
    649  g.delegate = vals.key;
    650  addMarkObservers([vals.map, vals.key]);
    651  g.addMarkObservers([vals.key]);
    652  addMarkObservers([vals.val]);
    653  // Mark observers: map, key, delegate, value
    654 
    655  gc();
    656 
    657  // Mark the map gray. This will scan through all entries, find our key, and
    658  // add implicit edges from delegate -> key and delegate -> value.
    659  enqueueMark("set-color-gray");
    660  enqueueMark(vals.map); // Mark the map gray
    661  enqueueMark("yield"); // checkpoint 1
    662 
    663  enqueueMark("set-color-black");
    664  g.enqueueMark(vals.key); // Mark the delegate black, propagating to key.
    665 
    666  vals.map = null;
    667  vals.val = null;
    668  vals.key = null;
    669  g.delegate = null;
    670 
    671  const showmarks = () => {
    672    print("[map,key,delegate,value] marked " + JSON.stringify(getMarks()));
    673  };
    674 
    675  print("Starting incremental GC");
    676  startGCMarking(3);
    677  // Checkpoint 1, after marking map gray
    678  showmarks();
    679  var marks = getMarks();
    680  assertEq(marks[0], "gray", "map is gray");
    681  assertEq(marks[1], "unmarked", "key is not marked yet");
    682  assertEq(marks[2], "unmarked", "delegate is not marked yet");
    683  assertEq(marks[3], "unmarked", "value is not marked yet");
    684 
    685  finishgc();
    686  showmarks();
    687  marks = getMarks();
    688  assertEq(marks[0], "gray", "map is gray");
    689  assertEq(marks[1], "gray", "gray map + black delegate should mark key gray");
    690  assertEq(marks[2], "black", "delegate is black");
    691  assertEq(marks[3], "gray", "gray map + gray key => gray value via delegate -> value");
    692 
    693  clearMarkQueue();
    694  clearMarkObservers();
    695  grayRoot().length = 0;
    696  g.eval("grayRoot().length = 0");
    697 }
    698 
    699 if (this.enqueueMark)
    700  runtest(blackDuringGrayImplicit);