Revision: 3891
Author: [email protected]
Date: Fri Dec 4 11:38:45 2009
Log: Fix toInt32 and toUint32 around NaN and Infinity
http://codereview.appspot.com/157109
Operation.fold depended on a faulty implementation of toInt32 and toUint32.
[email protected], [email protected]
http://code.google.com/p/google-caja/source/detail?r=3891
Modified:
/trunk/src/com/google/caja/parser/js/Operation.java
/trunk/tests/com/google/caja/parser/js/ExpressionTest.java
=======================================
--- /trunk/src/com/google/caja/parser/js/Operation.java Thu Nov 19 15:57:15
2009
+++ /trunk/src/com/google/caja/parser/js/Operation.java Fri Dec 4 11:38:45
2009
@@ -125,6 +125,12 @@
return this;
case NOT: case TYPEOF: return
children().get(0).simplifyForSideEffect();
case VOID: return children().get(0).simplifyForSideEffect();
+ case POST_INCREMENT: // i++ => ++i
+ return Operation.create(
+ getFilePosition(), Operator.PRE_INCREMENT, children().get(0));
+ case POST_DECREMENT:
+ return Operation.create(
+ getFilePosition(), Operator.PRE_DECREMENT, children().get(0));
default: break;
}
return this;
@@ -587,7 +593,7 @@
if (lhs instanceof Number && rhs instanceof Number) {
double a = ((Number) lhs).doubleValue();
double b = ((Number) rhs).doubleValue();
- if (isIntOp(op) && !Double.isNaN(a) && !Double.isNaN(b)) {
+ if (isIntOp(op)) {
long result;
switch (op) {
case BITWISE_AND: result = toInt32(a) & toInt32(b); break;
@@ -677,11 +683,44 @@
}
}
- private static long toInt32(double n) {
- return (int) n;
+
+ private static final long TWO_TO_THE_32 = 0x100000000L;
+
+ // For ToInt32 and ToUInt32, see sections 9.5 and 9.6 of ES5
+ //
http://wiki.ecmascript.org/doku.php?id=es3.1:es3.1_proposal_working_draft
+ //
+ // For Java %, see
+ //
http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.17.3
+ //
+ // For Java (int) and (long), see
+ //
http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#25363
+
+ static long toInt32(double number) {
+ // 1. Let number be the result of calling ToNumber on the input
argument.
+ // 2. If number is NaN, +0, -0, +Inf, or -Inf, return +0.
+ // 3. Let posInt be sign(number) * floor(abs(number)).
+ number = (number < 0.0 ? Math.ceil(number) : Math.floor(number));
+ // 4. Let int32bit be posInt modulo 2**32; that is, a finite integer
value k
+ // of Number type with positive sign and less than 2**32 in magnitude
such
+ // that the mathematical difference of posInt and k is mathematically
an
+ // integer multiple of 2**32.
+ double int32bit = number % TWO_TO_THE_32; // Handles Inf and NaN from
(2)
+ // 5. If int32bit is greater than or equal to 2**31, return
+ // int32bit - 2**32, otherwise return int32bit.
+ return (int) (long) int32bit;
}
- private static long toUint32(double n) {
- return ((long) n) & 0xffffffffL;
+ static long toUint32(double number) {
+ // 1. Let number be the result of calling ToNumber on the input
argument.
+ // 2. If number is NaN, +0, -0, +Inf, or -Inf, return +0.
+ // 3. Let posInt be sign(number) * floor(abs(number)).
+ number = (number < 0.0 ? Math.ceil(number) : Math.floor(number));
+ // 4. Let int32bit be posInt modulo 2**32; that is, a finite integer
value k
+ // of Number type with positive sign and less than 2**32 in magnitude
such
+ // that the mathematical difference of posInt and k is mathematically
an
+ // integer multiple of 2**32.
+ double int32bit = number % TWO_TO_THE_32; // Handles Inf and NaN from
(2)
+ // 5. Return int32bit.
+ return ((long) int32bit) & 0xffffffffL;
}
}
=======================================
--- /trunk/tests/com/google/caja/parser/js/ExpressionTest.java Thu Nov 19
15:57:15 2009
+++ /trunk/tests/com/google/caja/parser/js/ExpressionTest.java Fri Dec 4
11:38:45 2009
@@ -104,6 +104,9 @@
assertSimplified("foo()", "1 && foo()");
assertSimplified(null, "1 && 2");
assertSimplified("++x", "++x");
+ assertSimplified("++x", "x++");
+ assertSimplified("--x", "--x");
+ assertSimplified("--x", "x--");
assertSimplified("x -= 2", "x -= 2");
assertSimplified("x = 2", "x = 2");
assertSimplified("x + y", "x + y"); // coercion might be
side-effecting
@@ -193,27 +196,53 @@
assertFolded("(-1/0)", "-1 / 0");
assertFolded("(0/0)", "0 / 0");
assertFolded("1.0", "1 % 3");
+ assertFolded("1.0", "1 % -3");
+ assertFolded("-1.0", "-1 % 3");
assertFolded("1.0", "1 % -3");
assertFolded("-1.0", "-1 % 3");
assertFolded("-1.0", "-1 % -3");
+ assertFolded("" + 0x7fffffffL, "0x7fffffff | 0");
+ assertFolded("-" + 0x7fffffffL, "-0x7fffffff | 0");
+ assertFolded("-" + 0x80000000L, "0x80000000 | 0");
+ assertFolded("-1", "0xffffffff | 0");
+ assertFolded("-1", "0x1ffffffff | 0");
+ assertFolded("0", "0x100000000 | 0");
+ assertFolded("0", "0x200000000 | 0");
+ assertFolded("1", "0x100000001 | 0");
+ assertFolded("1661992960", "1e20 | 0"); // Outside the range of java
longs
+ assertFolded("-1661992960", "-1e20 | 0");
assertFolded("1024", "1 << 10");
assertFolded("" + (1 << 20), "1 << 20");
+ assertFolded("1", "1 << 0/0");
+ assertFolded("0", "0/0 << 0");
+ assertFolded("1", "1 << 1/0");
+ assertFolded("0", "1/0 << 0");
assertFolded("2", "4 >> 1");
assertFolded("1", "4 >> 2");
assertFolded("0", "4 >> 3");
assertFolded("-1", "-1 >> 1");
+ assertFolded("1", "1 >> 1/0");
+ assertFolded("0", "1/0 >> 0");
assertFolded("" + (-1 >>> 1), "-1 >>> 1");
assertFolded("1", "4 >>> 2");
+ assertFolded("1", "1 >>> 1/0");
+ assertFolded("0", "1/0 >>> 0");
assertFolded("0", "1 & 2");
assertFolded("2", "2 & 3");
assertFolded("2", "3 & 2");
+ assertFolded("0", "1 & 1/0");
+ assertFolded("0", "1/0 & 0");
assertFolded("3", "1 | 2");
assertFolded("7", "6 | 5");
assertFolded("11", "3 | 9");
+ assertFolded("1", "1 | 1/0");
+ assertFolded("0", "1/0 | 0");
assertFolded("0", "0 ^ 0");
assertFolded("0", "1 ^ 1");
assertFolded("3", "1 ^ 2");
assertFolded("-2", "-1 ^ 1");
+ assertFolded("1", "1 ^ 1/0");
+ assertFolded("0", "1/0 ^ 0");
assertFolded("4.0", "4.0");
assertFolded("4.0", "+4.0");
assertFolded("-1", "~0");
@@ -224,6 +253,78 @@
assertFolded("1", "'foo'.indexOf('o')");
assertFolded("-1", "'foo'.indexOf('bar')");
}
+
+ public final void testToInt32() {
+ assertEquals(0, Operation.toInt32(0d));
+ assertEquals(0, Operation.toInt32(-0d));
+ assertEquals(0, Operation.toInt32(Double.POSITIVE_INFINITY));
+ assertEquals(0, Operation.toInt32(Double.NEGATIVE_INFINITY));
+ assertEquals(0, Operation.toInt32(Double.NaN));
+ assertEquals(0, Operation.toInt32(0x100000000L));
+ assertEquals(0, Operation.toInt32(-0x100000000L));
+ assertEquals(0x7fffffffL, Operation.toInt32(0x7fffffffL));
+ assertEquals(-0x7fffffffL, Operation.toInt32(-0x7fffffffL));
+ assertEquals(-0x80000000L, Operation.toInt32(-0x80000000L));
+ assertEquals(-0x80000000L, Operation.toInt32(0x80000000L));
+ assertEquals(1, Operation.toInt32(1));
+ assertEquals(-1, Operation.toInt32(-1));
+ assertEquals(2, Operation.toInt32(2));
+ assertEquals(-2, Operation.toInt32(-2));
+ assertEquals((long) 1e6, Operation.toInt32(1e6));
+ assertEquals((long) 1e7, Operation.toInt32(1e7));
+ assertEquals((long) 1e8, Operation.toInt32(1e8));
+ assertEquals((long) 1e9, Operation.toInt32(1e9));
+ assertEquals(1410065408L, Operation.toInt32(1e10));
+ assertEquals(1215752192L, Operation.toInt32(1e11));
+ assertEquals(-727379968L, Operation.toInt32(1e12));
+ assertEquals((long) -1e6, Operation.toInt32(-1e6));
+ assertEquals((long) -1e7, Operation.toInt32(-1e7));
+ assertEquals((long) -1e8, Operation.toInt32(-1e8));
+ assertEquals((long) -1e9, Operation.toInt32(-1e9));
+ assertEquals(-1410065408L, Operation.toInt32(-1e10));
+ assertEquals(-1215752192L, Operation.toInt32(-1e11));
+ assertEquals(727379968L, Operation.toInt32(-1e12));
+ assertEquals(0, Operation.toInt32(0.5d));
+ assertEquals(0, Operation.toInt32(-0.5d));
+ assertEquals(1, Operation.toInt32(1.5d));
+ assertEquals(-1, Operation.toInt32(-1.5d));
+ }
+
+ public final void testToUint32() {
+ assertEquals(0, Operation.toUint32(0d));
+ assertEquals(0, Operation.toUint32(-0d));
+ assertEquals(0, Operation.toUint32(Double.POSITIVE_INFINITY));
+ assertEquals(0, Operation.toUint32(Double.NEGATIVE_INFINITY));
+ assertEquals(0, Operation.toUint32(Double.NaN));
+ assertEquals(0, Operation.toUint32(0x100000000L));
+ assertEquals(0, Operation.toUint32(-0x100000000L));
+ assertEquals(0x7fffffffL, Operation.toUint32(0x7fffffffL));
+ assertEquals(0x80000001L, Operation.toUint32(-0x7fffffffL));
+ assertEquals(0x80000000L, Operation.toUint32(-0x80000000L));
+ assertEquals(0x80000000L, Operation.toUint32(0x80000000L));
+ assertEquals(1, Operation.toUint32(1));
+ assertEquals(0xffffffffL, Operation.toUint32(-1));
+ assertEquals(2, Operation.toUint32(2));
+ assertEquals(0xfffffffeL, Operation.toUint32(-2));
+ assertEquals((long) 1e6, Operation.toUint32(1e6));
+ assertEquals((long) 1e7, Operation.toUint32(1e7));
+ assertEquals((long) 1e8, Operation.toUint32(1e8));
+ assertEquals((long) 1e9, Operation.toUint32(1e9));
+ assertEquals(1410065408L, Operation.toUint32(1e10));
+ assertEquals(1215752192L, Operation.toUint32(1e11));
+ assertEquals(3567587328L, Operation.toUint32(1e12));
+ assertEquals((long) (0x100000000L - 1e6), Operation.toUint32(-1e6));
+ assertEquals((long) (0x100000000L - 1e7), Operation.toUint32(-1e7));
+ assertEquals((long) (0x100000000L - 1e8), Operation.toUint32(-1e8));
+ assertEquals((long) (0x100000000L - 1e9), Operation.toUint32(-1e9));
+ assertEquals(2884901888L, Operation.toUint32(-1e10));
+ assertEquals(3079215104L, Operation.toUint32(-1e11));
+ assertEquals(727379968L, Operation.toUint32(-1e12));
+ assertEquals(0, Operation.toUint32(0.5d));
+ assertEquals(0, Operation.toUint32(-0.5d));
+ assertEquals(1, Operation.toUint32(1.5d));
+ assertEquals(0xffffffffL, Operation.toUint32(-1.5d));
+ }
private void assertSimplified(String golden, String input)
throws ParseException {
@@ -241,17 +342,19 @@
private void assertFolded(String result, String expr) throws
ParseException {
Expression input = jsExpr(fromString(expr));
- // Fold operands so we can test negative numbers.
if (input instanceof Operation) {
Operation op = (Operation) input;
for (Expression operand : op.children()) {
- if (Operation.is(operand, Operator.NEGATION)
+ // Fold some operands so we can test negative numbers.
+ if ((Operation.is(operand, Operator.NEGATION)
+ // and so that we can test corner cases around NaN and
Infinity.
+ || Operation.is(operand, Operator.DIVISION))
&& operand.children().get(0) instanceof NumberLiteral) {
op.replaceChild(operand.fold(), operand);
}
}
}
Expression actual = input.fold();
- assertEquals(result, actual != null ? render(actual) : null);
+ assertEquals(expr, result, actual != null ? render(actual) : null);
}
}