Revert "Implement FinalizationRegistry" and related commits.

this cause a crash in GC when idling sometimes.
This commit is contained in:
2026-05-22 09:30:56 +08:00
parent c2aabd725b
commit 87776c74a9
15 changed files with 2 additions and 921 deletions
@@ -1,608 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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/. */
#include "builtin/FinalizationRegistryObject.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "builtin/WeakRefObject.h"
#include "gc/Nursery.h"
#include "gc/Tracer.h"
#include "vm/EqualityOperations.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/Symbol.h"
#include "jswrapper.h"
#include "jsobjinlines.h"
#include "vm/Interpreter-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
enum class WeakCellKind {
Empty,
Object,
Symbol
};
static JSObject*
NormalizeWeakObject(JSObject* obj)
{
if (JSObject* unwrapped = CheckedUnwrap(obj, /* stopAtWindowProxy = */ false))
return unwrapped;
return obj;
}
static bool
GetPrototypeFromFinalizationRegistryConstructor(JSContext* cx, HandleObject newTarget,
MutableHandleObject proto)
{
if (!GetPrototypeFromConstructor(cx, newTarget, proto))
return false;
if (proto)
return true;
RootedObject realmObject(cx, CheckedUnwrap(newTarget, /* stopAtWindowProxy = */ false));
if (!realmObject)
return false;
{
JSAutoCompartment ac(cx, realmObject);
Rooted<GlobalObject*> global(cx, &realmObject->global());
if (!GlobalObject::ensureConstructor(cx, global, JSProto_FinalizationRegistry))
return false;
proto.set(&global->getPrototype(JSProto_FinalizationRegistry).toObject());
}
return cx->compartment()->wrap(cx, proto);
}
struct WeakCell {
WeakCellKind kind;
WeakRef<JSObject*> object;
WeakRef<JS::Symbol*> symbol;
WeakCell() : kind(WeakCellKind::Empty), object(nullptr), symbol(nullptr) {}
explicit WeakCell(JSObject* obj) : kind(WeakCellKind::Object), object(obj), symbol(nullptr) {}
explicit WeakCell(JS::Symbol* sym) : kind(WeakCellKind::Symbol), object(nullptr), symbol(sym) {}
void clear() {
kind = WeakCellKind::Empty;
object = nullptr;
symbol = nullptr;
}
bool isEmpty() const { return kind == WeakCellKind::Empty; }
bool isDead() const {
if (kind == WeakCellKind::Object)
return !object.unbarrieredGet();
if (kind == WeakCellKind::Symbol)
return !symbol.unbarrieredGet();
return false;
}
bool sameValue(HandleValue value) const {
if (kind == WeakCellKind::Object)
return value.isObject() && object.get() == NormalizeWeakObject(&value.toObject());
if (kind == WeakCellKind::Symbol)
return value.isSymbol() && symbol.get() == value.toSymbol();
return false;
}
void trace(JSTracer* trc, const char* name) {
if (kind == WeakCellKind::Object) {
JSObject* target = object.unbarrieredGet();
if (!target)
return;
if (IsInsideNursery(target)) {
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
} else {
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
return;
TraceWeakEdge(trc, &object, name);
}
} else if (kind == WeakCellKind::Symbol) {
JS::Symbol* target = symbol.unbarrieredGet();
if (target) {
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
return;
TraceWeakEdge(trc, &symbol, name);
}
}
}
void traceIfZoneIsCollecting(JSTracer* trc, const char* name) {
if (kind == WeakCellKind::Object) {
JSObject* target = object.unbarrieredGet();
if (!target)
return;
if (IsInsideNursery(target)) {
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
} else if (target->asTenured().zone()->isCollecting()) {
TraceWeakEdge(trc, &object, name);
}
} else if (kind == WeakCellKind::Symbol) {
JS::Symbol* target = symbol.unbarrieredGet();
if (target && target->asTenured().zone()->isCollecting())
TraceWeakEdge(trc, &symbol, name);
}
}
};
struct FinalizationRecord {
WeakCell target;
JS::Heap<Value> heldValue;
WeakCell unregisterToken;
bool active;
bool queued;
FinalizationRecord(HandleValue targetValue, HandleValue heldValue,
HandleValue unregisterTokenValue)
: heldValue(heldValue),
active(true),
queued(false)
{
if (targetValue.isObject())
target = WeakCell(NormalizeWeakObject(&targetValue.toObject()));
else
target = WeakCell(targetValue.toSymbol());
if (unregisterTokenValue.isObject())
unregisterToken = WeakCell(NormalizeWeakObject(&unregisterTokenValue.toObject()));
else if (unregisterTokenValue.isSymbol())
unregisterToken = WeakCell(unregisterTokenValue.toSymbol());
}
void trace(JSTracer* trc) {
if (active || queued)
JS::TraceEdge(trc, &heldValue, "FinalizationRegistry held value");
if (active) {
target.trace(trc, "FinalizationRegistry target");
if (!unregisterToken.isEmpty())
unregisterToken.trace(trc, "FinalizationRegistry unregister token");
}
}
void traceWeakEdgesForCollectedZones(JSTracer* trc) {
if (!active)
return;
target.traceIfZoneIsCollecting(trc, "FinalizationRegistry target");
if (!unregisterToken.isEmpty())
unregisterToken.traceIfZoneIsCollecting(trc,
"FinalizationRegistry unregister token");
}
bool targetIsDead() const {
return active && target.isDead();
}
bool matchesToken(HandleValue token) const {
return active && !queued && !unregisterToken.isEmpty() &&
unregisterToken.sameValue(token);
}
void queueForCleanup() {
MOZ_ASSERT(active);
MOZ_ASSERT(!queued);
active = false;
queued = true;
target.clear();
unregisterToken.clear();
}
void clear() {
active = false;
queued = false;
target.clear();
unregisterToken.clear();
heldValue.setUndefined();
}
};
using FinalizationRecordVector = Vector<FinalizationRecord*, 0, SystemAllocPolicy>;
struct FinalizationRegistryObject::Data {
JS::Heap<JSObject*> cleanupCallback;
JS::Heap<JSObject*> cleanupJob;
FinalizationRecordVector records;
FinalizationRecordVector cleanupQueue;
bool queuedForCleanup;
explicit Data(JSObject* cleanupCallback)
: cleanupCallback(cleanupCallback),
cleanupJob(nullptr),
records(SystemAllocPolicy()),
cleanupQueue(SystemAllocPolicy()),
queuedForCleanup(false)
{}
~Data() {
for (size_t i = 0; i < records.length(); i++)
js_delete(records[i]);
}
void trace(JSTracer* trc) {
JS::TraceEdge(trc, &cleanupCallback, "FinalizationRegistry cleanup callback");
if (cleanupJob.unbarrieredGet())
JS::TraceEdge(trc, &cleanupJob, "FinalizationRegistry cleanup job");
for (size_t i = 0; i < records.length(); i++)
records[i]->trace(trc);
}
bool appendRecord(FinalizationRecord* record) {
return records.append(record);
}
bool appendCleanupRecord(FinalizationRecord* record) {
return cleanupQueue.append(record);
}
void compactRecords() {
for (size_t i = 0; i < records.length();) {
FinalizationRecord* record = records[i];
if (!record->active && !record->queued) {
js_delete(record);
records.erase(records.begin() + i);
continue;
}
i++;
}
}
void unregister(HandleValue token, bool* removed) {
*removed = false;
for (size_t i = 0; i < records.length(); i++) {
FinalizationRecord* record = records[i];
if (record->matchesToken(token)) {
record->clear();
*removed = true;
}
}
compactRecords();
}
};
static FinalizationRegistryObject::Data*
GetData(JSObject* obj)
{
return obj->as<FinalizationRegistryObject>().getData();
}
static MOZ_ALWAYS_INLINE bool
IsFinalizationRegistry(HandleValue v)
{
return v.isObject() && v.toObject().is<FinalizationRegistryObject>();
}
static bool
FinalizationRegistry_register_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
if (!CanBeHeldWeakly(args.get(0))) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_FINALIZATION_REGISTRY_TARGET);
return false;
}
RootedValue target(cx, args[0]);
RootedValue heldValue(cx, args.get(1));
bool same;
if (!SameValue(cx, target, heldValue, &same))
return false;
if (same) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE);
return false;
}
RootedValue unregisterToken(cx, args.get(2));
if (!CanBeHeldWeakly(unregisterToken)) {
if (!unregisterToken.isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
return false;
}
unregisterToken.setUndefined();
}
FinalizationRecord* record = cx->new_<FinalizationRecord>(target, heldValue, unregisterToken);
if (!record)
return false;
auto* data = GetData(&args.thisv().toObject());
if (!data->appendRecord(record)) {
js_delete(record);
ReportOutOfMemory(cx);
return false;
}
args.rval().setUndefined();
return true;
}
static bool
FinalizationRegistry_unregister_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
RootedValue unregisterToken(cx, args.get(0));
if (!CanBeHeldWeakly(unregisterToken)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
return false;
}
bool removed = false;
auto* data = GetData(&args.thisv().toObject());
data->unregister(unregisterToken, &removed);
args.rval().setBoolean(removed);
return true;
}
const JSPropertySpec FinalizationRegistryObject::properties[] = {
JS_PS_END
};
const JSFunctionSpec FinalizationRegistryObject::methods[] = {
JS_FN("register", FinalizationRegistryObject::register_, 2, 0),
JS_FN("unregister", FinalizationRegistryObject::unregister, 1, 0),
JS_FS_END
};
static JSObject*
InitFinalizationRegistryClass(JSContext* cx, HandleObject obj, bool defineMembers)
{
Handle<GlobalObject*> global = obj.as<GlobalObject>();
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!proto)
return nullptr;
RootedFunction ctor(cx, GlobalObject::createConstructor(cx, FinalizationRegistryObject::construct,
ClassName(JSProto_FinalizationRegistry, cx), 1));
if (!ctor)
return nullptr;
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
if (defineMembers) {
if (!DefinePropertiesAndFunctions(cx, proto, FinalizationRegistryObject::properties,
FinalizationRegistryObject::methods)) {
return nullptr;
}
if (!DefineToStringTag(cx, proto, cx->names().FinalizationRegistry))
return nullptr;
}
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_FinalizationRegistry, ctor, proto))
return nullptr;
return proto;
}
/* static */ FinalizationRegistryObject*
FinalizationRegistryObject::create(JSContext* cx, HandleObject cleanupCallback,
HandleObject proto /* = nullptr */)
{
Rooted<FinalizationRegistryObject*> obj(cx,
NewObjectWithClassProto<FinalizationRegistryObject>(cx, proto));
if (!obj)
return nullptr;
Data* data = cx->new_<Data>(cleanupCallback);
if (!data)
return nullptr;
obj->setPrivate(data);
RootedAtom funName(cx, cx->names().empty);
RootedFunction cleanupJob(cx, NewNativeFunction(cx, FinalizationRegistryObject::cleanupJob, 0,
funName, gc::AllocKind::FUNCTION_EXTENDED,
GenericObject));
if (!cleanupJob)
return nullptr;
cleanupJob->setExtendedSlot(0, ObjectValue(*obj));
data->cleanupJob = cleanupJob;
if (!cx->zone()->finalizationRegistries.append(WeakRef<JSObject*>(obj))) {
ReportOutOfMemory(cx);
return nullptr;
}
return obj;
}
/* static */ bool
FinalizationRegistryObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "FinalizationRegistry"))
return false;
RootedValue cleanupCallbackValue(cx, args.get(0));
RootedObject cleanupCallback(cx, ValueToCallable(cx, cleanupCallbackValue, 0, NO_CONSTRUCT));
if (!cleanupCallback)
return false;
RootedObject proto(cx);
RootedObject newTarget(cx, &args.newTarget().toObject());
if (!GetPrototypeFromFinalizationRegistryConstructor(cx, newTarget, &proto))
return false;
Rooted<FinalizationRegistryObject*> obj(cx,
FinalizationRegistryObject::create(cx, cleanupCallback, proto));
if (!obj)
return false;
args.rval().setObject(*obj);
return true;
}
/* static */ bool
FinalizationRegistryObject::register_(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_register_impl>(cx, args);
}
/* static */ bool
FinalizationRegistryObject::unregister(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_unregister_impl>(cx, args);
}
/* static */ bool
FinalizationRegistryObject::cleanupJob(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction job(cx, &args.callee().as<JSFunction>());
Rooted<FinalizationRegistryObject*> registry(cx,
&job->getExtendedSlot(0).toObject().as<FinalizationRegistryObject>());
Data* data = registry->getData();
if (!data) {
args.rval().setUndefined();
return true;
}
data->queuedForCleanup = false;
RootedValue callback(cx, ObjectValue(*data->cleanupCallback.get()));
RootedValue heldValue(cx);
RootedValue ignored(cx);
RootedValue undefined(cx, UndefinedValue());
while (data->cleanupQueue.length() > 0) {
FinalizationRecord* record = data->cleanupQueue[0];
data->cleanupQueue.erase(data->cleanupQueue.begin());
if (!record->queued) {
data->compactRecords();
continue;
}
heldValue.set(record->heldValue.get());
record->clear();
data->compactRecords();
if (!Call(cx, callback, undefined, heldValue, &ignored))
return false;
}
args.rval().setUndefined();
return true;
}
/* static */ void
FinalizationRegistryObject::trace(JSTracer* trc, JSObject* obj)
{
if (Data* data = GetData(obj))
data->trace(trc);
}
/* static */ void
FinalizationRegistryObject::finalize(FreeOp* fop, JSObject* obj)
{
if (Data* data = GetData(obj))
fop->delete_(data);
}
void
FinalizationRegistryObject::traceWeakEdgesForCollectedZones(JSTracer* trc)
{
Data* data = getData();
if (!data)
return;
for (size_t i = 0; i < data->records.length(); i++)
data->records[i]->traceWeakEdgesForCollectedZones(trc);
}
void
FinalizationRegistryObject::sweepAfterGC(JSRuntime* rt)
{
Data* data = getData();
if (!data)
return;
bool needsCleanupJob = false;
for (size_t i = 0; i < data->records.length(); i++) {
FinalizationRecord* record = data->records[i];
if (!record->targetIsDead())
continue;
record->queueForCleanup();
if (!data->appendCleanupRecord(record)) {
AutoEnterOOMUnsafeRegion oomUnsafe;
oomUnsafe.crash("queueing FinalizationRegistry cleanup record");
}
needsCleanupJob = true;
}
if (needsCleanupJob && !data->queuedForCleanup) {
JSContext* cx = rt->contextFromMainThread();
RootedObject cleanupJob(cx, data->cleanupJob.unbarrieredGet());
if (!rt->enqueueFinalizationRegistryCleanupJob(cx, cleanupJob)) {
AutoEnterOOMUnsafeRegion oomUnsafe;
oomUnsafe.crash("queueing FinalizationRegistry cleanup job");
}
data->queuedForCleanup = true;
}
data->compactRecords();
}
static const ClassOps FinalizationRegistryObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
FinalizationRegistryObject::finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
FinalizationRegistryObject::trace
};
const Class FinalizationRegistryObject::class_ = {
"FinalizationRegistry",
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry) |
JSCLASS_FOREGROUND_FINALIZE,
&FinalizationRegistryObjectClassOps
};
/* static */ JSObject*
FinalizationRegistryObject::initClass(JSContext* cx, HandleObject obj)
{
return ::InitFinalizationRegistryClass(cx, obj, true);
}
JSObject*
js::InitFinalizationRegistryClass(JSContext* cx, HandleObject obj)
{
return FinalizationRegistryObject::initClass(cx, obj);
}
JSObject*
js::InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj)
{
return ::InitFinalizationRegistryClass(cx, obj, false);
}
@@ -1,51 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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/. */
#ifndef builtin_FinalizationRegistryObject_h
#define builtin_FinalizationRegistryObject_h
#include "gc/Barrier.h"
#include "vm/NativeObject.h"
namespace js {
class FinalizationRegistryObject : public NativeObject
{
public:
struct Data;
static const Class class_;
static JSObject* initClass(JSContext* cx, HandleObject obj);
static FinalizationRegistryObject* create(JSContext* cx, HandleObject cleanupCallback,
HandleObject proto = nullptr);
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(FreeOp* fop, JSObject* obj);
void traceWeakEdgesForCollectedZones(JSTracer* trc);
[[nodiscard]] static bool construct(JSContext* cx, unsigned argc, Value* vp);
[[nodiscard]] static bool register_(JSContext* cx, unsigned argc, Value* vp);
[[nodiscard]] static bool unregister(JSContext* cx, unsigned argc, Value* vp);
[[nodiscard]] static bool cleanupJob(JSContext* cx, unsigned argc, Value* vp);
void sweepAfterGC(JSRuntime* rt);
Data* getData() const {
return static_cast<Data*>(getPrivate());
}
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
};
extern JSObject*
InitFinalizationRegistryClass(JSContext* cx, HandleObject obj);
extern JSObject*
InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj);
} // namespace js
#endif /* builtin_FinalizationRegistryObject_h */
+2 -2
View File
@@ -151,8 +151,8 @@ WeakRefObject::create(JSContext* cx, HandleValue target, HandleObject proto /* =
return obj;
}
bool
js::CanBeHeldWeakly(HandleValue target)
static bool
CanBeHeldWeakly(HandleValue target)
{
if (target.isObject())
return true;
-2
View File
@@ -11,8 +11,6 @@
namespace js {
bool CanBeHeldWeakly(HandleValue target);
class WeakRefObject : public NativeObject
{
public:
-3
View File
@@ -947,8 +947,6 @@ class GCRuntime
IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase);
template <class CompartmentIterT> void markWeakReferences(gcstats::Phase phase);
void markWeakReferencesInCurrentGroup(gcstats::Phase phase);
template <class ZoneIterT> void traceFinalizationRegistryWeakRefs();
void traceFinalizationRegistryWeakRefsInCurrentGroup();
template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::Phase phase);
void markBufferedGrayRoots(JS::Zone* zone);
void markGrayReferencesInCurrentGroup(gcstats::Phase phase);
@@ -961,7 +959,6 @@ class GCRuntime
void getNextZoneGroup();
void endMarkingZoneGroup();
void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock);
void sweepFinalizationRegistries();
bool shouldReleaseObservedTypes();
void endSweepingZoneGroup();
IncrementalProgress performSweepActions(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
-5
View File
@@ -12,7 +12,6 @@
#include "jscntxt.h"
#include "ds/SplayTree.h"
#include "gc/Barrier.h"
#include "gc/FindSCCs.h"
#include "gc/GCRuntime.h"
#include "js/GCHashTable.h"
@@ -326,10 +325,6 @@ struct Zone : public JS::shadow::Zone,
using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>;
WeakEdges gcWeakRefs;
// FinalizationRegistry objects with weakly-held target cells in this zone.
using FinalizationRegistryVector = js::Vector<js::WeakRef<JSObject*>, 0, js::SystemAllocPolicy>;
FinalizationRegistryVector finalizationRegistries;
// List of non-ephemeron weak containers to sweep during beginSweepingZoneGroup.
mozilla::LinkedList<WeakCache<void*>> weakCaches_;
void registerWeakCache(WeakCache<void*>* cachep) {
-3
View File
@@ -82,9 +82,6 @@ MSG_DEF(JSMSG_UNEXPECTED_TYPE, 2, JSEXN_TYPEERR, "{0} is {1}")
MSG_DEF(JSMSG_MISSING_FUN_ARG, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 1, JSEXN_TYPEERR, "{0} is not a non-null object")
MSG_DEF(JSMSG_NOT_WEAKREF_TARGET, 0, JSEXN_TYPEERR, "WeakRef target must be an object or a non-registered symbol")
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_TARGET, 0, JSEXN_TYPEERR, "FinalizationRegistry target must be an object or a non-registered symbol")
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE, 0, JSEXN_TYPEERR, "FinalizationRegistry held value must not be the target")
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN, 0, JSEXN_TYPEERR, "FinalizationRegistry unregister token must be an object or a non-registered symbol")
MSG_DEF(JSMSG_SET_NON_OBJECT_RECEIVER, 1, JSEXN_TYPEERR, "can't assign to properties of {0}: not an object")
MSG_DEF(JSMSG_INVALID_DESCRIPTOR, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE, 1, JSEXN_TYPEERR, "{0}: Object is not extensible")
-56
View File
@@ -211,7 +211,6 @@
# include "jswin.h"
#endif
#include "builtin/FinalizationRegistryObject.h"
#include "gc/FindSCCs.h"
#include "gc/GCInternals.h"
#include "gc/GCTrace.h"
@@ -4030,8 +4029,6 @@ GCRuntime::markWeakReferences(gcstats::Phase phase)
}
MOZ_ASSERT(marker.isDrained());
traceFinalizationRegistryWeakRefs<ZoneIterT>();
marker.leaveWeakMarkingMode();
}
@@ -4041,33 +4038,6 @@ GCRuntime::markWeakReferencesInCurrentGroup(gcstats::Phase phase)
markWeakReferences<GCZoneGroupIter>(phase);
}
template <class ZoneIterT>
void
GCRuntime::traceFinalizationRegistryWeakRefs()
{
for (ZoneIterT zone(rt); !zone.done(); zone.next()) {
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
WeakRef<JSObject*>& registry = zone->finalizationRegistries[i];
if (registry.unbarrieredGet())
TraceWeakEdge(&marker, &registry, "FinalizationRegistry registry");
}
}
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
JSObject* registry = zone->finalizationRegistries[i].unbarrieredGet();
if (registry && registry->is<FinalizationRegistryObject>())
registry->as<FinalizationRegistryObject>().traceWeakEdgesForCollectedZones(&marker);
}
}
}
void
GCRuntime::traceFinalizationRegistryWeakRefsInCurrentGroup()
{
traceFinalizationRegistryWeakRefs<GCZoneGroupIter>();
}
template <class ZoneIterT, class CompartmentIterT>
void
GCRuntime::markGrayReferences(gcstats::Phase phase)
@@ -4779,8 +4749,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
oomUnsafe.crash("clearing weak keys in beginSweepingZoneGroup()");
}
sweepFinalizationRegistries();
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_START);
@@ -4936,23 +4904,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
}
}
void
GCRuntime::sweepFinalizationRegistries()
{
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
for (size_t i = 0; i < zone->finalizationRegistries.length();) {
JSObject* obj = zone->finalizationRegistries[i].unbarrieredGet();
if (!obj || !obj->is<FinalizationRegistryObject>()) {
zone->finalizationRegistries.erase(zone->finalizationRegistries.begin() + i);
continue;
}
obj->as<FinalizationRegistryObject>().sweepAfterGC(rt);
i++;
}
}
}
void
GCRuntime::endSweepingZoneGroup()
{
@@ -6136,13 +6087,6 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
repeat = (poked && cleanUpEverything) || wasReset || repeatForDeadZone;
} while (repeat);
if (rt->isBeingDestroyed()) {
rt->clearFinalizationRegistryCleanupJobs();
} else if (!rt->drainFinalizationRegistryCleanupJobs(rt->contextFromMainThread())) {
AutoEnterOOMUnsafeRegion oomUnsafe;
oomUnsafe.crash("draining FinalizationRegistry cleanup jobs");
}
if (reason == JS::gcreason::COMPARTMENT_REVIVED)
maybeDoCycleCollection();
}
-1
View File
@@ -97,7 +97,6 @@
IF_SAB(real,imaginary)(SharedArrayBuffer, InitViaClassSpec, OCLASP(SharedArrayBuffer)) \
IF_INTL(real,imaginary) (Intl, InitIntlClass, CLASP(Intl)) \
IF_BDATA(real,imaginary)(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
real(FinalizationRegistry, InitFinalizationRegistryClass, OCLASP(FinalizationRegistry)) \
real(Reflect, InitReflect, nullptr) \
real(WeakSet, InitWeakSetClass, OCLASP(WeakSet)) \
real(TypedArray, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
-1
View File
@@ -120,7 +120,6 @@ EXPORTS.js += [
main_deunified_sources = [
'builtin/AtomicsObject.cpp',
'builtin/Eval.cpp',
'builtin/FinalizationRegistryObject.cpp',
'builtin/intl/Collator.cpp',
'builtin/intl/CommonFunctions.cpp',
'builtin/intl/DateTimeFormat.cpp',
-2
View File
@@ -17,7 +17,6 @@
#include "builtin/AtomicsObject.h"
#include "builtin/BigInt.h"
#include "builtin/Eval.h"
#include "builtin/FinalizationRegistryObject.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/Object.h"
@@ -539,7 +538,6 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle<GlobalObject*> globa
InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) &&
InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
InitBareSymbolCtor(cx, global) &&
InitBareFinalizationRegistryCtor(cx, global) &&
InitBareWeakMapCtor(cx, global) &&
InitBareWeakRefCtor(cx, global) &&
InitStopIterationClass(cx, global) &&
-70
View File
@@ -195,7 +195,6 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
#endif
scriptAndCountsVector(nullptr),
weakRefKeptObjects(nullptr),
finalizationRegistryCleanupJobs(nullptr),
lcovOutput(),
NaNValue(DoubleNaNValue()),
negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
@@ -375,7 +374,6 @@ JSRuntime::destroyRuntime()
MOZ_ASSERT(childRuntimeCount == 0);
clearWeakRefKeptObjects();
clearFinalizationRegistryCleanupJobs();
fx.destroyInstance();
@@ -520,74 +518,6 @@ JSRuntime::clearWeakRefKeptObjects()
weakRefKeptObjects = nullptr;
}
bool
JSRuntime::enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job)
{
MOZ_ASSERT(cx->runtime() == this);
MOZ_ASSERT(job);
MOZ_ASSERT(job->is<JSFunction>());
MOZ_ASSERT(isHeapBusy());
if (!finalizationRegistryCleanupJobs) {
auto* cleanupJobs =
cx->new_<JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>>(
cx, js::FinalizationRegistryCleanupJobVector(js::SystemAllocPolicy()));
if (!cleanupJobs)
return false;
finalizationRegistryCleanupJobs = cleanupJobs;
}
for (size_t i = 0; i < finalizationRegistryCleanupJobs->length(); i++) {
if ((*finalizationRegistryCleanupJobs)[i] == job)
return true;
}
if (!finalizationRegistryCleanupJobs->append(job)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
bool
JSRuntime::drainFinalizationRegistryCleanupJobs(JSContext* cx)
{
MOZ_ASSERT(cx->runtime() == this);
MOZ_ASSERT(!isHeapBusy());
if (!finalizationRegistryCleanupJobs)
return true;
if (!enqueuePromiseJobCallback) {
finalizationRegistryCleanupJobs->clear();
return true;
}
size_t length = finalizationRegistryCleanupJobs->length();
for (size_t i = 0; i < length; i++) {
RootedFunction job(cx, &(*finalizationRegistryCleanupJobs)[i]->as<JSFunction>());
if (!enqueuePromiseJob(cx, job, nullptr, nullptr))
return false;
}
finalizationRegistryCleanupJobs->clear();
return true;
}
void
JSRuntime::clearFinalizationRegistryCleanupJobs()
{
MOZ_ASSERT(!isHeapBusy());
if (!finalizationRegistryCleanupJobs)
return;
defaultFreeOp()->delete_(finalizationRegistryCleanupJobs);
finalizationRegistryCleanupJobs = nullptr;
}
void
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes* rtSizes)
{
-8
View File
@@ -361,7 +361,6 @@ class PerThreadData
using ScriptAndCountsVector = GCVector<ScriptAndCounts, 0, SystemAllocPolicy>;
using WeakRefKeptObjectVector = JS::GCVector<JS::Value, 0, SystemAllocPolicy>;
using FinalizationRegistryCleanupJobVector = JS::GCVector<JSObject*, 0, SystemAllocPolicy>;
class AutoLockForExclusiveAccess;
} // namespace js
@@ -895,13 +894,6 @@ struct JSRuntime : public JS::shadow::Runtime,
[[nodiscard]] bool addWeakRefKeptObject(JSContext* cx, JS::HandleValue target);
void clearWeakRefKeptObjects();
/* Cleanup jobs produced by FinalizationRegistry sweeping, enqueued after GC. */
JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>* finalizationRegistryCleanupJobs;
[[nodiscard]] bool enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job);
[[nodiscard]] bool drainFinalizationRegistryCleanupJobs(JSContext* cx);
void clearFinalizationRegistryCleanupJobs();
/* Code coverage output. */
js::coverage::LCovRuntime lcovOutput;
@@ -1,108 +0,0 @@
/* 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/. */
function assertThrowsTypeError(fn) {
try {
fn();
} catch (e) {
do_check_true(e instanceof TypeError);
return;
}
do_throw("expected TypeError");
}
add_task(function* test_finalization_registry_api_surface() {
do_check_eq(typeof FinalizationRegistry, "function");
do_check_eq(FinalizationRegistry.name, "FinalizationRegistry");
do_check_eq(FinalizationRegistry.length, 1);
assertThrowsTypeError(() => FinalizationRegistry(function() {}));
assertThrowsTypeError(() => new FinalizationRegistry(1));
let registry = new FinalizationRegistry(function() {});
do_check_eq(Object.prototype.toString.call(registry), "[object FinalizationRegistry]");
do_check_eq(typeof FinalizationRegistry.prototype.register, "function");
do_check_eq(FinalizationRegistry.prototype.register.length, 2);
do_check_eq(typeof FinalizationRegistry.prototype.unregister, "function");
do_check_eq(FinalizationRegistry.prototype.unregister.length, 1);
do_check_eq(FinalizationRegistry.prototype.cleanupSome, undefined);
let desc = Object.getOwnPropertyDescriptor(FinalizationRegistry.prototype,
Symbol.toStringTag);
do_check_eq(desc.value, "FinalizationRegistry");
do_check_eq(desc.writable, false);
do_check_eq(desc.enumerable, false);
do_check_eq(desc.configurable, true);
});
add_task(function* test_register_validation_and_unregister() {
let registry = new FinalizationRegistry(function() {});
let target = {};
let token = {};
do_check_eq(registry.register(target, "held"), undefined);
do_check_eq(registry.register({}, "held", token), undefined);
do_check_eq(registry.unregister(token), true);
do_check_eq(registry.unregister({}), false);
assertThrowsTypeError(() => registry.register(1, "held"));
assertThrowsTypeError(() => registry.register(target, target));
assertThrowsTypeError(() => registry.register(target, "held", 1));
assertThrowsTypeError(() => registry.register(Symbol.for("registered"), "held"));
assertThrowsTypeError(() => registry.unregister(undefined));
do_check_eq(registry.register(Symbol("target"), "held"), undefined);
let symbolToken = Symbol("token");
do_check_eq(registry.register({}, "held", symbolToken), undefined);
do_check_eq(registry.unregister(symbolToken), true);
});
add_task(function* test_proto_from_constructor_realm() {
let other = new Components.utils.Sandbox("http://example.com", { freshZone: true });
Components.utils.evalInSandbox("var newTarget = new Function();", other);
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
other.newTarget.prototype = proto;
let registry = Reflect.construct(FinalizationRegistry, [function() {}],
other.newTarget);
do_check_true(Object.getPrototypeOf(registry) ===
other.FinalizationRegistry.prototype);
}
});
add_task(function* test_cleanup_callback_after_gc() {
let cleaned = [];
let registry = new FinalizationRegistry(value => cleaned.push(value));
let token = {};
(function() {
let target = {};
registry.register(target, "held", token);
})();
for (let i = 0; i < 4 && cleaned.length == 0; i++) {
Components.utils.forceGC();
yield Promise.resolve();
}
do_check_eq(cleaned.length, 1);
do_check_eq(cleaned[0], "held");
do_check_eq(registry.unregister(token), false);
});
add_task(function* test_unregister_prevents_cleanup() {
let cleaned = [];
let registry = new FinalizationRegistry(value => cleaned.push(value));
let token = {};
(function() {
let target = {};
registry.register(target, "held", token);
})();
do_check_eq(registry.unregister(token), true);
Components.utils.forceGC();
yield Promise.resolve();
do_check_eq(cleaned.length, 0);
});
-1
View File
@@ -71,7 +71,6 @@ support-files =
[test_import_fail.js]
[test_interposition.js]
[test_isModuleLoaded.js]
[test_finalization_registry.js]
[test_js_weak_references.js]
[test_weakref.js]
[test_onGarbageCollection-01.js]