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);
   }
 }

Reply via email to