tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }