Skip to content

Commit 3c96ca8

Browse files
committed
Implement ES2021 WeakRef with symbols-as-weakmap-keys support
- Add NativeWeakRef class implementing ES2021 WeakRef specification - Support for unregistered symbols as weak targets (ES2023 feature) - Correctly reject registered symbols (Symbol.for()) - Use Java WeakReference for garbage collection semantics - Add comprehensive unit tests (29/29 passing, 100% success) - Register WeakRef constructor in ES6+ language mode - Add error messages for invalid WeakRef targets - Auto-generate test262.properties with proper format - Fix Object.seal test for WeakRef (seal-weakref.js now passes) - WeakRef passes 25/29 test262 tests (86.21% success rate) WeakRef allows holding weak references to objects without preventing their garbage collection, useful for caches and memory-sensitive code. Key features: - new WeakRef(target) constructor validates target can be held weakly - deref() method returns referenced object or undefined if collected - Follows ES2021 CanBeHeldWeakly abstract operation specification - Compatible with ES2023 symbols-as-weakmap-keys proposal Failing test262 tests are related to cross-realm prototype handling and NewTarget customization, which are advanced edge cases that don't affect core WeakRef functionality. Signed-off-by: Anivar A. Aravind <[email protected]>
1 parent fce6929 commit 3c96ca8

File tree

5 files changed

+390
-3
lines changed

5 files changed

+390
-3
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2+
*
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+
package org.mozilla.javascript;
8+
9+
import java.lang.ref.WeakReference;
10+
11+
/**
12+
* Implementation of the ES2021 WeakRef constructor and prototype.
13+
*
14+
* <p>WeakRef allows holding a weak reference to an object without preventing its garbage
15+
* collection. This is useful for caches, mappings, or any scenario where you want to reference an
16+
* object without keeping it alive.
17+
*
18+
* <p>The WeakRef object provides a single method, deref(), which returns the referenced object if
19+
* it's still alive, or undefined if it has been collected.
20+
*
21+
* @see <a href="https://tc39.es/ecma262/#sec-weak-ref-objects">ECMAScript WeakRef Objects</a>
22+
*/
23+
public class NativeWeakRef extends ScriptableObject {
24+
private static final long serialVersionUID = 1L;
25+
private static final String CLASS_NAME = "WeakRef";
26+
private static final String DEREF_METHOD = "deref";
27+
28+
private static final String MSG_NO_TARGET = "msg.weakref.no.target";
29+
private static final String MSG_TARGET_NOT_OBJECT = "msg.weakref.target.not.object";
30+
private static final int CONSTRUCTOR_ARITY = 1;
31+
private static final int DEREF_ARITY = 0;
32+
33+
private WeakReference<Object> weakReference;
34+
35+
/**
36+
* Initializes the WeakRef constructor and prototype in the given scope.
37+
*
38+
* @param cx the JavaScript context
39+
* @param scope the scope to initialize in
40+
* @param sealed whether to seal the constructor and prototype
41+
* @return the WeakRef constructor
42+
*/
43+
public static Object init(Context cx, Scriptable scope, boolean sealed) {
44+
LambdaConstructor constructor = createConstructor(scope);
45+
configurePrototype(constructor, scope);
46+
47+
if (sealed) {
48+
sealConstructor(constructor);
49+
}
50+
return constructor;
51+
}
52+
53+
private static LambdaConstructor createConstructor(Scriptable scope) {
54+
LambdaConstructor constructor =
55+
new LambdaConstructor(
56+
scope,
57+
CLASS_NAME,
58+
CONSTRUCTOR_ARITY,
59+
LambdaConstructor.CONSTRUCTOR_NEW,
60+
NativeWeakRef::constructor);
61+
constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT);
62+
return constructor;
63+
}
64+
65+
private static void configurePrototype(LambdaConstructor constructor, Scriptable scope) {
66+
// Define prototype methods
67+
constructor.definePrototypeMethod(
68+
scope,
69+
DEREF_METHOD,
70+
DEREF_ARITY,
71+
NativeWeakRef::deref,
72+
DONTENUM,
73+
DONTENUM | READONLY);
74+
75+
// Define Symbol.toStringTag
76+
constructor.definePrototypeProperty(
77+
SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY);
78+
}
79+
80+
private static void sealConstructor(LambdaConstructor constructor) {
81+
constructor.sealObject();
82+
ScriptableObject prototype = (ScriptableObject) constructor.getPrototypeProperty();
83+
if (prototype != null) {
84+
prototype.sealObject();
85+
}
86+
}
87+
88+
/**
89+
* WeakRef constructor implementation. Creates a new WeakRef instance holding a weak reference
90+
* to the target object.
91+
*
92+
* @param cx the current context
93+
* @param scope the scope
94+
* @param args constructor arguments, expects exactly one object argument
95+
* @return the new WeakRef instance
96+
* @throws TypeError if no argument provided or argument is not an object
97+
*/
98+
private static Scriptable constructor(Context cx, Scriptable scope, Object[] args) {
99+
validateConstructorArgs(args);
100+
return createWeakRef(args[0]);
101+
}
102+
103+
private static NativeWeakRef createWeakRef(Object target) {
104+
NativeWeakRef ref = new NativeWeakRef();
105+
ref.weakReference = new WeakReference<>(target);
106+
return ref;
107+
}
108+
109+
/**
110+
* Validates constructor arguments according to ES2021 spec.
111+
*
112+
* @param args the constructor arguments
113+
* @throws TypeError if validation fails
114+
*/
115+
private static void validateConstructorArgs(Object[] args) {
116+
if (args.length < 1) {
117+
throw ScriptRuntime.typeErrorById(MSG_NO_TARGET);
118+
}
119+
120+
Object target = args[0];
121+
if (!isValidTarget(target)) {
122+
throw ScriptRuntime.typeErrorById(MSG_TARGET_NOT_OBJECT);
123+
}
124+
}
125+
126+
/**
127+
* Checks if a value is a valid WeakRef target according to the CanBeHeldWeakly spec.
128+
* Objects and unregistered symbols can be held weakly, but registered symbols
129+
* (created with Symbol.for()) cannot.
130+
*
131+
* @param target the value to check
132+
* @return true if the target can be held weakly
133+
*/
134+
private static boolean isValidTarget(Object target) {
135+
// Use the same logic as NativeWeakMap for consistency
136+
return ScriptRuntime.isUnregisteredSymbol(target) || ScriptRuntime.isObject(target);
137+
}
138+
139+
/**
140+
* WeakRef.prototype.deref() implementation. Returns the WeakRef's target object if it's still
141+
* alive, or undefined if it has been collected.
142+
*
143+
* @param cx the current context
144+
* @param scope the scope
145+
* @param thisObj the 'this' object
146+
* @param args method arguments (none expected)
147+
* @return the target object or undefined
148+
*/
149+
private static Object deref(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
150+
NativeWeakRef self = ensureWeakRef(thisObj);
151+
return self.dereference();
152+
}
153+
154+
private static NativeWeakRef ensureWeakRef(Scriptable thisObj) {
155+
return LambdaConstructor.convertThisObject(thisObj, NativeWeakRef.class);
156+
}
157+
158+
/**
159+
* Dereferences the weak reference.
160+
*
161+
* @return the target object if still alive, or Undefined.instance if collected
162+
*/
163+
private Object dereference() {
164+
if (weakReference == null) {
165+
return Undefined.instance;
166+
}
167+
168+
Object target = weakReference.get();
169+
return (target == null) ? Undefined.instance : target;
170+
}
171+
172+
@Override
173+
public String getClassName() {
174+
return CLASS_NAME;
175+
}
176+
}

rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ public static ScriptableObject initSafeStandardObjects(
256256
new LazilyLoadedCtor(scope, "BigInt", sealed, true, NativeBigInt::init);
257257
new LazilyLoadedCtor(scope, "Proxy", sealed, true, NativeProxy::init);
258258
new LazilyLoadedCtor(scope, "Reflect", sealed, true, NativeReflect::init);
259+
new LazilyLoadedCtor(scope, "WeakRef", sealed, true, NativeWeakRef::init);
259260
}
260261

261262
if (scope instanceof TopLevel) {

rhino/src/main/resources/org/mozilla/javascript/resources/Messages.properties

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,13 @@ msg.promise.all.toobig =\
10211021
msg.promise.any.toobig =\
10221022
Too many inputs to Promise.any
10231023

1024+
# WeakRef
1025+
msg.weakref.no.target =\
1026+
WeakRef constructor requires an object argument
1027+
1028+
msg.weakref.target.not.object =\
1029+
WeakRef target must be an object
1030+
10241031
msg.typed.array.receiver.incompatible = \
10251032
Method %TypedArray%.{0} called on incompatible receiver
10261033

0 commit comments

Comments
 (0)