test-js-weak-refs.cc 38.5 KB
Newer Older
1 2 3 4
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
#include "src/execution/isolate.h"
#include "src/execution/microtask-queue.h"
7
#include "src/handles/handles-inl.h"
8
#include "src/heap/factory-inl.h"
9
#include "src/heap/heap-inl.h"
10
#include "src/objects/js-objects.h"
11
#include "src/objects/js-weak-refs-inl.h"
12
#include "test/cctest/cctest.h"
13
#include "test/cctest/heap/heap-utils.h"
14 15 16 17

namespace v8 {
namespace internal {

18 19
namespace {

20 21
Handle<JSFinalizationRegistry> ConstructJSFinalizationRegistry(
    Isolate* isolate) {
22
  Factory* factory = isolate->factory();
23
  Handle<String> finalization_registry_name =
24
      factory->NewStringFromStaticChars("FinalizationRegistry");
25 26
  Handle<Object> global =
      handle(isolate->native_context()->global_object(), isolate);
27 28
  Handle<JSFunction> finalization_registry_fun = Handle<JSFunction>::cast(
      Object::GetProperty(isolate, global, finalization_registry_name)
29
          .ToHandleChecked());
30 31
  auto finalization_registry = Handle<JSFinalizationRegistry>::cast(
      JSObject::New(finalization_registry_fun, finalization_registry_fun,
32 33
                    Handle<AllocationSite>::null())
          .ToHandleChecked());
34 35 36 37 38 39 40 41

  // JSObject::New filled all of the internal fields with undefined. Some of
  // them have more restrictive types, so set those now.
  finalization_registry->set_native_context(*isolate->native_context());
  finalization_registry->set_cleanup(
      isolate->native_context()->empty_function());
  finalization_registry->set_flags(0);

42
#ifdef VERIFY_HEAP
43
  finalization_registry->JSFinalizationRegistryVerify(isolate);
44
#endif  // VERIFY_HEAP
45
  return finalization_registry;
46 47
}

48 49
Handle<JSWeakRef> ConstructJSWeakRef(Handle<JSReceiver> target,
                                     Isolate* isolate) {
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
  Factory* factory = isolate->factory();
  Handle<String> weak_ref_name = factory->WeakRef_string();
  Handle<Object> global =
      handle(isolate->native_context()->global_object(), isolate);
  Handle<JSFunction> weak_ref_fun = Handle<JSFunction>::cast(
      Object::GetProperty(isolate, global, weak_ref_name).ToHandleChecked());
  auto weak_ref = Handle<JSWeakRef>::cast(
      JSObject::New(weak_ref_fun, weak_ref_fun, Handle<AllocationSite>::null())
          .ToHandleChecked());
  weak_ref->set_target(*target);
#ifdef VERIFY_HEAP
  weak_ref->JSWeakRefVerify(isolate);
#endif  // VERIFY_HEAP
  return weak_ref;
}

66 67 68 69 70 71 72 73 74 75 76
Handle<JSObject> CreateKey(const char* key_prop_value, Isolate* isolate) {
  Factory* factory = isolate->factory();
  Handle<String> key_string = factory->NewStringFromStaticChars("key_string");
  Handle<JSObject> key =
      isolate->factory()->NewJSObject(isolate->object_function());
  JSObject::AddProperty(isolate, key, key_string,
                        factory->NewStringFromAsciiChecked(key_prop_value),
                        NONE);
  return key;
}

77 78
Handle<WeakCell> FinalizationRegistryRegister(
    Handle<JSFinalizationRegistry> finalization_registry,
79 80 81 82 83 84 85 86 87 88 89
    Handle<JSObject> target, Handle<Object> held_value,
    Handle<Object> unregister_token, Isolate* isolate) {
  Factory* factory = isolate->factory();
  Handle<JSFunction> regfunc = Handle<JSFunction>::cast(
      Object::GetProperty(isolate, finalization_registry,
                          factory->NewStringFromStaticChars("register"))
          .ToHandleChecked());
  Handle<Object> args[] = {target, held_value, unregister_token};
  Execution::Call(isolate, regfunc, finalization_registry, arraysize(args),
                  args)
      .ToHandleChecked();
90
  CHECK(finalization_registry->active_cells().IsWeakCell());
91
  Handle<WeakCell> weak_cell =
92
      handle(WeakCell::cast(finalization_registry->active_cells()), isolate);
93
#ifdef VERIFY_HEAP
94
  weak_cell->WeakCellVerify(isolate);
95 96 97 98
#endif  // VERIFY_HEAP
  return weak_cell;
}

99 100 101
Handle<WeakCell> FinalizationRegistryRegister(
    Handle<JSFinalizationRegistry> finalization_registry,
    Handle<JSObject> target, Isolate* isolate) {
102 103
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);
104 105
  return FinalizationRegistryRegister(finalization_registry, target, undefined,
                                      undefined, isolate);
106 107 108
}

void NullifyWeakCell(Handle<WeakCell> weak_cell, Isolate* isolate) {
109
  auto empty_func = [](HeapObject object, ObjectSlot slot, Object target) {};
110 111
  weak_cell->Nullify(isolate, empty_func);
#ifdef VERIFY_HEAP
112
  weak_cell->WeakCellVerify(isolate);
113 114 115
#endif  // VERIFY_HEAP
}

116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
Object PopClearedCellHoldings(
    Handle<JSFinalizationRegistry> finalization_registry, Isolate* isolate) {
  // PopClearedCell is implemented in Torque. Reproduce that implementation here
  // for testing.
  Handle<WeakCell> weak_cell =
      handle(WeakCell::cast(finalization_registry->cleared_cells()), isolate);
  DCHECK(weak_cell->prev().IsUndefined(isolate));
  finalization_registry->set_cleared_cells(weak_cell->next());
  weak_cell->set_next(ReadOnlyRoots(isolate).undefined_value());

  if (finalization_registry->cleared_cells().IsWeakCell()) {
    WeakCell cleared_cells_head =
        WeakCell::cast(finalization_registry->cleared_cells());
    DCHECK_EQ(cleared_cells_head.prev(), *weak_cell);
    cleared_cells_head.set_prev(ReadOnlyRoots(isolate).undefined_value());
  } else {
    DCHECK(finalization_registry->cleared_cells().IsUndefined(isolate));
  }

  if (!weak_cell->unregister_token().IsUndefined(isolate)) {
    JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap(
        isolate, finalization_registry->ptr(), weak_cell->ptr());
  }

  return weak_cell->holdings();
}

143 144 145 146 147 148 149 150 151 152
// Usage: VerifyWeakCellChain(isolate, list_head, n, cell1, cell2, ..., celln);
// verifies that list_head == cell1 and cell1, cell2, ..., celln. form a list.
void VerifyWeakCellChain(Isolate* isolate, Object list_head, int n_args, ...) {
  CHECK_GE(n_args, 0);

  va_list args;
  va_start(args, n_args);

  if (n_args == 0) {
    // Verify empty list
153
    CHECK(list_head.IsUndefined(isolate));
154 155 156
  } else {
    WeakCell current = WeakCell::cast(Object(va_arg(args, Address)));
    CHECK_EQ(current, list_head);
157
    CHECK(current.prev().IsUndefined(isolate));
158 159 160

    for (int i = 1; i < n_args; i++) {
      WeakCell next = WeakCell::cast(Object(va_arg(args, Address)));
161 162
      CHECK_EQ(current.next(), next);
      CHECK_EQ(next.prev(), current);
163 164
      current = next;
    }
165
    CHECK(current.next().IsUndefined(isolate));
166 167 168 169 170 171
  }
  va_end(args);
}

// Like VerifyWeakCellChain but verifies the chain created with key_list_prev
// and key_list_next instead of prev and next.
172 173
void VerifyWeakCellKeyChain(Isolate* isolate, SimpleNumberDictionary key_map,
                            Object unregister_token, int n_args, ...) {
174 175 176 177 178
  CHECK_GE(n_args, 0);

  va_list args;
  va_start(args, n_args);

179 180 181 182 183 184
  Object hash = unregister_token.GetHash();
  InternalIndex entry = InternalIndex::NotFound();
  if (!hash.IsUndefined(isolate)) {
    uint32_t key = Smi::ToInt(hash);
    entry = key_map.FindEntry(isolate, key);
  }
185 186
  if (n_args == 0) {
    // Verify empty list
187
    CHECK(entry.is_not_found());
188
  } else {
189
    CHECK(entry.is_found());
190
    WeakCell current = WeakCell::cast(Object(va_arg(args, Address)));
191
    Object list_head = key_map.ValueAt(entry);
192
    CHECK_EQ(current, list_head);
193
    CHECK(current.key_list_prev().IsUndefined(isolate));
194 195 196

    for (int i = 1; i < n_args; i++) {
      WeakCell next = WeakCell::cast(Object(va_arg(args, Address)));
197 198
      CHECK_EQ(current.key_list_next(), next);
      CHECK_EQ(next.key_list_prev(), current);
199 200
      current = next;
    }
201
    CHECK(current.key_list_next().IsUndefined(isolate));
202 203
  }
  va_end(args);
204 205
}

206 207 208 209 210 211 212 213 214 215 216
Handle<JSWeakRef> MakeWeakRefAndKeepDuringJob(Isolate* isolate) {
  HandleScope inner_scope(isolate);

  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());
  Handle<JSWeakRef> inner_weak_ref = ConstructJSWeakRef(js_object, isolate);
  isolate->heap()->KeepDuringJob(js_object);

  return inner_scope.CloseAndEscape(inner_weak_ref);
}

217 218
}  // namespace

219
TEST(TestRegister) {
220 221 222 223
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
224 225
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
226 227 228
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

229 230
  // Register a weak reference and verify internal data structures.
  Handle<WeakCell> weak_cell1 =
231
      FinalizationRegistryRegister(finalization_registry, js_object, isolate);
232

233
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 1,
234
                      *weak_cell1);
235 236
  CHECK(weak_cell1->key_list_prev().IsUndefined(isolate));
  CHECK(weak_cell1->key_list_next().IsUndefined(isolate));
237

238
  CHECK(finalization_registry->cleared_cells().IsUndefined(isolate));
239 240

  // No key was used during registration, key-based map stays uninitialized.
241
  CHECK(finalization_registry->key_map().IsUndefined(isolate));
242 243 244

  // Register another weak reference and verify internal data structures.
  Handle<WeakCell> weak_cell2 =
245
      FinalizationRegistryRegister(finalization_registry, js_object, isolate);
246

247
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 2,
248
                      *weak_cell2, *weak_cell1);
249 250
  CHECK(weak_cell2->key_list_prev().IsUndefined(isolate));
  CHECK(weak_cell2->key_list_next().IsUndefined(isolate));
251

252 253
  CHECK(finalization_registry->cleared_cells().IsUndefined(isolate));
  CHECK(finalization_registry->key_map().IsUndefined(isolate));
254 255
}

256
TEST(TestRegisterWithKey) {
257 258 259 260
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
261 262
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
263 264 265
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

266 267
  Handle<JSObject> token1 = CreateKey("token1", isolate);
  Handle<JSObject> token2 = CreateKey("token2", isolate);
268 269 270 271
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

  // Register a weak reference with a key and verify internal data structures.
272 273
  Handle<WeakCell> weak_cell1 = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token1, isolate);
274 275

  {
276
    SimpleNumberDictionary key_map =
277
        SimpleNumberDictionary::cast(finalization_registry->key_map());
278 279
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1);
    VerifyWeakCellKeyChain(isolate, key_map, *token2, 0);
280 281 282 283
  }

  // Register another weak reference with a different key and verify internal
  // data structures.
284 285
  Handle<WeakCell> weak_cell2 = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token2, isolate);
286 287

  {
288
    SimpleNumberDictionary key_map =
289
        SimpleNumberDictionary::cast(finalization_registry->key_map());
290 291
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1);
    VerifyWeakCellKeyChain(isolate, key_map, *token2, 1, *weak_cell2);
292 293
  }

294
  // Register another weak reference with token1 and verify internal data
295
  // structures.
296 297
  Handle<WeakCell> weak_cell3 = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token1, isolate);
298

299
  {
300
    SimpleNumberDictionary key_map =
301
        SimpleNumberDictionary::cast(finalization_registry->key_map());
302
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell3,
303
                           *weak_cell1);
304
    VerifyWeakCellKeyChain(isolate, key_map, *token2, 1, *weak_cell2);
305 306 307 308 309 310 311 312
  }
}

TEST(TestWeakCellNullify1) {
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
313 314
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
315 316 317 318
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  Handle<WeakCell> weak_cell1 =
319
      FinalizationRegistryRegister(finalization_registry, js_object, isolate);
320
  Handle<WeakCell> weak_cell2 =
321
      FinalizationRegistryRegister(finalization_registry, js_object, isolate);
322 323

  // Nullify the first WeakCell and verify internal data structures.
324
  NullifyWeakCell(weak_cell1, isolate);
325
  CHECK_EQ(finalization_registry->active_cells(), *weak_cell2);
326 327
  CHECK(weak_cell2->prev().IsUndefined(isolate));
  CHECK(weak_cell2->next().IsUndefined(isolate));
328
  CHECK_EQ(finalization_registry->cleared_cells(), *weak_cell1);
329 330
  CHECK(weak_cell1->prev().IsUndefined(isolate));
  CHECK(weak_cell1->next().IsUndefined(isolate));
331

332
  // Nullify the second WeakCell and verify internal data structures.
333
  NullifyWeakCell(weak_cell2, isolate);
334 335
  CHECK(finalization_registry->active_cells().IsUndefined(isolate));
  CHECK_EQ(finalization_registry->cleared_cells(), *weak_cell2);
336
  CHECK_EQ(weak_cell2->next(), *weak_cell1);
337
  CHECK(weak_cell2->prev().IsUndefined(isolate));
338
  CHECK_EQ(weak_cell1->prev(), *weak_cell2);
339
  CHECK(weak_cell1->next().IsUndefined(isolate));
340 341
}

342
TEST(TestWeakCellNullify2) {
343 344 345 346
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
347 348
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
349 350 351
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

352
  Handle<WeakCell> weak_cell1 =
353
      FinalizationRegistryRegister(finalization_registry, js_object, isolate);
354
  Handle<WeakCell> weak_cell2 =
355
      FinalizationRegistryRegister(finalization_registry, js_object, isolate);
356

357
  // Like TestWeakCellNullify1 but nullify the WeakCells in opposite order.
358
  NullifyWeakCell(weak_cell2, isolate);
359
  CHECK_EQ(finalization_registry->active_cells(), *weak_cell1);
360 361
  CHECK(weak_cell1->prev().IsUndefined(isolate));
  CHECK(weak_cell1->next().IsUndefined(isolate));
362
  CHECK_EQ(finalization_registry->cleared_cells(), *weak_cell2);
363 364
  CHECK(weak_cell2->prev().IsUndefined(isolate));
  CHECK(weak_cell2->next().IsUndefined(isolate));
365 366

  NullifyWeakCell(weak_cell1, isolate);
367 368
  CHECK(finalization_registry->active_cells().IsUndefined(isolate));
  CHECK_EQ(finalization_registry->cleared_cells(), *weak_cell1);
369
  CHECK_EQ(weak_cell1->next(), *weak_cell2);
370
  CHECK(weak_cell1->prev().IsUndefined(isolate));
371
  CHECK_EQ(weak_cell2->prev(), *weak_cell1);
372
  CHECK(weak_cell2->next().IsUndefined(isolate));
373 374
}

375
TEST(TestJSFinalizationRegistryPopClearedCellHoldings1) {
376 377 378
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
379
  Factory* factory = isolate->factory();
380
  HandleScope outer_scope(isolate);
381 382
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
383 384
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());
385 386 387 388
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

  Handle<Object> holdings1 = factory->NewStringFromAsciiChecked("holdings1");
389 390
  Handle<WeakCell> weak_cell1 = FinalizationRegistryRegister(
      finalization_registry, js_object, holdings1, undefined, isolate);
391
  Handle<Object> holdings2 = factory->NewStringFromAsciiChecked("holdings2");
392 393
  Handle<WeakCell> weak_cell2 = FinalizationRegistryRegister(
      finalization_registry, js_object, holdings2, undefined, isolate);
394
  Handle<Object> holdings3 = factory->NewStringFromAsciiChecked("holdings3");
395 396
  Handle<WeakCell> weak_cell3 = FinalizationRegistryRegister(
      finalization_registry, js_object, holdings3, undefined, isolate);
397 398 399 400

  NullifyWeakCell(weak_cell2, isolate);
  NullifyWeakCell(weak_cell3, isolate);

401
  CHECK(finalization_registry->NeedsCleanup());
402
  Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate);
403
  CHECK_EQ(cleared1, *holdings3);
404 405
  CHECK(weak_cell3->prev().IsUndefined(isolate));
  CHECK(weak_cell3->next().IsUndefined(isolate));
406

407
  CHECK(finalization_registry->NeedsCleanup());
408
  Object cleared2 = PopClearedCellHoldings(finalization_registry, isolate);
409
  CHECK_EQ(cleared2, *holdings2);
410 411
  CHECK(weak_cell2->prev().IsUndefined(isolate));
  CHECK(weak_cell2->next().IsUndefined(isolate));
412

413
  CHECK(!finalization_registry->NeedsCleanup());
414 415 416

  NullifyWeakCell(weak_cell1, isolate);

417
  CHECK(finalization_registry->NeedsCleanup());
418
  Object cleared3 = PopClearedCellHoldings(finalization_registry, isolate);
419
  CHECK_EQ(cleared3, *holdings1);
420 421
  CHECK(weak_cell1->prev().IsUndefined(isolate));
  CHECK(weak_cell1->next().IsUndefined(isolate));
422

423 424 425
  CHECK(!finalization_registry->NeedsCleanup());
  CHECK(finalization_registry->active_cells().IsUndefined(isolate));
  CHECK(finalization_registry->cleared_cells().IsUndefined(isolate));
426 427
}

428
TEST(TestJSFinalizationRegistryPopClearedCellHoldings2) {
429 430
  // Test that when all WeakCells for a key are popped, the key is removed from
  // the key map.
431 432 433
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
434
  Factory* factory = isolate->factory();
435
  HandleScope outer_scope(isolate);
436 437
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
438 439
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());
440
  Handle<JSObject> token1 = CreateKey("token1", isolate);
441

442
  Handle<Object> holdings1 = factory->NewStringFromAsciiChecked("holdings1");
443 444
  Handle<WeakCell> weak_cell1 = FinalizationRegistryRegister(
      finalization_registry, js_object, holdings1, token1, isolate);
445
  Handle<Object> holdings2 = factory->NewStringFromAsciiChecked("holdings2");
446 447
  Handle<WeakCell> weak_cell2 = FinalizationRegistryRegister(
      finalization_registry, js_object, holdings2, token1, isolate);
448

449 450
  NullifyWeakCell(weak_cell1, isolate);
  NullifyWeakCell(weak_cell2, isolate);
451

452 453 454
  // Nullifying doesn't affect the key chains (just moves WeakCells from
  // active_cells to cleared_cells).
  {
455
    SimpleNumberDictionary key_map =
456
        SimpleNumberDictionary::cast(finalization_registry->key_map());
457
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell2,
458 459
                           *weak_cell1);
  }
460

461
  Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate);
462
  CHECK_EQ(cleared1, *holdings2);
463 464

  {
465
    SimpleNumberDictionary key_map =
466
        SimpleNumberDictionary::cast(finalization_registry->key_map());
467
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1);
468 469
  }

470
  Object cleared2 = PopClearedCellHoldings(finalization_registry, isolate);
471
  CHECK_EQ(cleared2, *holdings1);
472 473

  {
474
    SimpleNumberDictionary key_map =
475
        SimpleNumberDictionary::cast(finalization_registry->key_map());
476
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 0);
477 478 479 480 481 482 483 484
  }
}

TEST(TestUnregisterActiveCells) {
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
485 486
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
487 488 489
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

490 491
  Handle<JSObject> token1 = CreateKey("token1", isolate);
  Handle<JSObject> token2 = CreateKey("token2", isolate);
492 493 494
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

495 496 497 498
  Handle<WeakCell> weak_cell1a = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token1, isolate);
  Handle<WeakCell> weak_cell1b = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token1, isolate);
499

500 501 502 503
  Handle<WeakCell> weak_cell2a = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token2, isolate);
  Handle<WeakCell> weak_cell2b = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token2, isolate);
504

505
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 4,
506
                      *weak_cell2b, *weak_cell2a, *weak_cell1b, *weak_cell1a);
507
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0);
508
  {
509
    SimpleNumberDictionary key_map =
510
        SimpleNumberDictionary::cast(finalization_registry->key_map());
511
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b,
512
                           *weak_cell1a);
513
    VerifyWeakCellKeyChain(isolate, key_map, *token2, 2, *weak_cell2b,
514 515 516
                           *weak_cell2a);
  }

517
  JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate);
518
  {
519
    SimpleNumberDictionary key_map =
520
        SimpleNumberDictionary::cast(finalization_registry->key_map());
521 522
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 0);
    VerifyWeakCellKeyChain(isolate, key_map, *token2, 2, *weak_cell2b,
523 524
                           *weak_cell2a);
  }
525

526
  // Both weak_cell1a and weak_cell1b removed from active_cells.
527
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 2,
528
                      *weak_cell2b, *weak_cell2a);
529
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0);
530 531
}

532
TEST(TestUnregisterActiveAndClearedCells) {
533 534 535 536
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
537 538
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
539 540 541
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

542 543
  Handle<JSObject> token1 = CreateKey("token1", isolate);
  Handle<JSObject> token2 = CreateKey("token2", isolate);
544 545
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);
546

547 548 549 550
  Handle<WeakCell> weak_cell1a = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token1, isolate);
  Handle<WeakCell> weak_cell1b = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token1, isolate);
551

552 553 554 555
  Handle<WeakCell> weak_cell2a = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token2, isolate);
  Handle<WeakCell> weak_cell2b = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token2, isolate);
556

557
  NullifyWeakCell(weak_cell2a, isolate);
558

559
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 3,
560
                      *weak_cell2b, *weak_cell1b, *weak_cell1a);
561
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 1,
562 563
                      *weak_cell2a);
  {
564
    SimpleNumberDictionary key_map =
565
        SimpleNumberDictionary::cast(finalization_registry->key_map());
566
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b,
567
                           *weak_cell1a);
568
    VerifyWeakCellKeyChain(isolate, key_map, *token2, 2, *weak_cell2b,
569 570 571
                           *weak_cell2a);
  }

572
  JSFinalizationRegistry::Unregister(finalization_registry, token2, isolate);
573

574
  // Both weak_cell2a and weak_cell2b removed.
575
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 2,
576
                      *weak_cell1b, *weak_cell1a);
577
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0);
578
  {
579
    SimpleNumberDictionary key_map =
580
        SimpleNumberDictionary::cast(finalization_registry->key_map());
581
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b,
582
                           *weak_cell1a);
583
    VerifyWeakCellKeyChain(isolate, key_map, *token2, 0);
584
  }
585 586
}

587
TEST(TestWeakCellUnregisterTwice) {
588 589 590 591
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
592 593
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
594 595 596
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

597
  Handle<JSObject> token1 = CreateKey("token1", isolate);
598 599 600
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

601 602
  Handle<WeakCell> weak_cell1 = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token1, isolate);
603

604
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 1,
605
                      *weak_cell1);
606
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0);
607
  {
608
    SimpleNumberDictionary key_map =
609
        SimpleNumberDictionary::cast(finalization_registry->key_map());
610
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 1, *weak_cell1);
611 612
  }

613
  JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate);
614

615 616
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0);
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0);
617
  {
618
    SimpleNumberDictionary key_map =
619
        SimpleNumberDictionary::cast(finalization_registry->key_map());
620
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 0);
621 622
  }

623
  JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate);
624

625 626
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0);
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0);
627
  {
628
    SimpleNumberDictionary key_map =
629
        SimpleNumberDictionary::cast(finalization_registry->key_map());
630
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 0);
631
  }
632 633
}

634
TEST(TestWeakCellUnregisterPopped) {
635 636 637
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
638
  Factory* factory = isolate->factory();
639
  HandleScope outer_scope(isolate);
640 641
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
642 643
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());
644
  Handle<JSObject> token1 = CreateKey("token1", isolate);
645
  Handle<Object> holdings1 = factory->NewStringFromAsciiChecked("holdings1");
646 647
  Handle<WeakCell> weak_cell1 = FinalizationRegistryRegister(
      finalization_registry, js_object, holdings1, token1, isolate);
648 649 650

  NullifyWeakCell(weak_cell1, isolate);

651
  CHECK(finalization_registry->NeedsCleanup());
652
  Object cleared1 = PopClearedCellHoldings(finalization_registry, isolate);
653 654
  CHECK_EQ(cleared1, *holdings1);

655 656
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0);
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0);
657
  {
658
    SimpleNumberDictionary key_map =
659
        SimpleNumberDictionary::cast(finalization_registry->key_map());
660
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 0);
661 662
  }

663
  JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate);
664

665 666
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 0);
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 0);
667
  {
668
    SimpleNumberDictionary key_map =
669
        SimpleNumberDictionary::cast(finalization_registry->key_map());
670
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 0);
671 672 673 674 675 676 677 678
  }
}

TEST(TestWeakCellUnregisterNonexistentKey) {
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
679 680
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
681
  Handle<JSObject> token1 = CreateKey("token1", isolate);
682

683
  JSFinalizationRegistry::Unregister(finalization_registry, token1, isolate);
684 685
}

686 687 688 689 690 691 692 693 694 695 696 697 698
TEST(TestJSWeakRef) {
  CcTest::InitializeVM();
  LocalContext context;

  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
  Handle<JSWeakRef> weak_ref;
  {
    HandleScope inner_scope(isolate);

    Handle<JSObject> js_object =
        isolate->factory()->NewJSObject(isolate->object_function());
    // This doesn't add the target into the KeepDuringJob set.
699
    Handle<JSWeakRef> inner_weak_ref = ConstructJSWeakRef(js_object, isolate);
700 701

    CcTest::CollectAllGarbage();
702
    CHECK(!inner_weak_ref->target().IsUndefined(isolate));
703 704 705 706

    weak_ref = inner_scope.CloseAndEscape(inner_weak_ref);
  }

707
  CHECK(!weak_ref->target().IsUndefined(isolate));
708 709 710

  CcTest::CollectAllGarbage();

711
  CHECK(weak_ref->target().IsUndefined(isolate));
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
}

TEST(TestJSWeakRefIncrementalMarking) {
  if (!FLAG_incremental_marking) {
    return;
  }
  ManualGCScope manual_gc_scope;
  CcTest::InitializeVM();
  LocalContext context;

  Isolate* isolate = CcTest::i_isolate();
  Heap* heap = isolate->heap();
  HandleScope outer_scope(isolate);
  Handle<JSWeakRef> weak_ref;
  {
    HandleScope inner_scope(isolate);

    Handle<JSObject> js_object =
        isolate->factory()->NewJSObject(isolate->object_function());
    // This doesn't add the target into the KeepDuringJob set.
732
    Handle<JSWeakRef> inner_weak_ref = ConstructJSWeakRef(js_object, isolate);
733 734 735

    heap::SimulateIncrementalMarking(heap, true);
    CcTest::CollectAllGarbage();
736
    CHECK(!inner_weak_ref->target().IsUndefined(isolate));
737 738 739 740

    weak_ref = inner_scope.CloseAndEscape(inner_weak_ref);
  }

741
  CHECK(!weak_ref->target().IsUndefined(isolate));
742 743 744 745

  heap::SimulateIncrementalMarking(heap, true);
  CcTest::CollectAllGarbage();

746
  CHECK(weak_ref->target().IsUndefined(isolate));
747 748 749 750 751 752 753 754
}

TEST(TestJSWeakRefKeepDuringJob) {
  CcTest::InitializeVM();
  LocalContext context;

  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
755 756 757 758
  Handle<JSWeakRef> weak_ref = MakeWeakRefAndKeepDuringJob(isolate);
  CHECK(!weak_ref->target().IsUndefined(isolate));
  CcTest::CollectAllGarbage();
  CHECK(!weak_ref->target().IsUndefined(isolate));
759

760 761 762 763
  // Clears the KeepDuringJob set.
  context->GetIsolate()->ClearKeptObjects();
  CcTest::CollectAllGarbage();
  CHECK(weak_ref->target().IsUndefined(isolate));
764

765 766 767
  weak_ref = MakeWeakRefAndKeepDuringJob(isolate);
  CHECK(!weak_ref->target().IsUndefined(isolate));
  CcTest::CollectAllGarbage();
768
  CHECK(!weak_ref->target().IsUndefined(isolate));
769

770 771
  // ClearKeptObjects should be called by PerformMicrotasksCheckpoint.
  CcTest::isolate()->PerformMicrotaskCheckpoint();
772
  CcTest::CollectAllGarbage();
773
  CHECK(weak_ref->target().IsUndefined(isolate));
774

775
  weak_ref = MakeWeakRefAndKeepDuringJob(isolate);
776
  CHECK(!weak_ref->target().IsUndefined(isolate));
777
  CcTest::CollectAllGarbage();
778
  CHECK(!weak_ref->target().IsUndefined(isolate));
779

780 781 782
  // ClearKeptObjects should be called by MicrotasksScope::PerformCheckpoint.
  v8::MicrotasksScope::PerformCheckpoint(CcTest::isolate());
  CcTest::CollectAllGarbage();
783
  CHECK(weak_ref->target().IsUndefined(isolate));
784 785 786 787 788 789 790 791 792 793 794 795 796
}

TEST(TestJSWeakRefKeepDuringJobIncrementalMarking) {
  if (!FLAG_incremental_marking) {
    return;
  }
  ManualGCScope manual_gc_scope;
  CcTest::InitializeVM();
  LocalContext context;

  Isolate* isolate = CcTest::i_isolate();
  Heap* heap = isolate->heap();
  HandleScope outer_scope(isolate);
797
  Handle<JSWeakRef> weak_ref = MakeWeakRefAndKeepDuringJob(isolate);
798

799
  CHECK(!weak_ref->target().IsUndefined(isolate));
800 801 802 803

  heap::SimulateIncrementalMarking(heap, true);
  CcTest::CollectAllGarbage();

804
  CHECK(!weak_ref->target().IsUndefined(isolate));
805 806

  // Clears the KeepDuringJob set.
807
  context->GetIsolate()->ClearKeptObjects();
808 809 810
  heap::SimulateIncrementalMarking(heap, true);
  CcTest::CollectAllGarbage();

811
  CHECK(weak_ref->target().IsUndefined(isolate));
812 813
}

814 815 816 817 818
TEST(TestRemoveUnregisterToken) {
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
819 820
  Handle<JSFinalizationRegistry> finalization_registry =
      ConstructJSFinalizationRegistry(isolate);
821 822 823 824 825
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  Handle<JSObject> token1 = CreateKey("token1", isolate);
  Handle<JSObject> token2 = CreateKey("token2", isolate);
826
  Handle<HeapObject> undefined =
827 828
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

829 830 831 832
  Handle<WeakCell> weak_cell1a = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token1, isolate);
  Handle<WeakCell> weak_cell1b = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token1, isolate);
833

834 835 836 837
  Handle<WeakCell> weak_cell2a = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token2, isolate);
  Handle<WeakCell> weak_cell2b = FinalizationRegistryRegister(
      finalization_registry, js_object, undefined, token2, isolate);
838 839 840

  NullifyWeakCell(weak_cell2a, isolate);

841
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 3,
842
                      *weak_cell2b, *weak_cell1b, *weak_cell1a);
843
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 1,
844 845 846
                      *weak_cell2a);
  {
    SimpleNumberDictionary key_map =
847
        SimpleNumberDictionary::cast(finalization_registry->key_map());
848 849 850 851 852 853
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b,
                           *weak_cell1a);
    VerifyWeakCellKeyChain(isolate, key_map, *token2, 2, *weak_cell2b,
                           *weak_cell2a);
  }

854
  finalization_registry->RemoveUnregisterToken(
855 856 857 858 859 860 861
      JSReceiver::cast(*token2), isolate,
      [undefined](WeakCell matched_cell) {
        matched_cell.set_unregister_token(*undefined);
      },
      [](HeapObject, ObjectSlot, Object) {});

  // Both weak_cell2a and weak_cell2b remain on the weak cell chains.
862
  VerifyWeakCellChain(isolate, finalization_registry->active_cells(), 3,
863
                      *weak_cell2b, *weak_cell1b, *weak_cell1a);
864
  VerifyWeakCellChain(isolate, finalization_registry->cleared_cells(), 1,
865 866 867 868 869
                      *weak_cell2a);

  // But both weak_cell2a and weak_cell2b are removed from the key chain.
  {
    SimpleNumberDictionary key_map =
870
        SimpleNumberDictionary::cast(finalization_registry->key_map());
871 872 873 874 875 876
    VerifyWeakCellKeyChain(isolate, key_map, *token1, 2, *weak_cell1b,
                           *weak_cell1a);
    VerifyWeakCellKeyChain(isolate, key_map, *token2, 0);
  }
}

877
TEST(JSWeakRefScavengedInWorklist) {
878
  if (!FLAG_incremental_marking || FLAG_single_generation) {
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
    return;
  }

  ManualGCScope manual_gc_scope;
  CcTest::InitializeVM();
  Isolate* isolate = CcTest::i_isolate();
  Heap* heap = isolate->heap();

  {
    HandleScope outer_scope(isolate);
    Handle<JSWeakRef> weak_ref;

    // Make a WeakRef that points to a target, both of which become unreachable.
    {
      HandleScope inner_scope(isolate);
      Handle<JSObject> js_object =
          isolate->factory()->NewJSObject(isolate->object_function());
      Handle<JSWeakRef> inner_weak_ref = ConstructJSWeakRef(js_object, isolate);
      CHECK(Heap::InYoungGeneration(*js_object));
      CHECK(Heap::InYoungGeneration(*inner_weak_ref));

      weak_ref = inner_scope.CloseAndEscape(inner_weak_ref);
    }

903 904 905 906 907
    // Store weak_ref in Global such that it is part of the root set when
    // starting incremental marking.
    v8::Global<Value> global_weak_ref(
        CcTest::isolate(), Utils::ToLocal(Handle<Object>::cast(weak_ref)));

908 909
    // Do marking. This puts the WeakRef above into the js_weak_refs worklist
    // since its target isn't marked.
910 911
    CHECK(
        heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty());
912
    heap::SimulateIncrementalMarking(heap, true);
913
    heap->mark_compact_collector()->local_weak_objects()->Publish();
914 915 916
    CHECK(!heap->mark_compact_collector()
               ->weak_objects()
               ->js_weak_refs.IsEmpty());
917 918 919 920
  }

  // Now collect both weak_ref and its target. The worklist should be empty.
  CcTest::CollectGarbage(NEW_SPACE);
921
  CHECK(heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty());
922 923 924 925 926 927

  // The mark-compactor shouldn't see zapped WeakRefs in the worklist.
  CcTest::CollectAllGarbage();
}

TEST(JSWeakRefTenuredInWorklist) {
928 929
  if (!FLAG_incremental_marking || FLAG_single_generation ||
      FLAG_separate_gc_phases) {
930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951
    return;
  }

  ManualGCScope manual_gc_scope;
  CcTest::InitializeVM();
  Isolate* isolate = CcTest::i_isolate();
  Heap* heap = isolate->heap();

  HandleScope outer_scope(isolate);
  Handle<JSWeakRef> weak_ref;

  // Make a WeakRef that points to a target. The target becomes unreachable.
  {
    HandleScope inner_scope(isolate);
    Handle<JSObject> js_object =
        isolate->factory()->NewJSObject(isolate->object_function());
    Handle<JSWeakRef> inner_weak_ref = ConstructJSWeakRef(js_object, isolate);
    CHECK(Heap::InYoungGeneration(*js_object));
    CHECK(Heap::InYoungGeneration(*inner_weak_ref));

    weak_ref = inner_scope.CloseAndEscape(inner_weak_ref);
  }
952 953 954 955
  // Store weak_ref such that it is part of the root set when starting
  // incremental marking.
  v8::Global<Value> global_weak_ref(
      CcTest::isolate(), Utils::ToLocal(Handle<Object>::cast(weak_ref)));
956 957 958 959
  JSWeakRef old_weak_ref_location = *weak_ref;

  // Do marking. This puts the WeakRef above into the js_weak_refs worklist
  // since its target isn't marked.
960
  CHECK(heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty());
961
  heap::SimulateIncrementalMarking(heap, true);
962
  heap->mark_compact_collector()->local_weak_objects()->Publish();
963 964
  CHECK(
      !heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty());
965 966 967 968 969 970

  // Now collect weak_ref's target. We still have a Handle to weak_ref, so it is
  // moved and remains on the worklist.
  CcTest::CollectGarbage(NEW_SPACE);
  JSWeakRef new_weak_ref_location = *weak_ref;
  CHECK_NE(old_weak_ref_location, new_weak_ref_location);
971 972
  CHECK(
      !heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty());
973 974 975

  // The mark-compactor should see the moved WeakRef in the worklist.
  CcTest::CollectAllGarbage();
976
  CHECK(heap->mark_compact_collector()->weak_objects()->js_weak_refs.IsEmpty());
977 978 979
  CHECK(weak_ref->target().IsUndefined(isolate));
}

980 981 982 983 984 985 986 987 988 989 990 991 992
TEST(UnregisterTokenHeapVerifier) {
  if (!FLAG_incremental_marking) return;
  ManualGCScope manual_gc_scope;
#ifdef VERIFY_HEAP
  FLAG_verify_heap = true;
#endif

  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  Heap* heap = CcTest::heap();
  v8::HandleScope outer_scope(isolate);

  {
993 994
    // Make a new FinalizationRegistry and register two objects with the same
    // unregister token that's unreachable after the IIFE returns.
995 996 997 998 999
    v8::HandleScope scope(isolate);
    CompileRun(
        "var token = {}; "
        "var registry = new FinalizationRegistry(function ()  {}); "
        "(function () { "
1000 1001 1002 1003
        "  let o1 = {}; "
        "  let o2 = {}; "
        "  registry.register(o1, {}, token); "
        "  registry.register(o2, {}, token); "
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
        "})();");
  }

  // GC so the WeakCell corresponding to o is moved from the active_cells to
  // cleared_cells.
  CcTest::CollectAllGarbage();
  CcTest::CollectAllGarbage();

  {
    // Override the unregister token to make the original object collectible.
    v8::HandleScope scope(isolate);
    CompileRun("token = 0;");
  }

  heap::SimulateIncrementalMarking(heap, true);

  // Pump message loop to run the finalizer task, then the incremental marking
  // task. The finalizer task will pop the WeakCell from the cleared list. This
  // should make the unregister_token slot undefined. That slot is iterated as a
  // custom weak pointer, so if it is not made undefined, the verifier as part
  // of the incremental marking task will crash.
  EmptyMessageQueues(isolate);
}

1028 1029
}  // namespace internal
}  // namespace v8