// 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.

#include "src/execution/isolate.h"
#include "src/execution/microtask-queue.h"
#include "src/handles/handles-inl.h"
#include "src/heap/factory-inl.h"
#include "src/objects/js-objects.h"
#include "src/objects/js-weak-refs-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"

namespace v8 {
namespace internal {

namespace {

Handle<JSFinalizationGroup> ConstructJSFinalizationGroup(Isolate* isolate) {
  Factory* factory = isolate->factory();
  Handle<String> finalization_group_name =
      factory->NewStringFromStaticChars("FinalizationGroup");
  Handle<Object> global =
      handle(isolate->native_context()->global_object(), isolate);
  Handle<JSFunction> finalization_group_fun = Handle<JSFunction>::cast(
      Object::GetProperty(isolate, global, finalization_group_name)
          .ToHandleChecked());
  auto finalization_group = Handle<JSFinalizationGroup>::cast(
      JSObject::New(finalization_group_fun, finalization_group_fun,
                    Handle<AllocationSite>::null())
          .ToHandleChecked());
#ifdef VERIFY_HEAP
  finalization_group->JSFinalizationGroupVerify(isolate);
#endif  // VERIFY_HEAP
  return finalization_group;
}

Handle<JSWeakRef> ConstructJSWeakRef(Handle<JSReceiver> target,
                                     Isolate* isolate) {
  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;
}

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;
}

Handle<WeakCell> FinalizationGroupRegister(
    Handle<JSFinalizationGroup> finalization_group, Handle<JSObject> target,
    Handle<Object> holdings, Handle<Object> key, Isolate* isolate) {
  JSFinalizationGroup::Register(finalization_group, target, holdings, key,
                                isolate);
  CHECK(finalization_group->active_cells().IsWeakCell());
  Handle<WeakCell> weak_cell =
      handle(WeakCell::cast(finalization_group->active_cells()), isolate);
#ifdef VERIFY_HEAP
  weak_cell->WeakCellVerify(isolate);
#endif  // VERIFY_HEAP
  return weak_cell;
}

Handle<WeakCell> FinalizationGroupRegister(
    Handle<JSFinalizationGroup> finalization_group, Handle<JSObject> target,
    Isolate* isolate) {
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);
  return FinalizationGroupRegister(finalization_group, target, undefined,
                                   undefined, isolate);
}

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

// 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
    CHECK(list_head.IsUndefined(isolate));
  } else {
    WeakCell current = WeakCell::cast(Object(va_arg(args, Address)));
    CHECK_EQ(current, list_head);
    CHECK(current.prev().IsUndefined(isolate));

    for (int i = 1; i < n_args; i++) {
      WeakCell next = WeakCell::cast(Object(va_arg(args, Address)));
      CHECK_EQ(current.next(), next);
      CHECK_EQ(next.prev(), current);
      current = next;
    }
    CHECK(current.next().IsUndefined(isolate));
  }
  va_end(args);
}

// Like VerifyWeakCellChain but verifies the chain created with key_list_prev
// and key_list_next instead of prev and next.
void VerifyWeakCellKeyChain(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
    CHECK(list_head.IsTheHole(isolate));
  } else {
    WeakCell current = WeakCell::cast(Object(va_arg(args, Address)));
    CHECK_EQ(current, list_head);
    CHECK(current.key_list_prev().IsUndefined(isolate));

    for (int i = 1; i < n_args; i++) {
      WeakCell next = WeakCell::cast(Object(va_arg(args, Address)));
      CHECK_EQ(current.key_list_next(), next);
      CHECK_EQ(next.key_list_prev(), current);
      current = next;
    }
    CHECK(current.key_list_next().IsUndefined(isolate));
  }
  va_end(args);
}

}  // namespace

TEST(TestRegister) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  // Register a weak reference and verify internal data structures.
  Handle<WeakCell> weak_cell1 =
      FinalizationGroupRegister(finalization_group, js_object, isolate);

  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 1,
                      *weak_cell1);
  CHECK(weak_cell1->key_list_prev().IsUndefined(isolate));
  CHECK(weak_cell1->key_list_next().IsUndefined(isolate));

  CHECK(finalization_group->cleared_cells().IsUndefined(isolate));

  // No key was used during registration, key-based map stays uninitialized.
  CHECK(finalization_group->key_map().IsUndefined(isolate));

  // Register another weak reference and verify internal data structures.
  Handle<WeakCell> weak_cell2 =
      FinalizationGroupRegister(finalization_group, js_object, isolate);

  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 2,
                      *weak_cell2, *weak_cell1);
  CHECK(weak_cell2->key_list_prev().IsUndefined(isolate));
  CHECK(weak_cell2->key_list_next().IsUndefined(isolate));

  CHECK(finalization_group->cleared_cells().IsUndefined(isolate));
  CHECK(finalization_group->key_map().IsUndefined(isolate));
}

TEST(TestRegisterWithKey) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  Handle<JSObject> key1 = CreateKey("key1", isolate);
  Handle<JSObject> key2 = CreateKey("key2", isolate);
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

  // Register a weak reference with a key and verify internal data structures.
  Handle<WeakCell> weak_cell1 = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key1, isolate);

  {
    CHECK(finalization_group->key_map().IsObjectHashTable());
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 1, *weak_cell1);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key2), 0);
  }

  // Register another weak reference with a different key and verify internal
  // data structures.
  Handle<WeakCell> weak_cell2 = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key2, isolate);

  {
    CHECK(finalization_group->key_map().IsObjectHashTable());
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 1, *weak_cell1);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key2), 1, *weak_cell2);
  }

  // Register another weak reference with key1 and verify internal data
  // structures.
  Handle<WeakCell> weak_cell3 = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key1, isolate);

  {
    CHECK(finalization_group->key_map().IsObjectHashTable());
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 2, *weak_cell3,
                           *weak_cell1);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key2), 1, *weak_cell2);
  }
}

TEST(TestWeakCellNullify1) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  Handle<WeakCell> weak_cell1 =
      FinalizationGroupRegister(finalization_group, js_object, isolate);
  Handle<WeakCell> weak_cell2 =
      FinalizationGroupRegister(finalization_group, js_object, isolate);

  // Nullify the first WeakCell and verify internal data structures.
  NullifyWeakCell(weak_cell1, isolate);
  CHECK_EQ(finalization_group->active_cells(), *weak_cell2);
  CHECK(weak_cell2->prev().IsUndefined(isolate));
  CHECK(weak_cell2->next().IsUndefined(isolate));
  CHECK_EQ(finalization_group->cleared_cells(), *weak_cell1);
  CHECK(weak_cell1->prev().IsUndefined(isolate));
  CHECK(weak_cell1->next().IsUndefined(isolate));

  // Nullify the second WeakCell and verify internal data structures.
  NullifyWeakCell(weak_cell2, isolate);
  CHECK(finalization_group->active_cells().IsUndefined(isolate));
  CHECK_EQ(finalization_group->cleared_cells(), *weak_cell2);
  CHECK_EQ(weak_cell2->next(), *weak_cell1);
  CHECK(weak_cell2->prev().IsUndefined(isolate));
  CHECK_EQ(weak_cell1->prev(), *weak_cell2);
  CHECK(weak_cell1->next().IsUndefined(isolate));
}

TEST(TestWeakCellNullify2) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  Handle<WeakCell> weak_cell1 =
      FinalizationGroupRegister(finalization_group, js_object, isolate);
  Handle<WeakCell> weak_cell2 =
      FinalizationGroupRegister(finalization_group, js_object, isolate);

  // Like TestWeakCellNullify1 but nullify the WeakCells in opposite order.
  NullifyWeakCell(weak_cell2, isolate);
  CHECK_EQ(finalization_group->active_cells(), *weak_cell1);
  CHECK(weak_cell1->prev().IsUndefined(isolate));
  CHECK(weak_cell1->next().IsUndefined(isolate));
  CHECK_EQ(finalization_group->cleared_cells(), *weak_cell2);
  CHECK(weak_cell2->prev().IsUndefined(isolate));
  CHECK(weak_cell2->next().IsUndefined(isolate));

  NullifyWeakCell(weak_cell1, isolate);
  CHECK(finalization_group->active_cells().IsUndefined(isolate));
  CHECK_EQ(finalization_group->cleared_cells(), *weak_cell1);
  CHECK_EQ(weak_cell1->next(), *weak_cell2);
  CHECK(weak_cell1->prev().IsUndefined(isolate));
  CHECK_EQ(weak_cell2->prev(), *weak_cell1);
  CHECK(weak_cell2->next().IsUndefined(isolate));
}

TEST(TestJSFinalizationGroupPopClearedCellHoldings1) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

  Handle<Object> holdings1 = factory->NewStringFromAsciiChecked("holdings1");
  Handle<WeakCell> weak_cell1 = FinalizationGroupRegister(
      finalization_group, js_object, holdings1, undefined, isolate);
  Handle<Object> holdings2 = factory->NewStringFromAsciiChecked("holdings2");
  Handle<WeakCell> weak_cell2 = FinalizationGroupRegister(
      finalization_group, js_object, holdings2, undefined, isolate);
  Handle<Object> holdings3 = factory->NewStringFromAsciiChecked("holdings3");
  Handle<WeakCell> weak_cell3 = FinalizationGroupRegister(
      finalization_group, js_object, holdings3, undefined, isolate);

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

  CHECK(finalization_group->NeedsCleanup());
  Object cleared1 =
      JSFinalizationGroup::PopClearedCellHoldings(finalization_group, isolate);
  CHECK_EQ(cleared1, *holdings3);
  CHECK(weak_cell3->prev().IsUndefined(isolate));
  CHECK(weak_cell3->next().IsUndefined(isolate));

  CHECK(finalization_group->NeedsCleanup());
  Object cleared2 =
      JSFinalizationGroup::PopClearedCellHoldings(finalization_group, isolate);
  CHECK_EQ(cleared2, *holdings2);
  CHECK(weak_cell2->prev().IsUndefined(isolate));
  CHECK(weak_cell2->next().IsUndefined(isolate));

  CHECK(!finalization_group->NeedsCleanup());

  NullifyWeakCell(weak_cell1, isolate);

  CHECK(finalization_group->NeedsCleanup());
  Object cleared3 =
      JSFinalizationGroup::PopClearedCellHoldings(finalization_group, isolate);
  CHECK_EQ(cleared3, *holdings1);
  CHECK(weak_cell1->prev().IsUndefined(isolate));
  CHECK(weak_cell1->next().IsUndefined(isolate));

  CHECK(!finalization_group->NeedsCleanup());
  CHECK(finalization_group->active_cells().IsUndefined(isolate));
  CHECK(finalization_group->cleared_cells().IsUndefined(isolate));
}

TEST(TestJSFinalizationGroupPopClearedCellHoldings2) {
  // Test that when all WeakCells for a key are popped, the key is removed from
  // the key map.
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());
  Handle<JSObject> key1 = CreateKey("key1", isolate);

  Handle<Object> holdings1 = factory->NewStringFromAsciiChecked("holdings1");
  Handle<WeakCell> weak_cell1 = FinalizationGroupRegister(
      finalization_group, js_object, holdings1, key1, isolate);
  Handle<Object> holdings2 = factory->NewStringFromAsciiChecked("holdings2");
  Handle<WeakCell> weak_cell2 = FinalizationGroupRegister(
      finalization_group, js_object, holdings2, key1, isolate);

  NullifyWeakCell(weak_cell1, isolate);
  NullifyWeakCell(weak_cell2, isolate);

  // Nullifying doesn't affect the key chains (just moves WeakCells from
  // active_cells to cleared_cells).
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 2, *weak_cell2,
                           *weak_cell1);
  }

  Object cleared1 =
      JSFinalizationGroup::PopClearedCellHoldings(finalization_group, isolate);
  CHECK_EQ(cleared1, *holdings2);

  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 1, *weak_cell1);
  }

  Object cleared2 =
      JSFinalizationGroup::PopClearedCellHoldings(finalization_group, isolate);
  CHECK_EQ(cleared2, *holdings1);

  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 0);
  }
}

TEST(TestUnregisterActiveCells) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  Handle<JSObject> key1 = CreateKey("key1", isolate);
  Handle<JSObject> key2 = CreateKey("key2", isolate);
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

  Handle<WeakCell> weak_cell1a = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key1, isolate);
  Handle<WeakCell> weak_cell1b = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key1, isolate);

  Handle<WeakCell> weak_cell2a = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key2, isolate);
  Handle<WeakCell> weak_cell2b = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key2, isolate);

  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 4,
                      *weak_cell2b, *weak_cell2a, *weak_cell1b, *weak_cell1a);
  VerifyWeakCellChain(isolate, finalization_group->cleared_cells(), 0);
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 2, *weak_cell1b,
                           *weak_cell1a);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key2), 2, *weak_cell2b,
                           *weak_cell2a);
  }

  JSFinalizationGroup::Unregister(finalization_group, key1, isolate);
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 0);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key2), 2, *weak_cell2b,
                           *weak_cell2a);
  }

  // Both weak_cell1a and weak_cell1b removed from active_cells.
  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 2,
                      *weak_cell2b, *weak_cell2a);
  VerifyWeakCellChain(isolate, finalization_group->cleared_cells(), 0);
}

TEST(TestUnregisterActiveAndClearedCells) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  Handle<JSObject> key1 = CreateKey("key1", isolate);
  Handle<JSObject> key2 = CreateKey("key2", isolate);
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

  Handle<WeakCell> weak_cell1a = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key1, isolate);
  Handle<WeakCell> weak_cell1b = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key1, isolate);

  Handle<WeakCell> weak_cell2a = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key2, isolate);
  Handle<WeakCell> weak_cell2b = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key2, isolate);

  NullifyWeakCell(weak_cell2a, isolate);

  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 3,
                      *weak_cell2b, *weak_cell1b, *weak_cell1a);
  VerifyWeakCellChain(isolate, finalization_group->cleared_cells(), 1,
                      *weak_cell2a);
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 2, *weak_cell1b,
                           *weak_cell1a);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key2), 2, *weak_cell2b,
                           *weak_cell2a);
  }

  JSFinalizationGroup::Unregister(finalization_group, key2, isolate);

  // Both weak_cell2a and weak_cell2b removed.
  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 2,
                      *weak_cell1b, *weak_cell1a);
  VerifyWeakCellChain(isolate, finalization_group->cleared_cells(), 0);
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 2, *weak_cell1b,
                           *weak_cell1a);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key2), 0);
  }
}

TEST(TestWeakCellUnregisterTwice) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  Handle<JSObject> key1 = CreateKey("key1", isolate);
  Handle<Object> undefined =
      handle(ReadOnlyRoots(isolate).undefined_value(), isolate);

  Handle<WeakCell> weak_cell1 = FinalizationGroupRegister(
      finalization_group, js_object, undefined, key1, isolate);

  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 1,
                      *weak_cell1);
  VerifyWeakCellChain(isolate, finalization_group->cleared_cells(), 0);
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 1, *weak_cell1);
  }

  JSFinalizationGroup::Unregister(finalization_group, key1, isolate);

  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 0);
  VerifyWeakCellChain(isolate, finalization_group->cleared_cells(), 0);
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 0);
  }

  JSFinalizationGroup::Unregister(finalization_group, key1, isolate);

  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 0);
  VerifyWeakCellChain(isolate, finalization_group->cleared_cells(), 0);
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 0);
  }
}

TEST(TestWeakCellUnregisterPopped) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> js_object =
      isolate->factory()->NewJSObject(isolate->object_function());
  Handle<JSObject> key1 = CreateKey("key1", isolate);
  Handle<Object> holdings1 = factory->NewStringFromAsciiChecked("holdings1");
  Handle<WeakCell> weak_cell1 = FinalizationGroupRegister(
      finalization_group, js_object, holdings1, key1, isolate);

  NullifyWeakCell(weak_cell1, isolate);

  CHECK(finalization_group->NeedsCleanup());
  Object cleared1 =
      JSFinalizationGroup::PopClearedCellHoldings(finalization_group, isolate);
  CHECK_EQ(cleared1, *holdings1);

  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 0);
  VerifyWeakCellChain(isolate, finalization_group->cleared_cells(), 0);
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 0);
  }

  JSFinalizationGroup::Unregister(finalization_group, key1, isolate);

  VerifyWeakCellChain(isolate, finalization_group->active_cells(), 0);
  VerifyWeakCellChain(isolate, finalization_group->cleared_cells(), 0);
  {
    Handle<ObjectHashTable> key_map =
        handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
    VerifyWeakCellKeyChain(isolate, key_map->Lookup(key1), 0);
  }
}

TEST(TestWeakCellUnregisterNonexistentKey) {
  FLAG_harmony_weak_refs = true;
  CcTest::InitializeVM();
  LocalContext context;
  Isolate* isolate = CcTest::i_isolate();
  HandleScope outer_scope(isolate);
  Handle<JSFinalizationGroup> finalization_group =
      ConstructJSFinalizationGroup(isolate);
  Handle<JSObject> key1 = CreateKey("key1", isolate);

  JSFinalizationGroup::Unregister(finalization_group, key1, isolate);
}

TEST(TestJSWeakRef) {
  FLAG_harmony_weak_refs = true;
  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.
    Handle<JSWeakRef> inner_weak_ref = ConstructJSWeakRef(js_object, isolate);

    CcTest::CollectAllGarbage();
    CHECK(!inner_weak_ref->target().IsUndefined(isolate));

    weak_ref = inner_scope.CloseAndEscape(inner_weak_ref);
  }

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

  CcTest::CollectAllGarbage();

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

TEST(TestJSWeakRefIncrementalMarking) {
  FLAG_harmony_weak_refs = true;
  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.
    Handle<JSWeakRef> inner_weak_ref = ConstructJSWeakRef(js_object, isolate);

    heap::SimulateIncrementalMarking(heap, true);
    CcTest::CollectAllGarbage();
    CHECK(!inner_weak_ref->target().IsUndefined(isolate));

    weak_ref = inner_scope.CloseAndEscape(inner_weak_ref);
  }

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

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

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

TEST(TestJSWeakRefKeepDuringJob) {
  FLAG_harmony_weak_refs = true;
  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());
    Handle<JSWeakRef> inner_weak_ref = ConstructJSWeakRef(js_object, isolate);
    heap->KeepDuringJob(js_object);

    weak_ref = inner_scope.CloseAndEscape(inner_weak_ref);
  }

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

  CcTest::CollectAllGarbage();

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

  // Clears the KeepDuringJob set.
  context->GetIsolate()->ClearKeptObjects();
  CcTest::CollectAllGarbage();

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

TEST(TestJSWeakRefKeepDuringJobIncrementalMarking) {
  FLAG_harmony_weak_refs = true;
  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());
    Handle<JSWeakRef> inner_weak_ref = ConstructJSWeakRef(js_object, isolate);
    heap->KeepDuringJob(js_object);

    weak_ref = inner_scope.CloseAndEscape(inner_weak_ref);
  }

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

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

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

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

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

}  // namespace internal
}  // namespace v8
