summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/FinalizationRegistryObject.h
blob: 288ebea424cf7c5c0f75611f1dd8376b96d6c819 (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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/* -*- 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/. */

/*
 * FinalizationRegistry objects allow a program to register to receive a
 * callback after a 'target' object dies. The callback is passed a 'held value'
 * (that hopefully doesn't entrain the target). An 'unregister token' is an
 * object which can be used to remove multiple previous registrations in one go.
 *
 * To arrange this, the following data structures are used:
 *
 *   +---------------------------------------+-------------------------------+
 *   |   FinalizationRegistry compartment    |   Target zone / compartment   |
 *   |                                       |                               |
 *   |        +----------------------+       |     +------------------+      |
 *   |  +-----+ FinalizationRegistry |       |     |       Zone       |      |
 *   |  |     +----------+-----------+       |     +---------+--------+      |
 *   |  |                |                   |               |               |
 *   |  |                v                   |               v               |
 *   |  |  +-------------+-------------+     | +-------------+------------+  |
 *   |  |  |       Registrations       |     | |  FinalizationObservers   |  |
 *   |  |  |         weak map          |     | +-------------+------------+  |
 *   |  |  +---------------------------+     |               |               |
 *   |  |  | Unregister  :   Records   |     |               v               |
 *   |  |  |   token     :   object    |     |  +------------+------------+  |
 *   |  |  +--------------------+------+     |  |      RecordMap map      |  |
 *   |  |                       |            |  +-------------------------+  |
 *   |  |                       v            |  |  Target  : Finalization |  |
 *   |  |  +--------------------+------+     |  |  object  : RecordVector |  |
 *   |  |  |       Finalization        |     |  +----+-------------+------+  |
 *   |  |  |    RegistrationsObject    |     |       |             |         |
 *   |  |  +---------------------------+     |       v             v         |
 *   |  |  |       RecordVector        |     |  +----+-----+  +----+-----+   |
 *   |  |  +-------------+-------------+     |  |  Target  |  | (CCW if  |   |
 *   |  |                |                   |  | JSObject |  |  needed) |   |
 *   |  |              * v                   |  +----------+  +----+-----+   |
 *   |  |  +-------------+-------------+ *   |                     |         |
 *   |  |  | FinalizationRecordObject  +<--------------------------+         |
 *   |  |  +---------------------------+     |                               |
 *   |  |  | Queue                     +--+  |                               |
 *   |  |  +---------------------------+  |  |                               |
 *   |  |  | Held value                |  |  |                               |
 *   |  |  +---------------------------+  |  |                               |
 *   |  |                                 |  |                               |
 *   |  +--------------+   +--------------+  |                               |
 *   |                 |   |                 |                               |
 *   |                 v   v                 |                               |
 *   |      +----------+---+----------+      |                               |
 *   |      | FinalizationQueueObject |      |                               |
 *   |      +-------------------------+      |                               |
 *   |                                       |                               |
 *   +---------------------------------------+-------------------------------+
 *
 * A FinalizationRegistry consists of two parts: the FinalizationRegistry that
 * consumers see and a FinalizationQueue used internally to queue and call the
 * cleanup callbacks.
 *
 * Registering a target with a FinalizationRegistry creates a FinalizationRecord
 * containing a pointer to the queue and the heldValue. This is added to a
 * vector of records associated with the target, implemented as a map on the
 * target's Zone. All finalization records are treated as GC roots.
 *
 * When a target is registered an unregister token may be supplied. If so, this
 * is also recorded by the registry and is stored in a weak map of
 * registrations. The values of this map are FinalizationRegistrationsObject
 * objects. It's necessary to have another JSObject here because our weak map
 * implementation only supports JS types as values.
 *
 * When targets are unregistered, the registration is looked up in the weakmap
 * and the corresponding records are cleared.

 * The finalization record maps are swept during GC to check for records that
 * have been cleared by unregistration, for FinalizationRecords that are dead
 * and for nuked CCWs. In all cases the record is removed and the cleanup
 * callback is not run.
 *
 * Following this the targets are checked to see if they are dying. For such
 * targets the associated record list is processed and for each record the
 * heldValue is queued on the FinalizationQueue. At a later time this causes the
 * client's cleanup callback to be run.
 */

#ifndef builtin_FinalizationRegistryObject_h
#define builtin_FinalizationRegistryObject_h

#include "gc/Barrier.h"
#include "js/GCVector.h"
#include "vm/NativeObject.h"

namespace js {

class FinalizationRegistryObject;
class FinalizationRecordObject;
class FinalizationQueueObject;
class ObjectWeakMap;

using HandleFinalizationRegistryObject = Handle<FinalizationRegistryObject*>;
using HandleFinalizationRecordObject = Handle<FinalizationRecordObject*>;
using HandleFinalizationQueueObject = Handle<FinalizationQueueObject*>;
using RootedFinalizationRegistryObject = Rooted<FinalizationRegistryObject*>;
using RootedFinalizationRecordObject = Rooted<FinalizationRecordObject*>;
using RootedFinalizationQueueObject = Rooted<FinalizationQueueObject*>;

// A finalization record: a pair of finalization queue and held value.
//
// A finalization record represents the registered interest of a finalization
// registry in a target's finalization.
//
// Finalization records created in the 'registered' state but may be
// unregistered. This happens when:
//  - the heldValue is passed to the registry's cleanup callback
//  - the registry's unregister method removes the registration
//
// Finalization records are added to a per-zone record map. They are removed
// when the record is queued for cleanup, or if the interest in finalization is
// cancelled. See FinalizationObservers::shouldRemoveRecord for the possible
// reasons.

class FinalizationRecordObject : public NativeObject {
  enum { QueueSlot = 0, HeldValueSlot, InMapSlot, SlotCount };

 public:
  static const JSClass class_;

  static FinalizationRecordObject* create(JSContext* cx,
                                          HandleFinalizationQueueObject queue,
                                          HandleValue heldValue);

  FinalizationQueueObject* queue() const;
  Value heldValue() const;
  bool isRegistered() const;
  bool isInRecordMap() const;

  void setInRecordMap(bool newValue);
  void clear();
};

// A vector of weakly-held FinalizationRecordObjects.
using WeakFinalizationRecordVector =
    GCVector<WeakHeapPtr<FinalizationRecordObject*>, 1, js::CellAllocPolicy>;

// A JS object containing a vector of weakly-held FinalizationRecordObjects,
// which holds the records corresponding to the registrations for a particular
// registration token. These are used as the values in the registration
// weakmap. Since the contents of the vector are weak references they are not
// traced.
class FinalizationRegistrationsObject : public NativeObject {
  enum { RecordsSlot = 0, SlotCount };

 public:
  static const JSClass class_;

  static FinalizationRegistrationsObject* create(JSContext* cx);

  WeakFinalizationRecordVector* records();
  const WeakFinalizationRecordVector* records() const;

  bool isEmpty() const;

  bool append(HandleFinalizationRecordObject record);
  void remove(HandleFinalizationRecordObject record);

  bool traceWeak(JSTracer* trc);

 private:
  static const JSClassOps classOps_;

  void* privatePtr() const;

  static void trace(JSTracer* trc, JSObject* obj);
  static void finalize(JS::GCContext* gcx, JSObject* obj);
};

using FinalizationRecordVector =
    GCVector<HeapPtr<FinalizationRecordObject*>, 1, js::CellAllocPolicy>;

// The JS FinalizationRegistry object itself.
class FinalizationRegistryObject : public NativeObject {
  enum { QueueSlot = 0, RegistrationsSlot, SlotCount };

 public:
  static const JSClass class_;
  static const JSClass protoClass_;

  FinalizationQueueObject* queue() const;
  ObjectWeakMap* registrations() const;

  void traceWeak(JSTracer* trc);

  static bool unregisterRecord(FinalizationRecordObject* record);

  static bool cleanupQueuedRecords(JSContext* cx,
                                   HandleFinalizationRegistryObject registry,
                                   HandleObject callback = nullptr);

 private:
  static const JSClassOps classOps_;
  static const ClassSpec classSpec_;
  static const JSFunctionSpec methods_[];
  static const JSPropertySpec properties_[];

  static bool construct(JSContext* cx, unsigned argc, Value* vp);
  static bool register_(JSContext* cx, unsigned argc, Value* vp);
  static bool unregister(JSContext* cx, unsigned argc, Value* vp);
  static bool cleanupSome(JSContext* cx, unsigned argc, Value* vp);

  static bool addRegistration(JSContext* cx,
                              HandleFinalizationRegistryObject registry,
                              HandleObject unregisterToken,
                              HandleFinalizationRecordObject record);
  static void removeRegistrationOnError(
      HandleFinalizationRegistryObject registry, HandleObject unregisterToken,
      HandleFinalizationRecordObject record);

  static bool preserveDOMWrapper(JSContext* cx, HandleObject obj);

  static void trace(JSTracer* trc, JSObject* obj);
  static void finalize(JS::GCContext* gcx, JSObject* obj);
};

// Contains information about the cleanup callback and the records queued to
// be cleaned up. This is not exposed to content JS.
class FinalizationQueueObject : public NativeObject {
  enum {
    CleanupCallbackSlot = 0,
    IncumbentObjectSlot,
    RecordsToBeCleanedUpSlot,
    IsQueuedForCleanupSlot,
    DoCleanupFunctionSlot,
    HasRegistrySlot,
    SlotCount
  };

  enum DoCleanupFunctionSlots {
    DoCleanupFunction_QueueSlot = 0,
  };

 public:
  static const JSClass class_;

  JSObject* cleanupCallback() const;
  JSObject* incumbentObject() const;
  FinalizationRecordVector* recordsToBeCleanedUp() const;
  bool isQueuedForCleanup() const;
  JSFunction* doCleanupFunction() const;
  bool hasRegistry() const;

  void queueRecordToBeCleanedUp(FinalizationRecordObject* record);
  void setQueuedForCleanup(bool value);

  void setHasRegistry(bool newValue);

  static FinalizationQueueObject* create(JSContext* cx,
                                         HandleObject cleanupCallback);

  static bool cleanupQueuedRecords(JSContext* cx,
                                   HandleFinalizationQueueObject registry,
                                   HandleObject callback = nullptr);

 private:
  static const JSClassOps classOps_;

  static bool doCleanup(JSContext* cx, unsigned argc, Value* vp);

  static void trace(JSTracer* trc, JSObject* obj);
  static void finalize(JS::GCContext* gcx, JSObject* obj);
};

}  // namespace js

#endif /* builtin_FinalizationRegistryObject_h */