Revision: 8748
Author: jbrosenb...@google.com
Date: Fri Sep 10 06:27:47 2010
Log: Enum name obfuscation.  Added configuration property
(compiler.enum.obfuscate.names) to optionally obfuscatate enum names from
production output.

Review at http://gwt-code-reviews.appspot.com/827802

http://code.google.com/p/google-web-toolkit/source/detail?r=8748

Added:
 /trunk/dev/core/src/com/google/gwt/dev/jjs/EnumNameObfuscator.java
/trunk/user/test/com/google/gwt/dev/jjs/EnumsWithNameObfuscationSuite.gwt.xml
 /trunk/user/test/com/google/gwt/dev/jjs/EnumsWithNameObfuscationSuite.java
/trunk/user/test/com/google/gwt/dev/jjs/test/EnumsWithNameObfuscationTest.java
 /trunk/user/test/com/google/gwt/emultest/java/math/MathContextTest.java
Modified:
 /trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
 /trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
/trunk/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java
 /trunk/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
 /trunk/user/src/com/google/gwt/rpc/client/ast/IdentityValueCommand.java
 /trunk/user/src/com/google/gwt/rpc/client/impl/SimplePayloadSink.java
 /trunk/user/src/com/google/gwt/rpc/server/SimplePayloadDecoder.java
 /trunk/user/super/com/google/gwt/emul/java/lang/Enum.java
 /trunk/user/super/com/google/gwt/emul/java/math/MathContext.java
 /trunk/user/super/com/google/gwt/emul/java/math/RoundingMode.java
 /trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
 /trunk/user/test/com/google/gwt/emultest/EmulSuite.java
/trunk/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java

=======================================
--- /dev/null
+++ /trunk/dev/core/src/com/google/gwt/dev/jjs/EnumNameObfuscator.java Fri Sep 10 06:27:47 2010
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNonNullType;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JReturnStatement;
+import com.google.gwt.dev.jjs.ast.JThisRef;
+
+import java.util.List;
+
+/**
+ * Performs optimizations on Enums.
+ */
+public class EnumNameObfuscator {
+
+  private static class EnumNameCallChecker extends JModVisitor {
+
+    private final TreeLogger logger;
+    private final JMethod enumNameMethod;
+    private final JMethod enumToStringMethod;
+    private final JMethod enumValueOfMethod;
+    private final JDeclaredType classType;
+    private final JDeclaredType enumType;
+    private final JDeclaredType stringType;
+
+    public EnumNameCallChecker(JProgram jprogram, TreeLogger logger) {
+      this.logger = logger;
+      this.enumNameMethod = jprogram.getIndexedMethod("Enum.name");
+      this.enumToStringMethod = jprogram.getIndexedMethod("Enum.toString");
+      this.classType = jprogram.getIndexedType("Class");
+      this.enumType = jprogram.getIndexedType("Enum");
+      this.stringType = jprogram.getIndexedType("String");
+
+      /*
+       * Find the correct version of enumValueOfMethod.
+       * Note: it doesn't work to check against a ref returned by
+       * jprogram.getIndexedMethod("Enum.valueOf"), since there are
+       * 2 different versions of Enum.valueOf in our jre emulation library,
+       * and the indexed ref won't reliably flag the public instance, which
+       * is the one we want here (i.e. Enum.valueOf(Class<T>,String)).
+       * The other version is protected, but is called by the generated
+ * constructors for sub-classes of Enum, and we don't want to warn for
+       * those cases.
+       */
+      JMethod foundMethod = null;
+      List<JMethod> enumMethods = enumType.getMethods();
+      for (JMethod enumMethod : enumMethods) {
+        if ("valueOf".equals(enumMethod.getName())) {
+          List<JParameter> jParameters = enumMethod.getParams();
+          if (jParameters.size() == 2 &&
+              jParameters.get(0).getType() == classType &&
+              jParameters.get(1).getType() == stringType) {
+            foundMethod = enumMethod;
+            break;
+          }
+        }
+      }
+      this.enumValueOfMethod = foundMethod;
+    }
+
+    @Override
+    public void endVisit(JMethodCall x, Context ctx) {
+      JMethod target = x.getTarget();
+      JDeclaredType type = target.getEnclosingType();
+
+      if (type instanceof JClassType) {
+        JClassType cType = (JClassType) type;
+
+        if (target == enumNameMethod ||
+              target == enumToStringMethod ||
+              target == enumValueOfMethod) {
+          warn(x);
+        } else if (cType.isEnumOrSubclass() != null) {
+          if ("valueOf".equals(target.getName())) {
+            /*
+ * Check for calls to the auto-generated EnumSubType.valueOf(String). + * Note, the check of the signature for the single String arg version
+             * is to avoid flagging user-defined overloaded versions of the
+             * method, which are presumably ok.
+             */
+            List<JParameter> jParameters = target.getParams();
+            if (jParameters.size() == 1 &&
+                jParameters.get(0).getType() == stringType) {
+              warn(x);
+            }
+          }
+        }
+      }
+    }
+
+    @Override
+    public boolean visit(JClassType x, Context ctx) {
+      if (x == enumType) {
+ // don't traverse into Enum class itself, don't warn on internal method calls
+        return false;
+      }
+      return true;
+    }
+
+    private void warn(JMethodCall x) {
+ /* TODO: add a way to suppress warning with annotation if you know what
+       * you're doing.
+       */
+      logger.log(TreeLogger.WARN, "Call to Enum method "
+ + x.getTarget().getName() + " when enum obfuscation is enabled: "
+          + x.getSourceInfo().getFileName() + ":"
+          + x.getSourceInfo().getStartLine());
+    }
+  }
+
+  private static class EnumNameReplacer extends JModVisitor {
+
+    private final TreeLogger logger;
+    private final JProgram jprogram;
+    private final JMethod enumObfuscatedName;
+    private final JNonNullType enumType;
+
+    public EnumNameReplacer(JProgram jprogram, TreeLogger logger) {
+      this.logger = logger;
+      this.jprogram = jprogram;
+ this.enumType = jprogram.getNonNullType(jprogram.getIndexedType("Enum")); + this.enumObfuscatedName = jprogram.getIndexedMethod("Enum.obfuscatedName");
+    }
+
+    @Override
+    public void endVisit(JReturnStatement x, Context ctx) {
+      info(x);
+      JReturnStatement toReturn = new JReturnStatement(x.getSourceInfo(),
+          new JMethodCall(x.getSourceInfo(),
+          new JThisRef(x.getSourceInfo(),enumType), enumObfuscatedName));
+      ctx.replaceMe(toReturn);
+    }
+
+    public void exec() {
+      accept(jprogram.getIndexedMethod("Enum.name"));
+    }
+
+    private void info(JReturnStatement x) {
+      logger.log(TreeLogger.INFO, "Replacing Enum.name method :  "
+          + x.getSourceInfo().getFileName() + ":"
+          + x.getSourceInfo().getStartLine());
+    }
+  }
+
+  public static void exec(JProgram jprogram, TreeLogger logger) {
+    new EnumNameCallChecker(jprogram, logger).accept(jprogram);
+    new EnumNameReplacer(jprogram, logger).exec();
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/EnumsWithNameObfuscationSuite.gwt.xml Fri Sep 10 06:27:47 2010
@@ -0,0 +1,19 @@
+<!-- --> +<!-- Copyright 2010 Google Inc. --> +<!-- Licensed under the Apache License, Version 2.0 (the "License"); you --> +<!-- may not use this file except in compliance with the License. You may --> +<!-- may obtain a copy of the License at --> +<!-- --> +<!-- http://www.apache.org/licenses/LICENSE-2.0 --> +<!-- --> +<!-- Unless required by applicable law or agreed to in writing, software --> +<!-- distributed under the License is distributed on an "AS IS" BASIS, --> +<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or --> +<!-- implied. License for the specific language governing permissions and --> +<!-- limitations under the License. -->
+
+<module>
+  <inherits name="com.google.gwt.dev.jjs.CompilerSuite"/>
+
+ <set-configuration-property name="compiler.enum.obfuscate.names" value="true" />
+</module>
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/EnumsWithNameObfuscationSuite.java Fri Sep 10 06:27:47 2010
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs;
+
+import com.google.gwt.dev.jjs.test.EnumsWithNameObfuscationTest;
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * The complete compiler suite.
+ */
+public class EnumsWithNameObfuscationSuite {
+
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite(
+        "Test for com.google.gwt.dev.jjs (with enum name obfuscation)");
+
+    // $JUnit-BEGIN$
+    suite.addTestSuite(EnumsWithNameObfuscationTest.class);
+    // $JUnit-END$
+
+    return suite;
+  }
+
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/test/EnumsWithNameObfuscationTest.java Fri Sep 10 06:27:47 2010
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.test;
+
+import com.google.gwt.core.client.JavaScriptException;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+
+/**
+ * Tests enum with names obfuscated functionality.
+ *
+ * Note: If running in an environment with WARN logging enabled, check to see
+ * that calls to name(), toString(), and valueOf() below, are logged in the
+ * precompile phase.
+ */
+...@donotrunwith(Platform.Devel)
+public class EnumsWithNameObfuscationTest extends EnumsTest {
+
+  enum Fruit {
+    APPLE, BERRY, CHERRY
+  }
+
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.EnumsWithNameObfuscationSuite";
+  }
+
+  public void testObfuscatedFruit() {
+    /*
+     * Note: Also check that the strings "APPLE" or "BERRY" never appear
+     * anywhere in the compiled output, if compilation is done with
+     * optimization and obfuscation.  "CHERRY" will appear, since it
+     * is used as a string literal in the comparison tests below.
+     */
+    assertEquals(0, Fruit.APPLE.ordinal());
+    assertEquals(1, Fruit.BERRY.ordinal());
+    assertEquals(2, Fruit.CHERRY.ordinal());
+
+    assertFalse(Fruit.CHERRY.toString().equals("CHERRY"));
+    assertFalse(Fruit.CHERRY.name().equals("CHERRY"));
+
+ assertTrue(Fruit.CHERRY.toString().equals("" + Fruit.CHERRY.ordinal()));
+    assertTrue(Fruit.CHERRY.name().equals("" + Fruit.CHERRY.ordinal()));
+
+    try {
+      Fruit.valueOf("CHERRIESONTOP");
+      fail("Enum.valueOf(), expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      Fruit.valueOf("CHERRY");
+      fail("Enum.valueOf(), expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      Enum.valueOf(Fruit.class,"CHERRIESONTOP");
+      fail("Enum.valueOf(), expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      Enum.valueOf(Fruit.class,"CHERRY");
+      fail("Enum.valueOf(), expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+
+  @Override
+  public void testCompareTo() {
+
+    try {
+      assertTrue(Basic.A.compareTo(Basic.valueOf("A")) == 0);
+      fail("Basic.valueOf(), expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    assertTrue(Basic.B.compareTo(Basic.A) > 0);
+    assertTrue(Basic.A.compareTo(Basic.B) < 0);
+
+    try {
+      assertTrue(Complex.A.compareTo(Complex.valueOf("A")) == 0);
+      fail("Complex.valueOf(), expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    assertTrue(Complex.B.compareTo(Complex.A) > 0);
+    assertTrue(Complex.A.compareTo(Complex.B) < 0);
+
+    try {
+      assertTrue(Subclassing.A.compareTo(Subclassing.valueOf("A")) == 0);
+      fail("Subclassing.valueOf(), expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    assertTrue(Subclassing.B.compareTo(Subclassing.A) > 0);
+    assertTrue(Subclassing.A.compareTo(Subclassing.B) < 0);
+  }
+
+  @Override
+  public void testName() {
+    assertFalse("A".equals(Basic.A.name()));
+    assertFalse("B".equals(Basic.B.name()));
+    assertFalse("C".equals(Basic.C.name()));
+
+    assertFalse("A".equals(Complex.A.name()));
+    assertFalse("B".equals(Complex.B.name()));
+    assertFalse("C".equals(Complex.C.name()));
+
+    assertFalse("A".equals(Subclassing.A.name()));
+    assertFalse("B".equals(Subclassing.B.name()));
+    assertFalse("C".equals(Subclassing.C.name()));
+  }
+
+  @Override
+  public void testValueOf() {
+
+    try {
+      Basic.valueOf("D");
+      fail("Basic.valueOf(\"D\") -- expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      Complex.valueOf("D");
+      fail("Complex.valueOf(\"D\") -- expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      Subclassing.valueOf("D");
+ fail("Subclassing.valueOf(\"D\") -- expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    enumValuesTest(Basic.class);
+    enumValuesTest(Complex.class);
+    enumValuesTest(Subclassing.class);
+
+    try {
+      Enum.valueOf(Basic.class, "foo");
+ fail("Passed an invalid enum constant name to Enum.valueOf; expected "
+          + "IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      Class fakeEnumClass = String.class;
+      Enum.valueOf(fakeEnumClass, "foo");
+      fail("Passed a non enum class to Enum.valueOf; expected "
+              + "IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      Class<Basic> nullEnumClass = null;
+      Enum.valueOf(nullEnumClass, "foo");
+      fail("Passed a null enum class to Enum.valueOf; expected "
+          + "NullPointerException");
+    } catch (JavaScriptException expected) {
+    } catch (NullPointerException expected) {
+    }
+
+    try {
+      Enum.valueOf(Basic.class, null);
+      fail("Passed a null enum constant to Enum.valueOf; expected "
+          + "NullPointerException");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  private <T extends Enum<T>> void enumValuesTest(Class<T> enumClass) {
+    T[] constants = enumClass.getEnumConstants();
+    for (T constant : constants) {
+      assertEquals(constant, Enum.valueOf(enumClass, constant.name()));
+    }
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/emultest/java/math/MathContextTest.java Fri Sep 10 06:27:47 2010
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.emultest.java.math;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+import java.math.MathContext;
+import java.math.RoundingMode;
+
+/**
+ * Tests for {...@link MathContext}.
+ */
+public class MathContextTest extends EmulTestBase {
+
+  public void testMathContextSingleArgConstructor() {
+    MathContext mc1 = new MathContext("precision=16 roundingMode=CEILING");
+    assertTrue(mc1.getPrecision() == 16);
+    assertTrue(mc1.getRoundingMode().equals(RoundingMode.CEILING));
+
+    MathContext mc2 = new MathContext("precision=17 roundingMode=DOWN");
+    assertTrue(mc2.getPrecision() == 17);
+    assertTrue(mc2.getRoundingMode().equals(RoundingMode.DOWN));
+
+    MathContext mc3 = new MathContext("precision=18 roundingMode=FLOOR");
+    assertTrue(mc3.getPrecision() == 18);
+    assertTrue(mc3.getRoundingMode().equals(RoundingMode.FLOOR));
+
+ MathContext mc4 = new MathContext("precision=19 roundingMode=HALF_DOWN");
+    assertTrue(mc4.getPrecision() == 19);
+    assertTrue(mc4.getRoundingMode().equals(RoundingMode.HALF_DOWN));
+
+ MathContext mc5 = new MathContext("precision=20 roundingMode=HALF_EVEN");
+    assertTrue(mc5.getPrecision() == 20);
+    assertTrue(mc5.getRoundingMode().equals(RoundingMode.HALF_EVEN));
+
+    MathContext mc6 = new MathContext("precision=21 roundingMode=HALF_UP");
+    assertTrue(mc6.getPrecision() == 21);
+    assertTrue(mc6.getRoundingMode().equals(RoundingMode.HALF_UP));
+
+ MathContext mc7 = new MathContext("precision=22 roundingMode=UNNECESSARY");
+    assertTrue(mc7.getPrecision() == 22);
+    assertTrue(mc7.getRoundingMode().equals(RoundingMode.UNNECESSARY));
+
+    MathContext mc8 = new MathContext("precision=23 roundingMode=UP");
+    assertTrue(mc8.getPrecision() == 23);
+    assertTrue(mc8.getRoundingMode().equals(RoundingMode.UP));
+
+    // try some badly formatted args
+    try {
+      new MathContext("prcision=27 roundingMode=CEILING");
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      new MathContext("precision=26 roundingMoe=CEILING");
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      new MathContext("precision=25 roundingMode=CEILINGFAN");
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      new MathContext("precision=24 roundingMode=HALF");
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      new MathContext("precision=23 roundingMode=UPSIDEDOWN");
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      new MathContext("precision=22roundingMode=UP");
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      new MathContext("");
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      new MathContext(null);
+      fail("Expected NullPointerException");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void testMathContextConstructorEquality() {
+    MathContext mc1 = new MathContext(16,RoundingMode.CEILING);
+ MathContext mc1a = new MathContext("precision=16 roundingMode=CEILING");
+    assertTrue(mc1.equals(mc1a));
+
+    MathContext mc2 = new MathContext(17,RoundingMode.DOWN);
+    MathContext mc2a = new MathContext("precision=17 roundingMode=DOWN");
+    assertTrue(mc2.equals(mc2a));
+
+    MathContext mc3 = new MathContext(18,RoundingMode.FLOOR);
+    MathContext mc3a = new MathContext("precision=18 roundingMode=FLOOR");
+    assertTrue(mc3.equals(mc3a));
+
+    MathContext mc4 = new MathContext(19,RoundingMode.HALF_DOWN);
+ MathContext mc4a = new MathContext("precision=19 roundingMode=HALF_DOWN");
+    assertTrue(mc4.equals(mc4a));
+
+    MathContext mc5 = new MathContext(20,RoundingMode.HALF_EVEN);
+ MathContext mc5a = new MathContext("precision=20 roundingMode=HALF_EVEN");
+    assertTrue(mc5.equals(mc5a));
+
+    MathContext mc6 = new MathContext(21,RoundingMode.HALF_UP);
+ MathContext mc6a = new MathContext("precision=21 roundingMode=HALF_UP");
+    assertTrue(mc6.equals(mc6a));
+
+    MathContext mc7 = new MathContext(22,RoundingMode.UNNECESSARY);
+ MathContext mc7a = new MathContext("precision=22 roundingMode=UNNECESSARY");
+    assertTrue(mc7.equals(mc7a));
+
+    MathContext mc8 = new MathContext(23,RoundingMode.UP);
+    MathContext mc8a = new MathContext("precision=23 roundingMode=UP");
+    assertTrue(mc8.equals(mc8a));
+  }
+}
=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java Thu Sep 9 08:24:17 2010 +++ /trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java Fri Sep 10 06:27:47 2010
@@ -34,6 +34,7 @@
 import com.google.gwt.core.ext.soyc.impl.StoryRecorder;
 import com.google.gwt.core.linker.SoycReportLinker;
 import com.google.gwt.dev.Permutation;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.jdt.RebindPermutationOracle;
 import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
@@ -161,6 +162,10 @@
  * JavaScript source.
  */
 public class JavaToJavaScriptCompiler {
+
+  private static final String ENUM_NAME_OBFUSCATION_PROPERTY =
+                                                
"compiler.enum.obfuscate.names";
+
   private static class PermutationResultImpl implements PermutationResult {
     private final ArtifactSet artifacts = new ArtifactSet();
     private final byte[][] js;
@@ -537,7 +542,17 @@

       Memory.maybeDumpMemory("AstOnly");
       maybeDumpAST(jprogram);
-
+
+      // See if we should run the EnumNameObfuscator
+      if (module != null) {
+        ConfigurationProperty enumNameObfuscationProp =
+ (ConfigurationProperty) module.getProperties().find(ENUM_NAME_OBFUSCATION_PROPERTY);
+        if (enumNameObfuscationProp != null &&
+            Boolean.parseBoolean(enumNameObfuscationProp.getValue())) {
+          EnumNameObfuscator.exec(jprogram, logger);
+        }
+      }
+
       // (3) Perform Java AST normalizations.
       ArtificialRescueRecorder.exec(jprogram);
       FixAssignmentToUnbox.exec(jprogram);
@@ -558,7 +573,7 @@
       ReplaceRebinds.exec(logger, jprogram, rpo);

       // Fix up GWT.runAsync()
-      if (options.isRunAsyncEnabled()) {
+      if (module != null && options.isRunAsyncEnabled()) {
         ReplaceRunAsyncs.exec(logger, jprogram);
         CodeSplitter.pickInitialLoadSequence(logger, jprogram,
             module.getProperties());
=======================================
--- /trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml Fri Jul 23 11:50:52 2010 +++ /trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml Fri Sep 10 06:27:47 2010
@@ -33,6 +33,15 @@
   <set-property name="compiler.predeclare.cross.fragment.references"
     value="false" />

+  <!--
+    Whether or not the compiler should alter Enum.name() to return
+    ordinal() as a way of obfuscating Enum field identifiers.
+  -->
+   <define-configuration-property name="compiler.enum.obfuscate.names"
+      is-multi-valued='false' />
+   <set-configuration-property name="compiler.enum.obfuscate.names"
+      value="false" />
+
<!-- From here down, the properties are unsupported and are only available for test cases -->

   <!--
@@ -53,7 +62,7 @@
     JsIEBlockSizeVisitor has its usual effect.
   -->
   <define-configuration-property name="iframe.linker.script.chunk.size"
-  is-multi-valued="false" />
+    is-multi-valued="false" />
   <set-configuration-property name="iframe.linker.script.chunk.size"
     value="30000" />

=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java Thu Sep 9 14:34:00 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java Fri Sep 10 06:27:47 2010
@@ -139,9 +139,9 @@
     ReturnRecord.fillKeys(returnedJso, keys);

     Set<EntityProxyId> toRemove = new HashSet<EntityProxyId>();
-    if (keys.contains(WriteOperation.CREATE.name())) {
+    if (keys.contains(WriteOperation.CREATE.getUnObfuscatedEnumName())) {
JsArray<ReturnRecord> newRecords = ReturnRecord.getRecords(returnedJso,
-          WriteOperation.CREATE.name());
+          WriteOperation.CREATE.getUnObfuscatedEnumName());
       int length = newRecords.length();
       for (int i = 0; i < length; i++) {
         ReturnRecord newRecord = newRecords.get(i);
@@ -181,9 +181,9 @@
     processToRemove(toRemove, WriteOperation.CREATE);
     toRemove.clear();

-    if (keys.contains(WriteOperation.DELETE.name())) {
+    if (keys.contains(WriteOperation.DELETE.getUnObfuscatedEnumName())) {
       JsArray<ReturnRecord> deletedRecords = ReturnRecord.getRecords(
-          returnedJso, WriteOperation.DELETE.name());
+          returnedJso, WriteOperation.DELETE.getUnObfuscatedEnumName());
       int length = deletedRecords.length();
       for (int i = 0; i < length; i++) {
         ReturnRecord deletedRecord = deletedRecords.get(i);
@@ -206,9 +206,9 @@
       }
     }

-    if (keys.contains(WriteOperation.UPDATE.name())) {
+    if (keys.contains(WriteOperation.UPDATE.getUnObfuscatedEnumName())) {
       JsArray<ReturnRecord> updatedRecords = ReturnRecord.getRecords(
-          returnedJso, WriteOperation.UPDATE.name());
+          returnedJso, WriteOperation.UPDATE.getUnObfuscatedEnumName());
       int length = updatedRecords.length();
       for (int i = 0; i < length; i++) {
         ReturnRecord updatedRecord = updatedRecords.get(i);
@@ -354,7 +354,7 @@
     if (recordsMap.size() == 0) {
       return "";
     }
- StringBuffer requestData = new StringBuffer("\"" + writeOperation.name() + StringBuffer requestData = new StringBuffer("\"" + writeOperation.getUnObfuscatedEnumName()
         + "\":[");
     boolean first = true;
for (Map.Entry<EntityProxyId, ProxyJsoImpl> entry : recordsMap.entrySet()) {
@@ -389,7 +389,7 @@
         return updates;
       default:
         throw new IllegalStateException("unknow writeOperation "
-            + writeOperation.name());
+            + writeOperation.getUnObfuscatedEnumName());
     }
   }

=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java Wed Aug 25 17:41:41 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java Fri Sep 10 06:27:47 2010
@@ -24,5 +24,19 @@
  * The write operation enum used in DeltaValueStore.
  */
 public enum WriteOperation {
-  CREATE, UPDATE, DELETE
-}
+  CREATE("CREATE"),
+  UPDATE("UPDATE"),
+  DELETE("DELETE");
+
+ // use an unObfuscatedEnumName field to bypass the implicit name() method,
+  // to be safe in the case enum name obfuscation is enabled.
+  private final String unObfuscatedEnumName;
+
+  private WriteOperation(String unObfuscatedEnumName) {
+    this.unObfuscatedEnumName = unObfuscatedEnumName;
+  }
+
+  public String getUnObfuscatedEnumName() {
+    return this.unObfuscatedEnumName;
+  }
+}
=======================================
--- /trunk/user/src/com/google/gwt/rpc/client/ast/IdentityValueCommand.java Wed Oct 28 09:10:53 2009 +++ /trunk/user/src/com/google/gwt/rpc/client/ast/IdentityValueCommand.java Fri Sep 10 06:27:47 2010
@@ -18,7 +18,7 @@
 /**
  * Represents a hierarchy of value types that must maintain distinct object
  * identity on the client. This type finalizes <code>equals</code> and
- * <code>hashCode</code> to give subtypes identity equality sematics.
+ * <code>hashCode</code> to give subtypes identity equality semantics.
  */
 public abstract class IdentityValueCommand extends ValueCommand {
   @Override
=======================================
--- /trunk/user/src/com/google/gwt/rpc/client/impl/SimplePayloadSink.java Wed Oct 28 09:10:53 2009 +++ /trunk/user/src/com/google/gwt/rpc/client/impl/SimplePayloadSink.java Fri Sep 10 06:27:47 2010
@@ -73,10 +73,11 @@

     @Override
     public void endVisit(EnumValueCommand x, Context ctx) {
-      // ETypeSeedName~"9~FieldName
+      // ETypeSeedName~IOrdinal~
       if (appendIdentity(x)) {
appendTypedData(ENUM_TYPE, x.getValue().getDeclaringClass().getName());
-        accept(new StringValueCommand(x.getValue().name()));
+ // use ordinal (and not name), since name might have been obfuscated
+        appendTypedData(INT_TYPE, String.valueOf(x.getValue().ordinal()));
       }
     }

=======================================
--- /trunk/user/src/com/google/gwt/rpc/server/SimplePayloadDecoder.java Wed Oct 28 09:10:53 2009 +++ /trunk/user/src/com/google/gwt/rpc/server/SimplePayloadDecoder.java Fri Sep 10 06:27:47 2010
@@ -198,16 +198,22 @@
         break;
       }
       case ENUM_TYPE: {
-        // ETypeSeedName~"9~FieldName
+        // ETypeSeedName~IOrdinal~
         EnumValueCommand x = new EnumValueCommand();
         push(x);
-        String name = readCommand(StringValueCommand.class).getValue();
+
+ // use ordinal (and not name), since name might have been obfuscated
+        int ordinal = readCommand(IntValueCommand.class).getValue();

         @SuppressWarnings("unchecked")
Class<? extends Enum> clazz = findClass(token).asSubclass(Enum.class);
-        @SuppressWarnings("unchecked")
-        Enum<?> enumValue = Enum.valueOf(clazz, name);
-        x.setValue(enumValue);
+
+        /*
+         * TODO: Note this approach could be prone to subtle corruption or
+ * an ArrayOutOfBoundsException if the client and server have drifted.
+         */
+        Enum<?> enumConstants[] = clazz.getEnumConstants();
+        x.setValue(enumConstants[ordinal]);
         break;
       }
       case ARRAY_TYPE: {
=======================================
--- /trunk/user/super/com/google/gwt/emul/java/lang/Enum.java Fri May 28 09:34:24 2010 +++ /trunk/user/super/com/google/gwt/emul/java/lang/Enum.java Fri Sep 10 06:27:47 2010
@@ -39,7 +39,7 @@
       T[] enumConstants) {
     JavaScriptObject result = JavaScriptObject.createObject();
     for (T value : enumConstants) {
-      put0(result, ":" + value.name, value);
+       put0(result, ":" + value.name(), value);
     }
     return result;
   }
@@ -114,6 +114,10 @@
   public final String name() {
     return name;
   }
+
+  public final String obfuscatedName() {
+    return "" + ordinal;
+  }

   public final int ordinal() {
     return ordinal;
@@ -121,7 +125,6 @@

   @Override
   public String toString() {
-    return name;
-  }
-
-}
+    return name();
+  }
+}
=======================================
--- /trunk/user/super/com/google/gwt/emul/java/math/MathContext.java Thu Feb 25 10:33:31 2010 +++ /trunk/user/super/com/google/gwt/emul/java/math/MathContext.java Fri Sep 10 06:27:47 2010
@@ -162,6 +162,9 @@
    *           or if the precision specified is < 0.
    */
   public MathContext(String val) {
+    if (val == null) {
+      throw new NullPointerException("null string");
+    }
     char[] charVal = val.toCharArray();
     int i; // Index of charVal
     int j; // Index of chRoundingMode
@@ -219,8 +222,13 @@
       // math.0E=bad string format
throw new IllegalArgumentException("bad string format"); //$NON-NLS-1$
     }
+
     // Parsing the value for "roundingMode"...
-    this.roundingMode = RoundingMode.valueOf(String.valueOf(charVal, i,
+    /*
+ * don't use implicit calls to RoundingMode.valueOf here, since it will break
+     * if enum name obfuscation is enabled.
+     */
+ this.roundingMode = RoundingMode.valueOfExplicit(String.valueOf(charVal, i,
         charVal.length - i));
   }

@@ -237,7 +245,8 @@
   @Override
   public boolean equals(Object x) {
     return ((x instanceof MathContext)
- && (((MathContext) x).getPrecision() == precision) && (((MathContext) x).getRoundingMode() == roundingMode));
+        && (((MathContext) x).getPrecision() == precision)
+        && (((MathContext) x).getRoundingMode() == roundingMode));
   }

   /**
=======================================
--- /trunk/user/super/com/google/gwt/emul/java/math/RoundingMode.java Mon Jun 7 12:20:31 2010 +++ /trunk/user/super/com/google/gwt/emul/java/math/RoundingMode.java Fri Sep 10 06:27:47 2010
@@ -92,7 +92,18 @@
    */
   UNNECESSARY(BigDecimal.ROUND_UNNECESSARY);

-
+  /**
+   * Some constant char arrays for optimized comparisons
+   */
+  private static final char[] chCEILING = {'C','E','I','L','I','N','G'};
+  private static final char[] chDOWN = {'D','O','W','N'};
+  private static final char[] chFLOOR = {'F','L','O','O','R'};
+ private static final char[] chHALF_DOWN = {'H','A','L','F','_','D','O','W','N'}; + private static final char[] chHALF_EVEN = {'H','A','L','F','_','E','V','E','N'};
+  private static final char[] chHALF_UP = {'H','A','L','F','_','U','P'};
+ private static final char[] chUNNECESSARY = {'U','N','N','E','C','E','S','S','A','R','Y'};
+  private static final char[] chUP = {'U','P'};
+
   /**
* Converts rounding mode constants from class {...@code BigDecimal} into {...@code
    * RoundingMode} values.
@@ -123,7 +134,76 @@
throw new IllegalArgumentException("Invalid rounding mode"); //$NON-NLS-1$
     }
   }
-
+
+  /**
+ * Bypasses calls to the implicit valueOf(String) method, which will break + * if enum name obfuscation is enabled. This should be package visible only.
+   *
+ * @param mode rounding mode string as defined in class {...@code BigDecimal}
+   * @return corresponding rounding mode object
+   */
+  static RoundingMode valueOfExplicit(String mode) {
+    /*
+     * Note this is optimized to avoid multiple String compares,
+     * using specific knowledge of the set of allowed enum constants.
+     */
+
+    if (mode == null) {
+      throw new NullPointerException();
+    }
+
+    char[] modeChars = mode.toCharArray();
+    int len = modeChars.length;
+    if (len < chUP.length || len > chUNNECESSARY.length) {
+      throw new IllegalArgumentException();
+    }
+
+    char[] targetChars = null;
+    RoundingMode target = null;
+    if (modeChars[0] == 'C') {
+      target = RoundingMode.CEILING;
+      targetChars = chCEILING;
+    } else if (modeChars[0] == 'D') {
+      target = RoundingMode.DOWN;
+      targetChars = chDOWN;
+    } else if (modeChars[0] == 'F') {
+      target = RoundingMode.FLOOR;
+      targetChars = chFLOOR;
+    } else if (modeChars[0] == 'H') {
+      if (len > 6) {
+        if (modeChars[5] == 'D') {
+          target = RoundingMode.HALF_DOWN;
+          targetChars = chHALF_DOWN;
+        } else if (modeChars[5] == 'E') {
+          target = RoundingMode.HALF_EVEN;
+          targetChars = chHALF_EVEN;
+        } else if (modeChars[5] == 'U') {
+          target = RoundingMode.HALF_UP;
+          targetChars = chHALF_UP;
+        }
+      }
+    } else if (modeChars[0] == 'U') {
+      if (modeChars[1] == 'P') {
+        target = RoundingMode.UP;
+        targetChars = chUP;
+      } else if (modeChars[1] == 'N') {
+        target = RoundingMode.UNNECESSARY;
+        targetChars = chUNNECESSARY;
+      }
+    }
+
+    if (target != null && len == targetChars.length) {
+      int i;
+      for (i = 1; i < len && modeChars[i] == targetChars[i]; i++) {
+      }
+      if (i == len) {
+        return target;
+      }
+    }
+
+    throw new IllegalArgumentException();
+  }
+
   /**
    * Set the old constant.
    * @param rm unused
=======================================
--- /trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.java Mon Feb 1 07:58:03 2010 +++ /trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.java Fri Sep 10 06:27:47 2010
@@ -26,6 +26,7 @@
 import com.google.gwt.dev.jjs.test.CoverageTest;
 import com.google.gwt.dev.jjs.test.EnhancedForLoopTest;
 import com.google.gwt.dev.jjs.test.EnumsTest;
+import com.google.gwt.dev.jjs.test.EnumsWithNameObfuscationTest;
 import com.google.gwt.dev.jjs.test.GenericCastTest;
 import com.google.gwt.dev.jjs.test.HostedTest;
 import com.google.gwt.dev.jjs.test.InitialLoadSequenceTest;
@@ -72,6 +73,7 @@
     suite.addTestSuite(CoverageTest.class);
     suite.addTestSuite(EnhancedForLoopTest.class);
     suite.addTestSuite(EnumsTest.class);
+    suite.addTestSuite(EnumsWithNameObfuscationTest.class);
     suite.addTestSuite(GenericCastTest.class);
     suite.addTestSuite(HostedTest.class);
     suite.addTestSuite(InitialLoadSequenceTest.class);
=======================================
--- /trunk/user/test/com/google/gwt/emultest/EmulSuite.java Fri Aug 13 12:36:25 2010 +++ /trunk/user/test/com/google/gwt/emultest/EmulSuite.java Fri Sep 10 06:27:47 2010
@@ -30,6 +30,7 @@
 import com.google.gwt.emultest.java.lang.StringBufferTest;
 import com.google.gwt.emultest.java.lang.StringTest;
 import com.google.gwt.emultest.java.lang.SystemTest;
+import com.google.gwt.emultest.java.math.MathContextTest;
 import com.google.gwt.emultest.java.math.RoundingModeTest;
 import com.google.gwt.emultest.java.security.MessageDigestTest;
 import com.google.gwt.emultest.java.sql.SqlDateTest;
@@ -88,6 +89,7 @@
     // BigDecimal is tested in {...@link BigDecimalSuite}
     // BigInteger is tested in {...@link BigIntegerSuite}
     suite.addTestSuite(RoundingModeTest.class);
+    suite.addTestSuite(MathContextTest.class);

     //-- java.util
     suite.addTestSuite(ApacheMapTest.class);
=======================================
--- /trunk/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java Tue Sep 7 10:09:37 2010 +++ /trunk/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java Fri Sep 10 06:27:47 2010
@@ -82,7 +82,7 @@
         valueStore, requestFactory);
     String json = deltaValueStore.toJson();
     JSONObject jsonObject = (JSONObject) JSONParser.parseLenient(json);
-    assertFalse(jsonObject.containsKey(WriteOperation.CREATE.name()));
+ assertFalse(jsonObject.containsKey(WriteOperation.CREATE.getUnObfuscatedEnumName()));
   }

   public void testCreateWithSet() {
@@ -152,19 +152,19 @@
     assertTrue(deltaValueStore.isChanged());
     String jsonString = deltaValueStore.toJson();
JSONObject jsonObject = (JSONObject) JSONParser.parseLenient(jsonString);
-    assertFalse(jsonObject.containsKey(WriteOperation.DELETE.name()));
-    assertTrue(jsonObject.containsKey(WriteOperation.CREATE.name()));
-    assertTrue(jsonObject.containsKey(WriteOperation.UPDATE.name()));
+ assertFalse(jsonObject.containsKey(WriteOperation.DELETE.getUnObfuscatedEnumName())); + assertTrue(jsonObject.containsKey(WriteOperation.CREATE.getUnObfuscatedEnumName())); + assertTrue(jsonObject.containsKey(WriteOperation.UPDATE.getUnObfuscatedEnumName()));

     JSONArray createOperationArray = jsonObject.get(
-        WriteOperation.CREATE.name()).isArray();
+        WriteOperation.CREATE.getUnObfuscatedEnumName()).isArray();
     assertEquals(1, createOperationArray.size());
     assertEquals("harry", createOperationArray.get(0).isObject().get(
         SIMPLE_FOO_CLASS_NAME).isObject().get(
         SimpleFooProxy.userName.getName()).isString().stringValue());

     JSONArray updateOperationArray = jsonObject.get(
-        WriteOperation.UPDATE.name()).isArray();
+        WriteOperation.UPDATE.getUnObfuscatedEnumName()).isArray();
     assertEquals(1, updateOperationArray.size());
     assertEquals("bovik", updateOperationArray.get(0).isObject().get(
         SIMPLE_FOO_CLASS_NAME).isObject().get(
@@ -189,13 +189,14 @@
JSONObject jsonObject = (JSONObject) JSONParser.parseLenient(jsonString);
     for (WriteOperation writeOperation : WriteOperation.values()) {
       if (writeOperation != currentWriteOperation) {
-        assertFalse(jsonObject.containsKey(writeOperation.name()));
+ assertFalse(jsonObject.containsKey(writeOperation.getUnObfuscatedEnumName()));
       } else {
-        assertTrue(jsonObject.containsKey(writeOperation.name()));
+ assertTrue(jsonObject.containsKey(writeOperation.getUnObfuscatedEnumName()));
       }
     }

- JSONArray writeOperationArray = jsonObject.get(currentWriteOperation.name()).isArray();
+    JSONArray writeOperationArray =
+ jsonObject.get(currentWriteOperation.getUnObfuscatedEnumName()).isArray();
     assertEquals(1, writeOperationArray.size());

     JSONObject proxyWithName = writeOperationArray.get(0).isObject();

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to