tor-browser

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

FinalizationRegistryObject.cpp (27837B)


      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 // Implementation of JS FinalizationRegistry objects.
      8 
      9 #include "builtin/FinalizationRegistryObject.h"
     10 
     11 #include "mozilla/ScopeExit.h"
     12 
     13 #include "jsapi.h"
     14 
     15 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     16 #include "vm/GlobalObject.h"
     17 #include "vm/Interpreter.h"
     18 
     19 #include "gc/GCContext-inl.h"
     20 #include "gc/WeakMap-inl.h"
     21 #include "vm/JSObject-inl.h"
     22 #include "vm/NativeObject-inl.h"
     23 
     24 using namespace js;
     25 
     26 ///////////////////////////////////////////////////////////////////////////
     27 // FinalizationRecordObject
     28 
     29 const JSClassOps FinalizationRecordObject::classOps_ = {
     30    nullptr,   // addProperty
     31    nullptr,   // delProperty
     32    nullptr,   // enumerate
     33    nullptr,   // newEnumerate
     34    nullptr,   // resolve
     35    nullptr,   // mayResolve
     36    finalize,  // finalize
     37    nullptr,   // call
     38    nullptr,   // construct
     39    nullptr,   // trace
     40 };
     41 
     42 const JSClass FinalizationRecordObject::class_ = {
     43    "FinalizationRecord",
     44    JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE,
     45    &classOps_,
     46    JS_NULL_CLASS_SPEC,
     47    &classExtension_,
     48 };
     49 
     50 /* static */
     51 FinalizationRecordObject* FinalizationRecordObject::create(
     52    JSContext* cx, HandleFinalizationQueueObject queue, HandleValue heldValue) {
     53  MOZ_ASSERT(queue);
     54 
     55  auto record = NewObjectWithGivenProto<FinalizationRecordObject>(cx, nullptr);
     56  if (!record) {
     57    return nullptr;
     58  }
     59 
     60  MOZ_ASSERT(queue->compartment() == record->compartment());
     61 
     62  record->initReservedSlot(QueueSlot, ObjectValue(*queue));
     63  record->initReservedSlot(HeldValueSlot, heldValue);
     64 
     65  return record;
     66 }
     67 
     68 /* static */
     69 void FinalizationRecordObject::finalize(JS::GCContext* gcx, JSObject* obj) {
     70  auto* record = &obj->as<FinalizationRecordObject>();
     71  MOZ_ASSERT_IF(!record->isInRecordMap(), !record->isInList());
     72  record->unlink();
     73 }
     74 
     75 FinalizationQueueObject* FinalizationRecordObject::queue() const {
     76  Value value = getReservedSlot(QueueSlot);
     77  if (value.isUndefined()) {
     78    return nullptr;
     79  }
     80  return &value.toObject().as<FinalizationQueueObject>();
     81 }
     82 
     83 Value FinalizationRecordObject::heldValue() const {
     84  return getReservedSlot(HeldValueSlot);
     85 }
     86 
     87 bool FinalizationRecordObject::isRegistered() const {
     88  MOZ_ASSERT_IF(!queue(), heldValue().isUndefined());
     89  return queue();
     90 }
     91 
     92 #ifdef DEBUG
     93 
     94 void FinalizationRecordObject::setState(State state) {
     95  Value value;
     96  if (state != Unknown) {
     97    value = Int32Value(int32_t(state));
     98  }
     99  setReservedSlot(DebugStateSlot, value);
    100 }
    101 
    102 FinalizationRecordObject::State FinalizationRecordObject::getState() const {
    103  Value value = getReservedSlot(DebugStateSlot);
    104  if (value.isUndefined()) {
    105    return Unknown;
    106  }
    107 
    108  State state = State(value.toInt32());
    109  MOZ_ASSERT(state == InRecordMap || state == InQueue);
    110  return state;
    111 }
    112 
    113 #endif
    114 
    115 void FinalizationRecordObject::setInRecordMap(bool newValue) {
    116 #ifdef DEBUG
    117  State newState = newValue ? InRecordMap : Unknown;
    118  MOZ_ASSERT(getState() != newState);
    119  setState(newState);
    120 #endif
    121 }
    122 
    123 void FinalizationRecordObject::setInQueue(bool newValue) {
    124 #ifdef DEBUG
    125  State newState = newValue ? InQueue : Unknown;
    126  MOZ_ASSERT(getState() != newState);
    127  setState(newState);
    128 #endif
    129 }
    130 
    131 void FinalizationRecordObject::clear() {
    132  MOZ_ASSERT(queue());
    133  setReservedSlot(QueueSlot, UndefinedValue());
    134  setReservedSlot(HeldValueSlot, UndefinedValue());
    135  MOZ_ASSERT(!isRegistered());
    136 }
    137 
    138 ///////////////////////////////////////////////////////////////////////////
    139 // FinalizationRegistryObject
    140 
    141 // Bug 1600300: FinalizationRegistryObject is foreground finalized so that
    142 // HeapPtr destructors never see referents with released arenas. When this is
    143 // fixed we may be able to make this background finalized again.
    144 const JSClass FinalizationRegistryObject::class_ = {
    145    "FinalizationRegistry",
    146    JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry) |
    147        JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE,
    148    &classOps_,
    149    &classSpec_,
    150 };
    151 
    152 const JSClass FinalizationRegistryObject::protoClass_ = {
    153    "FinalizationRegistry.prototype",
    154    JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry),
    155    JS_NULL_CLASS_OPS,
    156    &classSpec_,
    157 };
    158 
    159 const JSClassOps FinalizationRegistryObject::classOps_ = {
    160    nullptr,                               // addProperty
    161    nullptr,                               // delProperty
    162    nullptr,                               // enumerate
    163    nullptr,                               // newEnumerate
    164    nullptr,                               // resolve
    165    nullptr,                               // mayResolve
    166    FinalizationRegistryObject::finalize,  // finalize
    167    nullptr,                               // call
    168    nullptr,                               // construct
    169    FinalizationRegistryObject::trace,     // trace
    170 };
    171 
    172 const ClassSpec FinalizationRegistryObject::classSpec_ = {
    173    GenericCreateConstructor<construct, 1, gc::AllocKind::FUNCTION>,
    174    GenericCreatePrototype<FinalizationRegistryObject>,
    175    nullptr,
    176    nullptr,
    177    methods_,
    178    properties_,
    179 };
    180 
    181 const JSFunctionSpec FinalizationRegistryObject::methods_[] = {
    182    JS_FN("register", register_, 2, 0),
    183    JS_FN("unregister", unregister, 1, 0),
    184    JS_FN("cleanupSome", cleanupSome, 0, 0),
    185    JS_FS_END,
    186 };
    187 
    188 const JSPropertySpec FinalizationRegistryObject::properties_[] = {
    189    JS_STRING_SYM_PS(toStringTag, "FinalizationRegistry", JSPROP_READONLY),
    190    JS_PS_END,
    191 };
    192 
    193 /* static */
    194 bool FinalizationRegistryObject::construct(JSContext* cx, unsigned argc,
    195                                           Value* vp) {
    196  CallArgs args = CallArgsFromVp(argc, vp);
    197 
    198  if (!ThrowIfNotConstructing(cx, args, "FinalizationRegistry")) {
    199    return false;
    200  }
    201 
    202  RootedObject cleanupCallback(
    203      cx, ValueToCallable(cx, args.get(0), 1, NO_CONSTRUCT));
    204  if (!cleanupCallback) {
    205    return false;
    206  }
    207 
    208  RootedObject proto(cx);
    209  if (!GetPrototypeFromBuiltinConstructor(
    210          cx, args, JSProto_FinalizationRegistry, &proto)) {
    211    return false;
    212  }
    213 
    214  Rooted<UniquePtr<FinalizationRecordVector>> records(
    215      cx, cx->make_unique<FinalizationRecordVector>(cx->zone()));
    216  if (!records) {
    217    return false;
    218  }
    219 
    220  Rooted<UniquePtr<RegistrationsMap>> registrations(
    221      cx, cx->make_unique<RegistrationsMap>(cx));
    222  if (!registrations) {
    223    return false;
    224  }
    225 
    226  RootedFinalizationQueueObject queue(
    227      cx, FinalizationQueueObject::create(cx, cleanupCallback));
    228  if (!queue) {
    229    return false;
    230  }
    231 
    232  RootedFinalizationRegistryObject registry(
    233      cx, NewObjectWithClassProto<FinalizationRegistryObject>(cx, proto));
    234  if (!registry) {
    235    return false;
    236  }
    237 
    238  registry->initReservedSlot(QueueSlot, ObjectValue(*queue));
    239  InitReservedSlot(registry, RecordsWithoutTokenSlot, records.release(),
    240                   MemoryUse::FinalizationRecordVector);
    241  InitReservedSlot(registry, RegistrationsSlot, registrations.release(),
    242                   MemoryUse::FinalizationRegistryRegistrations);
    243 
    244  if (!cx->runtime()->gc.addFinalizationRegistry(cx, registry)) {
    245    return false;
    246  }
    247 
    248  queue->setHasRegistry(true);
    249 
    250  args.rval().setObject(*registry);
    251  return true;
    252 }
    253 
    254 /* static */
    255 void FinalizationRegistryObject::trace(JSTracer* trc, JSObject* obj) {
    256  // Trace finalization records.
    257  auto* registry = &obj->as<FinalizationRegistryObject>();
    258  if (FinalizationRecordVector* records = registry->recordsWithoutToken()) {
    259    records->trace(trc);
    260  }
    261 
    262  // Trace the records referred to by the registrations map, but not its keys
    263  // which are weakly held.
    264  if (RegistrationsMap* registrations = registry->registrations()) {
    265    for (auto iter = registrations->iter(); !iter.done(); iter.next()) {
    266      iter.get().value().trace(trc);
    267    }
    268  }
    269 }
    270 
    271 void FinalizationRegistryObject::traceWeak(JSTracer* trc) {
    272  // Trace and update the contents of the registrations map's keys, which
    273  // are weakly held.
    274  MOZ_ASSERT(registrations());
    275 
    276  for (auto iter = registrations()->modIter(); !iter.done(); iter.next()) {
    277    auto result = TraceWeakEdge(trc, &iter.getMutable().mutableKey(),
    278                                "FinalizationRegistry unregister token");
    279    if (result.isDead()) {
    280      // The unregister token has died and can no longer be used to unregister
    281      // registrations. However those registrations remain valid.
    282      AutoEnterOOMUnsafeRegion oomUnsafe;
    283      if (!recordsWithoutToken()->appendAll(std::move(iter.get().value()))) {
    284        oomUnsafe.crash("FinalizationRegistryObject::traceWeak");
    285      }
    286      iter.remove();
    287    }
    288  }
    289 
    290  registrations()->compact();
    291 }
    292 
    293 /* static */
    294 void FinalizationRegistryObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    295  auto registry = &obj->as<FinalizationRegistryObject>();
    296 
    297  // The queue's flag should have been updated by
    298  // GCRuntime::sweepFinalizationRegistries.
    299  MOZ_ASSERT_IF(registry->queue(), !registry->queue()->hasRegistry());
    300 
    301  gcx->delete_(obj, registry->recordsWithoutToken(),
    302               MemoryUse::FinalizationRecordVector);
    303  gcx->delete_(obj, registry->registrations(),
    304               MemoryUse::FinalizationRegistryRegistrations);
    305 }
    306 
    307 FinalizationRecordVector* FinalizationRegistryObject::recordsWithoutToken()
    308    const {
    309  Value value = getReservedSlot(RecordsWithoutTokenSlot);
    310  if (value.isUndefined()) {
    311    return nullptr;
    312  }
    313  return static_cast<FinalizationRecordVector*>(value.toPrivate());
    314 }
    315 
    316 FinalizationQueueObject* FinalizationRegistryObject::queue() const {
    317  Value value = getReservedSlot(QueueSlot);
    318  if (value.isUndefined()) {
    319    return nullptr;
    320  }
    321  return &value.toObject().as<FinalizationQueueObject>();
    322 }
    323 
    324 FinalizationRegistryObject::RegistrationsMap*
    325 FinalizationRegistryObject::registrations() const {
    326  Value value = getReservedSlot(RegistrationsSlot);
    327  if (value.isUndefined()) {
    328    return nullptr;
    329  }
    330  return static_cast<RegistrationsMap*>(value.toPrivate());
    331 }
    332 
    333 // FinalizationRegistry.prototype.register(target, heldValue [, unregisterToken
    334 // ])
    335 // https://tc39.es/ecma262/#sec-finalization-registry.prototype.register
    336 /* static */
    337 bool FinalizationRegistryObject::register_(JSContext* cx, unsigned argc,
    338                                           Value* vp) {
    339  CallArgs args = CallArgsFromVp(argc, vp);
    340 
    341  // 1. Let finalizationRegistry be the this value.
    342  // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
    343  if (!args.thisv().isObject() ||
    344      !args.thisv().toObject().is<FinalizationRegistryObject>()) {
    345    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    346                              JSMSG_NOT_A_FINALIZATION_REGISTRY,
    347                              "Receiver of FinalizationRegistry.register call");
    348    return false;
    349  }
    350 
    351  RootedFinalizationRegistryObject registry(
    352      cx, &args.thisv().toObject().as<FinalizationRegistryObject>());
    353 
    354  // 3. If CanBeHeldWeakly(target) is false, throw a TypeError exception.
    355  RootedValue target(cx, args.get(0));
    356  if (!CanBeHeldWeakly(target)) {
    357    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    358                              JSMSG_BAD_FINALIZATION_REGISTRY_TARGET);
    359    return false;
    360  }
    361 
    362  // 4. If SameValue(target, heldValue) is true, throw a TypeError exception.
    363  HandleValue heldValue = args.get(1);
    364  if (heldValue == target) {
    365    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    366                              JSMSG_BAD_HELD_VALUE);
    367    return false;
    368  }
    369 
    370  // 5. If CanBeHeldWeakly(unregisterToken) is false, then:
    371  //    a. If unregisterToken is not undefined, throw a TypeError exception.
    372  //    b. Set unregisterToken to empty.
    373  RootedValue unregisterToken(cx, args.get(2));
    374  if (!CanBeHeldWeakly(unregisterToken) && !unregisterToken.isUndefined()) {
    375    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    376                              JSMSG_BAD_UNREGISTER_TOKEN,
    377                              "FinalizationRegistry.register");
    378    return false;
    379  }
    380 
    381  // Create the finalization record representing this target and heldValue.
    382  Rooted<FinalizationQueueObject*> queue(cx, registry->queue());
    383  Rooted<FinalizationRecordObject*> record(
    384      cx, FinalizationRecordObject::create(cx, queue, heldValue));
    385  if (!record) {
    386    return false;
    387  }
    388 
    389  if (!addRegistration(cx, registry, unregisterToken, record)) {
    390    return false;
    391  }
    392  auto registrationGuard = mozilla::MakeScopeExit(
    393      [&] { removeRegistrationOnError(registry, unregisterToken, record); });
    394 
    395  bool isPermanent = false;
    396  if (target.isObject()) {
    397    // Fully unwrap the target to register it with the GC.
    398    RootedObject object(cx, CheckedUnwrapDynamic(&target.toObject(), cx));
    399    if (!object) {
    400      ReportAccessDenied(cx);
    401      return false;
    402    }
    403 
    404    target = ObjectValue(*object);
    405 
    406    // If the target is a DOM wrapper, preserve it.
    407    if (!preserveDOMWrapper(cx, object)) {
    408      return false;
    409    }
    410  } else {
    411    JS::Symbol* symbol = target.toSymbol();
    412    isPermanent = symbol->isPermanentAndMayBeShared();
    413  }
    414 
    415  // Register the record with the target, unless the target is permanent.
    416  // (See the note following https://tc39.es/ecma262/#sec-canbeheldweakly)
    417  if (!isPermanent) {
    418    gc::GCRuntime* gc = &cx->runtime()->gc;
    419    if (!gc->registerWithFinalizationRegistry(cx, target, record)) {
    420      return false;
    421    }
    422  }
    423 
    424  // 8. Return undefined.
    425  registrationGuard.release();
    426  args.rval().setUndefined();
    427  return true;
    428 }
    429 
    430 /* static */
    431 bool FinalizationRegistryObject::preserveDOMWrapper(JSContext* cx,
    432                                                    HandleObject obj) {
    433  if (!MaybePreserveDOMWrapper(cx, obj)) {
    434    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    435                              JSMSG_BAD_FINALIZATION_REGISTRY_OBJECT);
    436    return false;
    437  }
    438 
    439  return true;
    440 }
    441 
    442 /* static */
    443 bool FinalizationRegistryObject::addRegistration(
    444    JSContext* cx, HandleFinalizationRegistryObject registry,
    445    HandleValue unregisterToken, HandleFinalizationRecordObject record) {
    446  // Add the record to the list of records associated with this unregister
    447  // token, or add it to the main list.
    448 
    449  MOZ_ASSERT(registry->registrations());
    450  MOZ_ASSERT(unregisterToken.isUndefined() || CanBeHeldWeakly(unregisterToken));
    451 
    452  if (unregisterToken.isUndefined()) {
    453    if (!registry->recordsWithoutToken()->append(record)) {
    454      ReportOutOfMemory(cx);
    455      return false;
    456    }
    457    return true;
    458  }
    459 
    460  auto& map = *registry->registrations();
    461  auto ptr = map.lookupForAdd(unregisterToken.get());
    462  if (!ptr.found() &&
    463      !map.add(ptr, unregisterToken, FinalizationRecordVector(cx->zone()))) {
    464    ReportOutOfMemory(cx);
    465    return false;
    466  }
    467 
    468  if (!ptr->value().append(record)) {
    469    ReportOutOfMemory(cx);
    470    return false;
    471  }
    472 
    473  return true;
    474 }
    475 
    476 /* static */
    477 void FinalizationRegistryObject::removeRegistrationOnError(
    478    HandleFinalizationRegistryObject registry, HandleValue unregisterToken,
    479    HandleFinalizationRecordObject record) {
    480  // Remove a registration if something went wrong before we added it to the
    481  // target zone's map. Note that this can't remove a registration after that
    482  // point.
    483 
    484  MOZ_ASSERT(registry->registrations());
    485  MOZ_ASSERT(unregisterToken.isUndefined() || CanBeHeldWeakly(unregisterToken));
    486  JS::AutoAssertNoGC nogc;
    487 
    488  if (unregisterToken.isUndefined()) {
    489    MOZ_ASSERT(registry->recordsWithoutToken()->back() == record);
    490    registry->recordsWithoutToken()->popBack();
    491    return;
    492  }
    493 
    494  auto ptr = registry->registrations()->lookup(unregisterToken);
    495  MOZ_ASSERT(ptr.found());
    496  FinalizationRecordVector& records = ptr->value();
    497  MOZ_ASSERT(records.back() == record);
    498  records.popBack();
    499  if (records.empty()) {
    500    registry->registrations()->remove(ptr);
    501  }
    502 }
    503 
    504 // FinalizationRegistry.prototype.unregister ( unregisterToken )
    505 // https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.unregister
    506 /* static */
    507 bool FinalizationRegistryObject::unregister(JSContext* cx, unsigned argc,
    508                                            Value* vp) {
    509  CallArgs args = CallArgsFromVp(argc, vp);
    510 
    511  // 1. Let finalizationRegistry be the this value.
    512  // 2. If Type(finalizationRegistry) is not Object, throw a TypeError
    513  //    exception.
    514  // 3. If finalizationRegistry does not have a [[Cells]] internal slot, throw a
    515  //    TypeError exception.
    516  if (!args.thisv().isObject() ||
    517      !args.thisv().toObject().is<FinalizationRegistryObject>()) {
    518    JS_ReportErrorNumberASCII(
    519        cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_REGISTRY,
    520        "Receiver of FinalizationRegistry.unregister call");
    521    return false;
    522  }
    523 
    524  RootedFinalizationRegistryObject registry(
    525      cx, &args.thisv().toObject().as<FinalizationRegistryObject>());
    526 
    527  // 4. If Type(unregisterToken) is not Object, throw a TypeError exception.
    528  RootedValue unregisterToken(cx, args.get(0));
    529  if (!CanBeHeldWeakly(unregisterToken)) {
    530    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    531                              JSMSG_BAD_UNREGISTER_TOKEN,
    532                              "FinalizationRegistry.unregister");
    533    return false;
    534  }
    535 
    536  // 5. Let removed be false.
    537  bool removed = false;
    538 
    539  // 6. For each Record { [[Target]], [[HeldValue]], [[UnregisterToken]] } cell
    540  //    that is an element of finalizationRegistry.[[Cells]], do
    541  //    a. If SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then
    542  //       i. Remove cell from finalizationRegistry.[[Cells]].
    543  //       ii. Set removed to true.
    544 
    545  RegistrationsMap* map = registry->registrations();
    546  auto ptr = map->lookup(unregisterToken);
    547  if (ptr) {
    548    FinalizationRecordVector& records = ptr->value();
    549    MOZ_ASSERT(!records.empty());
    550    for (FinalizationRecordObject* record : records) {
    551      if (unregisterRecord(record)) {
    552        removed = true;
    553      }
    554    }
    555    map->remove(unregisterToken);
    556  }
    557 
    558  // 7. Return removed.
    559  args.rval().setBoolean(removed);
    560  return true;
    561 }
    562 
    563 /* static */
    564 bool FinalizationRegistryObject::unregisterRecord(
    565    FinalizationRecordObject* record) {
    566  if (!record->isRegistered()) {
    567    MOZ_ASSERT(!record->isInList());
    568    return false;
    569  }
    570 
    571  // Remove record from the target list if present.
    572  record->unlink();
    573 
    574  // Clear the fields of this record, marking it as unregistered. It will be
    575  // removed from relevant data structures when they are next swept.
    576  record->clear();
    577  MOZ_ASSERT(!record->isRegistered());
    578 
    579  return true;
    580 }
    581 
    582 // FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
    583 // https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.cleanupSome
    584 bool FinalizationRegistryObject::cleanupSome(JSContext* cx, unsigned argc,
    585                                             Value* vp) {
    586  CallArgs args = CallArgsFromVp(argc, vp);
    587 
    588  // 1. Let finalizationRegistry be the this value.
    589  // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
    590  if (!args.thisv().isObject() ||
    591      !args.thisv().toObject().is<FinalizationRegistryObject>()) {
    592    JS_ReportErrorNumberASCII(
    593        cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_REGISTRY,
    594        "Receiver of FinalizationRegistry.cleanupSome call");
    595    return false;
    596  }
    597 
    598  RootedFinalizationRegistryObject registry(
    599      cx, &args.thisv().toObject().as<FinalizationRegistryObject>());
    600 
    601  // 3. If callback is not undefined and IsCallable(callback) is false, throw a
    602  //    TypeError exception.
    603  RootedObject cleanupCallback(cx);
    604  if (!args.get(0).isUndefined()) {
    605    cleanupCallback = ValueToCallable(cx, args.get(0), -1, NO_CONSTRUCT);
    606    if (!cleanupCallback) {
    607      return false;
    608    }
    609  }
    610 
    611  RootedFinalizationQueueObject queue(cx, registry->queue());
    612  if (!FinalizationQueueObject::cleanupQueuedRecords(cx, queue,
    613                                                     cleanupCallback)) {
    614    return false;
    615  }
    616 
    617  args.rval().setUndefined();
    618  return true;
    619 }
    620 
    621 ///////////////////////////////////////////////////////////////////////////
    622 // FinalizationQueueObject
    623 
    624 // Bug 1600300: FinalizationQueueObject is foreground finalized so that
    625 // HeapPtr destructors never see referents with released arenas. When this is
    626 // fixed we may be able to make this background finalized again.
    627 const JSClass FinalizationQueueObject::class_ = {
    628    "FinalizationQueue",
    629    JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE,
    630    &classOps_,
    631 };
    632 
    633 const JSClassOps FinalizationQueueObject::classOps_ = {
    634    nullptr,                            // addProperty
    635    nullptr,                            // delProperty
    636    nullptr,                            // enumerate
    637    nullptr,                            // newEnumerate
    638    nullptr,                            // resolve
    639    nullptr,                            // mayResolve
    640    FinalizationQueueObject::finalize,  // finalize
    641    nullptr,                            // call
    642    nullptr,                            // construct
    643    FinalizationQueueObject::trace,     // trace
    644 };
    645 
    646 /* static */
    647 FinalizationQueueObject* FinalizationQueueObject::create(
    648    JSContext* cx, HandleObject cleanupCallback) {
    649  MOZ_ASSERT(cleanupCallback);
    650 
    651  Rooted<UniquePtr<FinalizationRecordVector>> recordsToBeCleanedUp(
    652      cx, cx->make_unique<FinalizationRecordVector>(cx->zone()));
    653  if (!recordsToBeCleanedUp) {
    654    return nullptr;
    655  }
    656 
    657  Handle<PropertyName*> funName = cx->names().empty_;
    658  RootedFunction doCleanupFunction(
    659      cx, NewNativeFunction(cx, doCleanup, 0, funName,
    660                            gc::AllocKind::FUNCTION_EXTENDED));
    661  if (!doCleanupFunction) {
    662    return nullptr;
    663  }
    664 
    665  // It's problematic storing a CCW to a global in another compartment because
    666  // you don't know how far to unwrap it to get the original object
    667  // back. Instead store a CCW to a plain object in the same compartment as the
    668  // global (this uses Object.prototype).
    669  Rooted<JSObject*> hostDefinedData(cx);
    670  if (!GetObjectFromHostDefinedData(cx, &hostDefinedData)) {
    671    return nullptr;
    672  }
    673 
    674  FinalizationQueueObject* queue =
    675      NewObjectWithGivenProto<FinalizationQueueObject>(cx, nullptr);
    676  if (!queue) {
    677    return nullptr;
    678  }
    679 
    680  queue->initReservedSlot(CleanupCallbackSlot, ObjectValue(*cleanupCallback));
    681  queue->initReservedSlot(HostDefinedDataSlot,
    682                          JS::ObjectOrNullValue(hostDefinedData));
    683  InitReservedSlot(queue, RecordsToBeCleanedUpSlot,
    684                   recordsToBeCleanedUp.release(),
    685                   MemoryUse::FinalizationRegistryRecordVector);
    686  queue->initReservedSlot(IsQueuedForCleanupSlot, BooleanValue(false));
    687  queue->initReservedSlot(DoCleanupFunctionSlot,
    688                          ObjectValue(*doCleanupFunction));
    689  queue->initReservedSlot(HasRegistrySlot, BooleanValue(false));
    690 
    691  doCleanupFunction->setExtendedSlot(DoCleanupFunction_QueueSlot,
    692                                     ObjectValue(*queue));
    693 
    694  return queue;
    695 }
    696 
    697 /* static */
    698 void FinalizationQueueObject::trace(JSTracer* trc, JSObject* obj) {
    699  auto queue = &obj->as<FinalizationQueueObject>();
    700 
    701  if (FinalizationRecordVector* records = queue->recordsToBeCleanedUp()) {
    702    records->trace(trc);
    703  }
    704 }
    705 
    706 /* static */
    707 void FinalizationQueueObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    708  auto* queue = &obj->as<FinalizationQueueObject>();
    709  gcx->delete_(obj, queue->recordsToBeCleanedUp(),
    710               MemoryUse::FinalizationRegistryRecordVector);
    711 }
    712 
    713 void FinalizationQueueObject::setHasRegistry(bool newValue) {
    714  MOZ_ASSERT(hasRegistry() != newValue);
    715 
    716  // Suppress our assertions about touching grey things. It's OK for us to set a
    717  // boolean slot even if this object is gray.
    718  AutoTouchingGrayThings atgt;
    719 
    720  setReservedSlot(HasRegistrySlot, BooleanValue(newValue));
    721 }
    722 
    723 bool FinalizationQueueObject::hasRegistry() const {
    724  return getReservedSlot(HasRegistrySlot).toBoolean();
    725 }
    726 
    727 inline JSObject* FinalizationQueueObject::cleanupCallback() const {
    728  Value value = getReservedSlot(CleanupCallbackSlot);
    729  if (value.isUndefined()) {
    730    return nullptr;
    731  }
    732  return &value.toObject();
    733 }
    734 
    735 JSObject* FinalizationQueueObject::getHostDefinedData() const {
    736  Value value = getReservedSlot(HostDefinedDataSlot);
    737  if (value.isUndefined()) {
    738    return nullptr;
    739  }
    740  return value.toObjectOrNull();
    741 }
    742 
    743 bool FinalizationQueueObject::hasRecordsToCleanUp() const {
    744  FinalizationRecordVector* records = recordsToBeCleanedUp();
    745  return records && !records->empty();
    746 }
    747 
    748 FinalizationRecordVector* FinalizationQueueObject::recordsToBeCleanedUp()
    749    const {
    750  Value value = getReservedSlot(RecordsToBeCleanedUpSlot);
    751  if (value.isUndefined()) {
    752    return nullptr;
    753  }
    754  return static_cast<FinalizationRecordVector*>(value.toPrivate());
    755 }
    756 
    757 bool FinalizationQueueObject::isQueuedForCleanup() const {
    758  return getReservedSlot(IsQueuedForCleanupSlot).toBoolean();
    759 }
    760 
    761 JSFunction* FinalizationQueueObject::doCleanupFunction() const {
    762  Value value = getReservedSlot(DoCleanupFunctionSlot);
    763  if (value.isUndefined()) {
    764    return nullptr;
    765  }
    766  return &value.toObject().as<JSFunction>();
    767 }
    768 
    769 void FinalizationQueueObject::queueRecordToBeCleanedUp(
    770    FinalizationRecordObject* record) {
    771  MOZ_ASSERT(hasRegistry());
    772 
    773  MOZ_ASSERT(!record->isInQueue());
    774  record->setInQueue(true);
    775 
    776  AutoEnterOOMUnsafeRegion oomUnsafe;
    777  if (!recordsToBeCleanedUp()->append(record)) {
    778    oomUnsafe.crash("FinalizationQueueObject::queueRecordsToBeCleanedUp");
    779  }
    780 }
    781 
    782 void FinalizationQueueObject::setQueuedForCleanup(bool value) {
    783  MOZ_ASSERT(value != isQueuedForCleanup());
    784  setReservedSlot(IsQueuedForCleanupSlot, BooleanValue(value));
    785 }
    786 
    787 /* static */
    788 bool FinalizationQueueObject::doCleanup(JSContext* cx, unsigned argc,
    789                                        Value* vp) {
    790  CallArgs args = CallArgsFromVp(argc, vp);
    791 
    792  RootedFunction callee(cx, &args.callee().as<JSFunction>());
    793 
    794  Value value = callee->getExtendedSlot(DoCleanupFunction_QueueSlot);
    795  RootedFinalizationQueueObject queue(
    796      cx, &value.toObject().as<FinalizationQueueObject>());
    797 
    798  queue->setQueuedForCleanup(false);
    799  return cleanupQueuedRecords(cx, queue);
    800 }
    801 
    802 // CleanupFinalizationRegistry ( finalizationRegistry [ , callback ] )
    803 // https://tc39.es/proposal-weakrefs/#sec-cleanup-finalization-registry
    804 /* static */
    805 bool FinalizationQueueObject::cleanupQueuedRecords(
    806    JSContext* cx, HandleFinalizationQueueObject queue,
    807    HandleObject callbackArg) {
    808  MOZ_ASSERT(cx->compartment() == queue->compartment());
    809 
    810  // 2. If callback is undefined, set callback to
    811  //    finalizationRegistry.[[CleanupCallback]].
    812  RootedValue callback(cx);
    813  if (callbackArg) {
    814    callback.setObject(*callbackArg);
    815  } else {
    816    JSObject* cleanupCallback = queue->cleanupCallback();
    817    MOZ_ASSERT(cleanupCallback);
    818    callback.setObject(*cleanupCallback);
    819  }
    820 
    821  // 3. While finalizationRegistry.[[Cells]] contains a Record cell such that
    822  //    cell.[[WeakRefTarget]] is empty, then an implementation may perform the
    823  //    following steps,
    824  //    a. Choose any such cell.
    825  //    b. Remove cell from finalizationRegistry.[[Cells]].
    826  //    c. Perform ? Call(callback, undefined, « cell.[[HeldValue]] »).
    827 
    828  RootedValue heldValue(cx);
    829  RootedValue rval(cx);
    830  FinalizationRecordVector* records = queue->recordsToBeCleanedUp();
    831  while (!records->empty()) {
    832    FinalizationRecordObject* record = records->popCopy();
    833    MOZ_ASSERT(!record->isInRecordMap());
    834 
    835    JS::ExposeObjectToActiveJS(record);
    836 
    837    MOZ_ASSERT(record->isInQueue());
    838    record->setInQueue(false);
    839 
    840    // Skip over records that have been unregistered.
    841    if (!record->isRegistered()) {
    842      continue;
    843    }
    844 
    845    heldValue.set(record->heldValue());
    846 
    847    record->clear();
    848 
    849    if (!Call(cx, callback, UndefinedHandleValue, heldValue, &rval)) {
    850      return false;
    851    }
    852  }
    853 
    854  return true;
    855 }