From 2a6ec52f2fdc5754ea35087ae3174574c991c1bb Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Tue, 16 Dec 2025 22:28:11 -0500 Subject: [PATCH] Issue MoonchildProductions/UXP#2306: Implement WeakRef functionality --- js/src/builtin/WeakRefObject.cpp | 204 +++++++++++++++++++++++++++++++ js/src/builtin/WeakRefObject.h | 55 +++++++++ js/src/jsprototypes.h | 1 + js/src/moz.build | 1 + js/src/vm/GlobalObject.cpp | 2 + 5 files changed, 263 insertions(+) create mode 100644 js/src/builtin/WeakRefObject.cpp create mode 100644 js/src/builtin/WeakRefObject.h diff --git a/js/src/builtin/WeakRefObject.cpp b/js/src/builtin/WeakRefObject.cpp new file mode 100644 index 0000000000..4b02f8189f --- /dev/null +++ b/js/src/builtin/WeakRefObject.cpp @@ -0,0 +1,204 @@ +/* -*- 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/WeakRefObject.h" + +#include "jsapi.h" +#include "jscntxt.h" + +#include "gc/Nursery.h" +#include "gc/Tracer.h" +#include "vm/GlobalObject.h" + +#include "jsobjinlines.h" + +#include "vm/Interpreter-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; + +static WeakRefObject::Referent* +GetReferent(JSObject* obj) +{ + return obj->as().getData(); +} + +static MOZ_ALWAYS_INLINE bool +IsWeakRef(HandleValue v) +{ + return v.isObject() && v.toObject().is(); +} + +static bool +WeakRef_deref_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(IsWeakRef(args.thisv())); + + WeakRefObject::Referent* data = GetReferent(&args.thisv().toObject()); + JSObject* target = data ? data->target.get() : nullptr; + if (target) + args.rval().setObject(*target); + else + args.rval().setUndefined(); + return true; +} + +const JSPropertySpec WeakRefObject::properties[] = { + JS_PS_END +}; + +const JSFunctionSpec WeakRefObject::methods[] = { + JS_FN("deref", WeakRefObject::deref, 0, 0), + JS_FS_END +}; + +static JSObject* +InitWeakRefClass(JSContext* cx, HandleObject obj, bool defineMembers) +{ + Handle global = obj.as(); + RootedPlainObject proto(cx, NewBuiltinClassInstance(cx)); + if (!proto) + return nullptr; + + RootedFunction ctor(cx, GlobalObject::createConstructor(cx, WeakRefObject::construct, + ClassName(JSProto_WeakRef, cx), 1)); + if (!ctor) + return nullptr; + + if (!LinkConstructorAndPrototype(cx, ctor, proto)) + return nullptr; + + if (defineMembers) { + if (!DefinePropertiesAndFunctions(cx, proto, WeakRefObject::properties, WeakRefObject::methods)) + return nullptr; + if (!DefineToStringTag(cx, proto, cx->names().WeakRef)) + return nullptr; + } + + if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakRef, ctor, proto)) + return nullptr; + return proto; +} + +/* static */ WeakRefObject* +WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /* = nullptr */) +{ + Rooted obj(cx, NewObjectWithClassProto(cx, proto)); + if (!obj) + return nullptr; + + Referent* data = cx->new_(target); + if (!data) + return nullptr; + + obj->setPrivate(data); + return obj; +} + +/* static */ bool +WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "WeakRef")) + return false; + + if (!args.get(0).isObject()) { + UniqueChars bytes = + DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr); + if (!bytes) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, + bytes.get()); + return false; + } + + RootedObject target(cx, &args[0].toObject()); + + RootedObject proto(cx); + RootedObject newTarget(cx, &args.newTarget().toObject()); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) + return false; + + Rooted obj(cx, WeakRefObject::create(cx, target, proto)); + if (!obj) + return false; + + args.rval().setObject(*obj); + return true; +} + +/* static */ bool +WeakRefObject::deref(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ void +WeakRefObject::trace(JSTracer* trc, JSObject* obj) +{ + if (Referent* data = GetReferent(obj)) { + JSObject* target = data->target.unbarrieredGet(); + if (!target) + return; + + // Weak edges must be tenured; fall back to a strong trace while the + // referent is still in the nursery to avoid crashing the GC. + if (IsInsideNursery(target)) + TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef nursery referent"); + else + TraceWeakEdge(trc, &data->target, "WeakRef referent"); + } +} + +/* static */ void +WeakRefObject::finalize(FreeOp* fop, JSObject* obj) +{ + if (Referent* data = GetReferent(obj)) + fop->delete_(data); +} + +static const ClassOps WeakRefObjectClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + WeakRefObject::finalize, + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + WeakRefObject::trace +}; + +const Class WeakRefObject::class_ = { + "WeakRef", + JSCLASS_HAS_PRIVATE | + JSCLASS_HAS_CACHED_PROTO(JSProto_WeakRef) | + JSCLASS_BACKGROUND_FINALIZE, + &WeakRefObjectClassOps +}; + +/* static */ JSObject* +WeakRefObject::initClass(JSContext* cx, HandleObject obj) +{ + return ::InitWeakRefClass(cx, obj, true); +} + +JSObject* +js::InitWeakRefClass(JSContext* cx, HandleObject obj) +{ + return WeakRefObject::initClass(cx, obj); +} + +JSObject* +js::InitBareWeakRefCtor(JSContext* cx, HandleObject obj) +{ + return ::InitWeakRefClass(cx, obj, false); +} diff --git a/js/src/builtin/WeakRefObject.h b/js/src/builtin/WeakRefObject.h new file mode 100644 index 0000000000..e528f9fd37 --- /dev/null +++ b/js/src/builtin/WeakRefObject.h @@ -0,0 +1,55 @@ +/* -*- 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_WeakRefObject_h +#define builtin_WeakRefObject_h + +#include "gc/Barrier.h" +#include "vm/NativeObject.h" + +namespace js { + +class WeakRefObject : public NativeObject +{ + public: + struct Referent { + explicit Referent(JSObject* obj) : target(obj) {} + WeakRef target; + }; + + static const Class class_; + + static JSObject* initClass(JSContext* cx, HandleObject obj); + static WeakRefObject* create(JSContext* cx, HandleObject target, HandleObject proto = nullptr); + + static void trace(JSTracer* trc, JSObject* obj); + static void finalize(FreeOp* fop, JSObject* obj); + [[nodiscard]] static bool construct(JSContext* cx, unsigned argc, Value* vp); + static bool deref(JSContext* cx, unsigned argc, Value* vp); + + Referent* getData() const { + return static_cast(getPrivate()); + } + + WeakRef& target() { + MOZ_ASSERT(getData()); + return getData()->target; + } + + static const JSPropertySpec properties[]; + static const JSFunctionSpec methods[]; + + private: +}; + +extern JSObject* +InitWeakRefClass(JSContext* cx, HandleObject obj); + +extern JSObject* +InitBareWeakRefCtor(JSContext* cx, HandleObject obj); + +} // namespace js + +#endif /* builtin_WeakRefObject_h */ diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 9822a47b6c..6005724f89 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -119,6 +119,7 @@ IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \ imaginary(WritableStreamDefaultController,dummy, dummy) \ real(ByteLengthQueuingStrategy, InitViaClassSpec, &js::ByteLengthQueuingStrategy::class_) \ real(CountQueuingStrategy, InitViaClassSpec, &js::CountQueuingStrategy::class_) \ + real(WeakRef, InitWeakRefClass, OCLASP(WeakRef)) \ #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro) diff --git a/js/src/moz.build b/js/src/moz.build index baa4e1a19e..6672e41220 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -142,6 +142,7 @@ main_deunified_sources = [ 'builtin/TestingFunctions.cpp', 'builtin/TypedObject.cpp', 'builtin/WeakMapObject.cpp', + 'builtin/WeakRefObject.cpp', 'builtin/WeakSetObject.cpp', 'devtools/sharkctl.cpp', 'ds/LifoAlloc.cpp', diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index be82c18f11..9d1dab322e 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -27,6 +27,7 @@ #include "builtin/SymbolObject.h" #include "builtin/TypedObject.h" #include "builtin/WeakMapObject.h" +#include "builtin/WeakRefObject.h" #include "builtin/WeakSetObject.h" #include "vm/Debugger.h" #include "vm/EnvironmentObject.h" @@ -539,6 +540,7 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle globa InitBareBuiltinCtor(cx, global, JSProto_Int32Array) && InitBareSymbolCtor(cx, global) && InitBareWeakMapCtor(cx, global) && + InitBareWeakRefCtor(cx, global) && InitStopIterationClass(cx, global) && DefineFunctions(cx, global, builtins, AsIntrinsic); }