Fix corner case in server-side Stack trace debofuscation.

Some fields and methods have special hardcoded obfuscated names, e. g.
Object::getClass() -> gC, Object::__clazz -> cZ, etc. Those names
were not prevented to clash with obfuscated method names, creating
ambiguous symbols in the Symbol Map. If a method that had one of
those ambiguous symbols appeard in the stack, it would have been
deobfuscated incorrectly.

This patch fixes by only loading symbols for methods not fields, and
removes all special handling for java.lang.Object fields and methods.

Change-Id: Id4b1c8ef9d4e1ec4bc96db2a04ed2cd8ab377a71
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 24045c5..8a4d6ae 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -23,7 +23,6 @@
 import com.google.gwt.dev.cfg.PermutationProperties;
 import com.google.gwt.dev.jjs.HasSourceInfo;
 import com.google.gwt.dev.jjs.InternalCompilerException;
-import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.jjs.ast.Context;
@@ -436,10 +435,7 @@
         recordSymbol(x, jsName);
       } else {
         JsName jsName;
-        if (specialObfuscatedFields.containsKey(x)) {
-          jsName = scopeStack.peek().declareName(mangleNameSpecialObfuscate(x));
-          jsName.setObfuscatable(false);
-        } else if (x.isJsTypeMember()) {
+        if (x.isJsTypeMember()) {
           jsName = scopeStack.peek().declareName(name, name);
           jsName.setObfuscatable(false);
         } else {
@@ -567,10 +563,6 @@
             // so that it can be referred when generating the vtable of a subclass that
             // increases the visibility of this method.
             polymorphicNames.put(typeOracle.getTopMostDefinition(x), polyName);
-          } else if (specialObfuscatedMethodSigs.containsKey(x.getSignature())) {
-            polyName = interfaceScope.declareName(mangleNameSpecialObfuscate(x));
-            polyName.setObfuscatable(false);
-            // if a JsType and we can set set the interface method to non-obfuscatable
           } else if (x.isOrOverridesJsTypeMethod() && !typeOracle.needsJsInteropBridgeMethod(x)) {
             if (x.isOrOverridesJsProperty()) {
               // Prevent JsProperty functions like x() from colliding with intended JS native
@@ -3390,8 +3382,6 @@
    */
   private Set<HasName> nameOfTargets = Sets.newHashSet();
 
-  private final JsOutputOption output;
-
   private final boolean optimize;
 
   private final TreeLogger logger;
@@ -3401,16 +3391,6 @@
   private final boolean incremental;
 
   /**
-   * All of the fields in String and Array need special handling for interop.
-   */
-  private final Map<JField, String> specialObfuscatedFields = Maps.newHashMap();
-
-  /**
-   * All of the methods in String and Array need special handling for interop.
-   */
-  private final Map<String, String> specialObfuscatedMethodSigs = Maps.newHashMap();
-
-  /**
    * If true, polymorphic functions are made anonymous vtable declarations and
    * not assigned topScope identifiers.
    */
@@ -3458,7 +3438,6 @@
     this.properties = properties;
 
     PrecompileTaskOptions options = compilerContext.getOptions();
-    this.output = options.getOutput();
     this.optimize = options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT;
     this.methodNameMappingMode = options.getMethodNameDisplayMode();
     assert methodNameMappingMode != null;
@@ -3466,45 +3445,6 @@
 
     this.stripStack = JsStackEmulator.getStackMode(properties) == JsStackEmulator.StackMode.STRIP;
     this.closureCompilerFormatEnabled = options.isClosureCompilerFormatEnabled();
-
-    /*
-     * Because we modify the JavaScript String prototype, all fields and
-     * polymorphic methods on String and super types need special handling.
-     */
-
-    // Object polymorphic
-    Map<String, String> namesToIdents = Maps.newHashMap();
-    namesToIdents.put("getClass", "gC");
-    namesToIdents.put("hashCode", "hC");
-    namesToIdents.put("equals", "eQ");
-    namesToIdents.put("toString", "tS");
-    namesToIdents.put("finalize", "fZ");
-
-    List<JMethod> methods = Lists.newArrayList(program.getTypeJavaLangObject().getMethods());
-    for (JMethod method : methods) {
-      if (method.canBePolymorphic()) {
-        String ident = namesToIdents.get(method.getName());
-        assert ident != null : method.getEnclosingType().getName() + "::" + method.getName() +
-            " is not in the list of known methods.";
-        specialObfuscatedMethodSigs.put(method.getSignature(), ident);
-      }
-    }
-
-    namesToIdents.clear();
-    // Object fields
-    namesToIdents.put("expando", "eX");
-    namesToIdents.put("typeMarker", "tM");
-    namesToIdents.put("castableTypeMap", "cM");
-    namesToIdents.put("___clazz", "cZ");
-
-    for (JField field : program.getTypeJavaLangObject().getFields()) {
-      if (!field.isStatic()) {
-        String ident = namesToIdents.get(field.getName());
-        assert ident != null : field.getEnclosingType().getName() + "::" + field.getName() +
-            " is not in the list of known fields.";
-        specialObfuscatedFields.put(field, ident);
-      }
-    }
   }
 
   /**
@@ -3581,32 +3521,6 @@
     return StringInterner.get().intern(JjsUtils.constructManglingSignature(x, mangledName));
   }
 
-  String mangleNameSpecialObfuscate(JField x) {
-    assert (specialObfuscatedFields.containsKey(x));
-    switch (output) {
-      case OBFUSCATED:
-        return specialObfuscatedFields.get(x);
-      case PRETTY:
-        return x.getName() + "$";
-      case DETAILED:
-        return mangleName(x) + "$";
-    }
-    throw new InternalCompilerException("Unknown output mode");
-  }
-
-  String mangleNameSpecialObfuscate(JMethod x) {
-    assert (specialObfuscatedMethodSigs.containsKey(x.getSignature()));
-    switch (output) {
-      case OBFUSCATED:
-        return specialObfuscatedMethodSigs.get(x.getSignature());
-      case PRETTY:
-        return x.getName() + "$";
-      case DETAILED:
-        return mangleNameForPoly(x) + "$";
-    }
-    throw new InternalCompilerException("Unknown output mode");
-  }
-
   private final Map<JType, JDeclarationStatement> classLiteralDeclarationsByType =
       Maps.newLinkedHashMap();
 
diff --git a/dev/core/test/com/google/gwt/core/ext/linker/SymbolMapTest.java b/dev/core/test/com/google/gwt/core/ext/linker/SymbolMapTest.java
index 4b80662..8b9beb0 100644
--- a/dev/core/test/com/google/gwt/core/ext/linker/SymbolMapTest.java
+++ b/dev/core/test/com/google/gwt/core/ext/linker/SymbolMapTest.java
@@ -22,8 +22,10 @@
 import com.google.gwt.dev.util.arg.OptionOptimize;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.thirdparty.guava.common.base.Function;
+import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Iterables;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
+import com.google.gwt.thirdparty.guava.common.collect.Multimap;
 import com.google.gwt.util.tools.Utility;
 
 import junit.framework.TestCase;
@@ -34,6 +36,7 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.Map;
 
 /**
@@ -217,32 +220,52 @@
       new com.google.gwt.dev.Compiler(options).run(logger);
       // Change parentDir for cached/pre-built reports
       String parentDir = options.getExtraDir() + "/" + benchmark;
-      for (Map<String, SimpleSymbolData> symbolDataBySymbolName :
+      for (Map<String, SimpleSymbolData> symbolDataByJsniIdentifier :
           loadSymbolMaps(new File(parentDir + "/symbolMaps/"))) {
-        assertTrue(!symbolDataBySymbolName.isEmpty());
-        assertNotNull(symbolDataBySymbolName.get(JSE_METHOD));
-        assertTrue(symbolDataBySymbolName.get(JSE_METHOD).isMethod());
-        assertFalse(symbolDataBySymbolName.get(JSE_METHOD).isField());
-        assertFalse(symbolDataBySymbolName.get(JSE_METHOD).isClass());
-        assertNotNull(symbolDataBySymbolName.get(JSE_FIELD));
-        assertTrue(symbolDataBySymbolName.get(JSE_FIELD).isField());
-        assertFalse(symbolDataBySymbolName.get(JSE_FIELD).isMethod());
-        assertFalse(symbolDataBySymbolName.get(JSE_FIELD).isClass());
-        assertNotNull(symbolDataBySymbolName.get(JSE_CLASS));
-        assertTrue(symbolDataBySymbolName.get(JSE_CLASS).isClass());
-        assertFalse(symbolDataBySymbolName.get(JSE_CLASS).isField());
-        assertFalse(symbolDataBySymbolName.get(JSE_CLASS).isMethod());
+        assertTrue(!symbolDataByJsniIdentifier.isEmpty());
+        assertNotNull(symbolDataByJsniIdentifier.get(JSE_METHOD));
+        assertTrue(symbolDataByJsniIdentifier.get(JSE_METHOD).isMethod());
+        assertFalse(symbolDataByJsniIdentifier.get(JSE_METHOD).isField());
+        assertFalse(symbolDataByJsniIdentifier.get(JSE_METHOD).isClass());
+        assertNotNull(symbolDataByJsniIdentifier.get(JSE_FIELD));
+        assertTrue(symbolDataByJsniIdentifier.get(JSE_FIELD).isField());
+        assertFalse(symbolDataByJsniIdentifier.get(JSE_FIELD).isMethod());
+        assertFalse(symbolDataByJsniIdentifier.get(JSE_FIELD).isClass());
+        assertNotNull(symbolDataByJsniIdentifier.get(JSE_CLASS));
+        assertTrue(symbolDataByJsniIdentifier.get(JSE_CLASS).isClass());
+        assertFalse(symbolDataByJsniIdentifier.get(JSE_CLASS).isField());
+        assertFalse(symbolDataByJsniIdentifier.get(JSE_CLASS).isMethod());
         if (optimizeLevel == OptionOptimize.OPTIMIZE_LEVEL_DRAFT) {
-          assertNotNull(symbolDataBySymbolName.get(UNINSTANTIABLE_CLASS));
+          assertNotNull(symbolDataByJsniIdentifier.get(UNINSTANTIABLE_CLASS));
         } else {
-          assertNull(symbolDataBySymbolName.get(UNINSTANTIABLE_CLASS));
+          assertNull(symbolDataByJsniIdentifier.get(UNINSTANTIABLE_CLASS));
         }
+        assertSymbolUniquenessForMethods(symbolDataByJsniIdentifier);
       }
     } finally {
       Util.recursiveDelete(work, false);
     }
   }
 
+  private void assertSymbolUniquenessForMethods(
+      Map<String, SimpleSymbolData> symbolDataByJsniIdentifier) {
+    Multimap<String, SymbolData> methodSymbolDataBySymbol = HashMultimap.create();
+    for (SymbolData symbolData : symbolDataByJsniIdentifier.values()) {
+      if (symbolData.isMethod()) {
+        methodSymbolDataBySymbol.put(symbolData.getSymbolName(), symbolData);
+      }
+    }
+    Iterator<String> iterator = methodSymbolDataBySymbol.keySet().iterator();
+    while (iterator.hasNext()) {
+      String key = iterator.next();
+      if (methodSymbolDataBySymbol.get(key).size() <= 1) {
+        iterator.remove();
+      }
+    }
+    assertTrue("The following method symbols where not unique " + methodSymbolDataBySymbol,
+        methodSymbolDataBySymbol.isEmpty());
+  }
+
   public void testSymbolMapSanityDraft() throws Exception {
     assertSymbolMapSanity(OptionOptimize.OPTIMIZE_LEVEL_DRAFT);
   }
diff --git a/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java b/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java
index 7798933..4344807 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java
@@ -172,7 +172,7 @@
         "var stackIndex;$stack[stackIndex=++$stackDepth]=onModuleLoad;" +
         "$location[stackIndex]='EntryPoint.java:'+'6',$clinit_EntryPoint();" +
         "throw new RuntimeException(" +
-        "($tmp=($location[stackIndex]='EntryPoint.java:'+'4',thing).toString$()," +
+        "($tmp=($location[stackIndex]='EntryPoint.java:'+'4',thing).toString()," +
         "$location[stackIndex]='EntryPoint.java:'+'7',$tmp))" +
         "}");
   }
diff --git a/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java b/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java
index 967913e..776e4cf 100644
--- a/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java
+++ b/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java
@@ -400,6 +400,13 @@
           int idx = line.indexOf(',');
           String symbol = line.substring(0, idx);
           String symbolData = line.substring(idx + 1);
+
+          // Is it a method symbol?
+          if (!symbolData.substring(0, symbolData.indexOf(",")).contains(")")) {
+            // Methods jsni names have to contain parens.
+            continue;
+          }
+
           if (requiredSymbols.contains(symbol) || !lazyLoad) {
             symbolsLeftToFind.remove(symbol);
             toReturn.put(symbol, symbolData);