diff --git a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java index 0dec3cdbd3..82110f2ca1 100644 --- a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java +++ b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java @@ -306,6 +306,22 @@ public void defineConstructorMethod( defineProperty(name, f, attributes); } + /** + * Replace the default "Object" prototype with a prototype of a specific implementation. This is + * only necessary for a few built-in constructors, like Boolean, that must have their prototype + * be an object with a specific "internal data slot." + */ + public void setPrototypeScriptable(ScriptableObject proto) { + proto.setParentScope(getParentScope()); + setPrototypeProperty(proto); + Scriptable objectProto = getObjectPrototype(this); + if (proto != objectProto) { + // not the one we just made, it must remain grounded + proto.setPrototype(objectProto); + } + proto.defineProperty("constructor", this, DONTENUM); + } + /** * A convenience method to convert JavaScript's "this" object into a target class and throw a * TypeError if it does not match. This is useful for implementing lambda functions, as "this" diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeBigInt.java b/rhino/src/main/java/org/mozilla/javascript/NativeBigInt.java index c65a17ce52..252163c48d 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeBigInt.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeBigInt.java @@ -10,14 +10,65 @@ import java.util.Arrays; /** This class implements the BigInt native object. */ -final class NativeBigInt extends IdScriptableObject { +final class NativeBigInt extends ScriptableObject { private static final long serialVersionUID = 1335609231306775449L; - private static final Object BIG_INT_TAG = "BigInt"; + private static final String CLASS_NAME = "BigInt"; + + private final BigInteger bigIntValue; static void init(Scriptable scope, boolean sealed) { - NativeBigInt obj = new NativeBigInt(BigInteger.ZERO); - obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + LambdaConstructor constructor = + new LambdaConstructor( + scope, + CLASS_NAME, + 1, + NativeBigInt::js_constructorFunc, + NativeBigInt::js_constructor); + constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); + constructor.defineConstructorMethod( + scope, + "asIntN", + 2, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + js_asIntOrUintN(true, args), + DONTENUM, + DONTENUM | READONLY); + constructor.defineConstructorMethod( + scope, + "asUintN", + 2, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + js_asIntOrUintN(false, args), + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, "toString", 0, NativeBigInt::js_toString, DONTENUM, DONTENUM | READONLY); + // Alias toLocaleString to toString + constructor.definePrototypeMethod( + scope, + "toLocaleString", + 0, + NativeBigInt::js_toString, + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, "toSource", 0, NativeBigInt::js_toSource, DONTENUM, DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "valueOf", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + toSelf(thisObj).bigIntValue, + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeProperty( + SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); + + ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, DONTENUM); + if (sealed) { + constructor.sealObject(); + } } NativeBigInt(BigInteger bigInt) { @@ -26,198 +77,71 @@ static void init(Scriptable scope, boolean sealed) { @Override public String getClassName() { - return "BigInt"; + return CLASS_NAME; } - @Override - protected void fillConstructorProperties(IdFunctionObject ctor) { - addIdFunctionProperty(ctor, BIG_INT_TAG, ConstructorId_asIntN, "asIntN", 2); - addIdFunctionProperty(ctor, BIG_INT_TAG, ConstructorId_asUintN, "asUintN", 2); + private static NativeBigInt toSelf(Scriptable thisObj) { + return LambdaConstructor.convertThisObject(thisObj, NativeBigInt.class); + } - super.fillConstructorProperties(ctor); + private static Object js_constructorFunc( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + return (args.length >= 1) ? ScriptRuntime.toBigInt(args[0]) : BigInteger.ZERO; } - @Override - protected void initPrototypeId(int id) { - if (id == SymbolId_toStringTag) { - initPrototypeValue( - SymbolId_toStringTag, - SymbolKey.TO_STRING_TAG, - getClassName(), - DONTENUM | READONLY); - return; - } + private static Scriptable js_constructor(Context cx, Scriptable scope, Object[] args) { + throw ScriptRuntime.typeErrorById("msg.no.new", CLASS_NAME); + } - String s; - int arity; - switch (id) { - case Id_constructor: - arity = 1; - s = "constructor"; - break; - case Id_toString: - arity = 0; - s = "toString"; - break; - case Id_toLocaleString: - arity = 0; - s = "toLocaleString"; - break; - case Id_toSource: - arity = 0; - s = "toSource"; - break; - case Id_valueOf: - arity = 0; - s = "valueOf"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(BIG_INT_TAG, id, s, arity); + private static Object js_toString( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + int base = + (args.length == 0 || args[0] == Undefined.instance) + ? 10 + : ScriptRuntime.toInt32(args[0]); + BigInteger value = toSelf(thisObj).bigIntValue; + return ScriptRuntime.bigIntToString(value, base); } - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - if (!f.hasTag(BIG_INT_TAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); - } - int id = f.methodId(); - if (id == Id_constructor) { - if (thisObj == null) { - // new BigInt(val) throws TypeError. - throw ScriptRuntime.typeErrorById("msg.not.ctor", BIG_INT_TAG); - } - BigInteger val = (args.length >= 1) ? ScriptRuntime.toBigInt(args[0]) : BigInteger.ZERO; - // BigInt(val) converts val to a BigInteger value. - return val; + private static Object js_toSource( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + return "(new BigInt(" + ScriptRuntime.toString(toSelf(thisObj).bigIntValue) + "))"; + } - } else if (id < Id_constructor) { - return execConstructorCall(id, args); - } + private static Object js_asIntOrUintN(boolean isSigned, Object[] args) { + int bits = ScriptRuntime.toIndex(args.length < 1 ? Undefined.instance : args[0]); + BigInteger bigInt = ScriptRuntime.toBigInt(args.length < 2 ? Undefined.instance : args[1]); - // The rest of BigInt.prototype methods require thisObj to be BigInt - BigInteger value = ensureType(thisObj, NativeBigInt.class, f).bigIntValue; - - switch (id) { - case Id_toString: - case Id_toLocaleString: - { - // toLocaleString is just an alias for toString for now - int base = - (args.length == 0 || args[0] == Undefined.instance) - ? 10 - : ScriptRuntime.toInt32(args[0]); - return ScriptRuntime.bigIntToString(value, base); - } - - case Id_toSource: - return "(new BigInt(" + ScriptRuntime.toString(value) + "))"; - - case Id_valueOf: - return value; - - default: - throw new IllegalArgumentException(String.valueOf(id)); + if (bits == 0) { + return BigInteger.ZERO; } - } - private static Object execConstructorCall(int id, Object[] args) { - switch (id) { - case ConstructorId_asIntN: - case ConstructorId_asUintN: - { - int bits = - ScriptRuntime.toIndex(args.length < 1 ? Undefined.instance : args[0]); - BigInteger bigInt = - ScriptRuntime.toBigInt(args.length < 2 ? Undefined.instance : args[1]); - - if (bits == 0) { - return BigInteger.ZERO; - } - - byte[] bytes = bigInt.toByteArray(); - - int newBytesLen = (bits / Byte.SIZE) + 1; - if (newBytesLen > bytes.length) { - return bigInt; - } - - byte[] newBytes = - Arrays.copyOfRange(bytes, bytes.length - newBytesLen, bytes.length); - - int mod = bits % Byte.SIZE; - switch (id) { - case ConstructorId_asIntN: - if (mod == 0) { - newBytes[0] = newBytes[1] < 0 ? (byte) -1 : 0; - } else if ((newBytes[0] & (1 << (mod - 1))) != 0) { - newBytes[0] = (byte) (newBytes[0] | (-1 << mod)); - } else { - newBytes[0] = (byte) (newBytes[0] & ((1 << mod) - 1)); - } - break; - case ConstructorId_asUintN: - newBytes[0] = (byte) (newBytes[0] & ((1 << mod) - 1)); - break; - } - return new BigInteger(newBytes); - } - - default: - throw new IllegalArgumentException(String.valueOf(id)); + byte[] bytes = bigInt.toByteArray(); + + int newBytesLen = (bits / Byte.SIZE) + 1; + if (newBytesLen > bytes.length) { + return bigInt; } - } - @Override - public String toString() { - return ScriptRuntime.bigIntToString(bigIntValue, 10); - } + byte[] newBytes = Arrays.copyOfRange(bytes, bytes.length - newBytesLen, bytes.length); - @Override - protected int findPrototypeId(Symbol k) { - if (SymbolKey.TO_STRING_TAG.equals(k)) { - return SymbolId_toStringTag; + int mod = bits % Byte.SIZE; + if (isSigned) { + if (mod == 0) { + newBytes[0] = newBytes[1] < 0 ? (byte) -1 : 0; + } else if ((newBytes[0] & (1 << (mod - 1))) != 0) { + newBytes[0] = (byte) (newBytes[0] | (-1 << mod)); + } else { + newBytes[0] = (byte) (newBytes[0] & ((1 << mod) - 1)); + } + } else { + newBytes[0] = (byte) (newBytes[0] & ((1 << mod) - 1)); } - return 0; + return new BigInteger(newBytes); } @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "constructor": - id = Id_constructor; - break; - case "toString": - id = Id_toString; - break; - case "toLocaleString": - id = Id_toLocaleString; - break; - case "toSource": - id = Id_toSource; - break; - case "valueOf": - id = Id_valueOf; - break; - default: - id = 0; - break; - } - return id; + public String toString() { + return ScriptRuntime.bigIntToString(bigIntValue, 10); } - - private static final int ConstructorId_asIntN = -1, - ConstructorId_asUintN = -2, - Id_constructor = 1, - Id_toString = 2, - Id_toLocaleString = 3, - Id_toSource = 4, - Id_valueOf = 5, - SymbolId_toStringTag = 6, - MAX_PROTOTYPE_ID = SymbolId_toStringTag; - - private BigInteger bigIntValue; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeBoolean.java b/rhino/src/main/java/org/mozilla/javascript/NativeBoolean.java index b286836992..96ccd9df91 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeBoolean.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeBoolean.java @@ -11,14 +11,36 @@ * * @author Norris Boyd */ -final class NativeBoolean extends IdScriptableObject { +final class NativeBoolean extends ScriptableObject { private static final long serialVersionUID = -3716996899943880933L; - private static final Object BOOLEAN_TAG = "Boolean"; + private static final String CLASS_NAME = "Boolean"; + + private final boolean booleanValue; static void init(Scriptable scope, boolean sealed) { - NativeBoolean obj = new NativeBoolean(false); - obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + LambdaConstructor constructor = + new LambdaConstructor( + scope, + CLASS_NAME, + 1, + NativeBoolean::js_constructorFunc, + NativeBoolean::js_constructor); + constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); + // Boolean is an unusual object in that the prototype is itself a Boolean + constructor.setPrototypeScriptable(new NativeBoolean(false)); + + constructor.definePrototypeMethod( + scope, "toString", 0, NativeBoolean::js_toString, DONTENUM, DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, "toSource", 0, NativeBoolean::js_toSource, DONTENUM, DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, "valueOf", 0, NativeBoolean::js_valueOf, DONTENUM, DONTENUM | READONLY); + + ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, DONTENUM); + if (sealed) { + constructor.sealObject(); + } } NativeBoolean(boolean b) { @@ -27,7 +49,11 @@ static void init(Scriptable scope, boolean sealed) { @Override public String getClassName() { - return "Boolean"; + return CLASS_NAME; + } + + private static boolean toValue(Scriptable thisObj) { + return LambdaConstructor.ensureType(thisObj, NativeBoolean.class, "Boolean").booleanValue; } @Override @@ -38,109 +64,29 @@ public Object getDefaultValue(Class typeHint) { return super.getDefaultValue(typeHint); } - @Override - protected void initPrototypeId(int id) { - String s; - int arity; - switch (id) { - case Id_constructor: - arity = 1; - s = "constructor"; - break; - case Id_toString: - arity = 0; - s = "toString"; - break; - case Id_toSource: - arity = 0; - s = "toSource"; - break; - case Id_valueOf: - arity = 0; - s = "valueOf"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(BOOLEAN_TAG, id, s, arity); + private static Object js_constructorFunc( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + boolean b = ScriptRuntime.toBoolean(args.length > 0 ? args[0] : Undefined.instance); + return ScriptRuntime.wrapBoolean(b); } - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - if (!f.hasTag(BOOLEAN_TAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); - } - int id = f.methodId(); - - if (id == Id_constructor) { - boolean b; - if (args.length == 0) { - b = false; - } else { - // see special handling in ScriptRuntime.toBoolean(Object) - // avoidObjectDetection() is used to implement document.all - // see Note on page - // - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean - b = - ((args[0] instanceof ScriptableObject) - && ((ScriptableObject) args[0]).avoidObjectDetection()) - ? false - : ScriptRuntime.toBoolean(args[0]); - } - if (thisObj == null) { - // new Boolean(val) creates a new boolean object. - return new NativeBoolean(b); - } - // Boolean(val) converts val to a boolean. - return ScriptRuntime.wrapBoolean(b); - } - - // The rest of Boolean.prototype methods require thisObj to be Boolean - boolean value = ensureType(thisObj, NativeBoolean.class, f).booleanValue; - - switch (id) { - case Id_toString: - return value ? "true" : "false"; - - case Id_toSource: - return value ? "(new Boolean(true))" : "(new Boolean(false))"; - - case Id_valueOf: - return ScriptRuntime.wrapBoolean(value); - } - throw new IllegalArgumentException(String.valueOf(id)); + private static NativeBoolean js_constructor(Context cx, Scriptable scope, Object[] args) { + boolean b = ScriptRuntime.toBoolean(args.length > 0 ? args[0] : Undefined.instance); + return new NativeBoolean(b); } - @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "constructor": - id = Id_constructor; - break; - case "toString": - id = Id_toString; - break; - case "toSource": - id = Id_toSource; - break; - case "valueOf": - id = Id_valueOf; - break; - default: - id = 0; - break; - } - return id; + private static String js_toString( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + return toValue(thisObj) ? "true" : "false"; } - private static final int Id_constructor = 1, - Id_toString = 2, - Id_toSource = 3, - Id_valueOf = 4, - MAX_PROTOTYPE_ID = 4; + private static Object js_valueOf( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + return toValue(thisObj); + } - private boolean booleanValue; + private static Object js_toSource( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + return "(new Boolean(" + ScriptRuntime.toString(toValue(thisObj)) + "))"; + } } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeNumber.java b/rhino/src/main/java/org/mozilla/javascript/NativeNumber.java index 2a932dd6a1..366de93707 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeNumber.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeNumber.java @@ -13,7 +13,7 @@ * * @author Norris Boyd */ -final class NativeNumber extends IdScriptableObject { +final class NativeNumber extends ScriptableObject { private static final long serialVersionUID = 3504516769741512101L; /** @@ -21,15 +21,106 @@ final class NativeNumber extends IdScriptableObject { */ public static final double MAX_SAFE_INTEGER = 9007199254740991.0; // Math.pow(2, 53) - 1 - private static final Object NUMBER_TAG = "Number"; + private static final String CLASS_NAME = "Number"; private static final int MAX_PRECISION = 100; private static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; - private static final double EPSILON = 2.220446049250313e-16; // Math.pow(2, -52) + private static final double EPSILON = 2.220446049250313e-16; + + private final double doubleValue; static void init(Scriptable scope, boolean sealed) { - NativeNumber obj = new NativeNumber(0.0); - obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + LambdaConstructor constructor = + new LambdaConstructor( + scope, + CLASS_NAME, + 1, + NativeNumber::js_constructorFunc, + NativeNumber::js_constructor); + constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT); + constructor.setPrototypeScriptable(new NativeNumber(0.0)); + + final int propAttr = DONTENUM | PERMANENT | READONLY; + + constructor.defineProperty("NaN", ScriptRuntime.NaNobj, propAttr); + constructor.defineProperty( + "POSITIVE_INFINITY", ScriptRuntime.wrapNumber(Double.POSITIVE_INFINITY), propAttr); + constructor.defineProperty( + "NEGATIVE_INFINITY", ScriptRuntime.wrapNumber(Double.NEGATIVE_INFINITY), propAttr); + constructor.defineProperty( + "MAX_VALUE", ScriptRuntime.wrapNumber(Double.MAX_VALUE), propAttr); + constructor.defineProperty( + "MIN_VALUE", ScriptRuntime.wrapNumber(Double.MIN_VALUE), propAttr); + constructor.defineProperty( + "MAX_SAFE_INTEGER", ScriptRuntime.wrapNumber(MAX_SAFE_INTEGER), propAttr); + constructor.defineProperty( + "MIN_SAFE_INTEGER", ScriptRuntime.wrapNumber(MIN_SAFE_INTEGER), propAttr); + constructor.defineProperty("EPSILON", ScriptRuntime.wrapNumber(EPSILON), propAttr); + + constructor.defineConstructorMethod( + scope, "isFinite", 1, NativeNumber::js_isFinite, DONTENUM, DONTENUM | READONLY); + constructor.defineConstructorMethod( + scope, "isNaN", 1, NativeNumber::js_isNaN, DONTENUM, DONTENUM | READONLY); + constructor.defineConstructorMethod( + scope, "isInteger", 1, NativeNumber::js_isInteger, DONTENUM, DONTENUM | READONLY); + constructor.defineConstructorMethod( + scope, + "isSafeInteger", + 1, + NativeNumber::js_isSafeInteger, + DONTENUM, + DONTENUM | READONLY); + + Object parseFloat = ScriptRuntime.getTopLevelProp(constructor, "parseFloat"); + if (parseFloat instanceof Function) { + constructor.defineProperty("parseFloat", parseFloat, DONTENUM); + } + Object parseInt = ScriptRuntime.getTopLevelProp(constructor, "parseInt"); + if (parseInt instanceof Function) { + constructor.defineProperty("parseInt", parseInt, DONTENUM); + } + + constructor.definePrototypeMethod( + scope, "toString", 1, NativeNumber::js_toString, DONTENUM, DONTENUM | READONLY); + // Alias toLocaleString to toString + constructor.definePrototypeMethod( + scope, + "toLocaleString", + 0, + NativeNumber::js_toString, + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, "toSource", 0, NativeNumber::js_toSource, DONTENUM, DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "valueOf", + 0, + (Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) -> + toSelf(thisObj).doubleValue, + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, "toFixed", 1, NativeNumber::js_toFixed, DONTENUM, DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "toExponential", + 1, + NativeNumber::js_toExponential, + DONTENUM, + DONTENUM | READONLY); + constructor.definePrototypeMethod( + scope, + "toPrecision", + 1, + NativeNumber::js_toPrecision, + DONTENUM, + DONTENUM | READONLY); + + ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, DONTENUM); + if (sealed) { + constructor.sealObject(); + } } NativeNumber(double number) { @@ -38,217 +129,88 @@ static void init(Scriptable scope, boolean sealed) { @Override public String getClassName() { - return "Number"; + return CLASS_NAME; } - @Override - protected void fillConstructorProperties(IdFunctionObject ctor) { - final int attr = - ScriptableObject.DONTENUM | ScriptableObject.PERMANENT | ScriptableObject.READONLY; - - ctor.defineProperty("NaN", ScriptRuntime.NaNobj, attr); - ctor.defineProperty( - "POSITIVE_INFINITY", ScriptRuntime.wrapNumber(Double.POSITIVE_INFINITY), attr); - ctor.defineProperty( - "NEGATIVE_INFINITY", ScriptRuntime.wrapNumber(Double.NEGATIVE_INFINITY), attr); - ctor.defineProperty("MAX_VALUE", ScriptRuntime.wrapNumber(Double.MAX_VALUE), attr); - ctor.defineProperty("MIN_VALUE", ScriptRuntime.wrapNumber(Double.MIN_VALUE), attr); - ctor.defineProperty("MAX_SAFE_INTEGER", ScriptRuntime.wrapNumber(MAX_SAFE_INTEGER), attr); - ctor.defineProperty("MIN_SAFE_INTEGER", ScriptRuntime.wrapNumber(MIN_SAFE_INTEGER), attr); - ctor.defineProperty("EPSILON", ScriptRuntime.wrapNumber(EPSILON), attr); - - addIdFunctionProperty(ctor, NUMBER_TAG, ConstructorId_isFinite, "isFinite", 1); - addIdFunctionProperty(ctor, NUMBER_TAG, ConstructorId_isNaN, "isNaN", 1); - addIdFunctionProperty(ctor, NUMBER_TAG, ConstructorId_isInteger, "isInteger", 1); - addIdFunctionProperty(ctor, NUMBER_TAG, ConstructorId_isSafeInteger, "isSafeInteger", 1); - Object parseFloat = ScriptRuntime.getTopLevelProp(ctor, "parseFloat"); - if (parseFloat instanceof IdFunctionObject) { - ((IdFunctionObject) parseFloat).addAsProperty(ctor); - } - Object parseInt = ScriptRuntime.getTopLevelProp(ctor, "parseInt"); - if (parseInt instanceof IdFunctionObject) { - ((IdFunctionObject) parseInt).addAsProperty(ctor); - } + private static Scriptable js_constructor(Context cx, Scriptable scope, Object[] args) { + double val = (args.length > 0) ? ScriptRuntime.toNumeric(args[0]).doubleValue() : 0.0; + return new NativeNumber(val); + } - super.fillConstructorProperties(ctor); + private static Object js_constructorFunc( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + return (args.length > 0) ? ScriptRuntime.toNumeric(args[0]).doubleValue() : 0.0; } - @Override - protected void initPrototypeId(int id) { - String s; - int arity; - switch (id) { - case Id_constructor: - arity = 1; - s = "constructor"; - break; - case Id_toString: - arity = 1; - s = "toString"; - break; - case Id_toLocaleString: - arity = 1; - s = "toLocaleString"; - break; - case Id_toSource: - arity = 0; - s = "toSource"; - break; - case Id_valueOf: - arity = 0; - s = "valueOf"; - break; - case Id_toFixed: - arity = 1; - s = "toFixed"; - break; - case Id_toExponential: - arity = 1; - s = "toExponential"; - break; - case Id_toPrecision: - arity = 1; - s = "toPrecision"; - break; - default: - throw new IllegalArgumentException(String.valueOf(id)); - } - initPrototypeMethod(NUMBER_TAG, id, s, arity); + private static Object js_toFixed( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + int precisionMin = cx.version < Context.VERSION_ES6 ? -20 : 0; + double value = toSelf(thisObj).doubleValue; + return num_to(value, args, DToA.DTOSTR_FIXED, DToA.DTOSTR_FIXED, precisionMin, 0); } - @Override - public Object execIdCall( - IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - if (!f.hasTag(NUMBER_TAG)) { - return super.execIdCall(f, cx, scope, thisObj, args); + private static Object js_toExponential( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + double value = toSelf(thisObj).doubleValue; + // Handle special values before range check + if (Double.isNaN(value)) { + return "NaN"; } - int id = f.methodId(); - if (id == Id_constructor) { - double val = (args.length >= 1) ? ScriptRuntime.toNumeric(args[0]).doubleValue() : 0.0; - if (thisObj == null) { - // new Number(val) creates a new Number object. - return new NativeNumber(val); + if (Double.isInfinite(value)) { + if (value >= 0) { + return "Infinity"; } - // Number(val) converts val to a number value. - return ScriptRuntime.wrapNumber(val); - - } else if (id < Id_constructor) { - return execConstructorCall(id, args); + return "-Infinity"; } + // General case + return num_to(value, args, DToA.DTOSTR_STANDARD_EXPONENTIAL, DToA.DTOSTR_EXPONENTIAL, 0, 1); + } - // The rest of Number.prototype methods require thisObj to be Number - double value = ensureType(thisObj, NativeNumber.class, f).doubleValue; - - switch (id) { - case Id_toString: - case Id_toLocaleString: - { - // toLocaleString is just an alias for toString for now - int base = - (args.length == 0 || Undefined.isUndefined(args[0])) - ? 10 - : ScriptRuntime.toInt32(args[0]); - return ScriptRuntime.numberToString(value, base); - } - - case Id_toSource: - return "(new Number(" + ScriptRuntime.toString(value) + "))"; - - case Id_valueOf: - return ScriptRuntime.wrapNumber(value); - - case Id_toFixed: - int precisionMin = cx.version < Context.VERSION_ES6 ? -20 : 0; - return num_to(value, args, DToA.DTOSTR_FIXED, DToA.DTOSTR_FIXED, precisionMin, 0); - - case Id_toExponential: - { - // Handle special values before range check - if (Double.isNaN(value)) { - return "NaN"; - } - if (Double.isInfinite(value)) { - if (value >= 0) { - return "Infinity"; - } - return "-Infinity"; - } - // General case - return num_to( - value, - args, - DToA.DTOSTR_STANDARD_EXPONENTIAL, - DToA.DTOSTR_EXPONENTIAL, - 0, - 1); - } - - case Id_toPrecision: - { - // Undefined precision, fall back to ToString() - if (args.length == 0 || Undefined.isUndefined(args[0])) { - return ScriptRuntime.numberToString(value, 10); - } - // Handle special values before range check - if (Double.isNaN(value)) { - return "NaN"; - } - if (Double.isInfinite(value)) { - if (value >= 0) { - return "Infinity"; - } - return "-Infinity"; - } - return num_to(value, args, DToA.DTOSTR_STANDARD, DToA.DTOSTR_PRECISION, 1, 0); - } - - default: - throw new IllegalArgumentException(String.valueOf(id)); + private static Object js_toPrecision( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + double value = toSelf(thisObj).doubleValue; + // Undefined precision, fall back to ToString() + if (args.length == 0 || Undefined.isUndefined(args[0])) { + return ScriptRuntime.numberToString(value, 10); + } + // Handle special values before range check + if (Double.isNaN(value)) { + return "NaN"; } + if (Double.isInfinite(value)) { + if (value >= 0) { + return "Infinity"; + } + return "-Infinity"; + } + return num_to(value, args, DToA.DTOSTR_STANDARD, DToA.DTOSTR_PRECISION, 1, 0); + } + + private static NativeNumber toSelf(Scriptable thisObj) { + return LambdaConstructor.convertThisObject(thisObj, NativeNumber.class); + } + + private static Object js_toString( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + int base = + (args.length == 0 || Undefined.isUndefined(args[0])) + ? 10 + : ScriptRuntime.toInt32(args[0]); + return ScriptRuntime.numberToString(toSelf(thisObj).doubleValue, base); } - private static Object execConstructorCall(int id, Object[] args) { - switch (id) { - case ConstructorId_isFinite: - if ((args.length == 0) || Undefined.isUndefined(args[0])) { - return Boolean.FALSE; - } - if (args[0] instanceof Number) { - // Match ES6 polyfill, which only works for "number" types - return isFinite(args[0]); - } - return Boolean.FALSE; - - case ConstructorId_isNaN: - if ((args.length == 0) || Undefined.isUndefined(args[0])) { - return Boolean.FALSE; - } - if (args[0] instanceof Number) { - return isNaN((Number) args[0]); - } - return Boolean.FALSE; - - case ConstructorId_isInteger: - if ((args.length == 0) || Undefined.isUndefined(args[0])) { - return Boolean.FALSE; - } - if (args[0] instanceof Number) { - return Boolean.valueOf(isInteger((Number) args[0])); - } - return Boolean.FALSE; - - case ConstructorId_isSafeInteger: - if ((args.length == 0) || (Undefined.instance == args[0])) { - return Boolean.FALSE; - } - if (args[0] instanceof Number) { - return Boolean.valueOf(isSafeInteger((Number) args[0])); - } - return Boolean.FALSE; - - default: - throw new IllegalArgumentException(String.valueOf(id)); + private static Object js_toSource( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + return "(new Number(" + ScriptRuntime.toString(toSelf(thisObj).doubleValue) + "))"; + } + + private static Number argToNumber(Object[] args) { + if (args.length > 0) { + if (args[0] instanceof Number) { + return (Number) args[0]; + } } + return null; } @Override @@ -284,101 +246,67 @@ private static String num_to( return sb.toString(); } + private static Object js_isFinite( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Number n = argToNumber(args); + return n == null ? Boolean.FALSE : isFinite(n); + } + static Object isFinite(Object val) { - double d = ScriptRuntime.toNumber(val); - Double nd = Double.valueOf(d); - return ScriptRuntime.wrapBoolean(!nd.isInfinite() && !nd.isNaN()); + double nd = ScriptRuntime.toNumber(val); + return ScriptRuntime.wrapBoolean(!Double.isInfinite(nd) && !Double.isNaN(nd)); } - private static Boolean isNaN(Number val) { + private static Object js_isNaN( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Number val = argToNumber(args); + if (val == null) { + return false; + } if (val instanceof Double) { - return Boolean.valueOf(((Double) val).isNaN()); + return ((Double) val).isNaN(); } - double d = val.doubleValue(); - return Boolean.valueOf(Double.isNaN(d)); + return Double.isNaN(d); } - private static boolean isInteger(Number val) { + private static Object js_isInteger( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Number val = argToNumber(args); + if (val == null) { + return false; + } if (val instanceof Double) { return isDoubleInteger((Double) val); } return isDoubleInteger(val.doubleValue()); } + private static Object js_isSafeInteger( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Number val = argToNumber(args); + if (val == null) { + return false; + } + if (val instanceof Double) { + return isDoubleSafeInteger((Double) val); + } + return isDoubleSafeInteger(val.doubleValue()); + } + private static boolean isDoubleInteger(Double d) { - return !d.isInfinite() && !d.isNaN() && (Math.floor(d.doubleValue()) == d.doubleValue()); + return !d.isInfinite() && !d.isNaN() && (Math.floor(d) == d); } private static boolean isDoubleInteger(double d) { return !Double.isInfinite(d) && !Double.isNaN(d) && (Math.floor(d) == d); } - private static boolean isSafeInteger(Number val) { - if (val instanceof Double) { - return isDoubleSafeInteger((Double) val); - } - return isDoubleSafeInteger(val.doubleValue()); - } - private static boolean isDoubleSafeInteger(Double d) { - return isDoubleInteger(d) - && (d.doubleValue() <= MAX_SAFE_INTEGER) - && (d.doubleValue() >= MIN_SAFE_INTEGER); + return isDoubleInteger(d) && (d <= MAX_SAFE_INTEGER) && (d >= MIN_SAFE_INTEGER); } private static boolean isDoubleSafeInteger(double d) { return isDoubleInteger(d) && (d <= MAX_SAFE_INTEGER) && (d >= MIN_SAFE_INTEGER); } - - @Override - protected int findPrototypeId(String s) { - int id; - switch (s) { - case "constructor": - id = Id_constructor; - break; - case "toString": - id = Id_toString; - break; - case "toLocaleString": - id = Id_toLocaleString; - break; - case "toSource": - id = Id_toSource; - break; - case "valueOf": - id = Id_valueOf; - break; - case "toFixed": - id = Id_toFixed; - break; - case "toExponential": - id = Id_toExponential; - break; - case "toPrecision": - id = Id_toPrecision; - break; - default: - id = 0; - break; - } - return id; - } - - private static final int ConstructorId_isFinite = -1, - ConstructorId_isNaN = -2, - ConstructorId_isInteger = -3, - ConstructorId_isSafeInteger = -4, - Id_constructor = 1, - Id_toString = 2, - Id_toLocaleString = 3, - Id_toSource = 4, - Id_valueOf = 5, - Id_toFixed = 6, - Id_toExponential = 7, - Id_toPrecision = 8, - MAX_PROTOTYPE_ID = 8; - - private double doubleValue; } diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java index c03ebaf795..fa276ae650 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java @@ -1076,7 +1076,7 @@ public static String toString(double val) { public static String numberToString(double d, int base) { if ((base < 2) || (base > 36)) { - throw Context.reportRuntimeErrorById("msg.bad.radix", Integer.toString(base)); + throw ScriptRuntime.rangeErrorById("msg.bad.radix", Integer.toString(base)); } if (Double.isNaN(d)) return "NaN"; diff --git a/tests/testsrc/test262.properties b/tests/testsrc/test262.properties index e99514aa8b..cb038dc676 100644 --- a/tests/testsrc/test262.properties +++ b/tests/testsrc/test262.properties @@ -341,24 +341,16 @@ built-ins/ArrayIteratorPrototype 1/27 (3.7%) ~built-ins/Atomics -built-ins/BigInt 13/75 (17.33%) +built-ins/BigInt 7/75 (9.33%) asIntN/bigint-tobigint-errors.js asIntN/bits-toindex-errors.js asIntN/bits-toindex-wrapped-values.js - asIntN/not-a-constructor.js asUintN/bigint-tobigint-errors.js asUintN/bits-toindex-errors.js asUintN/bits-toindex-wrapped-values.js - asUintN/not-a-constructor.js - prototype/toLocaleString/not-a-constructor.js - prototype/toString/not-a-constructor.js - prototype/toString/prototype-call.js Check IsInteger in ES2020, not IsSafeInteger, https://github.com/tc39/test262/commit/bf1b79d65a760a5f03df1198557da2d010f8f397#diff-3ecd6a0c50da5c8f8eff723afb6182a889b7315d99545b055559e22d302cc453 - prototype/valueOf/not-a-constructor.js wrapper-object-ordinary-toprimitive.js -built-ins/Boolean 3/51 (5.88%) - prototype/toString/not-a-constructor.js - prototype/valueOf/not-a-constructor.js +built-ins/Boolean 1/51 (1.96%) proto-from-ctor-realm.js built-ins/DataView 222/550 (40.36%) @@ -913,26 +905,13 @@ built-ins/NativeErrors 20/123 (16.26%) URIError/prototype/not-error-object.js URIError/proto-from-ctor-realm.js -built-ins/Number 23/335 (6.87%) - isFinite/not-a-constructor.js - isInteger/not-a-constructor.js - isNaN/not-a-constructor.js - isSafeInteger/not-a-constructor.js +built-ins/Number 10/335 (2.99%) parseFloat/not-a-constructor.js parseInt/not-a-constructor.js - prototype/toExponential/not-a-constructor.js prototype/toExponential/return-abrupt-tointeger-fractiondigits.js prototype/toExponential/return-abrupt-tointeger-fractiondigits-symbol.js prototype/toExponential/undefined-fractiondigits.js - prototype/toFixed/not-a-constructor.js - prototype/toLocaleString/length.js - prototype/toLocaleString/not-a-constructor.js prototype/toPrecision/nan.js - prototype/toPrecision/not-a-constructor.js - prototype/toString/not-a-constructor.js - prototype/toString/numeric-literal-tostring-radix-1.js - prototype/toString/numeric-literal-tostring-radix-37.js - prototype/valueOf/not-a-constructor.js proto-from-ctor-realm.js S9.3.1_A2_U180E.js {unsupported: [u180e]} S9.3.1_A3_T1_U180E.js {unsupported: [u180e]}