diff --git a/rhino/src/main/java/org/mozilla/javascript/AbstractEcmaObjectOperations.java b/rhino/src/main/java/org/mozilla/javascript/AbstractEcmaObjectOperations.java index ab5066e9d7..e3da280f3c 100644 --- a/rhino/src/main/java/org/mozilla/javascript/AbstractEcmaObjectOperations.java +++ b/rhino/src/main/java/org/mozilla/javascript/AbstractEcmaObjectOperations.java @@ -264,8 +264,19 @@ static Map> groupBy( Object items, Object callback, KEY_COERCION keyCoercion) { + return groupBy(cx, scope, f.getTag(), f.getFunctionName(), items, callback, keyCoercion); + } + + static Map> groupBy( + Context cx, + Scriptable scope, + Object classTag, + String functionName, + Object items, + Object callback, + KEY_COERCION keyCoercion) { if (cx.getLanguageVersion() >= Context.VERSION_ES6) { - ScriptRuntimeES6.requireObjectCoercible(cx, items, f); + ScriptRuntimeES6.requireObjectCoercible(cx, items, classTag, functionName); } if (!(callback instanceof Callable)) { throw ScriptRuntime.typeErrorById( diff --git a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java index 52a5958c82..0dec3cdbd3 100644 --- a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java +++ b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java @@ -192,6 +192,16 @@ public void definePrototypeProperty(Symbol key, Object value, int attributes) { proto.defineProperty(key, value, attributes); } + public void definePrototypeProperty(Context cx, String name, ScriptableObject descriptor) { + ScriptableObject proto = getPrototypeScriptable(); + proto.defineOwnProperty(cx, name, descriptor); + } + + public void definePrototypeProperty(Context cx, Symbol key, ScriptableObject descriptor) { + ScriptableObject proto = getPrototypeScriptable(); + proto.defineOwnProperty(cx, key, descriptor); + } + /** * Define a property on the prototype using a function. The function will be wired to a * JavaScript function, so the resulting property will look just like one that was defined using @@ -203,6 +213,12 @@ public void definePrototypeProperty( proto.defineProperty(cx, name, getter, null, attributes); } + public void definePrototypeProperty( + Context cx, Symbol key, ScriptableObject.LambdaGetterFunction getter, int attributes) { + ScriptableObject proto = getPrototypeScriptable(); + proto.defineProperty(cx, key, getter, null, attributes); + } + /** * Define a property on the prototype using functions for getter and setter. The function will * be wired to a JavaScript function, so the resulting property will look just like one that was @@ -218,6 +234,20 @@ public void definePrototypeProperty( proto.defineProperty(cx, name, getter, setter, attributes); } + /** Define a property on the prototype that has the same value as another property. */ + public void definePrototypeAlias(String name, SymbolKey alias, int attributes) { + ScriptableObject proto = getPrototypeScriptable(); + Object val = proto.get(name, proto); + proto.defineProperty(alias, val, attributes); + } + + /** Define a property on the prototype that has the same value as another property. */ + public void definePrototypeAlias(String name, String alias, int attributes) { + ScriptableObject proto = getPrototypeScriptable(); + Object val = proto.get(name, proto); + proto.defineProperty(alias, val, attributes); + } + /** * Define a function property directly on the constructor that is implemented under the covers * by a LambdaFunction. diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeMap.java b/rhino/src/main/java/org/mozilla/javascript/NativeMap.java index 512bf5519d..aa175ed204 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeMap.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeMap.java @@ -9,9 +9,9 @@ import java.util.List; import java.util.Map; -public class NativeMap extends IdScriptableObject { +public class NativeMap extends ScriptableObject { private static final long serialVersionUID = 1171922614280016891L; - private static final Object MAP_TAG = "Map"; + private static final String CLASS_NAME = "Map"; static final String ITERATOR_TAG = "Map Iterator"; private final Hashtable entries = new Hashtable(); @@ -19,104 +19,166 @@ public class NativeMap extends IdScriptableObject { private boolean instanceOfMap = false; static void init(Context cx, Scriptable scope, boolean sealed) { - NativeMap obj = new NativeMap(); - IdFunctionObject constructor = obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, false); - + LambdaConstructor constructor = + new LambdaConstructor( + scope, + CLASS_NAME, + 0, + LambdaConstructor.CONSTRUCTOR_NEW, + NativeMap::jsConstructor); + constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); + + constructor.defineConstructorMethod(scope, "groupBy", 2, NativeMap::jsGroupBy, DONTENUM); + + constructor.definePrototypeMethod( + scope, + "set", + 2, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "set") + .js_set(key(args), args.length > 1 ? args[1] : Undefined.instance), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "delete", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "delete").js_delete(key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "get", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "get").js_get(key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "has", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "has").js_has(key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "clear", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "clear").js_clear(), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "keys", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "keys") + .js_iterator(scope, NativeCollectionIterator.Type.KEYS), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "values", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "values") + .js_iterator(scope, NativeCollectionIterator.Type.VALUES), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "forEach", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "forEach") + .js_forEach( + lcx, + lscope, + args.length > 0 ? args[0] : Undefined.instance, + args.length > 1 ? args[1] : Undefined.instance), + DONTENUM, + DONTENUM | READONLY); + + constructor.definePrototypeMethod( + scope, + "entries", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "entries") + .js_iterator(scope, NativeCollectionIterator.Type.BOTH), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeAlias("entries", SymbolKey.ITERATOR, DONTENUM); + + // The spec requires very specific handling of the "size" prototype + // property that's not like other things that we already do. ScriptableObject desc = (ScriptableObject) cx.newObject(scope); desc.put("enumerable", desc, Boolean.FALSE); desc.put("configurable", desc, Boolean.TRUE); - desc.put("get", desc, obj.get(NativeSet.GETSIZE, obj)); - obj.defineOwnProperty(cx, "size", desc); + LambdaFunction sizeFunc = + new LambdaFunction( + scope, + "get size", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "size").js_getSize()); + sizeFunc.setPrototypeProperty(Undefined.instance); + desc.put("get", desc, sizeFunc); + constructor.definePrototypeProperty(cx, "size", desc); + constructor.definePrototypeProperty(cx, NativeSet.GETSIZE, desc); + + constructor.definePrototypeProperty( + SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); + ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, ScriptableObject.DONTENUM); if (sealed) { - obj.sealObject(); + constructor.sealObject(); } } @Override public String getClassName() { - return "Map"; + return CLASS_NAME; } - @Override - public void fillConstructorProperties(IdFunctionObject ctor) { - addIdFunctionProperty(ctor, MAP_TAG, ConstructorId_groupBy, "groupBy", 2); - super.fillConstructorProperties(ctor); + private static Scriptable jsConstructor(Context cx, Scriptable scope, Object[] args) { + NativeMap nm = new NativeMap(); + nm.instanceOfMap = true; + if (args.length > 0) { + loadFromIterable(cx, scope, nm, key(args)); + } + return nm; } - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - if (!f.hasTag(MAP_TAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); - } - int id = f.methodId(); - switch (id) { - case Id_constructor: - if (thisObj == null) { - NativeMap nm = new NativeMap(); - nm.instanceOfMap = true; - if (args.length > 0) { - loadFromIterable(cx, scope, nm, key(args)); - } - return nm; - } - throw ScriptRuntime.typeErrorById("msg.no.new", "Map"); - case Id_set: - return realThis(thisObj, f) - .js_set(key(args), args.length > 1 ? args[1] : Undefined.instance); - case Id_delete: - return realThis(thisObj, f).js_delete(key(args)); - case Id_get: - return realThis(thisObj, f).js_get(key(args)); - case Id_has: - return realThis(thisObj, f).js_has(key(args)); - case Id_clear: - return realThis(thisObj, f).js_clear(); - case Id_keys: - return realThis(thisObj, f).js_iterator(scope, NativeCollectionIterator.Type.KEYS); - case Id_values: - return realThis(thisObj, f) - .js_iterator(scope, NativeCollectionIterator.Type.VALUES); - case Id_entries: - return realThis(thisObj, f).js_iterator(scope, NativeCollectionIterator.Type.BOTH); - case Id_forEach: - return realThis(thisObj, f) - .js_forEach( - cx, - scope, - args.length > 0 ? args[0] : Undefined.instance, - args.length > 1 ? args[1] : Undefined.instance); - case SymbolId_getSize: - return realThis(thisObj, f).js_getSize(); - - case ConstructorId_groupBy: - { - Object items = args.length < 1 ? Undefined.instance : args[0]; - Object callback = args.length < 2 ? Undefined.instance : args[1]; - - Map> groups = - AbstractEcmaObjectOperations.groupBy( - cx, - scope, - f, - items, - callback, - AbstractEcmaObjectOperations.KEY_COERCION.COLLECTION); - - NativeMap map = (NativeMap) cx.newObject(scope, "Map"); - - for (Map.Entry> entry : groups.entrySet()) { - Scriptable elements = cx.newArray(scope, entry.getValue().toArray()); - map.entries.put(entry.getKey(), elements); - } - - return map; - } + private static Object jsGroupBy( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Object items = args.length < 1 ? Undefined.instance : args[0]; + Object callback = args.length < 2 ? Undefined.instance : args[1]; + + Map> groups = + AbstractEcmaObjectOperations.groupBy( + cx, + scope, + CLASS_NAME, + "groupBy", + items, + callback, + AbstractEcmaObjectOperations.KEY_COERCION.COLLECTION); + + NativeMap map = (NativeMap) cx.newObject(scope, "Map"); + + for (Map.Entry> entry : groups.entrySet()) { + Scriptable elements = cx.newArray(scope, entry.getValue().toArray()); + map.entries.put(entry.getKey(), elements); } - throw new IllegalArgumentException("Map.prototype has no method: " + f.getFunctionName()); + + return map; } private Object js_set(Object k, Object v) { @@ -130,7 +192,7 @@ private Object js_set(Object k, Object v) { } private Object js_delete(Object arg) { - return Boolean.valueOf(entries.deleteEntry(arg)); + return entries.deleteEntry(arg); } private Object js_get(Object arg) { @@ -142,11 +204,11 @@ private Object js_get(Object arg) { } private Object js_has(Object arg) { - return Boolean.valueOf(entries.has(arg)); + return entries.has(arg); } private Object js_getSize() { - return Integer.valueOf(entries.size()); + return entries.size(); } private Object js_iterator(Scriptable scope, NativeCollectionIterator.Type type) { @@ -213,156 +275,15 @@ static void loadFromIterable(Context cx, Scriptable scope, ScriptableObject map, (key, value) -> set.call(cx, scope, map, new Object[] {key, value})); } - private static NativeMap realThis(Scriptable thisObj, IdFunctionObject f) { - final NativeMap nm = ensureType(thisObj, NativeMap.class, f); + private static NativeMap realThis(Scriptable thisObj, String name) { + NativeMap nm = LambdaConstructor.convertThisObject(thisObj, NativeMap.class); if (!nm.instanceOfMap) { // Check for "Map internal data tag" - throw ScriptRuntime.typeErrorById("msg.incompat.call", f.getFunctionName()); + throw ScriptRuntime.typeErrorById("msg.incompat.call", name); } - return nm; } - @Override - protected void initPrototypeId(int id) { - switch (id) { - case SymbolId_getSize: - initPrototypeMethod(MAP_TAG, id, NativeSet.GETSIZE, "get size", 0); - return; - case SymbolId_toStringTag: - initPrototypeValue( - SymbolId_toStringTag, - SymbolKey.TO_STRING_TAG, - getClassName(), - DONTENUM | READONLY); - return; - // fallthrough - } - - String s, fnName = null; - int arity; - switch (id) { - case Id_constructor: - arity = 0; - s = "constructor"; - break; - case Id_set: - arity = 2; - s = "set"; - break; - case Id_get: - arity = 1; - s = "get"; - break; - case Id_delete: - arity = 1; - s = "delete"; - break; - case Id_has: - arity = 1; - s = "has"; - break; - case Id_clear: - arity = 0; - s = "clear"; - break; - case Id_keys: - arity = 0; - s = "keys"; - break; - case Id_values: - arity = 0; - s = "values"; - break; - case Id_entries: - arity = 0; - s = "entries"; - break; - case Id_forEach: - arity = 1; - s = "forEach"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(MAP_TAG, id, s, fnName, arity); - } - - @Override - protected int findPrototypeId(Symbol k) { - if (NativeSet.GETSIZE.equals(k)) { - return SymbolId_getSize; - } - if (SymbolKey.ITERATOR.equals(k)) { - // ECMA spec says that the "Symbol.iterator" property of the prototype has the - // "same value" as the "entries" property. We implement this by returning the - // ID of "entries" when the iterator symbol is accessed. - return Id_entries; - } - if (SymbolKey.TO_STRING_TAG.equals(k)) { - return SymbolId_toStringTag; - } - return 0; - } - - @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "constructor": - id = Id_constructor; - break; - case "set": - id = Id_set; - break; - case "get": - id = Id_get; - break; - case "delete": - id = Id_delete; - break; - case "has": - id = Id_has; - break; - case "clear": - id = Id_clear; - break; - case "keys": - id = Id_keys; - break; - case "values": - id = Id_values; - break; - case "entries": - id = Id_entries; - break; - case "forEach": - id = Id_forEach; - break; - default: - id = 0; - break; - } - return id; - } - - // Note that "SymbolId_iterator" is not present here. That's because the spec - // requires that it be the same value as the "entries" prototype property. - private static final int ConstructorId_groupBy = -1, - Id_constructor = 1, - Id_set = 2, - Id_get = 3, - Id_delete = 4, - Id_has = 5, - Id_clear = 6, - Id_keys = 7, - Id_values = 8, - Id_entries = 9, - Id_forEach = 10, - SymbolId_getSize = 11, - SymbolId_toStringTag = 12, - MAX_PROTOTYPE_ID = SymbolId_toStringTag; - /** * Extracts the key from the first args entry if any and takes care of the Delegator. This is * used by {@code NativeSet}, {@code NativeWeakMap}, and {@code NativeWekSet} also. diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeSet.java b/rhino/src/main/java/org/mozilla/javascript/NativeSet.java index b4f3c0037c..c937307246 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeSet.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeSet.java @@ -6,9 +6,9 @@ package org.mozilla.javascript; -public class NativeSet extends IdScriptableObject { +public class NativeSet extends ScriptableObject { private static final long serialVersionUID = -8442212766987072986L; - private static final Object SET_TAG = "Set"; + private static final String CLASS_NAME = "Set"; static final String ITERATOR_TAG = "Set Iterator"; static final SymbolKey GETSIZE = new SymbolKey("[Symbol.getSize]"); @@ -18,70 +18,123 @@ public class NativeSet extends IdScriptableObject { private boolean instanceOfSet = false; static void init(Context cx, Scriptable scope, boolean sealed) { - NativeSet obj = new NativeSet(); - IdFunctionObject constructor = obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, false); - + LambdaConstructor constructor = + new LambdaConstructor( + scope, + CLASS_NAME, + 0, + LambdaConstructor.CONSTRUCTOR_NEW, + NativeSet::jsConstructor); + constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); + + constructor.definePrototypeMethod( + scope, + "add", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "add").js_add(NativeMap.key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "delete", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "delete").js_delete(NativeMap.key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "has", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "has").js_has(NativeMap.key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "clear", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "clear").js_clear(), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "values", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "values") + .js_iterator(scope, NativeCollectionIterator.Type.VALUES), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeAlias("values", "keys", DONTENUM | READONLY); + constructor.definePrototypeAlias("values", SymbolKey.ITERATOR, DONTENUM); + + constructor.definePrototypeMethod( + scope, + "forEach", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "forEach") + .js_forEach( + lcx, + lscope, + NativeMap.key(args), + args.length > 1 ? args[1] : Undefined.instance), + DONTENUM, + DONTENUM | READONLY); + + constructor.definePrototypeMethod( + scope, + "entries", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "entries") + .js_iterator(scope, NativeCollectionIterator.Type.BOTH), + DONTENUM, + DONTENUM | READONLY); + + // The spec requires very specific handling of the "size" prototype + // property that's not like other things that we already do. ScriptableObject desc = (ScriptableObject) cx.newObject(scope); desc.put("enumerable", desc, Boolean.FALSE); desc.put("configurable", desc, Boolean.TRUE); - desc.put("get", desc, obj.get(GETSIZE, obj)); - obj.defineOwnProperty(cx, "size", desc); + LambdaFunction sizeFunc = + new LambdaFunction( + scope, + "get size", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "size").js_getSize()); + sizeFunc.setPrototypeProperty(Undefined.instance); + desc.put("get", desc, sizeFunc); + constructor.definePrototypeProperty(cx, "size", desc); + constructor.definePrototypeProperty(cx, NativeSet.GETSIZE, desc); + + constructor.definePrototypeProperty( + SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); + ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, ScriptableObject.DONTENUM); if (sealed) { - obj.sealObject(); + constructor.sealObject(); } } @Override public String getClassName() { - return "Set"; + return CLASS_NAME; } - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - if (!f.hasTag(SET_TAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); - } - final int id = f.methodId(); - switch (id) { - case Id_constructor: - if (thisObj == null) { - NativeSet ns = new NativeSet(); - ns.instanceOfSet = true; - if (args.length > 0) { - loadFromIterable(cx, scope, ns, NativeMap.key(args)); - } - return ns; - } else { - throw ScriptRuntime.typeErrorById("msg.no.new", "Set"); - } - case Id_add: - return realThis(thisObj, f).js_add(NativeMap.key(args)); - case Id_delete: - return realThis(thisObj, f).js_delete(NativeMap.key(args)); - case Id_has: - return realThis(thisObj, f).js_has(NativeMap.key(args)); - case Id_clear: - return realThis(thisObj, f).js_clear(); - case Id_values: - return realThis(thisObj, f) - .js_iterator(scope, NativeCollectionIterator.Type.VALUES); - case Id_entries: - return realThis(thisObj, f).js_iterator(scope, NativeCollectionIterator.Type.BOTH); - case Id_forEach: - return realThis(thisObj, f) - .js_forEach( - cx, - scope, - NativeMap.key(args), - args.length > 1 ? args[1] : Undefined.instance); - case SymbolId_getSize: - return realThis(thisObj, f).js_getSize(); + private static Scriptable jsConstructor(Context cx, Scriptable scope, Object[] args) { + NativeSet ns = new NativeSet(); + ns.instanceOfSet = true; + if (args.length > 0) { + loadFromIterable(cx, scope, ns, NativeMap.key(args)); } - throw new IllegalArgumentException("Set.prototype has no method: " + f.getFunctionName()); + return ns; } private Object js_add(Object k) { @@ -95,11 +148,11 @@ private Object js_add(Object k) { } private Object js_delete(Object arg) { - return Boolean.valueOf(entries.deleteEntry(arg)); + return entries.deleteEntry(arg); } private Object js_has(Object arg) { - return Boolean.valueOf(entries.has(arg)); + return entries.has(arg); } private Object js_clear() { @@ -108,7 +161,7 @@ private Object js_clear() { } private Object js_getSize() { - return Integer.valueOf(entries.size()); + return entries.size(); } private Object js_iterator(Scriptable scope, NativeCollectionIterator.Type type) { @@ -173,139 +226,12 @@ static void loadFromIterable(Context cx, Scriptable scope, ScriptableObject set, } } - private static NativeSet realThis(Scriptable thisObj, IdFunctionObject f) { - final NativeSet ns = ensureType(thisObj, NativeSet.class, f); + private static NativeSet realThis(Scriptable thisObj, String name) { + NativeSet ns = LambdaConstructor.convertThisObject(thisObj, NativeSet.class); if (!ns.instanceOfSet) { // If we get here, then this object doesn't have the "Set internal data slot." - throw ScriptRuntime.typeErrorById("msg.incompat.call", f.getFunctionName()); + throw ScriptRuntime.typeErrorById("msg.incompat.call", name); } - return ns; } - - @Override - protected void initPrototypeId(int id) { - switch (id) { - case SymbolId_getSize: - initPrototypeMethod(SET_TAG, id, GETSIZE, "get size", 0); - return; - case SymbolId_toStringTag: - initPrototypeValue( - SymbolId_toStringTag, - SymbolKey.TO_STRING_TAG, - getClassName(), - DONTENUM | READONLY); - return; - // fallthrough - } - - String s, fnName = null; - int arity; - switch (id) { - case Id_constructor: - arity = 0; - s = "constructor"; - break; - case Id_add: - arity = 1; - s = "add"; - break; - case Id_delete: - arity = 1; - s = "delete"; - break; - case Id_has: - arity = 1; - s = "has"; - break; - case Id_clear: - arity = 0; - s = "clear"; - break; - case Id_entries: - arity = 0; - s = "entries"; - break; - case Id_values: - arity = 0; - s = "values"; - break; - case Id_forEach: - arity = 1; - s = "forEach"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(SET_TAG, id, s, fnName, arity); - } - - @Override - protected int findPrototypeId(Symbol k) { - if (GETSIZE.equals(k)) { - return SymbolId_getSize; - } - if (SymbolKey.ITERATOR.equals(k)) { - return Id_values; - } - if (SymbolKey.TO_STRING_TAG.equals(k)) { - return SymbolId_toStringTag; - } - return 0; - } - - @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "constructor": - id = Id_constructor; - break; - case "add": - id = Id_add; - break; - case "delete": - id = Id_delete; - break; - case "has": - id = Id_has; - break; - case "clear": - id = Id_clear; - break; - case "keys": - id = Id_keys; - break; - case "values": - id = Id_values; - break; - case "entries": - id = Id_entries; - break; - case "forEach": - id = Id_forEach; - break; - default: - id = 0; - break; - } - return id; - } - - // Note that SymbolId_iterator is not present because it is required to have the - // same value as the "values" entry. - // Similarly, "keys" is supposed to have the same value as "values," which is why - // both have the same ID. - private static final int Id_constructor = 1, - Id_add = 2, - Id_delete = 3, - Id_has = 4, - Id_clear = 5, - Id_keys = 6, - Id_values = 6, // These are deliberately the same to match the spec - Id_entries = 7, - Id_forEach = 8, - SymbolId_getSize = 9, - SymbolId_toStringTag = 10, - MAX_PROTOTYPE_ID = SymbolId_toStringTag; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeSymbol.java b/rhino/src/main/java/org/mozilla/javascript/NativeSymbol.java index cb23c5898b..a5b3bc9e4d 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeSymbol.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeSymbol.java @@ -22,7 +22,14 @@ public class NativeSymbol extends ScriptableObject implements Symbol { private static final Object GLOBAL_TABLE_KEY = new Object(); + enum SymbolKind { + REGULAR, // A regular symbol is created using the constructor + BUILT_IN, // A built-in symbol is one of the properties of the "Symbol" constructor + REGISTERED // A registered symbol was created using "Symbol.for" + } + private final SymbolKey key; + private final SymbolKind kind; private final NativeSymbol symbolData; public static void init(Context cx, Scriptable scope, boolean sealed) { @@ -40,7 +47,7 @@ public static void init(Context cx, Scriptable scope, boolean sealed) { scope, "for", 1, - (lcx, lscope, thisObj, args) -> NativeSymbol.js_for(lcx, lscope, args, ctor), + (lcx, lscope, thisObj, args) -> NativeSymbol.js_for(lscope, args, ctor), DONTENUM, DONTENUM | READONLY); ctor.defineConstructorMethod( @@ -64,19 +71,19 @@ public static void init(Context cx, Scriptable scope, boolean sealed) { ScriptableObject.defineProperty(scope, CLASS_NAME, ctor, DONTENUM); // Create all the predefined symbols and bind them to the scope. - createStandardSymbol(cx, scope, ctor, "iterator", SymbolKey.ITERATOR); - createStandardSymbol(cx, scope, ctor, "species", SymbolKey.SPECIES); - createStandardSymbol(cx, scope, ctor, "toStringTag", SymbolKey.TO_STRING_TAG); - createStandardSymbol(cx, scope, ctor, "hasInstance", SymbolKey.HAS_INSTANCE); - createStandardSymbol(cx, scope, ctor, "isConcatSpreadable", SymbolKey.IS_CONCAT_SPREADABLE); - createStandardSymbol(cx, scope, ctor, "isRegExp", SymbolKey.IS_REGEXP); - createStandardSymbol(cx, scope, ctor, "toPrimitive", SymbolKey.TO_PRIMITIVE); - createStandardSymbol(cx, scope, ctor, "match", SymbolKey.MATCH); - createStandardSymbol(cx, scope, ctor, "matchAll", SymbolKey.MATCH_ALL); - createStandardSymbol(cx, scope, ctor, "replace", SymbolKey.REPLACE); - createStandardSymbol(cx, scope, ctor, "search", SymbolKey.SEARCH); - createStandardSymbol(cx, scope, ctor, "split", SymbolKey.SPLIT); - createStandardSymbol(cx, scope, ctor, "unscopables", SymbolKey.UNSCOPABLES); + createStandardSymbol(scope, ctor, "iterator", SymbolKey.ITERATOR); + createStandardSymbol(scope, ctor, "species", SymbolKey.SPECIES); + createStandardSymbol(scope, ctor, "toStringTag", SymbolKey.TO_STRING_TAG); + createStandardSymbol(scope, ctor, "hasInstance", SymbolKey.HAS_INSTANCE); + createStandardSymbol(scope, ctor, "isConcatSpreadable", SymbolKey.IS_CONCAT_SPREADABLE); + createStandardSymbol(scope, ctor, "isRegExp", SymbolKey.IS_REGEXP); + createStandardSymbol(scope, ctor, "toPrimitive", SymbolKey.TO_PRIMITIVE); + createStandardSymbol(scope, ctor, "match", SymbolKey.MATCH); + createStandardSymbol(scope, ctor, "matchAll", SymbolKey.MATCH_ALL); + createStandardSymbol(scope, ctor, "replace", SymbolKey.REPLACE); + createStandardSymbol(scope, ctor, "search", SymbolKey.SEARCH); + createStandardSymbol(scope, ctor, "split", SymbolKey.SPLIT); + createStandardSymbol(scope, ctor, "unscopables", SymbolKey.UNSCOPABLES); if (sealed) { // Can't seal until we have created all the stuff above! @@ -84,14 +91,20 @@ public static void init(Context cx, Scriptable scope, boolean sealed) { } } - NativeSymbol(SymbolKey key) { + NativeSymbol(SymbolKey key, SymbolKind kind) { this.key = key; this.symbolData = this; + this.kind = kind; } public NativeSymbol(NativeSymbol s) { this.key = s.key; this.symbolData = s.symbolData; + this.kind = s.kind; + } + + SymbolKind getKind() { + return kind; } @Override @@ -99,23 +112,19 @@ public String getClassName() { return CLASS_NAME; } - /** - * Create a symbol directly. We use this internally to construct new symbols as if the - * constructor was called directly. - */ - private static NativeSymbol constructSymbol( - Context cx, Scriptable scope, LambdaConstructor ctor, SymbolKey key) { - return (NativeSymbol) ctor.call(cx, scope, null, new Object[] {Undefined.instance, key}); - } - - private static NativeSymbol constructSymbol( - Context cx, Scriptable scope, LambdaConstructor ctor, String name) { - return (NativeSymbol) ctor.call(cx, scope, null, new Object[] {name}); + private static NativeSymbol createRegisteredSymbol( + Scriptable scope, LambdaConstructor ctor, String name) { + NativeSymbol sym = new NativeSymbol(new SymbolKey(name), SymbolKind.REGISTERED); + sym.setPrototype(ctor.getClassPrototype()); + sym.setParentScope(scope); + return sym; } private static void createStandardSymbol( - Context cx, Scriptable scope, LambdaConstructor ctor, String name, SymbolKey key) { - NativeSymbol sym = constructSymbol(cx, scope, ctor, key); + Scriptable scope, LambdaConstructor ctor, String name, SymbolKey key) { + NativeSymbol sym = new NativeSymbol(key, SymbolKind.BUILT_IN); + sym.setPrototype(ctor.getClassPrototype()); + sym.setParentScope(scope); ctor.defineProperty(name, sym, DONTENUM | READONLY | PERMANENT); } @@ -130,10 +139,10 @@ private static NativeSymbol js_constructor(Context cx, Scriptable scope, Object[ } if (args.length > 1) { - return new NativeSymbol((SymbolKey) args[1]); + return new NativeSymbol((SymbolKey) args[1], SymbolKind.REGULAR); } - return new NativeSymbol(new SymbolKey(desc)); + return new NativeSymbol(new SymbolKey(desc), SymbolKind.REGULAR); } private static String js_toString( @@ -150,15 +159,14 @@ private static Object js_description(Scriptable thisObj) { return getSelf(thisObj).getKey().getDescription(); } - private static Object js_for( - Context cx, Scriptable scope, Object[] args, LambdaConstructor constructor) { + private static Object js_for(Scriptable scope, Object[] args, LambdaConstructor constructor) { String name = (args.length > 0 ? ScriptRuntime.toString(args[0]) : ScriptRuntime.toString(Undefined.instance)); Map table = getGlobalMap(scope); - return table.computeIfAbsent(name, (k) -> constructSymbol(cx, scope, constructor, name)); + return table.computeIfAbsent(name, (k) -> createRegisteredSymbol(scope, constructor, name)); } @SuppressWarnings("ReferenceEquality") diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeWeakMap.java b/rhino/src/main/java/org/mozilla/javascript/NativeWeakMap.java index 98075a4668..8026548829 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeWeakMap.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeWeakMap.java @@ -18,10 +18,10 @@ * reference. Therefore, we can use WeakHashMap as the basis of this implementation and preserve the * same semantics. */ -public class NativeWeakMap extends IdScriptableObject { +public class NativeWeakMap extends ScriptableObject { private static final long serialVersionUID = 8670434366883930453L; - private static final Object MAP_TAG = "WeakMap"; + private static final String CLASS_NAME = "WeakMap"; private boolean instanceOfWeakMap = false; @@ -29,60 +29,86 @@ public class NativeWeakMap extends IdScriptableObject { private static final Object NULL_VALUE = new Object(); - static void init(Scriptable scope, boolean sealed) { - NativeWeakMap m = new NativeWeakMap(); - m.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + static void init(Context cx, Scriptable scope, boolean sealed) { + LambdaConstructor constructor = + new LambdaConstructor( + scope, + CLASS_NAME, + 0, + LambdaConstructor.CONSTRUCTOR_NEW, + NativeWeakMap::jsConstructor); + constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); + + constructor.definePrototypeMethod( + scope, + "set", + 2, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "set") + .js_set( + NativeMap.key(args), + args.length > 1 ? args[1] : Undefined.instance), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "delete", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "delete").js_delete(NativeMap.key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "get", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "get").js_get(NativeMap.key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "has", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "has").js_has(NativeMap.key(args)), + DONTENUM, + DONTENUM | READONLY); + + constructor.definePrototypeProperty( + SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); + + ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); + ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, ScriptableObject.DONTENUM); + + if (sealed) { + constructor.sealObject(); + } } @Override public String getClassName() { - return "WeakMap"; + return CLASS_NAME; } - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - - if (!f.hasTag(MAP_TAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); + private static Scriptable jsConstructor(Context cx, Scriptable scope, Object[] args) { + NativeWeakMap nm = new NativeWeakMap(); + nm.instanceOfWeakMap = true; + if (args.length > 0) { + NativeMap.loadFromIterable(cx, scope, nm, NativeMap.key(args)); } - int id = f.methodId(); - switch (id) { - case Id_constructor: - if (thisObj == null) { - NativeWeakMap nm = new NativeWeakMap(); - nm.instanceOfWeakMap = true; - if (args.length > 0) { - NativeMap.loadFromIterable(cx, scope, nm, NativeMap.key(args)); - } - return nm; - } - throw ScriptRuntime.typeErrorById("msg.no.new", "WeakMap"); - case Id_delete: - return realThis(thisObj, f).js_delete(NativeMap.key(args)); - case Id_get: - return realThis(thisObj, f).js_get(NativeMap.key(args)); - case Id_has: - return realThis(thisObj, f).js_has(NativeMap.key(args)); - case Id_set: - return realThis(thisObj, f) - .js_set( - NativeMap.key(args), - args.length > 1 ? args[1] : Undefined.instance); - } - throw new IllegalArgumentException( - "WeakMap.prototype has no method: " + f.getFunctionName()); + return nm; } private Object js_delete(Object key) { - if (!ScriptRuntime.isObject(key)) { + if (!isValidKey(key)) { return Boolean.FALSE; } - return Boolean.valueOf(map.remove(key) != null); + return map.remove(key) != null; } private Object js_get(Object key) { - if (!ScriptRuntime.isObject(key)) { + if (!isValidKey(key)) { return Undefined.instance; } Object result = map.get(key); @@ -95,10 +121,10 @@ private Object js_get(Object key) { } private Object js_has(Object key) { - if (!ScriptRuntime.isObject(key)) { + if (!isValidKey(key)) { return Boolean.FALSE; } - return Boolean.valueOf(map.containsKey(key)); + return map.containsKey(key); } private Object js_set(Object key, Object v) { @@ -106,7 +132,7 @@ private Object js_set(Object key, Object v) { // Use the default object equality here. ScriptableObject does not override // equals or hashCode, which means that in effect we are only keying on object identity. // This is all correct according to the ECMAscript spec. - if (!ScriptRuntime.isObject(key)) { + if (!isValidKey(key)) { throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(key)); } // Map.get() does not distinguish between "not found" and a null value. So, @@ -116,98 +142,20 @@ private Object js_set(Object key, Object v) { return this; } - private static NativeWeakMap realThis(Scriptable thisObj, IdFunctionObject f) { - final NativeWeakMap nm = ensureType(thisObj, NativeWeakMap.class, f); + private static boolean isValidKey(Object key) { + return ScriptRuntime.isUnregisteredSymbol(key) || ScriptRuntime.isObject(key); + } + + private static NativeWeakMap realThis(Scriptable thisObj, String name) { + NativeWeakMap nm = LambdaConstructor.convertThisObject(thisObj, NativeWeakMap.class); if (!nm.instanceOfWeakMap) { // Check for "Map internal data tag" - throw ScriptRuntime.typeErrorById("msg.incompat.call", f.getFunctionName()); + throw ScriptRuntime.typeErrorById("msg.incompat.call", name); } return nm; } - @Override - protected void initPrototypeId(int id) { - if (id == SymbolId_toStringTag) { - initPrototypeValue( - SymbolId_toStringTag, - SymbolKey.TO_STRING_TAG, - getClassName(), - DONTENUM | READONLY); - return; - } - - String s, fnName = null; - int arity; - switch (id) { - case Id_constructor: - arity = 0; - s = "constructor"; - break; - case Id_delete: - arity = 1; - s = "delete"; - break; - case Id_get: - arity = 1; - s = "get"; - break; - case Id_has: - arity = 1; - s = "has"; - break; - case Id_set: - arity = 2; - s = "set"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(MAP_TAG, id, s, fnName, arity); - } - - @Override - protected int findPrototypeId(Symbol k) { - if (SymbolKey.TO_STRING_TAG.equals(k)) { - return SymbolId_toStringTag; - } - return 0; - } - - @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "constructor": - id = Id_constructor; - break; - case "delete": - id = Id_delete; - break; - case "get": - id = Id_get; - break; - case "has": - id = Id_has; - break; - case "set": - id = Id_set; - break; - default: - id = 0; - break; - } - return id; - } - - private static final int Id_constructor = 1, - Id_delete = 2, - Id_get = 3, - Id_has = 4, - Id_set = 5, - SymbolId_toStringTag = 6, - MAX_PROTOTYPE_ID = SymbolId_toStringTag; - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); map = new WeakHashMap<>(); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeWeakSet.java b/rhino/src/main/java/org/mozilla/javascript/NativeWeakSet.java index 572b6e7b38..7f6688fe09 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeWeakSet.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeWeakSet.java @@ -16,53 +16,73 @@ * longer any reference to it other than the weak reference. That means that it is important that * the "value" that we put in the WeakHashMap here is not one that contains the key. */ -public class NativeWeakSet extends IdScriptableObject { +public class NativeWeakSet extends ScriptableObject { private static final long serialVersionUID = 2065753364224029534L; - private static final Object MAP_TAG = "WeakSet"; + private static final String CLASS_NAME = "WeakSet"; private boolean instanceOfWeakSet = false; private transient WeakHashMap map = new WeakHashMap<>(); - static void init(Scriptable scope, boolean sealed) { - NativeWeakSet m = new NativeWeakSet(); - m.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + static void init(Context cx, Scriptable scope, boolean sealed) { + LambdaConstructor constructor = + new LambdaConstructor( + scope, + CLASS_NAME, + 0, + LambdaConstructor.CONSTRUCTOR_NEW, + NativeWeakSet::jsConstructor); + constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); + + constructor.definePrototypeMethod( + scope, + "add", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "add").js_add(NativeMap.key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "delete", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "delete").js_delete(NativeMap.key(args)), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "has", + 1, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + realThis(thisObj, "has").js_has(NativeMap.key(args)), + DONTENUM, + DONTENUM | READONLY); + + constructor.definePrototypeProperty( + SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); + + ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); + ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, ScriptableObject.DONTENUM); + + if (sealed) { + constructor.sealObject(); + } } @Override public String getClassName() { - return "WeakSet"; + return CLASS_NAME; } - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - - if (!f.hasTag(MAP_TAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); + private static Scriptable jsConstructor(Context cx, Scriptable scope, Object[] args) { + NativeWeakSet ns = new NativeWeakSet(); + ns.instanceOfWeakSet = true; + if (args.length > 0) { + NativeSet.loadFromIterable(cx, scope, ns, NativeMap.key(args)); } - int id = f.methodId(); - switch (id) { - case Id_constructor: - if (thisObj == null) { - NativeWeakSet ns = new NativeWeakSet(); - ns.instanceOfWeakSet = true; - if (args.length > 0) { - NativeSet.loadFromIterable(cx, scope, ns, NativeMap.key(args)); - } - return ns; - } - throw ScriptRuntime.typeErrorById("msg.no.new", "WeakSet"); - case Id_add: - return realThis(thisObj, f).js_add(NativeMap.key(args)); - case Id_delete: - return realThis(thisObj, f).js_delete(NativeMap.key(args)); - case Id_has: - return realThis(thisObj, f).js_has(NativeMap.key(args)); - } - throw new IllegalArgumentException( - "WeakMap.prototype has no method: " + f.getFunctionName()); + return ns; } private Object js_add(Object key) { @@ -70,7 +90,7 @@ private Object js_add(Object key) { // Use the default object equality here. ScriptableObject does not override // equals or hashCode, which means that in effect we are only keying on object identity. // This is all correct according to the ECMAscript spec. - if (!ScriptRuntime.isObject(key)) { + if (!isValidValue(key)) { throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(key)); } // Add a value to the map, but don't make it the key -- otherwise the WeakHashMap @@ -80,103 +100,32 @@ private Object js_add(Object key) { } private Object js_delete(Object key) { - if (!ScriptRuntime.isObject(key)) { + if (!isValidValue(key)) { return Boolean.FALSE; } - return Boolean.valueOf(map.remove(key) != null); + return map.remove(key) != null; } private Object js_has(Object key) { - if (!ScriptRuntime.isObject(key)) { + if (!isValidValue(key)) { return Boolean.FALSE; } - return Boolean.valueOf(map.containsKey(key)); + return map.containsKey(key); + } + + private static boolean isValidValue(Object v) { + return ScriptRuntime.isUnregisteredSymbol(v) || ScriptRuntime.isObject(v); } - private static NativeWeakSet realThis(Scriptable thisObj, IdFunctionObject f) { - final NativeWeakSet ns = ensureType(thisObj, NativeWeakSet.class, f); + private static NativeWeakSet realThis(Scriptable thisObj, String name) { + NativeWeakSet ns = LambdaConstructor.convertThisObject(thisObj, NativeWeakSet.class); if (!ns.instanceOfWeakSet) { // Check for "Set internal data tag" - throw ScriptRuntime.typeErrorById("msg.incompat.call", f.getFunctionName()); + throw ScriptRuntime.typeErrorById("msg.incompat.call", name); } - return ns; } - @Override - protected void initPrototypeId(int id) { - if (id == SymbolId_toStringTag) { - initPrototypeValue( - SymbolId_toStringTag, - SymbolKey.TO_STRING_TAG, - getClassName(), - DONTENUM | READONLY); - return; - } - - String s, fnName = null; - int arity; - switch (id) { - case Id_constructor: - arity = 0; - s = "constructor"; - break; - case Id_add: - arity = 1; - s = "add"; - break; - case Id_delete: - arity = 1; - s = "delete"; - break; - case Id_has: - arity = 1; - s = "has"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(MAP_TAG, id, s, fnName, arity); - } - - @Override - protected int findPrototypeId(Symbol k) { - if (SymbolKey.TO_STRING_TAG.equals(k)) { - return SymbolId_toStringTag; - } - return 0; - } - - @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "constructor": - id = Id_constructor; - break; - case "add": - id = Id_add; - break; - case "delete": - id = Id_delete; - break; - case "has": - id = Id_has; - break; - default: - id = 0; - break; - } - return id; - } - - private static final int Id_constructor = 1, - Id_add = 2, - Id_delete = 3, - Id_has = 4, - SymbolId_toStringTag = 5, - MAX_PROTOTYPE_ID = SymbolId_toStringTag; - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); map = new WeakHashMap<>(); diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java index d85d6e123b..c03ebaf795 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java @@ -286,8 +286,8 @@ public static ScriptableObject initSafeStandardObjects( NativeMap.init(cx, scope, sealed); NativePromise.init(cx, scope, sealed); NativeSet.init(cx, scope, sealed); - NativeWeakMap.init(scope, sealed); - NativeWeakSet.init(scope, sealed); + NativeWeakMap.init(cx, scope, sealed); + NativeWeakSet.init(cx, scope, sealed); NativeBigInt.init(scope, sealed); NativeProxy.init(cx, scope, sealed); @@ -1272,7 +1272,8 @@ public static Scriptable toObject(Context cx, Scriptable scope, Object val) { if (isSymbol(val)) { if (val instanceof SymbolKey) { - NativeSymbol result = new NativeSymbol((SymbolKey) val); + NativeSymbol result = + new NativeSymbol((SymbolKey) val, NativeSymbol.SymbolKind.REGULAR); setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Symbol); return result; } @@ -5575,6 +5576,18 @@ static boolean isSymbol(Object obj) { || (obj instanceof SymbolKey); } + /** + * Return that the symbol was created by the constructor, or is a built-in Symbol, and was not + * put in the registry using "for". + */ + static boolean isUnregisteredSymbol(Object obj) { + if (obj instanceof NativeSymbol) { + NativeSymbol ns = (NativeSymbol) obj; + return ns.isSymbol() && ns.getKind() != NativeSymbol.SymbolKind.REGISTERED; + } + return (obj instanceof SymbolKey); + } + private static RuntimeException errorWithClassName(String msg, Object val) { return Context.reportRuntimeErrorById(msg, val.getClass().getName()); } diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java index 204e2422b0..3390f1296e 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java @@ -19,6 +19,14 @@ public static Object requireObjectCoercible( return val; } + public static Object requireObjectCoercible( + Context cx, Object val, Object tag, String functionName) { + if (val == null || Undefined.isUndefined(val)) { + throw ScriptRuntime.typeErrorById("msg.called.null.or.undefined", tag, functionName); + } + return val; + } + /** Registers the symbol [Symbol.species] on the given constructor function. */ public static void addSymbolSpecies( Context cx, Scriptable scope, IdScriptableObject constructor) { diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java b/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java index c97d4d1e8c..1d0dda2fbe 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java @@ -1790,26 +1790,44 @@ public void defineProperty( throw ScriptRuntime.typeError("at least one of {getter, setter} is required"); LambdaAccessorSlot newSlot = createLambdaAccessorSlot(name, 0, getter, setter, attributes); + replaceLambdaAccessorSlot(cx, name, newSlot); + } + + public void defineProperty( + Context cx, + Symbol key, + LambdaGetterFunction getter, + LambdaSetterFunction setter, + int attributes) { + if (getter == null && setter == null) + throw ScriptRuntime.typeError("at least one of {getter, setter} is required"); + + LambdaAccessorSlot newSlot = + createLambdaAccessorSlot(key.toString(), 0, getter, setter, attributes); + replaceLambdaAccessorSlot(cx, key, newSlot); + } + + private void replaceLambdaAccessorSlot(Context cx, Object key, LambdaAccessorSlot newSlot) { ScriptableObject newDesc = newSlot.buildPropertyDescriptor(cx); checkPropertyDefinition(newDesc); getMap().compute( this, - name, + key, 0, (id, index, existing) -> { if (existing != null) { // it's dangerous to use `this` as scope inside slotMap.compute. // It can cause deadlock when ThreadSafeSlotMapContainer is used - return replaceExistingLambdaSlot(cx, name, existing, newSlot); + return replaceExistingLambdaSlot(cx, key, existing, newSlot); } - checkPropertyChangeForSlot(name, null, newDesc); + checkPropertyChangeForSlot(key, null, newDesc); return newSlot; }); } private LambdaAccessorSlot replaceExistingLambdaSlot( - Context cx, String name, Slot existing, LambdaAccessorSlot newSlot) { + Context cx, Object key, Slot existing, LambdaAccessorSlot newSlot) { LambdaAccessorSlot replacedSlot; if (existing instanceof LambdaAccessorSlot) { replacedSlot = (LambdaAccessorSlot) existing; @@ -1820,7 +1838,7 @@ private LambdaAccessorSlot replaceExistingLambdaSlot( replacedSlot.replaceWith(newSlot); var replacedDesc = replacedSlot.buildPropertyDescriptor(cx); - checkPropertyChangeForSlot(name, existing, replacedDesc); + checkPropertyChangeForSlot(key, existing, replacedDesc); return replacedSlot; } diff --git a/tests/testsrc/jstests/es6/collections.js b/tests/testsrc/jstests/es6/collections.js index 38eed66a71..c36b53a496 100644 --- a/tests/testsrc/jstests/es6/collections.js +++ b/tests/testsrc/jstests/es6/collections.js @@ -78,7 +78,6 @@ function TestInvalidCalls(m) { assertThrows(function () { m.set(null, 0) }, TypeError); assertThrows(function () { m.set(0, 0) }, TypeError); assertThrows(function () { m.set('a-key', 0) }, TypeError); - assertThrows(function () { m.set(Symbol(), 0) }, TypeError); } TestInvalidCalls(new WeakMap); diff --git a/tests/testsrc/test262.properties b/tests/testsrc/test262.properties index bed9cb1490..e99514aa8b 100644 --- a/tests/testsrc/test262.properties +++ b/tests/testsrc/test262.properties @@ -882,17 +882,7 @@ built-ins/JSON 26/144 (18.06%) stringify/value-bigint-tojson-receiver.js stringify/value-object-proxy.js -built-ins/Map 12/171 (7.02%) - prototype/clear/not-a-constructor.js - prototype/delete/not-a-constructor.js - prototype/entries/not-a-constructor.js - prototype/forEach/not-a-constructor.js - prototype/get/not-a-constructor.js - prototype/has/not-a-constructor.js - prototype/keys/not-a-constructor.js - prototype/set/not-a-constructor.js - prototype/Symbol.iterator/not-a-constructor.js - prototype/values/not-a-constructor.js +built-ins/Map 2/171 (1.17%) proto-from-ctor-realm.js valid-keys.js @@ -1893,10 +1883,7 @@ built-ins/RegExp 1150/1854 (62.03%) built-ins/RegExpStringIteratorPrototype 0/17 (0.0%) -built-ins/Set 166/381 (43.57%) - prototype/add/not-a-constructor.js - prototype/clear/not-a-constructor.js - prototype/delete/not-a-constructor.js +built-ins/Set 158/381 (41.47%) prototype/difference/add-not-called.js prototype/difference/allows-set-like-class.js prototype/difference/allows-set-like-object.js @@ -1921,9 +1908,6 @@ built-ins/Set 166/381 (43.57%) prototype/difference/subclass.js prototype/difference/subclass-receiver-methods.js prototype/difference/subclass-symbol-species.js - prototype/entries/not-a-constructor.js - prototype/forEach/not-a-constructor.js - prototype/has/not-a-constructor.js prototype/intersection/add-not-called.js prototype/intersection/allows-set-like-class.js prototype/intersection/allows-set-like-object.js @@ -2007,7 +1991,6 @@ built-ins/Set 166/381 (43.57%) prototype/isSupersetOf/set-like-class-order.js prototype/isSupersetOf/size-is-a-number.js prototype/isSupersetOf/subclass-receiver-methods.js - prototype/Symbol.iterator/not-a-constructor.js prototype/symmetricDifference/add-not-called.js prototype/symmetricDifference/allows-set-like-class.js prototype/symmetricDifference/allows-set-like-object.js @@ -2057,7 +2040,6 @@ built-ins/Set 166/381 (43.57%) prototype/union/subclass-receiver-methods.js prototype/union/subclass-symbol-species.js prototype/union/union.js - prototype/values/not-a-constructor.js proto-from-ctor-realm.js valid-values.js @@ -3059,35 +3041,12 @@ built-ins/TypedArrayConstructors 559/735 (76.05%) Uint8Array/prototype/proto.js Uint8ClampedArray/prototype/proto.js -built-ins/WeakMap 14/102 (13.73%) - prototype/delete/delete-entry-with-symbol-key.js - prototype/delete/delete-entry-with-symbol-key-initial-iterable.js - prototype/delete/not-a-constructor.js - prototype/delete/returns-false-when-symbol-key-not-present.js - prototype/get/not-a-constructor.js - prototype/get/returns-undefined-with-symbol-key.js - prototype/get/returns-value-with-symbol-key.js - prototype/has/not-a-constructor.js - prototype/has/returns-false-when-symbol-key-not-present.js - prototype/has/returns-true-when-symbol-key-present.js - prototype/set/adds-symbol-element.js - prototype/set/not-a-constructor.js - iterable-with-symbol-keys.js +built-ins/WeakMap 1/102 (0.98%) proto-from-ctor-realm.js ~built-ins/WeakRef -built-ins/WeakSet 11/85 (12.94%) - prototype/add/adds-symbol-element.js - prototype/add/not-a-constructor.js - prototype/add/returns-this-symbol.js - prototype/add/returns-this-when-ignoring-duplicate-symbol.js - prototype/delete/delete-symbol-entry.js - prototype/delete/not-a-constructor.js - prototype/has/not-a-constructor.js - prototype/has/returns-false-when-symbol-value-not-present.js - prototype/has/returns-true-when-symbol-value-present.js - iterable-with-symbol-values.js +built-ins/WeakSet 1/85 (1.18%) proto-from-ctor-realm.js built-ins/decodeURI 3/55 (5.45%)