1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * GC support for FinalizationRegistry and WeakRef objects.
11 #include "gc/FinalizationObservers.h"
13 #include "mozilla/ScopeExit.h"
15 #include "builtin/FinalizationRegistryObject.h"
16 #include "builtin/WeakRefObject.h"
17 #include "gc/GCRuntime.h"
19 #include "vm/JSContext.h"
21 #include "gc/WeakMap-inl.h"
22 #include "vm/JSObject-inl.h"
23 #include "vm/NativeObject-inl.h"
26 using namespace js::gc
;
28 FinalizationObservers::FinalizationObservers(Zone
* zone
)
32 crossZoneRecords(zone
),
34 crossZoneWeakRefs(zone
) {}
36 FinalizationObservers::~FinalizationObservers() {
37 MOZ_ASSERT(registries
.empty());
38 MOZ_ASSERT(recordMap
.empty());
39 MOZ_ASSERT(crossZoneRecords
.empty());
40 MOZ_ASSERT(crossZoneWeakRefs
.empty());
43 bool GCRuntime::addFinalizationRegistry(
44 JSContext
* cx
, Handle
<FinalizationRegistryObject
*> registry
) {
45 if (!cx
->zone()->ensureFinalizationObservers() ||
46 !cx
->zone()->finalizationObservers()->addRegistry(registry
)) {
47 ReportOutOfMemory(cx
);
54 bool FinalizationObservers::addRegistry(
55 Handle
<FinalizationRegistryObject
*> registry
) {
56 return registries
.put(registry
);
59 bool GCRuntime::registerWithFinalizationRegistry(JSContext
* cx
,
61 HandleObject record
) {
62 MOZ_ASSERT(!IsCrossCompartmentWrapper(target
));
64 UncheckedUnwrapWithoutExpose(record
)->is
<FinalizationRecordObject
>());
65 MOZ_ASSERT(target
->compartment() == record
->compartment());
67 Zone
* zone
= cx
->zone();
68 if (!zone
->ensureFinalizationObservers() ||
69 !zone
->finalizationObservers()->addRecord(target
, record
)) {
70 ReportOutOfMemory(cx
);
77 bool FinalizationObservers::addRecord(HandleObject target
,
78 HandleObject record
) {
79 // Add a record to the record map and clean up on failure.
81 // The following must be updated and kept in sync:
82 // - the zone's recordMap (to observe the target)
83 // - the registry's global objects's recordSet (to trace the record)
84 // - the count of cross zone records (to calculate sweep groups)
86 MOZ_ASSERT(target
->zone() == zone
);
88 FinalizationRecordObject
* unwrappedRecord
=
89 &UncheckedUnwrapWithoutExpose(record
)->as
<FinalizationRecordObject
>();
91 Zone
* registryZone
= unwrappedRecord
->zone();
92 bool crossZone
= registryZone
!= zone
;
93 if (crossZone
&& !addCrossZoneWrapper(crossZoneRecords
, record
)) {
96 auto wrapperGuard
= mozilla::MakeScopeExit([&] {
98 removeCrossZoneWrapper(crossZoneRecords
, record
);
102 GlobalObject
* registryGlobal
= &unwrappedRecord
->global();
103 auto* globalData
= registryGlobal
->getOrCreateFinalizationRegistryData();
104 if (!globalData
|| !globalData
->addRecord(unwrappedRecord
)) {
107 auto globalDataGuard
= mozilla::MakeScopeExit(
108 [&] { globalData
->removeRecord(unwrappedRecord
); });
110 auto ptr
= recordMap
.lookupForAdd(target
);
111 if (!ptr
&& !recordMap
.add(ptr
, target
, RecordVector(zone
))) {
115 if (!ptr
->value().append(record
)) {
119 unwrappedRecord
->setInRecordMap(true);
121 globalDataGuard
.release();
122 wrapperGuard
.release();
126 bool FinalizationObservers::addCrossZoneWrapper(WrapperWeakSet
& weakSet
,
128 MOZ_ASSERT(IsCrossCompartmentWrapper(wrapper
));
129 MOZ_ASSERT(UncheckedUnwrapWithoutExpose(wrapper
)->zone() != zone
);
131 auto ptr
= weakSet
.lookupForAdd(wrapper
);
133 return weakSet
.add(ptr
, wrapper
, UndefinedValue());
136 void FinalizationObservers::removeCrossZoneWrapper(WrapperWeakSet
& weakSet
,
138 MOZ_ASSERT(IsCrossCompartmentWrapper(wrapper
));
139 MOZ_ASSERT(UncheckedUnwrapWithoutExpose(wrapper
)->zone() != zone
);
141 auto ptr
= weakSet
.lookupForAdd(wrapper
);
146 static FinalizationRecordObject
* UnwrapFinalizationRecord(JSObject
* obj
) {
147 obj
= UncheckedUnwrapWithoutExpose(obj
);
148 if (!obj
->is
<FinalizationRecordObject
>()) {
149 MOZ_ASSERT(JS_IsDeadWrapper(obj
));
150 // CCWs between the compartments have been nuked. The
151 // FinalizationRegistry's callback doesn't run in this case.
154 return &obj
->as
<FinalizationRecordObject
>();
157 void FinalizationObservers::clearRecords() {
158 // Clear table entries related to FinalizationRecordObjects, which are not
159 // processed after the start of shutdown.
161 // WeakRefs are still updated during shutdown to avoid the possibility of
162 // stale or dangling pointers.
169 crossZoneRecords
.clear();
172 void GCRuntime::traceWeakFinalizationObserverEdges(JSTracer
* trc
, Zone
* zone
) {
173 MOZ_ASSERT(CurrentThreadCanAccessRuntime(trc
->runtime()));
174 FinalizationObservers
* observers
= zone
->finalizationObservers();
176 observers
->traceWeakEdges(trc
);
180 void FinalizationObservers::traceRoots(JSTracer
* trc
) {
181 // The cross-zone wrapper weak maps are traced as roots; this does not keep
182 // any of their entries alive by itself.
183 crossZoneRecords
.trace(trc
);
184 crossZoneWeakRefs
.trace(trc
);
187 void FinalizationObservers::traceWeakEdges(JSTracer
* trc
) {
188 // Removing dead pointers from vectors may reorder live pointers to gray
189 // things in the vector. This is OK.
190 AutoTouchingGrayThings atgt
;
192 traceWeakWeakRefEdges(trc
);
193 traceWeakFinalizationRegistryEdges(trc
);
196 void FinalizationObservers::traceWeakFinalizationRegistryEdges(JSTracer
* trc
) {
197 // Sweep finalization registry data and queue finalization records for cleanup
198 // for any entries whose target is dying and remove them from the map.
200 GCRuntime
* gc
= &trc
->runtime()->gc
;
202 for (RegistrySet::Enum
e(registries
); !e
.empty(); e
.popFront()) {
203 auto result
= TraceWeakEdge(trc
, &e
.mutableFront(), "FinalizationRegistry");
204 if (result
.isDead()) {
206 &result
.initialTarget()->as
<FinalizationRegistryObject
>();
207 registry
->queue()->setHasRegistry(false);
210 result
.finalTarget()->as
<FinalizationRegistryObject
>().traceWeak(trc
);
214 for (RecordMap::Enum
e(recordMap
); !e
.empty(); e
.popFront()) {
215 RecordVector
& records
= e
.front().value();
217 // Sweep finalization records, updating any pointers moved by the GC and
218 // remove if necessary.
219 records
.mutableEraseIf([&](HeapPtr
<JSObject
*>& heapPtr
) {
220 auto result
= TraceWeakEdge(trc
, &heapPtr
, "FinalizationRecord");
222 result
.isLive() ? result
.finalTarget() : result
.initialTarget();
223 FinalizationRecordObject
* record
= UnwrapFinalizationRecord(obj
);
224 MOZ_ASSERT_IF(record
, record
->isInRecordMap());
226 bool shouldRemove
= !result
.isLive() || shouldRemoveRecord(record
);
227 if (shouldRemove
&& record
&& record
->isInRecordMap()) {
228 updateForRemovedRecord(obj
, record
);
235 for (JSObject
* obj
: records
) {
236 MOZ_ASSERT(UnwrapFinalizationRecord(obj
)->isInRecordMap());
240 // Queue finalization records for targets that are dying.
241 if (!TraceWeakEdge(trc
, &e
.front().mutableKey(),
242 "FinalizationRecord target")) {
243 for (JSObject
* obj
: records
) {
244 FinalizationRecordObject
* record
= UnwrapFinalizationRecord(obj
);
245 FinalizationQueueObject
* queue
= record
->queue();
246 updateForRemovedRecord(obj
, record
);
247 queue
->queueRecordToBeCleanedUp(record
);
248 gc
->queueFinalizationRegistryForCleanup(queue
);
256 bool FinalizationObservers::shouldRemoveRecord(
257 FinalizationRecordObject
* record
) {
258 // Records are removed from the target's vector for the following reasons:
259 return !record
|| // Nuked CCW to record.
260 !record
->isRegistered() || // Unregistered record.
261 !record
->queue()->hasRegistry(); // Dead finalization registry.
264 void FinalizationObservers::updateForRemovedRecord(
265 JSObject
* wrapper
, FinalizationRecordObject
* record
) {
266 // Remove other references to a record when it has been removed from the
267 // zone's record map. See addRecord().
268 MOZ_ASSERT(record
->isInRecordMap());
270 Zone
* registryZone
= record
->zone();
271 if (registryZone
!= zone
) {
272 removeCrossZoneWrapper(crossZoneRecords
, wrapper
);
275 GlobalObject
* registryGlobal
= &record
->global();
276 auto* globalData
= registryGlobal
->maybeFinalizationRegistryData();
277 globalData
->removeRecord(record
);
279 // The removed record may be gray, and that's OK.
280 AutoTouchingGrayThings atgt
;
282 record
->setInRecordMap(false);
285 void GCRuntime::nukeFinalizationRecordWrapper(
286 JSObject
* wrapper
, FinalizationRecordObject
* record
) {
287 if (record
->isInRecordMap()) {
288 FinalizationRegistryObject::unregisterRecord(record
);
289 FinalizationObservers
* observers
= wrapper
->zone()->finalizationObservers();
290 observers
->updateForRemovedRecord(wrapper
, record
);
294 void GCRuntime::queueFinalizationRegistryForCleanup(
295 FinalizationQueueObject
* queue
) {
296 // Prod the embedding to call us back later to run the finalization callbacks,
299 if (queue
->isQueuedForCleanup()) {
303 // Derive the incumbent global by unwrapping the incumbent global object and
304 // then getting its global.
305 JSObject
* object
= UncheckedUnwrapWithoutExpose(queue
->incumbentObject());
307 GlobalObject
* incumbentGlobal
= &object
->nonCCWGlobal();
309 callHostCleanupFinalizationRegistryCallback(queue
->doCleanupFunction(),
312 // The queue object may be gray, and that's OK.
313 AutoTouchingGrayThings atgt
;
315 queue
->setQueuedForCleanup(true);
318 // Insert a target -> weakRef mapping in the target's Zone so that a dying
319 // target will clear out the weakRef's target. If the weakRef is in a different
320 // Zone, then the crossZoneWeakRefs table will keep the weakRef alive. If the
321 // weakRef is in the same Zone, then it must be the actual WeakRefObject and
322 // not a cross-compartment wrapper, since nothing would keep that alive.
323 bool GCRuntime::registerWeakRef(HandleObject target
, HandleObject weakRef
) {
324 MOZ_ASSERT(!IsCrossCompartmentWrapper(target
));
325 MOZ_ASSERT(UncheckedUnwrap(weakRef
)->is
<WeakRefObject
>());
326 MOZ_ASSERT_IF(target
->zone() != weakRef
->zone(),
327 target
->compartment() == weakRef
->compartment());
329 Zone
* zone
= target
->zone();
330 return zone
->ensureFinalizationObservers() &&
331 zone
->finalizationObservers()->addWeakRefTarget(target
, weakRef
);
334 bool FinalizationObservers::addWeakRefTarget(HandleObject target
,
335 HandleObject weakRef
) {
336 WeakRefObject
* unwrappedWeakRef
=
337 &UncheckedUnwrapWithoutExpose(weakRef
)->as
<WeakRefObject
>();
339 Zone
* weakRefZone
= unwrappedWeakRef
->zone();
340 bool crossZone
= weakRefZone
!= zone
;
341 if (crossZone
&& !addCrossZoneWrapper(crossZoneWeakRefs
, weakRef
)) {
344 auto wrapperGuard
= mozilla::MakeScopeExit([&] {
346 removeCrossZoneWrapper(crossZoneWeakRefs
, weakRef
);
350 auto ptr
= weakRefMap
.lookupForAdd(target
);
351 if (!ptr
&& !weakRefMap
.add(ptr
, target
, WeakRefHeapPtrVector(zone
))) {
355 if (!ptr
->value().emplaceBack(weakRef
)) {
359 wrapperGuard
.release();
363 static WeakRefObject
* UnwrapWeakRef(JSObject
* obj
) {
364 MOZ_ASSERT(!JS_IsDeadWrapper(obj
));
365 obj
= UncheckedUnwrapWithoutExpose(obj
);
366 return &obj
->as
<WeakRefObject
>();
369 void FinalizationObservers::removeWeakRefTarget(
370 Handle
<JSObject
*> target
, Handle
<WeakRefObject
*> weakRef
) {
373 WeakRefHeapPtrVector
& weakRefs
= weakRefMap
.lookup(target
)->value();
374 JSObject
* wrapper
= nullptr;
375 weakRefs
.eraseIf([weakRef
, &wrapper
](JSObject
* obj
) {
376 if (UnwrapWeakRef(obj
) == weakRef
) {
384 updateForRemovedWeakRef(wrapper
, weakRef
);
387 void GCRuntime::nukeWeakRefWrapper(JSObject
* wrapper
, WeakRefObject
* weakRef
) {
388 // WeakRef wrappers can exist independently of the ones we create for the
389 // weakRefMap so don't assume |wrapper| is in the same zone as the WeakRef
391 JSObject
* target
= weakRef
->target();
396 FinalizationObservers
* observers
= target
->zone()->finalizationObservers();
398 observers
->unregisterWeakRefWrapper(wrapper
, weakRef
);
402 void FinalizationObservers::unregisterWeakRefWrapper(JSObject
* wrapper
,
403 WeakRefObject
* weakRef
) {
404 JSObject
* target
= weakRef
->target();
407 bool removed
= false;
408 WeakRefHeapPtrVector
& weakRefs
= weakRefMap
.lookup(target
)->value();
409 weakRefs
.eraseIf([wrapper
, &removed
](JSObject
* obj
) {
410 bool remove
= obj
== wrapper
;
418 updateForRemovedWeakRef(wrapper
, weakRef
);
422 void FinalizationObservers::updateForRemovedWeakRef(JSObject
* wrapper
,
423 WeakRefObject
* weakRef
) {
424 weakRef
->clearTarget();
426 Zone
* weakRefZone
= weakRef
->zone();
427 if (weakRefZone
!= zone
) {
428 removeCrossZoneWrapper(crossZoneWeakRefs
, wrapper
);
432 void FinalizationObservers::traceWeakWeakRefEdges(JSTracer
* trc
) {
433 for (WeakRefMap::Enum
e(weakRefMap
); !e
.empty(); e
.popFront()) {
434 // If target is dying, clear the target field of all weakRefs, and remove
435 // the entry from the map.
436 auto result
= TraceWeakEdge(trc
, &e
.front().mutableKey(), "WeakRef target");
437 if (result
.isDead()) {
438 for (JSObject
* obj
: e
.front().value()) {
439 updateForRemovedWeakRef(obj
, UnwrapWeakRef(obj
));
443 // Update the target field after compacting.
444 traceWeakWeakRefVector(trc
, e
.front().value(), result
.finalTarget());
449 void FinalizationObservers::traceWeakWeakRefVector(
450 JSTracer
* trc
, WeakRefHeapPtrVector
& weakRefs
, JSObject
* target
) {
451 weakRefs
.mutableEraseIf([&](HeapPtr
<JSObject
*>& obj
) -> bool {
452 auto result
= TraceWeakEdge(trc
, &obj
, "WeakRef");
453 if (result
.isDead()) {
454 JSObject
* wrapper
= result
.initialTarget();
455 updateForRemovedWeakRef(wrapper
, UnwrapWeakRef(wrapper
));
457 UnwrapWeakRef(result
.finalTarget())->setTargetUnbarriered(target
);
459 return result
.isDead();
464 void FinalizationObservers::checkTables() const {
465 // Check all cross-zone wrappers are present in the appropriate table.
466 size_t recordCount
= 0;
467 for (auto r
= recordMap
.all(); !r
.empty(); r
.popFront()) {
468 for (JSObject
* object
: r
.front().value()) {
469 FinalizationRecordObject
* record
= UnwrapFinalizationRecord(object
);
470 if (record
&& record
->isInRecordMap() && record
->zone() != zone
) {
471 MOZ_ASSERT(crossZoneRecords
.has(object
));
476 MOZ_ASSERT(crossZoneRecords
.count() == recordCount
);
478 size_t weakRefCount
= 0;
479 for (auto r
= weakRefMap
.all(); !r
.empty(); r
.popFront()) {
480 for (JSObject
* object
: r
.front().value()) {
481 WeakRefObject
* weakRef
= UnwrapWeakRef(object
);
482 if (weakRef
&& weakRef
->zone() != zone
) {
483 MOZ_ASSERT(crossZoneWeakRefs
.has(object
));
488 MOZ_ASSERT(crossZoneWeakRefs
.count() == weakRefCount
);
492 FinalizationRegistryGlobalData::FinalizationRegistryGlobalData(Zone
* zone
)
495 bool FinalizationRegistryGlobalData::addRecord(
496 FinalizationRecordObject
* record
) {
497 return recordSet
.putNew(record
);
500 void FinalizationRegistryGlobalData::removeRecord(
501 FinalizationRecordObject
* record
) {
502 MOZ_ASSERT_IF(!record
->runtimeFromMainThread()->gc
.isShuttingDown(),
503 recordSet
.has(record
));
504 recordSet
.remove(record
);
507 void FinalizationRegistryGlobalData::trace(JSTracer
* trc
) {
508 recordSet
.trace(trc
);