diff --git a/rhino/src/main/java/org/mozilla/javascript/ArrayLikeAbstractOperations.java b/rhino/src/main/java/org/mozilla/javascript/ArrayLikeAbstractOperations.java index e9b0a61d66..5f6e416a5a 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ArrayLikeAbstractOperations.java +++ b/rhino/src/main/java/org/mozilla/javascript/ArrayLikeAbstractOperations.java @@ -120,7 +120,8 @@ public static Object iterativeMethod( break; case FIND_INDEX: case FIND_LAST_INDEX: - if (ScriptRuntime.toBoolean(result)) return ScriptRuntime.wrapNumber(i); + if (ScriptRuntime.toBoolean(result)) + return ScriptRuntime.wrapNumber((double) i); break; } } diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java index 27e2c188d4..3dc542f6fe 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java @@ -458,7 +458,7 @@ public static double toNumber(Object[] args, int index) { // Preserve backward-compatibility with historical value of this. public static final double negativeZero = Double.longBitsToDouble(0x8000000000000000L); - public static final Double zeroObj = Double.valueOf(0.0); + public static final Integer zeroObj = Integer.valueOf(0); public static final Double negativeZeroObj = Double.valueOf(-0.0); static double stringPrefixToNumber(String s, int start, int radix) { @@ -2991,6 +2991,9 @@ public static Object add(Object val1, Object val2, Context cx) { || (val1 instanceof BigInteger && val2 instanceof Number)) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); } + if (val1 instanceof Integer && val2 instanceof Integer) { + return add((Integer) val1, (Integer) val2); + } if (val1 instanceof Number && val2 instanceof Number) { return wrapNumber(((Number) val1).doubleValue() + ((Number) val2).doubleValue()); } @@ -3064,6 +3067,8 @@ public static Number subtract(Number val1, Number val2) { return ((BigInteger) val1).subtract((BigInteger) val2); } else if (val1 instanceof BigInteger || val2 instanceof BigInteger) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); + } else if (val1 instanceof Integer && val2 instanceof Integer) { + return subtract((Integer) val1, (Integer) val2); } else { return val1.doubleValue() - val2.doubleValue(); } @@ -3074,6 +3079,8 @@ public static Number multiply(Number val1, Number val2) { return ((BigInteger) val1).multiply((BigInteger) val2); } else if (val1 instanceof BigInteger || val2 instanceof BigInteger) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); + } else if (val1 instanceof Integer && val2 instanceof Integer) { + return multiply((Integer) val1, (Integer) val2); } else { return val1.doubleValue() * val2.doubleValue(); } @@ -3088,6 +3095,8 @@ public static Number divide(Number val1, Number val2) { } else if (val1 instanceof BigInteger || val2 instanceof BigInteger) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); } else { + // Do not try to optimize for the integer case because JS doesn't + // have an integer type. return val1.doubleValue() / val2.doubleValue(); } } @@ -3101,10 +3110,41 @@ public static Number remainder(Number val1, Number val2) { } else if (val1 instanceof BigInteger || val2 instanceof BigInteger) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); } else { + // Do not try an integer-specific optimization because we need to get + // both +0 and -0 right. return val1.doubleValue() % val2.doubleValue(); } } + // Integer-optimized methods. + + public static Object add(Integer i1, Integer i2) { + // Try to add integers for efficiency, but account for overflow + long r = (long) i1.intValue() + (long) i2.intValue(); + if ((r >= Integer.MIN_VALUE) && (r <= Integer.MAX_VALUE)) { + return Integer.valueOf((int) r); + } + return Double.valueOf((double) r); + } + + public static Number subtract(Integer i1, Integer i2) { + // Account for overflow + long r = (long) i1.intValue() - (long) i2.intValue(); + if ((r >= Integer.MIN_VALUE) && (r <= Integer.MAX_VALUE)) { + return Integer.valueOf((int) r); + } + return Double.valueOf((double) r); + } + + public static Number multiply(Integer i1, Integer i2) { + // Aunt for overflow + long r = (long) i1.intValue() * (long) i2.intValue(); + if ((r >= Integer.MIN_VALUE) && (r <= Integer.MAX_VALUE)) { + return Integer.valueOf((int) r); + } + return Double.valueOf((double) r); + } + public static Number exponentiate(Number val1, Number val2) { if (val1 instanceof BigInteger && val2 instanceof BigInteger) { if (((BigInteger) val2).signum() == -1) { @@ -3130,6 +3170,8 @@ public static Number bitwiseAND(Number val1, Number val2) { return ((BigInteger) val1).and((BigInteger) val2); } else if (val1 instanceof BigInteger || val2 instanceof BigInteger) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); + } else if (val1 instanceof Integer && val2 instanceof Integer) { + return Integer.valueOf(((Integer) val1).intValue() & ((Integer) val2).intValue()); } else { int result = toInt32(val1.doubleValue()) & toInt32(val2.doubleValue()); return Double.valueOf(result); @@ -3141,6 +3183,8 @@ public static Number bitwiseOR(Number val1, Number val2) { return ((BigInteger) val1).or((BigInteger) val2); } else if (val1 instanceof BigInteger || val2 instanceof BigInteger) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); + } else if (val1 instanceof Integer && val2 instanceof Integer) { + return Integer.valueOf(((Integer) val1).intValue() | ((Integer) val2).intValue()); } else { int result = toInt32(val1.doubleValue()) | toInt32(val2.doubleValue()); return Double.valueOf(result); @@ -3152,6 +3196,8 @@ public static Number bitwiseXOR(Number val1, Number val2) { return ((BigInteger) val1).xor((BigInteger) val2); } else if (val1 instanceof BigInteger || val2 instanceof BigInteger) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); + } else if (val1 instanceof Integer && val2 instanceof Integer) { + return Integer.valueOf(((Integer) val1).intValue() ^ ((Integer) val2).intValue()); } else { int result = toInt32(val1.doubleValue()) ^ toInt32(val2.doubleValue()); return Double.valueOf(result); @@ -3169,6 +3215,8 @@ public static Number leftShift(Number val1, Number val2) { } } else if (val1 instanceof BigInteger || val2 instanceof BigInteger) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); + } else if (val1 instanceof Integer && val2 instanceof Integer) { + return Integer.valueOf(((Integer) val1).intValue() << ((Integer) val2).intValue()); } else { int result = toInt32(val1.doubleValue()) << toInt32(val2.doubleValue()); return Double.valueOf(result); @@ -3186,6 +3234,8 @@ public static Number signedRightShift(Number val1, Number val2) { } } else if (val1 instanceof BigInteger || val2 instanceof BigInteger) { throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt"); + } else if (val1 instanceof Integer && val2 instanceof Integer) { + return Integer.valueOf(((Integer) val1).intValue() >> ((Integer) val2).intValue()); } else { int result = toInt32(val1.doubleValue()) >> toInt32(val2.doubleValue()); return Double.valueOf(result); @@ -3195,6 +3245,8 @@ public static Number signedRightShift(Number val1, Number val2) { public static Number bitwiseNOT(Number val) { if (val instanceof BigInteger) { return ((BigInteger) val).not(); + } else if (val instanceof Integer) { + return Integer.valueOf(~((Integer) val).intValue()); } else { int result = ~toInt32(val.doubleValue()); return Double.valueOf(result); @@ -3292,6 +3344,12 @@ private static Object doScriptableIncrDecr( } else { result = ((BigInteger) number).subtract(BigInteger.ONE); } + } else if (number instanceof Integer) { + if ((incrDecrMask & Node.DECR_FLAG) == 0) { + result = ((Integer) number).intValue() + 1; + } else { + result = ((Integer) number).intValue() - 1; + } } else { if ((incrDecrMask & Node.DECR_FLAG) == 0) { result = number.doubleValue() + 1.0; @@ -3332,6 +3390,12 @@ public static Object elemIncrDecr( } else { result = ((BigInteger) number).subtract(BigInteger.ONE); } + } else if (number instanceof Integer) { + if ((incrDecrMask & Node.DECR_FLAG) == 0) { + result = ((Integer) number).intValue() + 1; + } else { + result = ((Integer) number).intValue() - 1; + } } else { if ((incrDecrMask & Node.DECR_FLAG) == 0) { result = number.doubleValue() + 1.0; @@ -3371,6 +3435,12 @@ public static Object refIncrDecr(Ref ref, Context cx, Scriptable scope, int incr } else { result = ((BigInteger) number).subtract(BigInteger.ONE); } + } else if (number instanceof Integer) { + if ((incrDecrMask & Node.DECR_FLAG) == 0) { + result = ((Integer) number).intValue() + 1; + } else { + result = ((Integer) number).intValue() - 1; + } } else { if ((incrDecrMask & Node.DECR_FLAG) == 0) { result = number.doubleValue() + 1.0; @@ -3390,6 +3460,17 @@ public static Number negate(Number val) { if (val instanceof BigInteger) { return ((BigInteger) val).negate(); } + if (val instanceof Integer) { + int iv = (Integer) val; + if (iv == 0) { + return negativeZeroObj; + } + if (iv > Integer.MIN_VALUE && iv < Integer.MAX_VALUE) { + // Account for twos-complement representation by not trying + // to negate values at the extremes + return Integer.valueOf(-((Integer) val).intValue()); + } + } return -val.doubleValue(); } diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/Codegen.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/Codegen.java index bfe7e112c2..4062d7ccf3 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/Codegen.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/Codegen.java @@ -1143,7 +1143,7 @@ void pushNumberAsObject(ClassFileWriter cfw, double num) { ByteCode.GETSTATIC, "org/mozilla/javascript/ScriptRuntime", "zeroObj", - "Ljava/lang/Double;"); + "Ljava/lang/Integer;"); } else { cfw.addPush(num); addDoubleWrap(cfw); @@ -1154,7 +1154,7 @@ void pushNumberAsObject(ClassFileWriter cfw, double num) { ByteCode.GETSTATIC, "org/mozilla/javascript/optimizer/OptRuntime", "oneObj", - "Ljava/lang/Double;"); + "Ljava/lang/Integer;"); return; } else if (num == -1.0) { @@ -1162,7 +1162,7 @@ void pushNumberAsObject(ClassFileWriter cfw, double num) { ByteCode.GETSTATIC, "org/mozilla/javascript/optimizer/OptRuntime", "minusOneObj", - "Ljava/lang/Double;"); + "Ljava/lang/Integer;"); } else if (Double.isNaN(num)) { cfw.add( diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/OptRuntime.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/OptRuntime.java index 4452dac468..6e117874fd 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/OptRuntime.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/OptRuntime.java @@ -21,8 +21,8 @@ import org.mozilla.javascript.Undefined; public final class OptRuntime extends ScriptRuntime { - public static final Double oneObj = Double.valueOf(1.0); - public static final Double minusOneObj = Double.valueOf(-1.0); + public static final Integer oneObj = Integer.valueOf(1); + public static final Integer minusOneObj = Integer.valueOf(-1); /** Implement ....() call shrinking optimizer code. */ public static Object call0(Callable fun, Scriptable thisObj, Context cx, Scriptable scope) { @@ -145,16 +145,7 @@ public static Object newObjectSpecial( } public static Double wrapDouble(double num) { - if (num == 0.0) { - if (1 / num > 0) { - // +0.0 - return zeroObj; - } - } else if (num == 1.0) { - return oneObj; - } else if (num == -1.0) { - return minusOneObj; - } else if (Double.isNaN(num)) { + if (Double.isNaN(num)) { return NaNobj; } return Double.valueOf(num); diff --git a/rhino/src/test/java/org/mozilla/javascript/tests/Bug687669Test.java b/rhino/src/test/java/org/mozilla/javascript/tests/Bug687669Test.java index 6a7d223907..927787da25 100644 --- a/rhino/src/test/java/org/mozilla/javascript/tests/Bug687669Test.java +++ b/rhino/src/test/java/org/mozilla/javascript/tests/Bug687669Test.java @@ -56,9 +56,9 @@ private String toSource(CharSequence cs) { public void eval() { // test EmptyStatement node doesn't infer with return values (in // contrast to wrapping EmptyExpression into an ExpressionStatement) - assertEquals(1d, eval("1;;;;")); + assertEquals(1, eval("1;;;;")); assertEquals(Undefined.instance, eval("(function(){1;;;;})()")); - assertEquals(1d, eval("(function(){return 1;;;;})()")); + assertEquals(1, eval("(function(){return 1;;;;})()")); } @Test diff --git a/rhino/src/test/java/org/mozilla/javascript/tests/DefineFunctionPropertiesTest.java b/rhino/src/test/java/org/mozilla/javascript/tests/DefineFunctionPropertiesTest.java index a7d13ae706..f9bda83c15 100644 --- a/rhino/src/test/java/org/mozilla/javascript/tests/DefineFunctionPropertiesTest.java +++ b/rhino/src/test/java/org/mozilla/javascript/tests/DefineFunctionPropertiesTest.java @@ -48,7 +48,7 @@ public static int f(int a) { public void simpleFunction() { try (Context cx = Context.enter()) { Object result = cx.evaluateString(global, "f(7) + 1", "test source", 1, null); - assertEquals(15.0, result); + assertEquals(15, result); } } diff --git a/tests/src/test/java/org/mozilla/javascript/tests/NativeConsoleTest.java b/tests/src/test/java/org/mozilla/javascript/tests/NativeConsoleTest.java index bed2726da5..016b6d74dd 100644 --- a/tests/src/test/java/org/mozilla/javascript/tests/NativeConsoleTest.java +++ b/tests/src/test/java/org/mozilla/javascript/tests/NativeConsoleTest.java @@ -440,8 +440,7 @@ public void testAssert() { assertPrintCalls( "console.assert(false, 'Fail', 1)", Collections.singletonList( - new PrinterCall( - Level.ERROR, new Object[] {"Assertion failed: Fail", 1.0}))); + new PrinterCall(Level.ERROR, new Object[] {"Assertion failed: Fail", 1}))); assertPrintMsg("console.assert(false, 'Fail', 1)", "Assertion failed: Fail 1"); assertPrintCalls(