Bug 1877642 - Disable browser_fullscreen-tab-close-race.js on apple_silicon !debug...
[gecko.git] / js / src / gc / FinalizationObservers.cpp
blob3a7a114645232b7f3e4fa3a942aa7486213020d0
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/. */
7 /*
8 * GC support for FinalizationRegistry and WeakRef objects.
9 */
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"
18 #include "gc/Zone.h"
19 #include "vm/JSContext.h"
21 #include "gc/WeakMap-inl.h"
22 #include "vm/JSObject-inl.h"
23 #include "vm/NativeObject-inl.h"
25 using namespace js;
26 using namespace js::gc;
28 FinalizationObservers::FinalizationObservers(Zone* zone)
29 : zone(zone),
30 registries(zone),
31 recordMap(zone),
32 crossZoneRecords(zone),
33 weakRefMap(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);
48 return false;
51 return true;
54 bool FinalizationObservers::addRegistry(
55 Handle<FinalizationRegistryObject*> registry) {
56 return registries.put(registry);
59 bool GCRuntime::registerWithFinalizationRegistry(JSContext* cx,
60 HandleObject target,
61 HandleObject record) {
62 MOZ_ASSERT(!IsCrossCompartmentWrapper(target));
63 MOZ_ASSERT(
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);
71 return false;
74 return true;
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)) {
94 return false;
96 auto wrapperGuard = mozilla::MakeScopeExit([&] {
97 if (crossZone) {
98 removeCrossZoneWrapper(crossZoneRecords, record);
102 GlobalObject* registryGlobal = &unwrappedRecord->global();
103 auto* globalData = registryGlobal->getOrCreateFinalizationRegistryData();
104 if (!globalData || !globalData->addRecord(unwrappedRecord)) {
105 return false;
107 auto globalDataGuard = mozilla::MakeScopeExit(
108 [&] { globalData->removeRecord(unwrappedRecord); });
110 auto ptr = recordMap.lookupForAdd(target);
111 if (!ptr && !recordMap.add(ptr, target, RecordVector(zone))) {
112 return false;
115 if (!ptr->value().append(record)) {
116 return false;
119 unwrappedRecord->setInRecordMap(true);
121 globalDataGuard.release();
122 wrapperGuard.release();
123 return true;
126 bool FinalizationObservers::addCrossZoneWrapper(WrapperWeakSet& weakSet,
127 JSObject* wrapper) {
128 MOZ_ASSERT(IsCrossCompartmentWrapper(wrapper));
129 MOZ_ASSERT(UncheckedUnwrapWithoutExpose(wrapper)->zone() != zone);
131 auto ptr = weakSet.lookupForAdd(wrapper);
132 MOZ_ASSERT(!ptr);
133 return weakSet.add(ptr, wrapper, UndefinedValue());
136 void FinalizationObservers::removeCrossZoneWrapper(WrapperWeakSet& weakSet,
137 JSObject* wrapper) {
138 MOZ_ASSERT(IsCrossCompartmentWrapper(wrapper));
139 MOZ_ASSERT(UncheckedUnwrapWithoutExpose(wrapper)->zone() != zone);
141 auto ptr = weakSet.lookupForAdd(wrapper);
142 MOZ_ASSERT(ptr);
143 weakSet.remove(ptr);
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.
152 return nullptr;
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.
164 #ifdef DEBUG
165 checkTables();
166 #endif
168 recordMap.clear();
169 crossZoneRecords.clear();
172 void GCRuntime::traceWeakFinalizationObserverEdges(JSTracer* trc, Zone* zone) {
173 MOZ_ASSERT(CurrentThreadCanAccessRuntime(trc->runtime()));
174 FinalizationObservers* observers = zone->finalizationObservers();
175 if (observers) {
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()) {
205 auto* registry =
206 &result.initialTarget()->as<FinalizationRegistryObject>();
207 registry->queue()->setHasRegistry(false);
208 e.removeFront();
209 } else {
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");
221 JSObject* obj =
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);
231 return shouldRemove;
234 #ifdef DEBUG
235 for (JSObject* obj : records) {
236 MOZ_ASSERT(UnwrapFinalizationRecord(obj)->isInRecordMap());
238 #endif
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);
250 e.removeFront();
255 // static
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,
297 // if necessary.
299 if (queue->isQueuedForCleanup()) {
300 return;
303 // Derive the incumbent global by unwrapping the incumbent global object and
304 // then getting its global.
305 JSObject* object = UncheckedUnwrapWithoutExpose(queue->incumbentObject());
306 MOZ_ASSERT(object);
307 GlobalObject* incumbentGlobal = &object->nonCCWGlobal();
309 callHostCleanupFinalizationRegistryCallback(queue->doCleanupFunction(),
310 incumbentGlobal);
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)) {
342 return false;
344 auto wrapperGuard = mozilla::MakeScopeExit([&] {
345 if (crossZone) {
346 removeCrossZoneWrapper(crossZoneWeakRefs, weakRef);
350 auto ptr = weakRefMap.lookupForAdd(target);
351 if (!ptr && !weakRefMap.add(ptr, target, WeakRefHeapPtrVector(zone))) {
352 return false;
355 if (!ptr->value().emplaceBack(weakRef)) {
356 return false;
359 wrapperGuard.release();
360 return true;
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) {
371 MOZ_ASSERT(target);
373 WeakRefHeapPtrVector& weakRefs = weakRefMap.lookup(target)->value();
374 JSObject* wrapper = nullptr;
375 weakRefs.eraseIf([weakRef, &wrapper](JSObject* obj) {
376 if (UnwrapWeakRef(obj) == weakRef) {
377 wrapper = obj;
378 return true;
380 return false;
383 MOZ_ASSERT(wrapper);
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
390 // target.
391 JSObject* target = weakRef->target();
392 if (!target) {
393 return;
396 FinalizationObservers* observers = target->zone()->finalizationObservers();
397 if (observers) {
398 observers->unregisterWeakRefWrapper(wrapper, weakRef);
402 void FinalizationObservers::unregisterWeakRefWrapper(JSObject* wrapper,
403 WeakRefObject* weakRef) {
404 JSObject* target = weakRef->target();
405 MOZ_ASSERT(target);
407 bool removed = false;
408 WeakRefHeapPtrVector& weakRefs = weakRefMap.lookup(target)->value();
409 weakRefs.eraseIf([wrapper, &removed](JSObject* obj) {
410 bool remove = obj == wrapper;
411 if (remove) {
412 removed = true;
414 return remove;
417 if (removed) {
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));
441 e.removeFront();
442 } else {
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));
456 } else {
457 UnwrapWeakRef(result.finalTarget())->setTargetUnbarriered(target);
459 return result.isDead();
463 #ifdef DEBUG
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));
472 recordCount++;
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));
484 weakRefCount++;
488 MOZ_ASSERT(crossZoneWeakRefs.count() == weakRefCount);
490 #endif
492 FinalizationRegistryGlobalData::FinalizationRegistryGlobalData(Zone* zone)
493 : recordSet(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);