summaryrefslogtreecommitdiffstats
path: root/js/src/gc/FinalizationRegistry.cpp
blob: 1d1575972f51290e466d3b12aecedba6f350c39e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Finalization registry GC implementation.
 */

#include "builtin/FinalizationRegistryObject.h"
#include "gc/GCRuntime.h"
#include "gc/Zone.h"
#include "vm/JSContext.h"

#include "gc/PrivateIterators-inl.h"
#include "vm/JSObject-inl.h"

using namespace js;
using namespace js::gc;

bool GCRuntime::addFinalizationRegistry(JSContext* cx,
                                        FinalizationRegistryObject* registry) {
  if (!cx->zone()->finalizationRegistries().put(registry)) {
    ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

bool GCRuntime::registerWithFinalizationRegistry(JSContext* cx,
                                                 HandleObject target,
                                                 HandleObject record) {
  MOZ_ASSERT(!IsCrossCompartmentWrapper(target));
  MOZ_ASSERT(
      UncheckedUnwrapWithoutExpose(record)->is<FinalizationRecordObject>());
  MOZ_ASSERT(target->compartment() == record->compartment());

  auto& map = target->zone()->finalizationRecordMap();
  auto ptr = map.lookupForAdd(target);
  if (!ptr) {
    if (!map.add(ptr, target, FinalizationRecordVector(target->zone()))) {
      ReportOutOfMemory(cx);
      return false;
    }
  }
  if (!ptr->value().append(record)) {
    ReportOutOfMemory(cx);
    return false;
  }
  return true;
}

void GCRuntime::markFinalizationRegistryRoots(JSTracer* trc) {
  // All finalization records stored in the zone maps are marked as roots.
  // Records can be removed from these maps during sweeping in which case they
  // die in the next collection.
  for (GCZonesIter zone(this); !zone.done(); zone.next()) {
    Zone::FinalizationRecordMap& map = zone->finalizationRecordMap();
    for (Zone::FinalizationRecordMap::Enum e(map); !e.empty(); e.popFront()) {
      e.front().value().trace(trc);
    }
  }
}

static FinalizationRecordObject* UnwrapFinalizationRecord(JSObject* obj) {
  obj = UncheckedUnwrapWithoutExpose(obj);
  if (!obj->is<FinalizationRecordObject>()) {
    MOZ_ASSERT(JS_IsDeadWrapper(obj));
    // CCWs between the compartments have been nuked. The
    // FinalizationRegistry's callback doesn't run in this case.
    return nullptr;
  }
  return &obj->as<FinalizationRecordObject>();
}

void GCRuntime::sweepFinalizationRegistries(Zone* zone) {
  // Sweep finalization registry data and queue finalization records for cleanup
  // for any entries whose target is dying and remove them from the map.

  Zone::FinalizationRegistrySet& set = zone->finalizationRegistries();
  for (Zone::FinalizationRegistrySet::Enum e(set); !e.empty(); e.popFront()) {
    if (IsAboutToBeFinalized(&e.mutableFront())) {
      e.front()->as<FinalizationRegistryObject>().queue()->setHasRegistry(
          false);
      e.removeFront();
    } else {
      e.front()->as<FinalizationRegistryObject>().sweep();
    }
  }

  Zone::FinalizationRecordMap& map = zone->finalizationRecordMap();
  for (Zone::FinalizationRecordMap::Enum e(map); !e.empty(); e.popFront()) {
    FinalizationRecordVector& records = e.front().value();

    // Update any pointers moved by the GC.
    records.sweep();

    // Sweep finalization records and remove records for:
    records.eraseIf([](JSObject* obj) {
      FinalizationRecordObject* record = UnwrapFinalizationRecord(obj);
      return !record ||                        // Nuked CCW to record.
             !record->isActive() ||            // Unregistered record.
             !record->queue()->hasRegistry();  // Dead finalization registry.
    });

    // Queue finalization records for targets that are dying.
    if (IsAboutToBeFinalized(&e.front().mutableKey())) {
      for (JSObject* obj : records) {
        FinalizationRecordObject* record = UnwrapFinalizationRecord(obj);
        FinalizationQueueObject* queue = record->queue();
        queue->queueRecordToBeCleanedUp(record);
        queueFinalizationRegistryForCleanup(queue);
      }
      e.removeFront();
    }
  }
}

void GCRuntime::queueFinalizationRegistryForCleanup(
    FinalizationQueueObject* queue) {
  // Prod the embedding to call us back later to run the finalization callbacks,
  // if necessary.

  if (queue->isQueuedForCleanup()) {
    return;
  }

  // Derive the incumbent global by unwrapping the incumbent global object and
  // then getting its global.
  JSObject* object = UncheckedUnwrapWithoutExpose(queue->incumbentObject());
  MOZ_ASSERT(object);
  GlobalObject* incumbentGlobal = &object->nonCCWGlobal();

  callHostCleanupFinalizationRegistryCallback(queue->doCleanupFunction(),
                                              incumbentGlobal);

  queue->setQueuedForCleanup(true);
}