FinalizationObservers.cpp (16425B)
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/. */ 6 7 /* 8 * GC support for FinalizationRegistry and WeakRef objects. 9 */ 10 11 #include "gc/FinalizationObservers.h" 12 13 #include "builtin/FinalizationRegistryObject.h" 14 #include "builtin/WeakRefObject.h" 15 #include "gc/GCRuntime.h" 16 #include "gc/Zone.h" 17 #include "vm/JSContext.h" 18 19 #include "gc/WeakMap-inl.h" 20 #include "vm/JSObject-inl.h" 21 #include "vm/NativeObject-inl.h" 22 23 using namespace js; 24 using namespace js::gc; 25 26 Zone* js::gc::GetWeakTargetZone(const Value& value) { 27 MOZ_ASSERT(CanBeHeldWeakly(value)); 28 return value.toGCThing()->zone(); 29 } 30 31 /* static */ 32 ObserverListPtr ObserverListPtr::fromValue(Value value) { 33 MOZ_ASSERT(value.isDouble()); // Stored as PrivateValue. 34 return ObserverListPtr(value); 35 } 36 37 ObserverListPtr::ObserverListPtr(ObserverListObject* element) 38 : ObserverListPtr(element, ElementKind) {} 39 40 ObserverListPtr::ObserverListPtr(ObserverList* list) 41 : ObserverListPtr(list, ListHeadKind) {} 42 43 ObserverListPtr::ObserverListPtr(void* ptr, Kind kind) 44 : value(PrivateValue(uintptr_t(ptr) | kind)) { 45 MOZ_ASSERT((uintptr_t(ptr) & KindMask) == 0); 46 } 47 48 ObserverListPtr::ObserverListPtr(Value value) : value(value) {} 49 50 template <typename F> 51 auto ObserverListPtr::map(F&& func) const { 52 if (isElement()) { 53 return func(asElement()); 54 } 55 56 return func(asList()); 57 } 58 59 bool ObserverListPtr::isElement() const { return kind() == ElementKind; } 60 61 ObserverListPtr::Kind ObserverListPtr::kind() const { 62 uintptr_t bits = uintptr_t(value.toPrivate()); 63 return static_cast<Kind>(bits & KindMask); 64 } 65 66 void* ObserverListPtr::ptr() const { 67 uintptr_t bits = uintptr_t(value.toPrivate()); 68 return reinterpret_cast<void*>(bits & ~KindMask); 69 } 70 71 ObserverListObject* ObserverListPtr::asElement() const { 72 MOZ_ASSERT(isElement()); 73 return static_cast<ObserverListObject*>(ptr()); 74 } 75 76 ObserverList* ObserverListPtr::asList() const { 77 MOZ_ASSERT(!isElement()); 78 return static_cast<ObserverList*>(ptr()); 79 } 80 81 ObserverListPtr ObserverListPtr::getNext() const { 82 return map([](auto* element) { return element->getNext(); }); 83 } 84 85 ObserverListPtr ObserverListPtr::getPrev() const { 86 return map([](auto* element) { return element->getPrev(); }); 87 } 88 89 void ObserverListPtr::setNext(ObserverListPtr next) { 90 map([next](auto* element) { element->setNext(next); }); 91 } 92 93 /* static */ 94 void ObserverListPtr::setPrev(ObserverListPtr prev) { 95 map([prev](auto* element) { element->setPrev(prev); }); 96 } 97 98 // An iterator for ObserverList that allows removing the current element from 99 // the list. 100 class ObserverList::Iter { 101 using Ptr = ObserverListPtr; 102 const Ptr end; 103 Ptr ptr; 104 Ptr nextPtr; 105 106 public: 107 explicit Iter(ObserverList& list) 108 : end(&list), ptr(end.getNext()), nextPtr(ptr.getNext()) { 109 MOZ_ASSERT(list.isEmpty() == done()); 110 } 111 112 bool done() const { return ptr == end; } 113 114 ObserverListObject* get() const { 115 MOZ_ASSERT(!done()); 116 return ptr.asElement(); 117 } 118 119 void next() { 120 MOZ_ASSERT(!done()); 121 ptr = nextPtr; 122 nextPtr = ptr.getNext(); 123 } 124 125 operator ObserverListObject*() const { return get(); } 126 ObserverListObject* operator->() const { return get(); } 127 }; 128 129 ObserverList::ObserverList() : next(this), prev(this) { MOZ_ASSERT(isEmpty()); } 130 131 ObserverList::~ObserverList() { MOZ_ASSERT(isEmpty()); } 132 133 ObserverList::ObserverList(ObserverList&& other) : ObserverList() { 134 MOZ_ASSERT(&other != this); 135 *this = std::move(other); 136 } 137 ObserverList& ObserverList::operator=(ObserverList&& other) { 138 MOZ_ASSERT(&other != this); 139 MOZ_ASSERT(isEmpty()); 140 141 AutoTouchingGrayThings atgt; 142 143 if (other.isEmpty()) { 144 return *this; 145 } 146 147 next = other.next; 148 prev = other.prev; 149 150 // Check other's list head is correctly linked to its neighbours. 151 MOZ_ASSERT(next.getPrev().asList() == &other); 152 MOZ_ASSERT(prev.getNext().asList() == &other); 153 154 // Update those neighbours to point to this object. 155 next.setPrev(this); 156 prev.setNext(this); 157 158 other.next = &other; 159 other.prev = &other; 160 MOZ_ASSERT(other.isEmpty()); 161 162 return *this; 163 } 164 165 bool ObserverList::isEmpty() const { 166 ObserverListPtr thisLink = const_cast<ObserverList*>(this); 167 MOZ_ASSERT((getNext() == thisLink) == (getPrev() == thisLink)); 168 return getNext() == thisLink; 169 } 170 171 ObserverListObject* ObserverList::getFirst() const { 172 MOZ_ASSERT(!isEmpty()); 173 return next.asElement(); 174 } 175 176 ObserverList::Iter ObserverList::iter() { return Iter(*this); } 177 178 void ObserverList::insertFront(ObserverListObject* obj) { 179 MOZ_ASSERT(!obj->isInList()); 180 181 // The other things in this list might be gray. 182 AutoTouchingGrayThings atgt; 183 184 Ptr oldNext = getNext(); 185 186 setNext(obj); 187 obj->setNext(oldNext); 188 189 oldNext.setPrev(obj); 190 obj->setPrev(this); 191 } 192 193 void ObserverList::setNext(Ptr link) { next = link; } 194 195 void ObserverList::setPrev(Ptr link) { prev = link; } 196 197 /* static */ 198 const ClassExtension ObserverListObject::classExtension_ = { 199 ObserverListObject::objectMoved, // objectMovedOp 200 }; 201 202 bool ObserverListObject::isInList() const { 203 bool inList = !getReservedSlot(NextSlot).isUndefined(); 204 MOZ_ASSERT(inList == !getReservedSlot(PrevSlot).isUndefined()); 205 return inList; 206 } 207 208 /* static */ 209 size_t ObserverListObject::objectMoved(JSObject* obj, JSObject* old) { 210 auto* self = static_cast<ObserverListObject*>(obj); 211 self->objectMovedFrom(static_cast<ObserverListObject*>(old)); 212 return 0; 213 } 214 215 void ObserverListObject::objectMovedFrom(ObserverListObject* old) { 216 AutoTouchingGrayThings atgt; 217 218 if (!isInList()) { 219 return; 220 } 221 222 #ifdef DEBUG 223 Ptr oldPtr = old; 224 MOZ_ASSERT(getNext() != oldPtr); 225 MOZ_ASSERT(getPrev() != oldPtr); 226 MOZ_ASSERT(getNext().getPrev() == oldPtr); 227 MOZ_ASSERT(getPrev().getNext() == oldPtr); 228 #endif 229 230 getNext().setPrev(this); 231 getPrev().setNext(this); 232 } 233 234 void ObserverListObject::unlink() { 235 AutoTouchingGrayThings atgt; 236 237 if (!isInList()) { 238 return; 239 } 240 241 Ptr next = getNext(); 242 Ptr prev = getPrev(); 243 244 #ifdef DEBUG 245 Ptr thisPtr = this; 246 MOZ_ASSERT(prev.getNext() == thisPtr); 247 MOZ_ASSERT(next.getPrev() == thisPtr); 248 #endif 249 250 next.setPrev(prev); 251 prev.setNext(next); 252 253 setReservedSlot(NextSlot, UndefinedValue()); 254 setReservedSlot(PrevSlot, UndefinedValue()); 255 MOZ_ASSERT(!isInList()); 256 } 257 258 ObserverListPtr ObserverListObject::getNext() const { 259 Value value = getReservedSlot(NextSlot); 260 return Ptr::fromValue(value); 261 } 262 263 ObserverListPtr ObserverListObject::getPrev() const { 264 Value value = getReservedSlot(PrevSlot); 265 return Ptr::fromValue(value); 266 } 267 268 void ObserverListObject::setNext(Ptr next) { 269 setReservedSlot(NextSlot, next.asValue()); 270 } 271 272 void ObserverListObject::setPrev(Ptr prev) { 273 setReservedSlot(PrevSlot, prev.asValue()); 274 } 275 276 FinalizationObservers::FinalizationObservers(Zone* zone) 277 : registries(zone), recordMap(zone), weakRefMap(zone) {} 278 279 FinalizationObservers::~FinalizationObservers() { 280 MOZ_ASSERT(registries.empty()); 281 MOZ_ASSERT(recordMap.empty()); 282 } 283 284 bool GCRuntime::addFinalizationRegistry( 285 JSContext* cx, Handle<FinalizationRegistryObject*> registry) { 286 if (!cx->zone()->ensureFinalizationObservers() || 287 !cx->zone()->finalizationObservers()->addRegistry(registry)) { 288 ReportOutOfMemory(cx); 289 return false; 290 } 291 292 return true; 293 } 294 295 bool FinalizationObservers::addRegistry( 296 Handle<FinalizationRegistryObject*> registry) { 297 return registries.put(registry); 298 } 299 300 bool GCRuntime::registerWithFinalizationRegistry( 301 JSContext* cx, HandleValue target, 302 Handle<FinalizationRecordObject*> record) { 303 MOZ_ASSERT_IF(target.isObject(), 304 !IsCrossCompartmentWrapper(&target.toObject())); 305 306 Zone* zone = GetWeakTargetZone(target); 307 if (!zone->ensureFinalizationObservers() || 308 !zone->finalizationObservers()->addRecord(target, record)) { 309 ReportOutOfMemory(cx); 310 return false; 311 } 312 313 return true; 314 } 315 316 bool FinalizationObservers::addRecord( 317 HandleValue target, Handle<FinalizationRecordObject*> record) { 318 // Add a record to the record map and clean up on failure. 319 // 320 // The following must be updated and kept in sync: 321 // - the zone's recordMap (to observe the target) 322 // - the registry's global objects's recordSet (to trace the record) 323 324 auto ptr = recordMap.lookupForAdd(target); 325 if (!ptr && !recordMap.add(ptr, target, ObserverList())) { 326 return false; 327 } 328 329 ptr->value().insertFront(record); 330 331 record->setInRecordMap(true); 332 return true; 333 } 334 335 void FinalizationObservers::clearRecords() { 336 // Clear table entries related to FinalizationRecordObjects, which are not 337 // processed after the start of shutdown. 338 // 339 // WeakRefs are still updated during shutdown to avoid the possibility of 340 // stale or dangling pointers. 341 for (RecordMap::Enum e(recordMap); !e.empty(); e.popFront()) { 342 ObserverList& records = e.front().value(); 343 for (auto iter = records.iter(); !iter.done(); iter.next()) { 344 iter->unlink(); 345 } 346 } 347 recordMap.clear(); 348 } 349 350 void GCRuntime::traceWeakFinalizationObserverEdges(JSTracer* trc, Zone* zone) { 351 MOZ_ASSERT(CurrentThreadCanAccessRuntime(trc->runtime())); 352 FinalizationObservers* observers = zone->finalizationObservers(); 353 if (observers) { 354 observers->traceWeakEdges(trc); 355 } 356 } 357 358 void FinalizationObservers::traceWeakEdges(JSTracer* trc) { 359 // Removing dead pointers from vectors may reorder live pointers to gray 360 // things in the vector. This is OK. 361 AutoTouchingGrayThings atgt; 362 363 traceWeakWeakRefEdges(trc); 364 traceWeakFinalizationRegistryEdges(trc); 365 } 366 367 void FinalizationObservers::traceWeakFinalizationRegistryEdges(JSTracer* trc) { 368 // Sweep finalization registry data and queue finalization records for cleanup 369 // for any entries whose target is dying and remove them from the map. 370 371 GCRuntime* gc = &trc->runtime()->gc; 372 373 for (RegistrySet::Enum e(registries); !e.empty(); e.popFront()) { 374 auto result = TraceWeakEdge(trc, &e.mutableFront(), "FinalizationRegistry"); 375 if (result.isDead()) { 376 auto* registry = result.initialTarget(); 377 registry->queue()->setHasRegistry(false); 378 e.removeFront(); 379 } else { 380 FinalizationRegistryObject* registry = result.finalTarget(); 381 registry->traceWeak(trc); 382 383 // Now we know the registry is alive we can queue any records for cleanup 384 // if this didn't happen already. See 385 // shouldQueueFinalizationRegistryForCleanup for details. 386 FinalizationQueueObject* queue = registry->queue(); 387 if (queue->hasRecordsToCleanUp()) { 388 MOZ_ASSERT(shouldQueueFinalizationRegistryForCleanup(queue)); 389 gc->queueFinalizationRegistryForCleanup(queue); 390 } 391 } 392 } 393 394 for (RecordMap::Enum e(recordMap); !e.empty(); e.popFront()) { 395 ObserverList& records = e.front().value(); 396 397 // Sweep finalization records, removing any dead ones. 398 for (auto iter = records.iter(); !iter.done(); iter.next()) { 399 auto* record = &iter->as<FinalizationRecordObject>(); 400 MOZ_ASSERT(record->isInRecordMap()); 401 auto result = 402 TraceManuallyBarrieredWeakEdge(trc, &record, "FinalizationRecord"); 403 if (result.isDead()) { 404 record = result.initialTarget(); 405 record->setInRecordMap(false); 406 record->unlink(); 407 } 408 } 409 410 // Queue remaining finalization records if the target is dying. 411 if (!TraceWeakEdge(trc, &e.front().mutableKey(), 412 "FinalizationRecord target")) { 413 for (auto iter = records.iter(); !iter.done(); iter.next()) { 414 auto* record = &iter->as<FinalizationRecordObject>(); 415 record->setInRecordMap(false); 416 record->unlink(); 417 FinalizationQueueObject* queue = record->queue(); 418 queue->queueRecordToBeCleanedUp(record); 419 if (shouldQueueFinalizationRegistryForCleanup(queue)) { 420 gc->queueFinalizationRegistryForCleanup(queue); 421 } 422 } 423 e.removeFront(); 424 } 425 } 426 } 427 428 bool FinalizationObservers::shouldQueueFinalizationRegistryForCleanup( 429 FinalizationQueueObject* queue) { 430 // FinalizationRegistries and their targets may be in different zones and 431 // therefore swept at different times during GC. If a target is observed to 432 // die but the registry's zone has not yet been swept then we don't whether we 433 // need to queue the registry for cleanup callbacks, as the registry itself 434 // might be dead. 435 // 436 // In this case we defer queuing the registry and this happens when the 437 // registry is swept. 438 Zone* zone = queue->zone(); 439 return !zone->wasGCStarted() || zone->gcState() >= Zone::Sweep; 440 } 441 442 void GCRuntime::queueFinalizationRegistryForCleanup( 443 FinalizationQueueObject* queue) { 444 // Prod the embedding to call us back later to run the finalization callbacks, 445 // if necessary. 446 447 MOZ_ASSERT(!IsAboutToBeFinalizedUnbarriered(queue)); 448 MOZ_ASSERT(!IsAboutToBeFinalizedUnbarriered(queue->doCleanupFunction())); 449 if (queue->isQueuedForCleanup()) { 450 return; 451 } 452 453 JSObject* unwrappedHostDefineData = nullptr; 454 455 if (JSObject* wrapped = queue->getHostDefinedData()) { 456 unwrappedHostDefineData = UncheckedUnwrapWithoutExpose(wrapped); 457 MOZ_ASSERT(unwrappedHostDefineData); 458 // If the hostDefined object becomes a dead wrapper here, the target global 459 // has already gone, and the finalization callback won't do anything to it 460 // anyway. 461 if (JS_IsDeadWrapper(unwrappedHostDefineData)) { 462 return; 463 } 464 } 465 466 callHostCleanupFinalizationRegistryCallback(queue->doCleanupFunction(), 467 unwrappedHostDefineData); 468 469 // The queue object may be gray, and that's OK. 470 AutoTouchingGrayThings atgt; 471 472 queue->setQueuedForCleanup(true); 473 } 474 475 // Register |target| such that when it dies |weakRef| will have its pointer to 476 // |target| cleared. 477 bool GCRuntime::registerWeakRef(JSContext* cx, HandleValue target, 478 Handle<WeakRefObject*> weakRef) { 479 MOZ_ASSERT_IF(target.isObject(), 480 !IsCrossCompartmentWrapper(&target.toObject())); 481 482 Zone* zone = GetWeakTargetZone(target); 483 if (!zone->ensureFinalizationObservers() || 484 !zone->finalizationObservers()->addWeakRefTarget(target, weakRef)) { 485 ReportOutOfMemory(cx); 486 return false; 487 } 488 489 return true; 490 } 491 492 bool FinalizationObservers::addWeakRefTarget(HandleValue target, 493 Handle<WeakRefObject*> weakRef) { 494 auto ptr = weakRefMap.lookupForAdd(target); 495 if (!ptr && !weakRefMap.relookupOrAdd(ptr, target, ObserverList())) { 496 return false; 497 } 498 499 ptr->value().insertFront(weakRef); 500 return true; 501 } 502 503 void FinalizationObservers::removeWeakRefTarget( 504 Handle<Value> target, Handle<WeakRefObject*> weakRef) { 505 MOZ_ASSERT(CanBeHeldWeakly(target)); 506 MOZ_ASSERT(weakRef->target() == target); 507 508 MOZ_ASSERT(weakRef->isInList()); 509 weakRef->clearTargetAndUnlink(); 510 511 auto ptr = weakRefMap.lookup(target); 512 MOZ_ASSERT(ptr); 513 ObserverList& list = ptr->value(); 514 if (list.isEmpty()) { 515 weakRefMap.remove(ptr); 516 } 517 } 518 519 void FinalizationObservers::traceWeakWeakRefEdges(JSTracer* trc) { 520 for (WeakRefMap::Enum e(weakRefMap); !e.empty(); e.popFront()) { 521 ObserverList& weakRefs = e.front().value(); 522 auto result = TraceWeakEdge(trc, &e.front().mutableKey(), "WeakRef target"); 523 if (result.isDead()) { 524 // Clear the observer list if the target is dying. 525 while (!weakRefs.isEmpty()) { 526 auto* weakRef = &weakRefs.getFirst()->as<WeakRefObject>(); 527 weakRef->clearTargetAndUnlink(); 528 } 529 e.removeFront(); 530 } else if (result.finalTarget() != result.initialTarget()) { 531 // Update WeakRef targets if the target has been moved. 532 traceWeakWeakRefList(trc, weakRefs, result.finalTarget()); 533 } 534 } 535 } 536 537 void FinalizationObservers::traceWeakWeakRefList(JSTracer* trc, 538 ObserverList& weakRefs, 539 Value target) { 540 MOZ_ASSERT(!IsForwarded(target.toGCThing())); 541 542 for (auto iter = weakRefs.iter(); !iter.done(); iter.next()) { 543 auto* weakRef = &iter.get()->as<WeakRefObject>(); 544 MOZ_ASSERT(!IsForwarded(weakRef)); 545 if (weakRef->target() != target) { 546 MOZ_ASSERT(MaybeForwarded(weakRef->target().toGCThing()) == 547 target.toGCThing()); 548 weakRef->setTargetUnbarriered(target); 549 } 550 } 551 }