Fix SingleJsoImpl hosted mode crash with contravariant return types in virtual
override scenario.

Put simply

class B extends A{}

interface I {
  A returnsA();
}

class Jso extends JavaScriptObject {
  B returnsA();
}

class JsoSub extends Jso implements I {}

crashes in CCL$MyInstanceMethodOracle.findOriginalDeclaringClass().

Web mode already handles this correctly.

Patch by: bobv
Review by: scottb



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6412 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 14f1a4c..61e9cea 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -35,7 +35,9 @@
 import com.google.gwt.dev.shell.rewrite.HasAnnotation;
 import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter;
 import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle;
+import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
 import com.google.gwt.dev.util.JsniRef;
+import com.google.gwt.dev.util.Name;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.Name.InternalName;
 import com.google.gwt.dev.util.Name.SourceOrBinaryName;
@@ -61,9 +63,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedMap;
+import java.util.SortedSet;
 import java.util.Stack;
-import java.util.TreeMap;
+import java.util.TreeSet;
 
 /**
  * An isolated {@link ClassLoader} for running all user code. All user files are
@@ -360,21 +362,34 @@
     private final Map<String, Set<JClassType>> signatureToDeclaringClasses = new HashMap<String, Set<JClassType>>();
 
     public MyInstanceMethodOracle(Set<JClassType> jsoTypes,
-        JClassType javaLangObject) {
-      // Populate the map.
+        JClassType javaLangObject, SingleJsoImplData jsoData) {
+
+      // Record that the JSO implements its own methods
       for (JClassType type : jsoTypes) {
         for (JMethod method : type.getMethods()) {
           if (!method.isStatic()) {
-            String signature = createSignature(method);
-            Set<JClassType> declaringClasses = signatureToDeclaringClasses.get(signature);
-            if (declaringClasses == null) {
-              declaringClasses = new HashSet<JClassType>();
-              signatureToDeclaringClasses.put(signature, declaringClasses);
-            }
-            declaringClasses.add(type);
+            assert !method.isAbstract() : "Abstract method in JSO type "
+                + method;
+            add(type, method);
           }
         }
       }
+
+      /*
+       * Record the implementing types for methods defined in SingleJsoImpl
+       * interfaces. We have to make this pass because of possible variance in
+       * the return types between the abstract method declaration in the
+       * interface and the concrete method.
+       */
+      for (String intfName : jsoData.getSingleJsoIntfTypes()) {
+        // We only store the name in the data block to keep it lightweight
+        JClassType intf = typeOracle.findType(Name.InternalName.toSourceName(intfName));
+        JClassType jso = typeOracle.getSingleJsoImpl(intf);
+        for (JMethod method : intf.getMethods()) {
+          add(jso, method);
+        }
+      }
+
       // Object clobbers everything.
       for (JMethod method : javaLangObject.getMethods()) {
         if (!method.isStatic()) {
@@ -389,6 +404,7 @@
     public String findOriginalDeclaringClass(String desc, String signature) {
       // Lookup the method.
       Set<JClassType> declaringClasses = signatureToDeclaringClasses.get(signature);
+      assert declaringClasses != null : "No classes for " + signature;
       if (declaringClasses.size() == 1) {
         // Shortcut: if there's only one answer, it must be right.
         return createDescriptor(declaringClasses.iterator().next());
@@ -413,6 +429,20 @@
           + signature + "' from class '" + desc + "'");
     }
 
+    /**
+     * Record that a given JSO type contains the concrete implementation of a
+     * (possibly abstract) method.
+     */
+    private void add(JClassType type, JMethod method) {
+      String signature = createSignature(method);
+      Set<JClassType> declaringClasses = signatureToDeclaringClasses.get(signature);
+      if (declaringClasses == null) {
+        declaringClasses = new HashSet<JClassType>();
+        signatureToDeclaringClasses.put(signature, declaringClasses);
+      }
+      declaringClasses.add(type);
+    }
+
     private String createDescriptor(JClassType type) {
       String jniSignature = type.getJNISignature();
       return jniSignature.substring(1, jniSignature.length() - 1);
@@ -432,6 +462,177 @@
   }
 
   /**
+   * Cook up the data we need to support JSO subtypes that implement interfaces
+   * with methods. This includes the set of SingleJsoImpl interfaces actually
+   * implemented by a JSO type, the mangled method names, and the names of the
+   * Methods that should actually implement the virtual functions.
+   * 
+   * Given the current implementation of JSO$ and incremental execution of
+   * rebinds, it's not possible for Generators to produce additional
+   * JavaScriptObject subtypes, so this data can remain static.
+   */
+  private class MySingleJsoImplData implements SingleJsoImplData {
+    private final SortedSet<String> mangledNames = new TreeSet<String>();
+    private final Map<String, com.google.gwt.dev.asm.commons.Method> mangledNamesToDeclarations = new HashMap<String, com.google.gwt.dev.asm.commons.Method>();
+    private final Map<String, com.google.gwt.dev.asm.commons.Method> mangledNamesToImplementations = new HashMap<String, com.google.gwt.dev.asm.commons.Method>();
+    private final SortedSet<String> unmodifiableNames = Collections.unmodifiableSortedSet(mangledNames);
+    private final Set<String> unmodifiableIntfNames = Collections.unmodifiableSet(singleJsoImplTypes);
+
+    public MySingleJsoImplData() {
+      // Loop over all interfaces with JSO implementations
+      typeLoop : for (JClassType type : typeOracle.getSingleJsoImplInterfaces()) {
+        assert type.isInterface() == type : "Expecting interfaces only";
+
+        /*
+         * By preemptively adding all possible mangled names by which a method
+         * could be called, we greatly simplify the logic necessary to rewrite
+         * the call-site.
+         * 
+         * interface A {void m();}
+         * 
+         * interface B extends A {void z();}
+         * 
+         * becomes
+         * 
+         * c_g_p_A_m() -> JsoA$.m$()
+         * 
+         * c_g_p_B_m() -> JsoA$.m$()
+         * 
+         * c_g_p_B_z() -> JsoB$.z$()
+         */
+        for (JMethod intfMethod : type.getOverridableMethods()) {
+          assert intfMethod.isAbstract() : "Expecting only abstract methods";
+
+          /*
+           * It is necessary to locate the implementing type on a per-method
+           * basis. Consider the case of
+           * 
+           * @SingleJsoImpl interface C extends A, B {}
+           * 
+           * Methods inherited from interfaces A and B must be dispatched to
+           * their respective JSO implementations.
+           */
+          JClassType implementingType = typeOracle.getSingleJsoImpl(intfMethod.getEnclosingType());
+
+          if (implementingType == null) {
+            /*
+             * This means that there is no concrete implementation of the
+             * interface by a JSO. Any implementation that might be created by a
+             * Generator won't be a JSO subtype, so we'll just ignore it as an
+             * actionable type. Were Generators ever able to create new JSO
+             * subtypes, we'd have to speculatively rewrite the callsite.
+             */
+            continue typeLoop;
+          }
+
+          /*
+           * Record the type as being actionable.
+           */
+          singleJsoImplTypes.add(canonicalizeClassName(getBinaryName(type)));
+
+          /*
+           * The mangled name adds the current interface like
+           * 
+           * com_foo_Bar_methodName
+           */
+          String mangledName = getBinaryName(type).replace('.', '_') + "_"
+              + intfMethod.getName();
+          mangledNames.add(mangledName);
+
+          JType[] parameterTypes = new JType[intfMethod.getParameters().length];
+          for (int i = 0; i < parameterTypes.length; i++) {
+            parameterTypes[i] = intfMethod.getParameters()[i].getType();
+          }
+
+          /*
+           * Handle virtual overrides by finding the method that we would
+           * normally invoke and using its declaring class as the dispatch
+           * target.
+           */
+          JMethod implementingMethod;
+          while ((implementingMethod = implementingType.findMethod(
+              intfMethod.getName(), parameterTypes)) == null) {
+            implementingType = implementingType.getSuperclass();
+          }
+          assert implementingMethod != null && implementingType != null : "Unable to find virtual override for "
+              + intfMethod.toString();
+
+          /*
+           * Create a pseudo-method declaration for the interface method. This
+           * should look something like
+           * 
+           * ReturnType method$ (ParamType, ParamType)
+           * 
+           * This must be kept in sync with the WriteJsoImpl class.
+           */
+          {
+            String decl = getBinaryOrPrimitiveName(intfMethod.getReturnType())
+                + " " + intfMethod.getName() + "(";
+            for (JType paramType : parameterTypes) {
+              decl += ",";
+              decl += getBinaryOrPrimitiveName(paramType);
+            }
+            decl += ")";
+
+            com.google.gwt.dev.asm.commons.Method declaration = com.google.gwt.dev.asm.commons.Method.getMethod(decl);
+            mangledNamesToDeclarations.put(mangledName, declaration);
+          }
+
+          /*
+           * Cook up the a pseudo-method declaration for the concrete type. This
+           * should look something like
+           * 
+           * ReturnType method$ (JsoType, ParamType, ParamType)
+           * 
+           * This must be kept in sync with the WriteJsoImpl class.
+           */
+          {
+            String returnName = getBinaryOrPrimitiveName(implementingMethod.getReturnType());
+            String jsoName = getBinaryOrPrimitiveName(implementingType);
+
+            String decl = returnName + " " + intfMethod.getName() + "$ ("
+                + jsoName;
+            for (JType paramType : parameterTypes) {
+              decl += ",";
+              decl += getBinaryOrPrimitiveName(paramType);
+            }
+            decl += ")";
+
+            com.google.gwt.dev.asm.commons.Method toImplement = com.google.gwt.dev.asm.commons.Method.getMethod(decl);
+            mangledNamesToImplementations.put(mangledName, toImplement);
+          }
+        }
+      }
+
+      if (logger.isLoggable(Type.SPAM)) {
+        TreeLogger dumpLogger = logger.branch(Type.SPAM,
+            "SingleJsoImpl method mappings");
+        for (Map.Entry<String, com.google.gwt.dev.asm.commons.Method> entry : mangledNamesToImplementations.entrySet()) {
+          dumpLogger.log(Type.SPAM, entry.getKey() + " -> " + entry.getValue());
+        }
+      }
+    }
+
+    public com.google.gwt.dev.asm.commons.Method getDeclaration(
+        String mangledName) {
+      return mangledNamesToDeclarations.get(mangledName);
+    }
+
+    public com.google.gwt.dev.asm.commons.Method getImplementation(
+        String mangledName) {
+      return mangledNamesToImplementations.get(mangledName);
+    }
+
+    public SortedSet<String> getMangledNames() {
+      return unmodifiableNames;
+    }
+
+    public Set<String> getSingleJsoIntfTypes() {
+      return unmodifiableIntfNames;
+    }
+  }
+
+  /**
    * The names of the bridge classes.
    */
   private static final Map<String, Class<?>> BRIDGE_CLASS_NAMES = new HashMap<String, Class<?>>();
@@ -643,15 +844,12 @@
         jsoSuperTypes.put(binaryName, types);
       }
 
-      // computeSingleJsoImplData has two out parameters
-      SortedMap<String, com.google.gwt.dev.asm.commons.Method> mangledNamesToImplementations = new TreeMap<String, com.google.gwt.dev.asm.commons.Method>();
-      computeSingleJsoImplData(singleJsoImplTypes,
-          mangledNamesToImplementations);
+      SingleJsoImplData singleJsoImplData = new MySingleJsoImplData();
 
       MyInstanceMethodOracle mapper = new MyInstanceMethodOracle(jsoTypes,
-          typeOracle.getJavaLangObject());
+          typeOracle.getJavaLangObject(), singleJsoImplData);
       classRewriter = new HostedModeClassRewriter(jsoTypeNames, jsoSuperTypes,
-          mangledNamesToImplementations, singleJsoImplTypes, mapper);
+          singleJsoImplData, mapper);
     } else {
       // If we couldn't find the JSO class, we don't need to do any rewrites.
       classRewriter = null;
@@ -838,125 +1036,6 @@
     return lookupClassName;
   }
 
-  /**
-   * Cook up the data we need to support JSO subtypes that implement interfaces
-   * with methods. This includes the set of SingleJsoImpl interfaces actually
-   * implemented by a JSO type, the mangled method names, and the names of the
-   * Methods that should actually implement the virtual functions.
-   * 
-   * Given the current implementation of JSO$ and incremental execution of
-   * rebinds, it's not possible for Generators to produce additional
-   * JavaScriptObject subtypes, so this data can remain static.
-   */
-  private void computeSingleJsoImplData(
-      Set<String> singleJsoImplTypes,
-      SortedMap<String, com.google.gwt.dev.asm.commons.Method> mangledNamesToImplementations) {
-
-    // Loop over all types declared with the SingleJsoImpl annotation
-    typeLoop : for (JClassType type : typeOracle.getSingleJsoImplInterfaces()) {
-      assert type.isInterface() == type : "Expecting interfaces only";
-
-      /*
-       * By preemptively adding all possible mangled names by which a method
-       * could be called, we greatly simplify the logic necessary to rewrite the
-       * call-site.
-       * 
-       * interface A {void m();}
-       * 
-       * interface B extends A {void z();}
-       * 
-       * becomes
-       * 
-       * c_g_p_A_m() -> JsoA$.m$()
-       * 
-       * c_g_p_B_m() -> JsoA$.m$()
-       * 
-       * c_g_p_B_z() -> JsoB$.z$()
-       */
-      for (JMethod m : type.getOverridableMethods()) {
-        assert m.isAbstract() : "Expecting only abstract methods";
-
-        /*
-         * It is necessary to locate the implementing type on a per-method
-         * basis. Consider the case of
-         * 
-         * @SingleJsoImpl interface C extends A, B {}
-         * 
-         * Methods inherited from interfaces A and B must be dispatched to their
-         * respective JSO implementations.
-         */
-        JClassType implementingType = typeOracle.getSingleJsoImpl(m.getEnclosingType());
-
-        if (implementingType == null) {
-          /*
-           * This means that there is no concrete implementation of the
-           * interface by a JSO. Any implementation that might be created by a
-           * Generator won't be a JSO subtype, so we'll just ignore it as an
-           * actionable type. Were Generators ever able to create new JSO
-           * subtypes, we'd have to speculatively rewrite the callsite.
-           */
-          continue typeLoop;
-        }
-
-        /*
-         * Record the type as being actionable.
-         */
-        singleJsoImplTypes.add(canonicalizeClassName(getBinaryName(type)));
-
-        /*
-         * The mangled name adds the current interface like
-         * 
-         * com_foo_Bar_methodName
-         */
-        String mangledName = getBinaryName(type).replace('.', '_') + "_"
-            + m.getName();
-
-        JType[] parameterTypes = new JType[m.getParameters().length];
-        for (int i = 0; i < parameterTypes.length; i++) {
-          parameterTypes[i] = m.getParameters()[i].getType();
-        }
-
-        /*
-         * Handle virtual overrides by finding the method that we would normally
-         * invoke and using its declaring class as the dispatch target.
-         */
-        while (implementingType.findMethod(m.getName(), parameterTypes) == null) {
-          implementingType = implementingType.getSuperclass();
-        }
-        assert implementingType != null : "Unable to find virtual override for "
-            + m.toString();
-
-        /*
-         * Cook up the a pseudo-method declaration for the concrete type. This
-         * should look something like
-         * 
-         * ReturnType method$ (JsoType, ParamType, ParamType)
-         * 
-         * This must be kept in sync with the WriteJsoImpl class.
-         */
-        String decl = getBinaryOrPrimitiveName(m.getReturnType()) + " "
-            + m.getName() + "$ (" + getBinaryOrPrimitiveName(implementingType);
-        for (JType paramType : parameterTypes) {
-          decl += ",";
-          decl += getBinaryOrPrimitiveName(paramType);
-        }
-        decl += ")";
-
-        com.google.gwt.dev.asm.commons.Method toImplement = com.google.gwt.dev.asm.commons.Method.getMethod(decl);
-
-        mangledNamesToImplementations.put(mangledName, toImplement);
-      }
-    }
-
-    if (logger.isLoggable(Type.SPAM)) {
-      TreeLogger dumpLogger = logger.branch(Type.SPAM,
-          "SingleJsoImpl method mappings");
-      for (Map.Entry<String, com.google.gwt.dev.asm.commons.Method> entry : mangledNamesToImplementations.entrySet()) {
-        dumpLogger.log(Type.SPAM, entry.getKey() + " -> " + entry.getValue());
-      }
-    }
-  }
-
   private byte[] findClassBytes(String className) {
     if (JavaScriptHost.class.getName().equals(className)) {
       // No need to rewrite.
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
index 03c32d5..79fc500 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
@@ -30,7 +30,7 @@
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedMap;
+import java.util.SortedSet;
 
 /**
  * This class performs any and all byte code rewriting needed to make hosted
@@ -86,6 +86,33 @@
     String findOriginalDeclaringClass(String declaredClass, String signature);
   }
 
+  /**
+   * Contains data about how SingleJsoImpl methods are to be dispatched.
+   */
+  public interface SingleJsoImplData {
+    /**
+     * Returns a Method corresponding to the declaration of the abstract method
+     * in an interface type.
+     */
+    Method getDeclaration(String mangledName);
+
+    /**
+     * Return a Method corresponding to the concrete implementation of the
+     * method in a JSO type.
+     */
+    Method getImplementation(String mangledName);
+
+    /**
+     * Returns all of the mangled method names for SingleJsoImpl methods.
+     */
+    SortedSet<String> getMangledNames();
+
+    /**
+     * Returns the internal names of all interface types implemented by JSOs.
+     */
+    Set<String> getSingleJsoIntfTypes();
+  }
+
   static final String JAVASCRIPTOBJECT_DESC = JsValueGlue.JSO_CLASS.replace(
       '.', '/');
 
@@ -114,6 +141,8 @@
    */
   private final Set<String> jsoIntfDescs;
 
+  private final SingleJsoImplData jsoData;
+
   /**
    * Records the superclass of every JSO for generating empty JSO interfaces.
    */
@@ -124,10 +153,6 @@
    */
   private InstanceMethodOracle mapper;
 
-  private final SortedMap<String, Method> mangledNamesToImplementations;
-
-  private final Set<String> singleJsoImplTypes;
-
   /**
    * Creates a new {@link HostedModeClassRewriter} for a specified set of
    * subclasses of JavaScriptObject.
@@ -137,9 +162,8 @@
    * @param mapper maps methods to the class in which they are declared
    */
   public HostedModeClassRewriter(Set<String> jsoSubtypes,
-      Map<String, List<String>> jsoSuperTypes,
-      SortedMap<String, Method> mangledNamesToImplementations,
-      Set<String> singleJsoImplTypes, InstanceMethodOracle mapper) {
+      Map<String, List<String>> jsoSuperTypes, SingleJsoImplData jsoData,
+      InstanceMethodOracle mapper) {
     Set<String> buildJsoIntfDescs = new HashSet<String>();
     Set<String> buildJsoImplDescs = new HashSet<String>();
     Map<String, List<String>> buildJsoSuperDescs = new HashMap<String, List<String>>();
@@ -160,8 +184,7 @@
     this.jsoIntfDescs = Collections.unmodifiableSet(buildJsoIntfDescs);
     this.jsoImplDescs = Collections.unmodifiableSet(buildJsoImplDescs);
     this.jsoSuperDescs = Collections.unmodifiableMap(buildJsoSuperDescs);
-    this.mangledNamesToImplementations = Collections.unmodifiableSortedMap(mangledNamesToImplementations);
-    this.singleJsoImplTypes = Collections.unmodifiableSet(singleJsoImplTypes);
+    this.jsoData = jsoData;
     this.mapper = mapper;
   }
 
@@ -202,14 +225,12 @@
     // v = new CheckClassAdapter(v);
     // v = new TraceClassVisitor(v, new PrintWriter(System.out));
 
-    v = new RewriteSingleJsoImplDispatches(v, typeOracle, singleJsoImplTypes,
-        mangledNamesToImplementations);
+    v = new RewriteSingleJsoImplDispatches(v, typeOracle, jsoData);
 
     v = new RewriteRefsToJsoClasses(v, jsoIntfDescs, mapper);
 
     if (jsoImplDescs.contains(desc)) {
-      v = WriteJsoImpl.create(v, desc, jsoIntfDescs, mapper,
-          mangledNamesToImplementations);
+      v = WriteJsoImpl.create(v, desc, jsoIntfDescs, mapper, jsoData);
     }
 
     v = new RewriteJsniMethods(v, anonymousClassMap);
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
index 00bdca1..717a975 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
@@ -24,6 +24,7 @@
 import com.google.gwt.dev.asm.Opcodes;
 import com.google.gwt.dev.asm.Type;
 import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
 import com.google.gwt.dev.util.collect.Maps;
 import com.google.gwt.dev.util.collect.Sets;
 
@@ -67,11 +68,10 @@
     public void visitMethodInsn(int opcode, String owner, String name,
         String desc) {
       if (opcode == Opcodes.INVOKEINTERFACE) {
-        if (singleJsoImplTypes.contains(owner)) {
+        if (jsoData.getSingleJsoIntfTypes().contains(owner)) {
           // Simple case; referring directly to a SingleJso interface.
           name = owner.replace('/', '_') + "_" + name;
-          assert mangledNamesToImplementations.containsKey(name) : "Missing "
-              + name;
+          assert jsoData.getMangledNames().contains(name) : "Missing " + name;
 
         } else {
           /*
@@ -86,7 +86,7 @@
            * void bar() { ((IB) object).foo(); }
            */
           for (String intf : computeAllInterfaces(owner)) {
-            if (singleJsoImplTypes.contains(intf)) {
+            if (jsoData.getSingleJsoIntfTypes().contains(intf)) {
               /*
                * Check that it really should be mangled and is not a reference
                * to a method defined in a non-singleJso super-interface. If
@@ -95,7 +95,7 @@
                * is undefined.
                */
               String maybeMangled = intf.replace('/', '_') + "_" + name;
-              Method method = mangledNamesToImplementations.get(maybeMangled);
+              Method method = jsoData.getImplementation(maybeMangled);
               if (method != null) {
                 /*
                  * Found a method with the right name, but we need to check the
@@ -127,17 +127,14 @@
   private final Set<String> implementedMethods = new HashSet<String>();
   private boolean inSingleJsoImplInterfaceType;
   private Map<String, Set<String>> intfNamesToAllInterfaces = Maps.create();
-  private final SortedMap<String, Method> mangledNamesToImplementations;
-  private final Set<String> singleJsoImplTypes;
+  private final SingleJsoImplData jsoData;
   private final TypeOracle typeOracle;
 
   public RewriteSingleJsoImplDispatches(ClassVisitor v, TypeOracle typeOracle,
-      Set<String> singleJsoImplTypes,
-      SortedMap<String, Method> mangledNamesToImplementations) {
+      SingleJsoImplData jsoData) {
     super(v);
     this.typeOracle = typeOracle;
-    this.singleJsoImplTypes = Collections.unmodifiableSet(singleJsoImplTypes);
-    this.mangledNamesToImplementations = Collections.unmodifiableSortedMap(mangledNamesToImplementations);
+    this.jsoData = jsoData;
   }
 
   @Override
@@ -156,7 +153,8 @@
     }
 
     currentTypeName = name;
-    inSingleJsoImplInterfaceType = singleJsoImplTypes.contains(name);
+    inSingleJsoImplInterfaceType = jsoData.getSingleJsoIntfTypes().contains(
+        name);
 
     /*
      * Implements objective #2: non-JSO types that implement a SingleJsoImpl
@@ -166,7 +164,7 @@
      */
     if (interfaces != null && (access & Opcodes.ACC_INTERFACE) == 0) {
       Set<String> toStub = computeAllInterfaces(interfaces);
-      toStub.retainAll(singleJsoImplTypes);
+      toStub.retainAll(jsoData.getSingleJsoIntfTypes());
 
       for (String stubIntr : toStub) {
         writeTrampoline(stubIntr);
@@ -261,8 +259,11 @@
     String name = typeName.replace('/', '_');
     String prefix = name + "_";
     String suffix = name + "`";
-    SortedMap<String, Method> toReturn = new TreeMap<String, Method>(
-        mangledNamesToImplementations.subMap(prefix, suffix));
+    SortedMap<String, Method> toReturn = new TreeMap<String, Method>();
+
+    for (String mangledName : jsoData.getMangledNames().subSet(prefix, suffix)) {
+      toReturn.put(mangledName, jsoData.getImplementation(mangledName));
+    }
     toReturn.keySet().removeAll(implementedMethods);
     return toReturn;
   }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteJsoImpl.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteJsoImpl.java
index 1054421..28081f1 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteJsoImpl.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteJsoImpl.java
@@ -23,9 +23,9 @@
 import com.google.gwt.dev.asm.Type;
 import com.google.gwt.dev.asm.commons.Method;
 import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle;
+import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
 
 import java.util.ArrayList;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -58,13 +58,13 @@
      * <code>JavaScriptObject</code> and all subclasses.
      */
     private final Set<String> jsoDescriptors;
-    private final Map<String, Method> methodsToImplement;
+    private final SingleJsoImplData jsoData;
 
     public ForJsoDollar(ClassVisitor cv, Set<String> jsoDescriptors,
-        InstanceMethodOracle mapper, Map<String, Method> methodsToImplement) {
+        InstanceMethodOracle mapper, SingleJsoImplData jsoData) {
       super(cv, mapper);
       this.jsoDescriptors = jsoDescriptors;
-      this.methodsToImplement = methodsToImplement;
+      this.jsoData = jsoData;
     }
 
     @Override
@@ -89,8 +89,9 @@
       }
 
       // Implement the trampoline methods
-      for (Map.Entry<String, Method> entry : methodsToImplement.entrySet()) {
-        writeTrampoline(entry.getKey(), entry.getValue());
+      for (String mangledName : jsoData.getMangledNames()) {
+        writeTrampoline(mangledName, jsoData.getDeclaration(mangledName),
+            jsoData.getImplementation(mangledName));
       }
     }
 
@@ -113,24 +114,31 @@
      * In Java, it might look like:
      * 
      * <pre>
-     * public String com_google_Interface_someMethod(int a, double b) {
-     *   return com.google.MyJso$.someMethod$(this, a, b);
+     * interface Interface {
+     *   String someMethod(int a, double b);
+     * }
+     * 
+     * class J extends JSO implements I {
+     *   public String com_google_Interface_someMethod(int a, double b) {
+     *     return com.google.MyJso$.someMethod$(this, a, b);
+     *   }
      * }
      * </pre>
      * 
      * @param mangledName {@code com_google_gwt_sample_hello_client_Interface_a}
+     * @param interfaceMethod {@code java.lang.String a(int, double)}
      * @param implementingMethod {@code static final java.lang.String
      *          a$(com.google.gwt.sample.hello.client.Jso, ...);}
      */
-    private void writeTrampoline(String mangledName, Method implementingMethod) {
-      /*
-       * We derive the local descriptor by simply removing the first argument
-       * from the static method we want to call.
-       */
+    private void writeTrampoline(String mangledName, Method interfaceMethod,
+        Method implementingMethod) {
       assert implementingMethod.getArgumentTypes().length > 0;
-      String localDescriptor = "("
-          + implementingMethod.getDescriptor().substring(
-              1 + implementingMethod.getArgumentTypes()[0].getDescriptor().length());
+
+      /*
+       * The local descriptor is the same as the descriptor from the abstract
+       * method in the interface.
+       */
+      String localDescriptor = interfaceMethod.getDescriptor();
       Method localMethod = new Method(mangledName, localDescriptor);
 
       /*
@@ -217,10 +225,10 @@
    */
   public static ClassVisitor create(ClassVisitor cv, String classDescriptor,
       Set<String> jsoDescriptors, InstanceMethodOracle mapper,
-      Map<String, Method> methodsToImplement) {
+      SingleJsoImplData singleJsoImplData) {
 
     if (classDescriptor.equals(HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC)) {
-      return new ForJsoDollar(cv, jsoDescriptors, mapper, methodsToImplement);
+      return new ForJsoDollar(cv, jsoDescriptors, mapper, singleJsoImplData);
     } else {
       return new ForJsoInterface(cv, mapper);
     }
diff --git a/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java b/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java
index 80966f5..c0f93d4 100644
--- a/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java
@@ -31,6 +31,12 @@
     int getLength();
 
     JavaScriptObject getObject(int i);
+
+    /**
+     * Used to test virtual override where the implementation has a narrower
+     * return type.
+     */
+    Wide wide();
   }
 
   /**
@@ -70,6 +76,15 @@
   }
 
   /**
+   * Used for testing virtual overrides.
+   */
+  static class Narrow extends Wide {
+    public String toString() {
+      return "Narrow";
+    }
+  }
+
+  /**
    * This is a base class that is used to test adding interfaces to a JSO via a
    * subclass.
    */
@@ -84,6 +99,10 @@
     public final native JavaScriptObject getObject(int i) /*-{
       return this[i];
     }-*/;
+
+    public final Narrow wide() {
+      return new Narrow();
+    }
   }
 
   /**
@@ -98,6 +117,12 @@
     }
   }
 
+  /**
+   * Used for testing virtual overrides.
+   */
+  static class Wide {
+  }
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.dev.jjs.CompilerSuite";
@@ -151,5 +176,6 @@
     Arrayish array = PlainJsoWithInterface.create();
     assertEquals(0, array.getLength());
     assertNull(array.getObject(0));
+    assertEquals("Narrow", array.wide().toString());
   }
 }