JsoNormalizer did not give unique names to devirtualized
methods from Object overrides or DualJsoImpl types.  The
obfuscated namer papered over this, but if you turn on
pretty mode, you can run into problems if two interfaces
have DualJsoImpl types with the same method names.

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10459 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 287f1c1..8cfa63e 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
@@ -153,6 +153,7 @@
 import com.google.gwt.dev.util.collect.IdentityHashSet;
 import com.google.gwt.dev.util.collect.Lists;
 import com.google.gwt.dev.util.collect.Maps;
+import com.google.gwt.dev.util.collect.Sets;
 
 import java.io.StringReader;
 import java.util.ArrayList;
@@ -774,6 +775,9 @@
           }
         }
       }
+      
+      // TODO(zundel): Check that each unique method has a unique
+      // name / poly name.
     }
 
     @Override
@@ -1419,7 +1423,13 @@
       if (x.getSuperClass() != null && !alreadyRan.contains(x)) {
         accept(x.getSuperClass());
       }
+      
+      return super.visit(x, ctx);
+    }
 
+    @Override
+    public boolean visit(JDeclaredType x, Context ctx) {
+      checkForDupMethods(x);
       return true;
     }
 
@@ -1610,6 +1620,20 @@
       return false;
     }
 
+    private void checkForDupMethods(JDeclaredType x) {
+      // Sanity check to see that all methods are uniquely named.
+      List<JMethod> methods = x.getMethods();
+      Set<String> methodSignatures = Sets.create();
+      for (JMethod method : methods) {
+        String sig = method.getSignature();
+        if (methodSignatures.contains(sig)) {
+          throw new InternalCompilerException("Signature collision in Type " + x.getName()
+              + " for method " + sig);
+        }
+        methodSignatures = Sets.add(methodSignatures, sig);
+      }
+    }
+
     private JsExpression createAssignment(JsExpression lhs, JsExpression rhs) {
       return new JsBinaryOperation(lhs.getSourceInfo(), JsBinaryOperator.ASG, lhs, rhs);
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
index 75b2522..31d952b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
@@ -151,6 +151,12 @@
   private final JMethod isJavaObjectMethod;
 
   /**
+   * Key is the method signature, value is the number of unique instances with
+   * the same signature.
+   */
+  private Map<String, Integer> jsoMethodInstances = new HashMap<String, Integer>();
+
+  /**
    * Contains the set of devirtualizing methods that replace polymorphic calls
    * to Object methods.
    */
@@ -224,10 +230,21 @@
     SourceInfo sourceInfo = jsoType.getSourceInfo().makeChild(SourceOrigin.UNKNOWN);
 
     // Create the new method.
-    String name = polyMethod.getName() + "__devirtual$";
+    String prefix;
+    Integer methodCount;
+    methodCount = jsoMethodInstances.get(polyMethod.getSignature());
+    if (methodCount == null) {
+      prefix = polyMethod.getName();
+      methodCount = 0;
+    } else {
+      prefix = polyMethod.getName() + methodCount;
+      methodCount++;
+    }
+    jsoMethodInstances.put(polyMethod.getSignature(), methodCount);
+    String devirtualName = prefix + "__devirtual$";
     JMethod newMethod =
-        program.createMethod(sourceInfo, name, jsoType, polyMethod.getType(), false, true, true,
-            AccessModifier.PUBLIC, false);
+        program.createMethod(sourceInfo, devirtualName, jsoType, polyMethod.getType(), false, true,
+            true, AccessModifier.PUBLIC, false);
     newMethod.setSynthetic();
 
     // Setup parameters.
@@ -297,4 +314,4 @@
     }
     return staticImpl;
   }
-}
\ No newline at end of file
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsoDevirtualizerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsoDevirtualizerTest.java
new file mode 100644
index 0000000..b91092f
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsoDevirtualizerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2011 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.impl;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JProgram;
+
+/**
+ * Tests for the {@link JsoDevirtualizer} visitor.
+ */
+public class JsoDevirtualizerTest extends OptimizerTestBase {
+
+  /**
+   * JsoDevirtualizer should allow dual Java/JSO implementations of the same
+   * interface, so long as there is only one of each. If there are multiple
+   * methods with the same method name, it should distinguish between them.
+   */
+  public void testDualJsoImpl() throws UnableToCompleteException {
+
+    sourceOracle.addOrReplace(new MockJavaResource("com.google.gwt.lang.Cast") {
+      @Override
+      public CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package com.google.gwt.lang;");
+        code.append("public class Cast {");
+        code.append("  public static boolean isJavaObject(Object o) { return true; };");
+        code.append("  public static boolean isJavaScriptObject(Object o) { return true; };");
+        code.append("}");
+        return code;
+      }
+    });
+    
+    addSnippetImport("com.google.gwt.lang.Cast");
+    addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
+    
+    addSnippetClassDecl(
+        "interface Iface1 { int a(); }",
+        "static class J1 implements Iface1 {",
+        "  public int a() { return 1; }",
+        "}",
+        "static class Jso1 extends JavaScriptObject implements Iface1 {",
+        "  protected Jso1() { }",
+        "  public final int a() { return 2; }",
+        "  public static native Jso1 create() /*-{ return {} }-*/;",
+        "}",
+        "static interface Iface2 { int a(); }",
+        "static class J2 implements Iface2 {",
+        "  public int a() { return 3; }",
+        "}",
+        "static class Jso2 extends JavaScriptObject implements Iface2 {",
+        "  protected Jso2() { }", "  public final int a() { return 4; }",
+        "  public static native Jso2 create() /*-{ return {} }-*/;", 
+        "}",
+        "static Iface1 val1 = new J1();",
+        "static Iface1 val2 = Jso1.create();",
+        "static Iface2 val3 = new J2();",
+        "static Iface2 val4 = Jso2.create();");        
+
+    StringBuilder code = new StringBuilder();    
+    code.append("int result = val1.a() + val2.a() + val3.a() + val4.a();");
+            
+    // The salient point in the results below is that the JSO method used for
+    // val1 and val1 has a different name the method used for val2 and val3.
+    StringBuffer expected = new StringBuffer();
+    expected.append("int result = ");
+    expected.append("JavaScriptObject.a__devirtual$(EntryPoint.val1) + ");
+    expected.append("JavaScriptObject.a__devirtual$(EntryPoint.val2) + ");
+    expected.append("JavaScriptObject.a0__devirtual$(EntryPoint.val3) + ");
+    expected.append("JavaScriptObject.a0__devirtual$(EntryPoint.val4);");
+    
+    optimize("void", code.toString()).intoString(expected.toString());
+  }
+
+  @Override
+  protected boolean optimizeMethod(JProgram program, JMethod method) {
+    JsoDevirtualizer.exec(program);
+    return true;
+  }
+}