Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/js/src/builtin/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 27 kB image not shown  

Quelle  FinalizationRegistryObject.cpp

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


// Implementation of JS FinalizationRegistry objects.

#include "builtin/FinalizationRegistryObject.h"

#include "mozilla/ScopeExit.h"

#include "jsapi.h"

#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"

#include "gc/GCContext-inl.h"
#include "gc/WeakMap-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"

using namespace js;

///////////////////////////////////////////////////////////////////////////
// FinalizationRecordObject

const JSClassOps FinalizationRecordObject::classOps_ = {
    .finalize = finalize,
};

const JSClass FinalizationRecordObject::class_ = {
    "FinalizationRecord",
    JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE,
    &classOps_,
    JS_NULL_CLASS_SPEC,
    &classExtension_,
};

/* static */
FinalizationRecordObject* FinalizationRecordObject::create(
    JSContext* cx, HandleFinalizationQueueObject queue, HandleValue heldValue) {
  MOZ_ASSERT(queue);

  auto record = NewObjectWithGivenProto<FinalizationRecordObject>(cx, nullptr);
  if (!record) {
    return nullptr;
  }

  MOZ_ASSERT(queue->compartment() == record->compartment());

  record->initReservedSlot(QueueSlot, ObjectValue(*queue));
  record->initReservedSlot(HeldValueSlot, heldValue);

  return record;
}

/* static */
void FinalizationRecordObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  auto* record = &obj->as<FinalizationRecordObject>();
  MOZ_ASSERT_IF(!record->isInRecordMap(), !record->isInList());
  record->unlink();
}

FinalizationQueueObject* FinalizationRecordObject::queue() const {
  Value value = getReservedSlot(QueueSlot);
  if (value.isUndefined()) {
    return nullptr;
  }
  return &value.toObject().as<FinalizationQueueObject>();
}

Value FinalizationRecordObject::heldValue() const {
  return getReservedSlot(HeldValueSlot);
}

bool FinalizationRecordObject::isRegistered() const {
  MOZ_ASSERT_IF(!queue(), heldValue().isUndefined());
  return queue();
}

#ifdef DEBUG

void FinalizationRecordObject::setState(State state) {
  Value value;
  if (state != Unknown) {
    value = Int32Value(int32_t(state));
  }
  setReservedSlot(DebugStateSlot, value);
}

FinalizationRecordObject::State FinalizationRecordObject::getState() const {
  Value value = getReservedSlot(DebugStateSlot);
  if (value.isUndefined()) {
    return Unknown;
  }

  State state = State(value.toInt32());
  MOZ_ASSERT(state == InRecordMap || state == InQueue);
  return state;
}

#endif

void FinalizationRecordObject::setInRecordMap(bool newValue) {
#ifdef DEBUG
  State newState = newValue ? InRecordMap : Unknown;
  MOZ_ASSERT(getState() != newState);
  setState(newState);
#endif
}

void FinalizationRecordObject::setInQueue(bool newValue) {
#ifdef DEBUG
  State newState = newValue ? InQueue : Unknown;
  MOZ_ASSERT(getState() != newState);
  setState(newState);
#endif
}

void FinalizationRecordObject::clear() {
  MOZ_ASSERT(queue());
  setReservedSlot(QueueSlot, UndefinedValue());
  setReservedSlot(HeldValueSlot, UndefinedValue());
  MOZ_ASSERT(!isRegistered());
}

///////////////////////////////////////////////////////////////////////////
// FinalizationRegistryObject

// Bug 1600300: FinalizationRegistryObject is foreground finalized so that
// HeapPtr destructors never see referents with released arenas. When this is
// fixed we may be able to make this background finalized again.
const JSClass FinalizationRegistryObject::class_ = {
    "FinalizationRegistry",
    JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry) |
        JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE,
    &classOps_,
    &classSpec_,
};

const JSClass FinalizationRegistryObject::protoClass_ = {
    "FinalizationRegistry.prototype",
    JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry),
    JS_NULL_CLASS_OPS,
    &classSpec_,
};

const JSClassOps FinalizationRegistryObject::classOps_ = {
    .finalize = FinalizationRegistryObject::finalize,
    .trace = FinalizationRegistryObject::trace,
};

const ClassSpec FinalizationRegistryObject::classSpec_ = {
    GenericCreateConstructor<construct, 1, gc::AllocKind::FUNCTION>,
    GenericCreatePrototype<FinalizationRegistryObject>,
    nullptr,
    nullptr,
    methods_,
    properties_,
};

const JSFunctionSpec FinalizationRegistryObject::methods_[] = {
    JS_FN("register", register_, 20),
    JS_FN("unregister", unregister, 10),
    JS_FN("cleanupSome", cleanupSome, 00),
    JS_FS_END,
};

const JSPropertySpec FinalizationRegistryObject::properties_[] = {
    JS_STRING_SYM_PS(toStringTag, "FinalizationRegistry", JSPROP_READONLY),
    JS_PS_END,
};

/* static */
bool FinalizationRegistryObject::construct(JSContext* cx, unsigned argc,
                                           Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!ThrowIfNotConstructing(cx, args, "FinalizationRegistry")) {
    return false;
  }

  RootedObject cleanupCallback(
      cx, ValueToCallable(cx, args.get(0), 1, NO_CONSTRUCT));
  if (!cleanupCallback) {
    return false;
  }

  RootedObject proto(cx);
  if (!GetPrototypeFromBuiltinConstructor(
          cx, args, JSProto_FinalizationRegistry, &proto)) {
    return false;
  }

  Rooted<UniquePtr<FinalizationRecordVector>> records(
      cx, cx->make_unique<FinalizationRecordVector>(cx->zone()));
  if (!records) {
    return false;
  }

  Rooted<UniquePtr<RegistrationsMap>> registrations(
      cx, cx->make_unique<RegistrationsMap>(cx));
  if (!registrations) {
    return false;
  }

  RootedFinalizationQueueObject queue(
      cx, FinalizationQueueObject::create(cx, cleanupCallback));
  if (!queue) {
    return false;
  }

  RootedFinalizationRegistryObject registry(
      cx, NewObjectWithClassProto<FinalizationRegistryObject>(cx, proto));
  if (!registry) {
    return false;
  }

  registry->initReservedSlot(QueueSlot, ObjectValue(*queue));
  InitReservedSlot(registry, RecordsWithoutTokenSlot, records.release(),
                   MemoryUse::FinalizationRecordVector);
  InitReservedSlot(registry, RegistrationsSlot, registrations.release(),
                   MemoryUse::FinalizationRegistryRegistrations);

  if (!cx->runtime()->gc.addFinalizationRegistry(cx, registry)) {
    return false;
  }

  queue->setHasRegistry(true);

  args.rval().setObject(*registry);
  return true;
}

/* static */
void FinalizationRegistryObject::trace(JSTracer* trc, JSObject* obj) {
  // Trace finalization records.
  auto* registry = &obj->as<FinalizationRegistryObject>();
  if (FinalizationRecordVector* records = registry->recordsWithoutToken()) {
    records->trace(trc);
  }

  // Trace the records referred to by the registrations map, but not its keys
  // which are weakly held.
  if (RegistrationsMap* registrations = registry->registrations()) {
    for (auto iter = registrations->iter(); !iter.done(); iter.next()) {
      iter.get().value().trace(trc);
    }
  }
}

void FinalizationRegistryObject::traceWeak(JSTracer* trc,
                                           bool* hasSymbolRegistrations) {
  // Trace and update the contents of the registrations map's keys, which are
  // weakly held. Remove any old records that have been queued or cleaned up.
  MOZ_ASSERT(recordsWithoutToken());
  MOZ_ASSERT(registrations());
  MOZ_ASSERT(hasSymbolRegistrations);

  recordsWithoutToken()->mutableEraseIf(
      [](FinalizationRecordObject* record) { return !record->isRegistered(); });

  for (auto iter = registrations()->modIter(); !iter.done(); iter.next()) {
    auto result = TraceWeakEdge(trc, &iter.getMutable().mutableKey(),
                                "FinalizationRegistry unregister token");
    if (result.isDead()) {
      // The unregister token has died and can no longer be used to unregister
      // registrations. However those registrations remain valid.
      AutoEnterOOMUnsafeRegion oomUnsafe;
      if (!recordsWithoutToken()->appendAll(std::move(iter.get().value()))) {
        oomUnsafe.crash("FinalizationRegistryObject::traceWeak");
      }
      iter.remove();
    } else {
      if (result.finalTarget().isSymbol()) {
        *hasSymbolRegistrations = true;
      }

      FinalizationRecordVector& records = iter.get().value();
      records.mutableEraseIf([](FinalizationRecordObject* record) {
        return !record->isRegistered();
      });

      if (records.empty()) {
        iter.remove();
      }
    }
  }

  registrations()->compact();
}

/* static */
void FinalizationRegistryObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  auto registry = &obj->as<FinalizationRegistryObject>();

  // The queue's flag should have been updated by
  // GCRuntime::sweepFinalizationRegistries.
  MOZ_ASSERT_IF(registry->queue(), !registry->queue()->hasRegistry());

  gcx->delete_(obj, registry->recordsWithoutToken(),
               MemoryUse::FinalizationRecordVector);
  gcx->delete_(obj, registry->registrations(),
               MemoryUse::FinalizationRegistryRegistrations);
}

FinalizationRecordVector* FinalizationRegistryObject::recordsWithoutToken()
    const {
  Value value = getReservedSlot(RecordsWithoutTokenSlot);
  if (value.isUndefined()) {
    return nullptr;
  }
  return static_cast<FinalizationRecordVector*>(value.toPrivate());
}

FinalizationQueueObject* FinalizationRegistryObject::queue() const {
  Value value = getReservedSlot(QueueSlot);
  if (value.isUndefined()) {
    return nullptr;
  }
  return &value.toObject().as<FinalizationQueueObject>();
}

FinalizationRegistryObject::RegistrationsMap*
FinalizationRegistryObject::registrations() const {
  Value value = getReservedSlot(RegistrationsSlot);
  if (value.isUndefined()) {
    return nullptr;
  }
  return static_cast<RegistrationsMap*>(value.toPrivate());
}

// FinalizationRegistry.prototype.register(target, heldValue [, unregisterToken
// ])
// https://tc39.es/ecma262/#sec-finalization-registry.prototype.register
/* static */
bool FinalizationRegistryObject::register_(JSContext* cx, unsigned argc,
                                           Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // 1. Let finalizationRegistry be the this value.
  // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
  if (!args.thisv().isObject() ||
      !args.thisv().toObject().is<FinalizationRegistryObject>()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_NOT_A_FINALIZATION_REGISTRY,
                              "Receiver of FinalizationRegistry.register call");
    return false;
  }

  RootedFinalizationRegistryObject registry(
      cx, &args.thisv().toObject().as<FinalizationRegistryObject>());

  // 3. If CanBeHeldWeakly(target) is false, throw a TypeError exception.
  RootedValue target(cx, args.get(0));
  if (!CanBeHeldWeakly(target)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_BAD_FINALIZATION_REGISTRY_TARGET);
    return false;
  }

  // 4. If SameValue(target, heldValue) is true, throw a TypeError exception.
  HandleValue heldValue = args.get(1);
  if (heldValue == target) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_BAD_HELD_VALUE);
    return false;
  }

  // 5. If CanBeHeldWeakly(unregisterToken) is false, then:
  //    a. If unregisterToken is not undefined, throw a TypeError exception.
  //    b. Set unregisterToken to empty.
  RootedValue unregisterToken(cx, args.get(2));
  if (!CanBeHeldWeakly(unregisterToken) && !unregisterToken.isUndefined()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_BAD_UNREGISTER_TOKEN,
                              "FinalizationRegistry.register");
    return false;
  }

  // Create the finalization record representing this target and heldValue.
  Rooted<FinalizationQueueObject*> queue(cx, registry->queue());
  Rooted<FinalizationRecordObject*> record(
      cx, FinalizationRecordObject::create(cx, queue, heldValue));
  if (!record) {
    return false;
  }

  if (!addRegistration(cx, registry, unregisterToken, record)) {
    return false;
  }
  auto registrationGuard = mozilla::MakeScopeExit(
      [&] { removeRegistrationOnError(registry, unregisterToken, record); });

  bool isPermanent = false;
  if (target.isObject()) {
    // Fully unwrap the target to register it with the GC.
    RootedObject object(cx, CheckedUnwrapDynamic(&target.toObject(), cx));
    if (!object) {
      ReportAccessDenied(cx);
      return false;
    }

    target = ObjectValue(*object);

    // If the target is a DOM wrapper, preserve it.
    if (!preserveDOMWrapper(cx, object)) {
      return false;
    }
  } else {
    JS::Symbol* symbol = target.toSymbol();
    isPermanent = symbol->isPermanentAndMayBeShared();
  }

  // Register the record with the target, unless the target is permanent.
  // (See the note following https://tc39.es/ecma262/#sec-canbeheldweakly)
  if (!isPermanent) {
    gc::GCRuntime* gc = &cx->runtime()->gc;
    if (!gc->registerWithFinalizationRegistry(cx, target, record)) {
      return false;
    }
  }

  // 8. Return undefined.
  registrationGuard.release();
  args.rval().setUndefined();
  return true;
}

/* static */
bool FinalizationRegistryObject::preserveDOMWrapper(JSContext* cx,
                                                    HandleObject obj) {
  if (!MaybePreserveDOMWrapper(cx, obj)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_BAD_FINALIZATION_REGISTRY_OBJECT);
    return false;
  }

  return true;
}

/* static */
bool FinalizationRegistryObject::addRegistration(
    JSContext* cx, HandleFinalizationRegistryObject registry,
    HandleValue unregisterToken, HandleFinalizationRecordObject record) {
  // Add the record to the list of records associated with this unregister
  // token, or add it to the main list.

  MOZ_ASSERT(registry->registrations());
  MOZ_ASSERT(unregisterToken.isUndefined() || CanBeHeldWeakly(unregisterToken));

  if (unregisterToken.isUndefined()) {
    if (!registry->recordsWithoutToken()->append(record)) {
      ReportOutOfMemory(cx);
      return false;
    }
    return true;
  }

  auto& map = *registry->registrations();
  auto ptr = map.lookupForAdd(unregisterToken.get());
  if (!ptr.found() &&
      !map.add(ptr, unregisterToken, FinalizationRecordVector(cx->zone()))) {
    ReportOutOfMemory(cx);
    return false;
  }

  if (!ptr->value().append(record)) {
    ReportOutOfMemory(cx);
    return false;
  }

  if (unregisterToken.isSymbol()) {
    cx->zone()->setGCFinalizationRegistriesMayHaveSymbolRegistrations();
  }

  return true;
}

/* static */
void FinalizationRegistryObject::removeRegistrationOnError(
    HandleFinalizationRegistryObject registry, HandleValue unregisterToken,
    HandleFinalizationRecordObject record) {
  // Remove a registration if something went wrong before we added it to the
  // target zone's map. Note that this can't remove a registration after that
  // point.

  MOZ_ASSERT(registry->registrations());
  MOZ_ASSERT(unregisterToken.isUndefined() || CanBeHeldWeakly(unregisterToken));
  JS::AutoAssertNoGC nogc;

  if (unregisterToken.isUndefined()) {
    MOZ_ASSERT(registry->recordsWithoutToken()->back() == record);
    registry->recordsWithoutToken()->popBack();
    return;
  }

  auto ptr = registry->registrations()->lookup(unregisterToken);
  MOZ_ASSERT(ptr.found());
  FinalizationRecordVector& records = ptr->value();
  MOZ_ASSERT(records.back() == record);
  records.popBack();
  if (records.empty()) {
    registry->registrations()->remove(ptr);
  }
}

// FinalizationRegistry.prototype.unregister ( unregisterToken )
// https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.unregister
/* static */
bool FinalizationRegistryObject::unregister(JSContext* cx, unsigned argc,
                                            Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // 1. Let finalizationRegistry be the this value.
  // 2. If Type(finalizationRegistry) is not Object, throw a TypeError
  //    exception.
  // 3. If finalizationRegistry does not have a [[Cells]] internal slot, throw a
  //    TypeError exception.
  if (!args.thisv().isObject() ||
      !args.thisv().toObject().is<FinalizationRegistryObject>()) {
    JS_ReportErrorNumberASCII(
        cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_REGISTRY,
        "Receiver of FinalizationRegistry.unregister call");
    return false;
  }

  RootedFinalizationRegistryObject registry(
      cx, &args.thisv().toObject().as<FinalizationRegistryObject>());

  // 4. If Type(unregisterToken) is not Object, throw a TypeError exception.
  RootedValue unregisterToken(cx, args.get(0));
  if (!CanBeHeldWeakly(unregisterToken)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_BAD_UNREGISTER_TOKEN,
                              "FinalizationRegistry.unregister");
    return false;
  }

  // 5. Let removed be false.
  bool removed = false;

  // 6. For each Record { [[Target]], [[HeldValue]], [[UnregisterToken]] } cell
  //    that is an element of finalizationRegistry.[[Cells]], do
  //    a. If SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then
  //       i. Remove cell from finalizationRegistry.[[Cells]].
  //       ii. Set removed to true.

  RegistrationsMap* map = registry->registrations();
  auto ptr = map->lookup(unregisterToken);
  if (ptr) {
    FinalizationRecordVector& records = ptr->value();
    MOZ_ASSERT(!records.empty());
    for (FinalizationRecordObject* record : records) {
      if (unregisterRecord(record)) {
        removed = true;
      }
    }
    map->remove(unregisterToken);
  }

  // 7. Return removed.
  args.rval().setBoolean(removed);
  return true;
}

/* static */
bool FinalizationRegistryObject::unregisterRecord(
    FinalizationRecordObject* record) {
  if (!record->isRegistered()) {
    MOZ_ASSERT(!record->isInList());
    return false;
  }

  // Remove record from the target list if present.
  record->unlink();

  // Clear the fields of this record, marking it as unregistered. It will be
  // removed from relevant data structures when they are next swept.
  record->clear();
  MOZ_ASSERT(!record->isRegistered());

  return true;
}

// FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
// https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.cleanupSome
bool FinalizationRegistryObject::cleanupSome(JSContext* cx, unsigned argc,
                                             Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // 1. Let finalizationRegistry be the this value.
  // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
  if (!args.thisv().isObject() ||
      !args.thisv().toObject().is<FinalizationRegistryObject>()) {
    JS_ReportErrorNumberASCII(
        cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_REGISTRY,
        "Receiver of FinalizationRegistry.cleanupSome call");
    return false;
  }

  RootedFinalizationRegistryObject registry(
      cx, &args.thisv().toObject().as<FinalizationRegistryObject>());

  // 3. If callback is not undefined and IsCallable(callback) is false, throw a
  //    TypeError exception.
  RootedObject cleanupCallback(cx);
  if (!args.get(0).isUndefined()) {
    cleanupCallback = ValueToCallable(cx, args.get(0), -1, NO_CONSTRUCT);
    if (!cleanupCallback) {
      return false;
    }
  }

  RootedFinalizationQueueObject queue(cx, registry->queue());
  if (!FinalizationQueueObject::cleanupQueuedRecords(cx, queue,
                                                     cleanupCallback)) {
    return false;
  }

  args.rval().setUndefined();
  return true;
}

///////////////////////////////////////////////////////////////////////////
// FinalizationQueueObject

// Bug 1600300: FinalizationQueueObject is foreground finalized so that
// HeapPtr destructors never see referents with released arenas. When this is
// fixed we may be able to make this background finalized again.
const JSClass FinalizationQueueObject::class_ = {
    "FinalizationQueue",
    JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE,
    &classOps_,
};

const JSClassOps FinalizationQueueObject::classOps_ = {
    .finalize = FinalizationQueueObject::finalize,
    .trace = FinalizationQueueObject::trace,
};

/* static */
FinalizationQueueObject* FinalizationQueueObject::create(
    JSContext* cx, HandleObject cleanupCallback) {
  MOZ_ASSERT(cleanupCallback);

  Rooted<UniquePtr<FinalizationRecordVector>> recordsToBeCleanedUp(
      cx, cx->make_unique<FinalizationRecordVector>(cx->zone()));
  if (!recordsToBeCleanedUp) {
    return nullptr;
  }

  Handle<PropertyName*> funName = cx->names().empty_;
  RootedFunction doCleanupFunction(
      cx, NewNativeFunction(cx, doCleanup, 0, funName,
                            gc::AllocKind::FUNCTION_EXTENDED));
  if (!doCleanupFunction) {
    return nullptr;
  }

  // It's problematic storing a CCW to a global in another compartment because
  // you don't know how far to unwrap it to get the original object
  // back. Instead store a CCW to a plain object in the same compartment as the
  // global (this uses Object.prototype).
  Rooted<JSObject*> incumbentGlobalRepresentative(cx);
  if (!GetIncumbentGlobalRepresentative(cx, &incumbentGlobalRepresentative)) {
    return nullptr;
  }

  FinalizationQueueObject* queue =
      NewObjectWithGivenProto<FinalizationQueueObject>(cx, nullptr);
  if (!queue) {
    return nullptr;
  }

  queue->initReservedSlot(CleanupCallbackSlot, ObjectValue(*cleanupCallback));
  queue->initReservedSlot(IncumbentGlobalRepresentative,
                          JS::ObjectOrNullValue(incumbentGlobalRepresentative));
  InitReservedSlot(queue, RecordsToBeCleanedUpSlot,
                   recordsToBeCleanedUp.release(),
                   MemoryUse::FinalizationRegistryRecordVector);
  queue->initReservedSlot(IsQueuedForCleanupSlot, BooleanValue(false));
  queue->initReservedSlot(DoCleanupFunctionSlot,
                          ObjectValue(*doCleanupFunction));
  queue->initReservedSlot(HasRegistrySlot, BooleanValue(false));

  doCleanupFunction->setExtendedSlot(DoCleanupFunction_QueueSlot,
                                     ObjectValue(*queue));

  return queue;
}

/* static */
void FinalizationQueueObject::trace(JSTracer* trc, JSObject* obj) {
  auto queue = &obj->as<FinalizationQueueObject>();

  if (FinalizationRecordVector* records = queue->recordsToBeCleanedUp()) {
    records->trace(trc);
  }
}

/* static */
void FinalizationQueueObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  auto* queue = &obj->as<FinalizationQueueObject>();
  gcx->delete_(obj, queue->recordsToBeCleanedUp(),
               MemoryUse::FinalizationRegistryRecordVector);
}

void FinalizationQueueObject::setHasRegistry(bool newValue) {
  MOZ_ASSERT(hasRegistry() != newValue);

  // Suppress our assertions about touching grey things. It's OK for us to set a
  // boolean slot even if this object is gray.
  AutoTouchingGrayThings atgt;

  setReservedSlot(HasRegistrySlot, BooleanValue(newValue));
}

void FinalizationQueueObject::clear() {
  MOZ_ASSERT(!hasRegistry());
  if (FinalizationRecordVector* records = recordsToBeCleanedUp()) {
    records->clear();
  }
}

bool FinalizationQueueObject::hasRegistry() const {
  return getReservedSlot(HasRegistrySlot).toBoolean();
}

inline JSObject* FinalizationQueueObject::cleanupCallback() const {
  Value value = getReservedSlot(CleanupCallbackSlot);
  if (value.isUndefined()) {
    return nullptr;
  }
  return &value.toObject();
}

JSObject* FinalizationQueueObject::getIncumbentGlobalRepresentative() const {
  Value value = getReservedSlot(IncumbentGlobalRepresentative);
  if (value.isUndefined()) {
    return nullptr;
  }
  return value.toObjectOrNull();
}

bool FinalizationQueueObject::hasRecordsToCleanUp() const {
  FinalizationRecordVector* records = recordsToBeCleanedUp();
  return records && !records->empty();
}

FinalizationRecordVector* FinalizationQueueObject::recordsToBeCleanedUp()
    const {
  Value value = getReservedSlot(RecordsToBeCleanedUpSlot);
  if (value.isUndefined()) {
    return nullptr;
  }
  return static_cast<FinalizationRecordVector*>(value.toPrivate());
}

bool FinalizationQueueObject::isQueuedForCleanup() const {
  return getReservedSlot(IsQueuedForCleanupSlot).toBoolean();
}

JSFunction* FinalizationQueueObject::doCleanupFunction() const {
  Value value = getReservedSlot(DoCleanupFunctionSlot);
  if (value.isUndefined()) {
    return nullptr;
  }
  return &value.toObject().as<JSFunction>();
}

void FinalizationQueueObject::queueRecordToBeCleanedUp(
    FinalizationRecordObject* record) {
  MOZ_ASSERT(hasRegistry());

  MOZ_ASSERT(!record->isInQueue());
  record->setInQueue(true);

  AutoEnterOOMUnsafeRegion oomUnsafe;
  if (!recordsToBeCleanedUp()->append(record)) {
    oomUnsafe.crash("FinalizationQueueObject::queueRecordsToBeCleanedUp");
  }
}

void FinalizationQueueObject::setQueuedForCleanup(bool value) {
  MOZ_ASSERT(value != isQueuedForCleanup());
  setReservedSlot(IsQueuedForCleanupSlot, BooleanValue(value));
}

/* static */
bool FinalizationQueueObject::doCleanup(JSContext* cx, unsigned argc,
                                        Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  RootedFunction callee(cx, &args.callee().as<JSFunction>());

  Value value = callee->getExtendedSlot(DoCleanupFunction_QueueSlot);
  RootedFinalizationQueueObject queue(
      cx, &value.toObject().as<FinalizationQueueObject>());

  queue->setQueuedForCleanup(false);
  return cleanupQueuedRecords(cx, queue);
}

// CleanupFinalizationRegistry ( finalizationRegistry [ , callback ] )
// https://tc39.es/proposal-weakrefs/#sec-cleanup-finalization-registry
/* static */
bool FinalizationQueueObject::cleanupQueuedRecords(
    JSContext* cx, HandleFinalizationQueueObject queue,
    HandleObject callbackArg) {
  MOZ_ASSERT(cx->compartment() == queue->compartment());

  // 2. If callback is undefined, set callback to
  //    finalizationRegistry.[[CleanupCallback]].
  RootedValue callback(cx);
  if (callbackArg) {
    callback.setObject(*callbackArg);
  } else {
    JSObject* cleanupCallback = queue->cleanupCallback();
    MOZ_ASSERT(cleanupCallback);
    callback.setObject(*cleanupCallback);
  }

  // 3. While finalizationRegistry.[[Cells]] contains a Record cell such that
  //    cell.[[WeakRefTarget]] is empty, then an implementation may perform the
  //    following steps,
  //    a. Choose any such cell.
  //    b. Remove cell from finalizationRegistry.[[Cells]].
  //    c. Perform ? Call(callback, undefined, « cell.[[HeldValue]] »).

  FinalizationRecordVector* records = queue->recordsToBeCleanedUp();
  MOZ_ASSERT_IF(!queue->hasRegistry(), records->empty());

  RootedValue heldValue(cx);
  RootedValue rval(cx);
  while (!records->empty()) {
    FinalizationRecordObject* record = records->popCopy();
    MOZ_ASSERT(!record->isInRecordMap());

    JS::ExposeObjectToActiveJS(record);

    MOZ_ASSERT(record->isInQueue());
    record->setInQueue(false);

    // Skip over records that have been unregistered.
    if (!record->isRegistered()) {
      continue;
    }

    heldValue.set(record->heldValue());

    record->clear();

    if (!Call(cx, callback, UndefinedHandleValue, heldValue, &rval)) {
      return false;
    }
  }

  return true;
}

Messung V0.5 in Prozent
C=89 H=98 G=93

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet am  2026-08-21) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.