Rolling back DevMode JSO change due to VerifyError. git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8250 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 e35484c..a789ac0 100644 --- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java +++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -19,8 +19,11 @@ import com.google.gwt.core.client.GwtScriptOnly; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.typeinfo.JArrayType; import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JParameter; import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.TypeOracle; @@ -31,11 +34,14 @@ import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.shell.rewrite.HasAnnotation; import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter; -import com.google.gwt.dev.shell.rewrite.SingleJsoImplSupport; +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; +import com.google.gwt.dev.util.collect.Lists; import com.google.gwt.util.tools.Utility; import org.apache.commons.collections.map.AbstractReferenceMap; @@ -58,7 +64,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedSet; import java.util.Stack; +import java.util.TreeSet; /** * An isolated {@link ClassLoader} for running all user code. All user files are @@ -286,6 +294,11 @@ return null; } + // Map JSO type references to the appropriate impl class. + if (classRewriter.isJsoIntf(cls.getName())) { + cls = getClassFromBinaryName(cls.getName() + "$"); + } + /* * we need to create a new DispatchClassInfo since we have never seen this * class before under any source or binary class name @@ -342,6 +355,325 @@ } /** + * Implements {@link InstanceMethodOracle} on behalf of the + * {@link HostedModeClassRewriter}. Implemented using {@link TypeOracle}. + */ + private class MyInstanceMethodOracle implements InstanceMethodOracle { + + private final Map<String, Set<JClassType>> signatureToDeclaringClasses = new HashMap<String, Set<JClassType>>(); + + public MyInstanceMethodOracle(Set<JClassType> jsoTypes, + JClassType javaLangObject, SingleJsoImplData jsoData) { + + // Record that the JSO implements its own methods + for (JClassType type : jsoTypes) { + for (JMethod method : type.getMethods()) { + if (!method.isStatic()) { + 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()) { + String signature = createSignature(method); + Set<JClassType> declaringClasses = new HashSet<JClassType>(); + signatureToDeclaringClasses.put(signature, declaringClasses); + declaringClasses.add(javaLangObject); + } + } + } + + 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()); + } + // Must check for assignability. + String sourceName = desc.replace('/', '.'); + sourceName = sourceName.replace('$', '.'); + JClassType declaredType = typeOracle.findType(sourceName); + + // Check if I declare this directly. + if (declaringClasses.contains(declaredType)) { + return desc; + } + + // Check to see what type I am assignable to. + for (JClassType possibleSupertype : declaringClasses) { + if (declaredType.isAssignableTo(possibleSupertype)) { + return createDescriptor(possibleSupertype); + } + } + throw new IllegalArgumentException("Could not resolve signature '" + + 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); + } + + private String createSignature(JMethod method) { + StringBuffer sb = new StringBuffer(method.getName()); + sb.append('('); + for (JParameter param : method.getParameters()) { + sb.append(param.getType().getJNISignature()); + } + sb.append(')'); + sb.append(method.getReturnType().getJNISignature()); + String signature = sb.toString(); + return signature; + } + } + + /** + * 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, List<com.google.gwt.dev.asm.commons.Method>> mangledNamesToDeclarations = new HashMap<String, List<com.google.gwt.dev.asm.commons.Method>>(); + private final Map<String, List<com.google.gwt.dev.asm.commons.Method>> mangledNamesToImplementations = new HashMap<String, List<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); + + /* + * 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 = findOverloadUsingErasure( + implementingType, intfMethod)) == null) { + implementingType = implementingType.getSuperclass(); + } + // implementingmethod and implementingType cannot be null here + + /* + * 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().getErasedType()) + + " " + intfMethod.getName() + "("; + for (JParameter param : intfMethod.getParameters()) { + decl += ","; + decl += getBinaryOrPrimitiveName(param.getType().getErasedType()); + } + decl += ")"; + + com.google.gwt.dev.asm.commons.Method declaration = com.google.gwt.dev.asm.commons.Method.getMethod(decl); + addToMap(mangledNamesToDeclarations, 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().getErasedType()); + String jsoName = getBinaryOrPrimitiveName(implementingType); + + String decl = returnName + " " + intfMethod.getName() + "$ (" + + jsoName; + for (JParameter param : implementingMethod.getParameters()) { + decl += ","; + decl += getBinaryOrPrimitiveName(param.getType().getErasedType()); + } + decl += ")"; + + com.google.gwt.dev.asm.commons.Method toImplement = com.google.gwt.dev.asm.commons.Method.getMethod(decl); + addToMap(mangledNamesToImplementations, mangledName, toImplement); + } + } + } + + if (logger.isLoggable(Type.SPAM)) { + TreeLogger dumpLogger = logger.branch(Type.SPAM, + "SingleJsoImpl method mappings"); + for (Map.Entry<String, List<com.google.gwt.dev.asm.commons.Method>> entry : mangledNamesToImplementations.entrySet()) { + dumpLogger.log(Type.SPAM, entry.getKey() + " -> " + entry.getValue()); + } + } + } + + public List<com.google.gwt.dev.asm.commons.Method> getDeclarations( + String mangledName) { + List<com.google.gwt.dev.asm.commons.Method> toReturn = mangledNamesToDeclarations.get(mangledName); + return toReturn == null ? null : Collections.unmodifiableList(toReturn); + } + + public List<com.google.gwt.dev.asm.commons.Method> getImplementations( + String mangledName) { + List<com.google.gwt.dev.asm.commons.Method> toReturn = mangledNamesToImplementations.get(mangledName); + return toReturn == null ? toReturn + : Collections.unmodifiableList(toReturn); + } + + public SortedSet<String> getMangledNames() { + return unmodifiableNames; + } + + public Set<String> getSingleJsoIntfTypes() { + return unmodifiableIntfNames; + } + + /** + * Assumes that the usual case is a 1:1 mapping. + */ + private <K, V> void addToMap(Map<K, List<V>> map, K key, V value) { + List<V> list = map.get(key); + if (list == null) { + map.put(key, Lists.create(value)); + } else { + List<V> maybeOther = Lists.add(list, value); + if (maybeOther != list) { + map.put(key, maybeOther); + } + } + } + + /** + * Looks for a concrete implementation of <code>intfMethod</code> in + * <code>implementingType</code>. + */ + private JMethod findOverloadUsingErasure(JClassType implementingType, + JMethod intfMethod) { + + int numParams = intfMethod.getParameters().length; + JType[] erasedTypes = new JType[numParams]; + for (int i = 0; i < numParams; i++) { + erasedTypes[i] = intfMethod.getParameters()[i].getType().getErasedType(); + } + + outer : for (JMethod method : implementingType.getOverloads(intfMethod.getName())) { + JParameter[] params = method.getParameters(); + if (params.length != numParams) { + continue; + } + for (int i = 0; i < numParams; i++) { + if (params[i].getType().getErasedType() != erasedTypes[i]) { + continue outer; + } + } + return method; + } + return null; + } + } + + /** * The names of the bridge classes. */ private static final Map<String, Class<?>> BRIDGE_CLASS_NAMES = new HashMap<String, Class<?>>(); @@ -351,7 +683,7 @@ * space (thus, they bridge across the spaces). */ private static final Class<?>[] BRIDGE_CLASSES = new Class<?>[] { - ShellJavaScriptHost.class, GWTBridge.class, SingleJsoImplSupport.class}; + ShellJavaScriptHost.class, GWTBridge.class}; private static final boolean CLASS_DUMP = Boolean.getBoolean("gwt.dev.classDump"); @@ -529,10 +861,36 @@ ensureJavaScriptHostBytes(logger); - // Create a class rewriter based on availability of JSO class. + // Create a class rewriter based on all the subtypes of the JSO class. JClassType jsoType = typeOracle.findType(JsValueGlue.JSO_CLASS); if (jsoType != null) { - classRewriter = new HostedModeClassRewriter(typeOracle); + + // Create a set of binary names. + Set<JClassType> jsoTypes = new HashSet<JClassType>(); + JClassType[] jsoSubtypes = jsoType.getSubtypes(); + Collections.addAll(jsoTypes, jsoSubtypes); + jsoTypes.add(jsoType); + + Set<String> jsoTypeNames = new HashSet<String>(); + Map<String, List<String>> jsoSuperTypes = new HashMap<String, List<String>>(); + for (JClassType type : jsoTypes) { + List<String> types = new ArrayList<String>(); + types.add(getBinaryName(type.getSuperclass())); + for (JClassType impl : type.getImplementedInterfaces()) { + types.add(getBinaryName(impl)); + } + + String binaryName = getBinaryName(type); + jsoTypeNames.add(binaryName); + jsoSuperTypes.put(binaryName, types); + } + + SingleJsoImplData singleJsoImplData = new MySingleJsoImplData(); + + MyInstanceMethodOracle mapper = new MyInstanceMethodOracle(jsoTypes, + typeOracle.getJavaLangObject(), singleJsoImplData); + classRewriter = new HostedModeClassRewriter(jsoTypeNames, jsoSuperTypes, + singleJsoImplData, mapper); } else { // If we couldn't find the JSO class, we don't need to do any rewrites. classRewriter = null; @@ -662,7 +1020,7 @@ * when loading a JSO interface class; just wait until the implementation * class is loaded. */ - { + if (!classRewriter.isJsoIntf(className)) { CompilationUnit unit = getUnitForClassName(canonicalizeClassName(className)); if (unit != null) { toInject.push(unit); @@ -710,7 +1068,13 @@ * Convert a binary class name into a resource-like name. */ private String canonicalizeClassName(String className) { - return className.replace('.', '/'); + String lookupClassName = className.replace('.', '/'); + // A JSO impl class ends with $, strip it + if (classRewriter != null && classRewriter.isJsoImpl(className)) { + lookupClassName = lookupClassName.substring(0, + lookupClassName.length() - 1); + } + return lookupClassName; } @SuppressWarnings("deprecation") @@ -718,12 +1082,15 @@ if (JavaScriptHost.class.getName().equals(className)) { // No need to rewrite. return javaScriptHostBytes; - } else if (className.endsWith(HostedModeClassRewriter.SINGLE_JSO_IMPL_ADJUNCT_SUFFIX)) { - byte[] bytes = classRewriter.writeSingleJsoImplAdjunct(className); + } + + if (classRewriter != null && classRewriter.isJsoIntf(className)) { + // Generate a synthetic JSO interface class. + byte[] newBytes = classRewriter.writeJsoIntf(className); if (CLASS_DUMP) { - classDump(className, bytes); + classDump(className, newBytes); } - return bytes; + return newBytes; } // A JSO impl class needs the class bytes for the original class. @@ -788,8 +1155,8 @@ if (unit != null) { anonymousClassMap = unit.getAnonymousClassMap(); } - byte[] newBytes = classRewriter.rewrite(className, classBytes, - anonymousClassMap); + byte[] newBytes = classRewriter.rewrite(typeOracle, className, + classBytes, anonymousClassMap); if (CLASS_DUMP) { if (!Arrays.equals(classBytes, newBytes)) { classDump(className, newBytes); @@ -845,8 +1212,6 @@ if (unit == null || unit.getJsniMethods() == null) { return; } - logger.log(TreeLogger.SPAM, "Injecting JSNI methods for " - + unit.getTypeName()); shellJavaScriptHost.createNativeMethods(logger, unit.getJsniMethods(), this); }
diff --git a/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java b/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java index fdefaed..02e2cdd 100644 --- a/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java +++ b/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java
@@ -16,7 +16,6 @@ package com.google.gwt.dev.shell; import com.google.gwt.core.ext.TreeLogger; -import com.google.gwt.dev.shell.rewrite.SingleJsoImplSupport; import com.google.gwt.dev.util.TypeInfo; import java.lang.reflect.Constructor; @@ -30,13 +29,11 @@ public final class JsValueGlue { public static final String HOSTED_MODE_REFERENCE = "hostedModeReference"; public static final String JSO_CLASS = "com.google.gwt.core.client.JavaScriptObject"; + public static final String JSO_IMPL_CLASS = "com.google.gwt.core.client.JavaScriptObject$"; /** - * Create a JavaScriptObject instance referring to this JavaScript object, - * optionally wrapping the object in the desired JSO facade type. + * Create a JavaScriptObject instance referring to this JavaScript object. * - * @param desiredType the subclass of JavaScriptObject that the calling code - * wishes to see, or <code>null</code> * @param classLoader the classLoader to create from * @return the constructed JavaScriptObject */ @@ -44,18 +41,23 @@ CompilingClassLoader classLoader) { Throwable caught; try { - Class<?> jsoType = Class.forName(JSO_CLASS, true, classLoader); - // See if there's already a wrapper object (assures identity comparison). Object jso = classLoader.getCachedJso(value.getJavaScriptObjectPointer()); - if (jso == null) { - // Instantiate the JSO instance. - Constructor<?> ctor = jsoType.getDeclaredConstructor(Object.class); - jso = ctor.newInstance(value); - - classLoader.putCachedJso(value.getJavaScriptObjectPointer(), jso); + if (jso != null) { + return jso; } + // Instantiate the JSO class. + Class<?> jsoType = Class.forName(JSO_IMPL_CLASS, true, classLoader); + Constructor<?> ctor = jsoType.getDeclaredConstructor(); + ctor.setAccessible(true); + jso = ctor.newInstance(); + + // Set the reference field to this JsValue using reflection. + Field referenceField = jsoType.getField(HOSTED_MODE_REFERENCE); + referenceField.set(jso, value); + + classLoader.putCachedJso(value.getJavaScriptObjectPointer(), jso); return jso; } catch (InstantiationException e) { caught = e; @@ -71,6 +73,8 @@ caught = e; } catch (ClassNotFoundException e) { caught = e; + } catch (NoSuchFieldException e) { + caught = e; } throw new RuntimeException("Error creating JavaScript object", caught); } @@ -84,7 +88,7 @@ * @param msgPrefix a prefix for error/warning messages * @return the object reference * @throws com.google.gwt.dev.shell.HostedModeException if the JavaScript - * object is not assignable to the supplied type. + * object is not assignable to the supplied type. */ @SuppressWarnings("unchecked") public static <T> T get(JsValue value, CompilingClassLoader cl, @@ -162,11 +166,7 @@ return type.cast(value.getString()); } if (value.isJavaScriptObject()) { - Object jso = createJavaScriptObject(value, cl); - if (type != Object.class) { - jso = SingleJsoImplSupport.cast(jso, type); - } - return type.cast(jso); + return type.cast(createJavaScriptObject(value, cl)); } // Just don't know what do to with this. @@ -217,8 +217,8 @@ } else { // not a boxed primitive try { - Class<?> jsoType = Class.forName(JSO_CLASS, false, cl); - if (jsoType.isInstance(obj)) { + Class<?> jsoType = Class.forName(JSO_IMPL_CLASS, false, cl); + if (jsoType == obj.getClass()) { JsValue jsObject = getUnderlyingObject(obj); value.setValue(jsObject); return; @@ -258,10 +258,9 @@ } intVal = (int) doubleVal; if (intVal != doubleVal) { - ModuleSpace.getLogger().log( - TreeLogger.WARN, + ModuleSpace.getLogger().log(TreeLogger.WARN, msgPrefix + ": Rounding double (" + doubleVal + ") to int for " - + typeName, null); + + typeName, null); } } else { throw new HostedModeException(msgPrefix + ": JS value of type "
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 095eda7..319aeb3 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
@@ -15,22 +15,22 @@ */ package com.google.gwt.dev.shell.rewrite; -import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.TypeOracle; -import com.google.gwt.dev.asm.ClassAdapter; import com.google.gwt.dev.asm.ClassReader; import com.google.gwt.dev.asm.ClassVisitor; import com.google.gwt.dev.asm.ClassWriter; import com.google.gwt.dev.asm.Opcodes; +import com.google.gwt.dev.asm.commons.Method; import com.google.gwt.dev.shell.JsValueGlue; -import com.google.gwt.dev.util.Name.InternalName; -import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Set; +import java.util.SortedSet; /** * This class performs any and all byte code rewriting needed to make hosted @@ -38,123 +38,25 @@ * <ol> * <li>Rewrites all native methods into non-native thunks to call JSNI via * {@link com.google.gwt.dev.shell.JavaScriptHost}.</li> - * <li>JavaScriptObject and its subtypes gain a static {@value #REWRAP_METHOD} - * method and new constructor methods.</li> - * <li>All uses of <code>==</code> object comparisons gain an explicit cast to - * Object to force canonicalization.</li> - * <li>Casts and assignments to Object, JavaScriptObject, and interface types - * have runtime type-fitting code added to support the not-really-a-type - * semantic.</li> - * <li>All interfaces that a JSO may implement have an adjunct class generated - * that aids the SingleJsoImpl cast operations.</li> + * <li>Rewrites all JSO types into an interface type (which retains the original + * name) and an implementation type (which has a $ appended).</li> + * <li>All JSO interface types are empty and mirror the original type hierarchy. + * </li> + * <li>All JSO impl types contain the guts of the original type, except that all + * instance methods are reimplemented as statics.</li> + * <li>Calls sites to JSO types rewritten to dispatch to impl types. Any virtual + * calls are also made static. Static field references to JSO types reference + * static fields in the the impl class.</li> + * <li>JavaScriptObject$ implements all the interface types and is the only + * instantiable type.</li> * </ol> * * @see RewriteJsniMethods + * @see RewriteRefsToJsoClasses + * @see WriteJsoInterface * @see WriteJsoImpl - * @see RewriteObjectComparisons - * @see RewriteJsoCasts - * @see WriteSingleJsoSupportCode */ public class HostedModeClassRewriter { - - /** - * An oracle to answer queries from the rewriting passes about the - * type-system. - */ - static class RewriterOracle { - private final JClassType jsoType; - /** - * The TypeOracle for the module being rewritten. We need the typeOracle to - * provide a live view of the module's type-system, since Generators may add - * new JSO types. - */ - private final TypeOracle typeOracle; - - public RewriterOracle(TypeOracle typeOracle) { - this.typeOracle = typeOracle; - jsoType = typeOracle.findType(JsValueGlue.JSO_CLASS); - assert jsoType != null : "No JavaScriptObject type"; - } - - /** - * Returns <code>true</code> if a given type is potentially assignable from - * a JSO subtype. - */ - public boolean couldContainJso(String internalName) { - return "java/lang/Object".equals(internalName) - || JAVASCRIPTOBJECT_DESC.equals(internalName) - || isJsoOrSubtype(internalName) - || (!internalName.startsWith("java/") && isInterface(internalName)); - } - - public String[] getAllSuperInterfaces(String[] interfaces) { - Set<String> toReturn = new HashSet<String>(); - Set<JClassType> seen = new HashSet<JClassType>(); - List<JClassType> queue = new LinkedList<JClassType>(); - - for (String intf : interfaces) { - JClassType type = typeOracle.findType(InternalName.toSourceName(intf)); - if (type == null) { - throw new RuntimeException("Unknown type " + intf); - } - queue.add(type); - } - - while (!queue.isEmpty()) { - JClassType intf = queue.remove(0); - if (seen.contains(intf)) { - continue; - } - seen.add(intf); - toReturn.add(intf.getQualifiedBinaryName().replace('.', '/')); - queue.addAll(Arrays.asList(intf.getImplementedInterfaces())); - } - - String[] array = toReturn.toArray(new String[toReturn.size()]); - Arrays.sort(array); - return array; - } - - public boolean isInterface(String internalName) { - String sourceName = InternalName.toSourceName(internalName); - if (sourceName.contains("$")) { - // Anonymous type - return false; - } - JClassType type = typeOracle.findType(sourceName); - return type != null && type.isInterface() != null; - } - - public boolean isJsoOrSubtype(String internalName) { - String sourceName = InternalName.toSourceName(internalName); - if (sourceName.contains("$")) { - // Anonymous type - return false; - } - JClassType type = typeOracle.findType(sourceName); - return type != null && jsoType.isAssignableFrom(type); - } - - public boolean isTagInterface(String internalName) { - String sourceName = InternalName.toSourceName(internalName); - if (sourceName.contains("$")) { - // Anonymous type - return false; - } - JClassType type = typeOracle.findType(sourceName); - return type != null && type.isInterface() != null - && type.getOverridableMethods().length == 0; - } - - /** - * Returns <code>true</code> if a JSO assigned to a slot/field of this type - * requires canonicalizing the JSO wrapper. - */ - public boolean jsoAssignmentRequiresCanonicalization(String internalName) { - return "java/lang/Object".equals(internalName); - } - } - /* * Note: this rewriter operates on a class-by-class basis and has no global * view on the entire system. However, its operation requires certain global @@ -163,66 +65,160 @@ */ /** - * Used in CompilingClassLoader to trigger a call to - * {@link #writeSingleJsoImplAdjunct}. + * Maps instance methods to the class in which they are declared. This must be + * provided by the caller since it requires global program state. */ - public static final String SINGLE_JSO_IMPL_ADJUNCT_SUFFIX = "$$singleJsoImpl"; + public interface InstanceMethodOracle { - static final String CANONICAL_FIELD = "canonicalJso"; + /** + * For a given instance method and declared enclosing class (which must be a + * JSO subtype), find the class in which that method was originally + * declared. Methods declared on Object will return "java/lang/Object". + * Static methods will always return <code>declaredClass</code>. + * + * @param declaredClass a descriptor of the static type of the qualifier + * @param signature the binary signature of the method + * @return the descriptor of the class in which that method was declared, + * which will either be <code>declaredClass</code> or some + * superclass + * @throws IllegalArgumentException if the method could not be found + */ + String findOriginalDeclaringClass(String declaredClass, String signature); + } - static final boolean EXTRA_DEBUG_DATA = Boolean.getBoolean("gwt.dev.classDump"); + /** + * Contains data about how SingleJsoImpl methods are to be dispatched. + */ + public interface SingleJsoImplData { + /** + * Returns the method declarations that should be generated for a given + * mangled method name. {@link #getDeclarations} and + * {@link #getImplementations} maintain a pairwise mapping. + */ + List<Method> getDeclarations(String mangledName); + + /** + * Returns the implementations that the method declarations above should + * delegate to.{@link #getDeclarations} and {@link #getImplementations} + * maintain a pairwise mapping. + */ + List<Method> getImplementations(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( '.', '/'); - static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE; - - static final String REWRAP_METHOD = "$rewrap"; - - static final String SINGLE_JSO_IMPL_FIELD = "$singleJsoImpl"; - - static final String SINGLE_JSO_IMPL_CAST_METHOD = "cast$"; - - static final String SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD = "castToObject$"; - - static final String SINGLE_JSO_IMPL_INSTANCEOF_METHOD = "instanceOf$"; - - static final String SINGLE_JSO_IMPL_SUPPORT_CLASS = SingleJsoImplSupport.class.getName().replace( + static final String JAVASCRIPTOBJECT_IMPL_DESC = JsValueGlue.JSO_IMPL_CLASS.replace( '.', '/'); - static final int SYSTEM_CLASS_VERSION = Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_6 - ? Opcodes.V1_5 : Opcodes.V1_6; + static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE; - private static String toInternalName(String jsoSubtype) { + static String addSyntheticThisParam(String owner, String methodDescriptor) { + return "(L" + owner + ";" + methodDescriptor.substring(1); + } + + private static String toDescriptor(String jsoSubtype) { return jsoSubtype.replace('.', '/'); } /** - * Passed into the rewriting visitors. + * An unmodifiable set of descriptors containing the implementation form of + * <code>JavaScriptObject</code> and all subclasses. */ - private final RewriterOracle rewriterOracle; + private final Set<String> jsoImplDescs; /** - * Creates a new {@link HostedModeClassRewriter}. - * - * @param typeOracle The TypeOracle for the GWT module that is being rewritten + * An unmodifiable set of descriptors containing the interface form of + * <code>JavaScriptObject</code> and all subclasses. */ - public HostedModeClassRewriter(TypeOracle typeOracle) { - rewriterOracle = new RewriterOracle(typeOracle); + private final Set<String> jsoIntfDescs; + + private final SingleJsoImplData jsoData; + + /** + * Records the superclass of every JSO for generating empty JSO interfaces. + */ + private final Map<String, List<String>> jsoSuperDescs; + + /** + * Maps methods to the class in which they are declared. + */ + private InstanceMethodOracle mapper; + + /** + * Creates a new {@link HostedModeClassRewriter} for a specified set of + * subclasses of JavaScriptObject. + * + * @param jsoSubtypes a set of binary type names representing JavaScriptObject + * and all of its subtypes of + * @param mapper maps methods to the class in which they are declared + */ + public HostedModeClassRewriter(Set<String> jsoSubtypes, + 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>>(); + for (String jsoSubtype : jsoSubtypes) { + String desc = toDescriptor(jsoSubtype); + buildJsoIntfDescs.add(desc); + buildJsoImplDescs.add(desc + "$"); + + List<String> superTypes = jsoSuperTypes.get(jsoSubtype); + assert (superTypes != null); + assert (superTypes.size() > 0); + for (ListIterator<String> i = superTypes.listIterator(); i.hasNext();) { + i.set(toDescriptor(i.next())); + } + buildJsoSuperDescs.put(desc, Collections.unmodifiableList(superTypes)); + } + + this.jsoIntfDescs = Collections.unmodifiableSet(buildJsoIntfDescs); + this.jsoImplDescs = Collections.unmodifiableSet(buildJsoImplDescs); + this.jsoSuperDescs = Collections.unmodifiableMap(buildJsoSuperDescs); + this.jsoData = jsoData; + this.mapper = mapper; + } + + /** + * Returns <code>true</code> if the class is the implementation class for a + * JSO subtype. + */ + public boolean isJsoImpl(String className) { + return jsoImplDescs.contains(toDescriptor(className)); + } + + /** + * Returns <code>true</code> if the class is the interface class for a JSO + * subtype. + */ + public boolean isJsoIntf(String className) { + return jsoIntfDescs.contains(toDescriptor(className)); } /** * Performs rewriting transformations on a class. * + * @param typeOracle a typeOracle modeling the user classes * @param className the name of the class * @param classBytes the bytes of the class * @param anonymousClassMap a map between the anonymous class names of java * compiler used to compile code and jdt. Emma-specific. */ - public byte[] rewrite(String className, byte[] classBytes, - Map<String, String> anonymousClassMap) { - classBytes = maybeUpgradeBytecode(classBytes); - String desc = toInternalName(className); + public byte[] rewrite(TypeOracle typeOracle, String className, + byte[] classBytes, Map<String, String> anonymousClassMap) { + String desc = toDescriptor(className); + assert (!jsoIntfDescs.contains(desc)); // The ASM model is to chain a bunch of visitors together. ClassWriter writer = new ClassWriter(0); @@ -231,63 +227,49 @@ // v = new CheckClassAdapter(v); // v = new TraceClassVisitor(v, new PrintWriter(System.out)); - v = new WriteSingleJsoSupportCode(v, rewriterOracle); + v = new RewriteSingleJsoImplDispatches(v, typeOracle, jsoData); - v = new RewriteJsoCasts(v, rewriterOracle); + v = new RewriteRefsToJsoClasses(v, jsoIntfDescs, mapper); - v = new RewriteObjectComparisons(v, rewriterOracle); - - if (rewriterOracle.isJsoOrSubtype(desc)) { - v = WriteJsoImpl.create(v, desc); + if (jsoImplDescs.contains(desc)) { + v = WriteJsoImpl.create(v, desc, jsoIntfDescs, mapper, jsoData); } v = new RewriteJsniMethods(v, anonymousClassMap); - if (SYSTEM_CLASS_VERSION < Opcodes.V1_6) { + if (Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_6) { v = new ForceClassVersion15(v); } - // We need EXPAND_FRAMES here for RewriteJsoCasts - new ClassReader(classBytes).accept(v, ClassReader.EXPAND_FRAMES); + new ClassReader(classBytes).accept(v, 0); return writer.toByteArray(); } - /** - * Synthesize a class file that implements an interface's SingleJsoImpl - * adjunct type. - */ - public byte[] writeSingleJsoImplAdjunct(String className) { - // Keep the synthetic class creation code next to where its consumed - return WriteSingleJsoSupportCode.writeSingleJsoImplAdjunct(className); - } + public byte[] writeJsoIntf(String className) { + String desc = toDescriptor(className); + assert (jsoIntfDescs.contains(desc)); + assert (jsoSuperDescs.containsKey(desc)); + List<String> superDescs = jsoSuperDescs.get(desc); + assert (superDescs != null); + assert (superDescs.size() > 0); - /** - * AnalyzerAdapter, and thus RewriteJsoCasts, requires StackFrameMap data - * which is new in version 50 (Java 1.6) bytecode. This method will only do - * work if the input bytecode is less than version 50. This would occur when - * running on Java 1.5 or if Emma support is enabled (since Emma is from - * 2005). - */ - private byte[] maybeUpgradeBytecode(byte[] classBytes) { - // Major version is stored at offset 7 in the class file format - if (classBytes[7] < Opcodes.V1_6) { - // Get ASM to generate the stack frame data - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - ClassVisitor v2 = cw; + // The ASM model is to chain a bunch of visitors together. + ClassWriter writer = new ClassWriter(0); + ClassVisitor v = writer; - // Upgrade to version 50 class format - v2 = new ClassAdapter(v2) { - @Override - public void visit(int version, int access, String name, - String signature, String superName, String[] interfaces) { - super.visit(Opcodes.V1_6, access, name, signature, superName, - interfaces); - } - }; + // v = new CheckClassAdapter(v); + // v = new TraceClassVisitor(v, new PrintWriter(System.out)); - new ClassReader(classBytes).accept(v2, 0); - classBytes = cw.toByteArray(); + String[] interfaces; + // TODO(bov): something better than linear? + if (superDescs.contains("java/lang/Object")) { + interfaces = null; + } else { + interfaces = superDescs.toArray(new String[superDescs.size()]); } - return classBytes; + v.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, desc, + null, "java/lang/Object", interfaces); + v.visitEnd(); + return writer.toByteArray(); } }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoCasts.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoCasts.java deleted file mode 100644 index a3fe1ce..0000000 --- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoCasts.java +++ /dev/null
@@ -1,617 +0,0 @@ -/* - * 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.shell.rewrite; - -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.CANONICAL_FIELD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.EXTRA_DEBUG_DATA; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.JAVASCRIPTOBJECT_DESC; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REWRAP_METHOD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_ADJUNCT_SUFFIX; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_CAST_METHOD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_INSTANCEOF_METHOD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_SUPPORT_CLASS; - -import com.google.gwt.dev.asm.Attribute; -import com.google.gwt.dev.asm.ByteVector; -import com.google.gwt.dev.asm.ClassAdapter; -import com.google.gwt.dev.asm.ClassVisitor; -import com.google.gwt.dev.asm.ClassWriter; -import com.google.gwt.dev.asm.Label; -import com.google.gwt.dev.asm.MethodVisitor; -import com.google.gwt.dev.asm.Opcodes; -import com.google.gwt.dev.asm.Type; -import com.google.gwt.dev.asm.commons.AnalyzerAdapter; -import com.google.gwt.dev.asm.commons.Method; -import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.RewriterOracle; - -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Ensure that an appropriate JSO wrapper type or canonical JavaScriptObject is - * used. Assignments to fields, locals, arrays, return statements, and method - * parameters will be updated with the following rules: - * <ul> - * <li>Changing from one JSO type to another results in a replacement JSO - * Wrapper type being placed on the stack</li> - * <li>Referring to a JSO or a JSO subype as Object results in the canonical JSO - * instance being placed on the stack</li> - * <li>An invocation of <code>getClass()</code> on a JSO subtype results in - * <code>JavaScriptObject.class</code> being placed on the stack.</li> - * </ul> - */ -class RewriteJsoCasts extends ClassAdapter { - - /** - * Injects necessary casting logic. - */ - private class MyMethodAdapter extends AnalyzerAdapter { - /* - * NOTE TO MAINTAINERS: It's tempting to sprinkle one-off cast-handling - * logic around this class. That way lies madness while trying to track down - * exactly how a particular bit of cast logic wound up in the bytecode. - * Instead, all casting logic should be routed through generateCast. - */ - - private final Map<Label, String> debugData = EXTRA_DEBUG_DATA - ? new LinkedHashMap<Label, String>() : null; - private final boolean returnNeedsCanonical; - private final Type returnType; - - public MyMethodAdapter(int access, String name, String desc, - MethodVisitor mv) { - super(currentClass, access, name, desc, mv); - - Method m = new Method(name, desc); - returnType = m.getReturnType(); - returnNeedsCanonical = returnType.getSort() == Type.OBJECT - && rewriterOracle.jsoAssignmentRequiresCanonicalization(returnType.getInternalName()); - } - - @Override - public void visitEnd() { - if (EXTRA_DEBUG_DATA && !debugData.isEmpty()) { - /* - * When things go wrong in the field, users can enable rewritten - * class-file dumping and send us the class files. Most of the other - * rewriting passes add whole classes or methods so their effects are - * easy to find. Since this rewriting pass is rather subtle, we'll add - * an extra method attribute that contains a UTF8-encoded diagnostic - * log. - */ - Attribute attr = new Attribute("com.google.gwt.RewriteJsoCasts") { - @Override - protected Label[] getLabels() { - return debugData.keySet().toArray(new Label[debugData.size()]); - } - - @Override - protected ByteVector write(ClassWriter cw, byte[] code, int len, - int maxStack, int maxLocals) { - StringBuilder sb = new StringBuilder(); - for (Map.Entry<Label, String> entry : debugData.entrySet()) { - String line = "Offset " + entry.getKey().getOffset() + " : " - + entry.getValue() + "\n"; - sb.append(line); - } - ByteVector toReturn = new ByteVector(); - toReturn.putUTF8(sb.toString()); - return toReturn; - } - }; - super.visitAttribute(attr); - } - super.visitEnd(); - } - - @Override - public void visitFieldInsn(int opcode, String owner, String name, - String desc) { - if (desc.charAt(0) == 'L' && !owner.equals(JAVASCRIPTOBJECT_DESC) - && !name.equals(CANONICAL_FIELD)) { - switch (opcode) { - case Opcodes.PUTFIELD: - case Opcodes.PUTSTATIC: - generateCast(desc.substring(1, desc.length() - 1)); - } - } - super.visitFieldInsn(opcode, owner, name, desc); - } - - /** - * Possibly canonicalize returned values. - */ - @Override - public void visitInsn(int opcode) { - if (opcode == Opcodes.ARETURN && returnNeedsCanonical) { - // Stack is: returnValue - generateCast(returnType.getInternalName()); - // Stack is: canonical - } else if (opcode == Opcodes.AASTORE) { - // Stack is: ..., arrayRef, index, value - Object topType = stack.get(stack.size() - 1); - if (topType instanceof String) { - String objectType = (String) topType; - String arrayType = (String) stack.get(stack.size() - 3); - Type t = Type.getObjectType(arrayType); - if (t.getDimensions() == 1 - && rewriterOracle.jsoAssignmentRequiresCanonicalization(objectType)) { - /* - * If the object being assigned needs canonicalization, then we want - * to ensure the canonical object is being stored in the array. - */ - generateCast(objectType); - } - } - } - super.visitInsn(opcode); - } - - /** - * Replace references to JSO subtype class literals with references to - * JavaScriptObject. - */ - @Override - public void visitLdcInsn(Object cst) { - if (cst instanceof Type) { - Type upcast = upcastJsoType((Type) cst); - if (upcast != null) { - cst = upcast; - } - } - super.visitLdcInsn(cst); - } - - /** - * Canonicalize the arguments to methods that require Object or - * JavaScriptObject as parameters. Additional local variables will be - * allocated to save as many of the arguments as necessary in order to cast - * the first parameter that requires canonicalization. - */ - @Override - public void visitMethodInsn(int opcode, String owner, String name, - String desc) { - - // Update calls to getClass() - if ("getClass".equals(name) && "()Ljava/lang/Class;".equals(desc)) { - // Stack is: instance - Type t = Type.getObjectType((String) stack.get(stack.size() - 1)); - if (t.getSort() == Type.OBJECT - && rewriterOracle.couldContainJso(t.getInternalName())) { - /* - * Make sure the canonical object is on the stack. This method call - * will pass nulls straight through, so the getClass() invocation will - * still trigger an NPE. - */ - super.visitMethodInsn(Opcodes.INVOKESTATIC, - SINGLE_JSO_IMPL_SUPPORT_CLASS, "ensureCanonical", - "(Ljava/lang/Object;)Ljava/lang/Object;"); - // Stack is: canonical object - super.visitMethodInsn(opcode, "java/lang/Object", name, desc); - // Stack is: classLit - return; - - } else if (t.getSort() == Type.ARRAY - && t.getElementType().getSort() == Type.OBJECT - && rewriterOracle.isJsoOrSubtype(t.getElementType().getInternalName())) { - // Call the method to throw an NPE if necessary - super.visitMethodInsn(opcode, "java/lang/Object", name, desc); - // Stack is: classLit - super.visitInsn(Opcodes.POP); - // Stack is: <empty> - super.visitLdcInsn(upcastJsoType(t)); - // Stack is: classLit - return; - } - } else if ("cast".equals(name) - && ("()L" + JAVASCRIPTOBJECT_DESC + ";").equals(desc) - && rewriterOracle.isJsoOrSubtype(owner)) { - // Remove calls to cast, since they trigger NPEs in older code - return; - } - - /* - * We want to ensure that all arguments passed into the method have been - * canonicalized. To do this, we'll find the first argument on the stack - * that requires canonicalization. Each argument on above the first - * argument on the stack will be stored in a temporary local variable and - * eventually restored. - * - * Assume the stack looks like this: - * - * instance, Ok, NeedsCanonicalization, Ok, Ok, NeedsCanonicalization - * - * (1) We'll store the top three arguments in locals until we have this: - * - * instance, Ok, NeedsCanonicalization - * - * (2) Then, we'll apply whatever canonicalization is necessary to the - * top-most element: - * - * instance, Ok, Canonicalized - * - * (3) The arguments stored in the temporary local variables will be - * pushed back onto the stack, possibly canonicalizing them in the - * process: - * - * instance, Ok, Canonicalized, Ok, Ok, Canonicalized - * - * This could be accomplished without the use of local variables if we - * were to track the last opcode that pushes an argument value onto the - * stack and buffer intervening opcodes or use a two-pass approach. Once - * the relevant opcode has been identified, the canonicalization logic - * could be immediately inserted. - */ - Method m = new Method(name, desc); - Type[] args = m.getArgumentTypes(); - int firstCast = -1; - boolean[] needsCanonicalization = new boolean[args.length]; - - // (1) Works backwards, since the last argument is on top of the stack - for (int i = args.length - 1, s = stack.size(); i >= 0; i--) { - Type arg = args[i]; - s -= arg.getSize(); - - // Ignore arguments that never require a canonicalizing cast - if (arg.getSort() != Type.OBJECT - || !rewriterOracle.jsoAssignmentRequiresCanonicalization(arg.getInternalName())) { - continue; - } - - Object onStack = stack.get(s); - assert !onStack.equals(Opcodes.TOP) : "Bad offset computation"; - - if (onStack instanceof String) { - String stackType = (String) onStack; - if (rewriterOracle.couldContainJso(stackType)) { - needsCanonicalization[i] = true; - firstCast = i; - } - } - } - - // Short-circuit if none of the arguments need canonicalization - if (firstCast >= 0) { - // These labels are used to provide information to the debugger - Label castStackStart = new Label(); - Label castStackEnd = new Label(); - - // (1) Pop N - 1 arguments off of the stack, storing them in locals - super.visitLabel(castStackStart); - @SuppressWarnings("unchecked") - List<Object> originalLocals = new ArrayList<Object>(locals); - int[] slots = new int[args.length]; - for (int i = args.length - 1; i >= firstCast + 1; i--) { - Type arg = args[i]; - int slot = locals.size(); - slots[i] = slot; - super.visitVarInsn(arg.getOpcode(Opcodes.ISTORE), slot); - } - - // (2) Convert the first argument - generateCast(args[firstCast].getInternalName()); - - // (3) Load arguments, converting those that need them - for (int i = firstCast + 1; i < args.length; i++) { - Type arg = args[i]; - int slot = slots[i]; - super.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); - if (needsCanonicalization[i]) { - generateCast(arg.getInternalName()); - } - } - - super.visitLabel(castStackEnd); - - // Record locals to aid debbuging and manual inspection of bytecode. - for (int i = firstCast + 1; i < args.length; i++) { - super.visitLocalVariable("$cast_" + i, args[i].getDescriptor(), null, - castStackStart, castStackEnd, slots[i]); - } - - // Record new stack / locals size - super.visitMaxs(stack.size(), locals.size()); - - /* - * Reset the local variables as though they were declared inside of a - * scoped block in the middle of a method. - */ - @SuppressWarnings("unchecked") - Object[] stackArray = stack.toArray(new Object[stack.size()]); - super.visitFrame(Opcodes.F_NEW, originalLocals.size(), - originalLocals.toArray(new Object[originalLocals.size()]), - stack.size(), stackArray); - } - - // Call the method - super.visitMethodInsn(opcode, owner, name, desc); - } - - /** - * Rewrite casts and instanceof tests. - */ - @Override - public void visitTypeInsn(int opcode, String type) { - Type parsed = Type.getObjectType(type); - switch (opcode) { - case Opcodes.CHECKCAST: - if (rewriterOracle.couldContainJso(type)) { - // Maybe generate casting code for JSO types - if (generateCast(type)) { - return; - } - } else if (parsed.getSort() == Type.ARRAY) { - // Always upcast JSO array types - Type t = upcastJsoType(parsed); - if (t != null) { - type = t.getInternalName(); - } - } - // Intentional fall-through to super invocation below - break; - - case Opcodes.INSTANCEOF: { - if (rewriterOracle.isInterface(parsed.getInternalName()) - && rewriterOracle.couldContainJso(parsed.getInternalName())) { - /* - * We need to use the interface's adjunct type if the interface - * could contain a JSO type. - */ - super.visitMethodInsn(Opcodes.INVOKESTATIC, type - + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, - SINGLE_JSO_IMPL_INSTANCEOF_METHOD, "(Ljava/lang/Object;)Z"); - return; - } - // Always try to upcast instanceof JSO tests - Type t = upcastJsoType(parsed); - if (t != null) { - type = t.getInternalName(); - } - break; - } - } - super.visitTypeInsn(opcode, type); - } - - /** - * The do-everything method for determining how to make whatever is on top - * of the stack fit correctly in a slot of the given type. Casts to Object - * should result in the canonical object winding up on the stack. Otherwise, - * we may need a new wrapper instance. - * <p> - * This method should generate code that is stack-neutral. Any non-trivial - * cast logic should be moved to a support class, either - * {@link SingleJsoImplSupport} or the synthetic interface adjunct classes. - * - * @param internalName the type to which the object on top of the stack - * should be assigned - * @return <code>true</code> if cast code was generated - */ - private boolean generateCast(String internalName) { - assert internalName.charAt(0) != 'L' - && internalName.charAt(internalName.length() - 1) != ';' : "Passed " - + "a descriptor instead of an internal name: " + internalName; - Object topStack = stack.get(stack.size() - 1); - if (!(topStack instanceof String)) { - return false; - } - - final String topType = (String) topStack; - - if (EXTRA_DEBUG_DATA) { - Label castLocation = new Label(); - debugData.put(castLocation, topType + " -> " + internalName); - super.visitLabel(castLocation); - } - - if (internalName.equals("java/lang/Object")) { - /* - * We're looking at code (probably created by one of our other visitors) - * that is casting a value to java.lang.Object. - */ - // Stack is: something - - if (rewriterOracle.isJsoOrSubtype(topType)) { - /* - * We know that the top of the stack is a wrapper type, so we need to - * canonicalize it by calling JavaScriptObject's synthetic rewrap - * method. - */ - // Stack is: jsoSubtype - super.visitMethodInsn(Opcodes.INVOKESTATIC, JAVASCRIPTOBJECT_DESC, - REWRAP_METHOD, "(L" + JAVASCRIPTOBJECT_DESC + ";)L" - + JAVASCRIPTOBJECT_DESC + ";"); - // Stack is: canonicalObject - return true; - - } else if (rewriterOracle.isInterface(topType) - && rewriterOracle.couldContainJso(topType)) { - /* - * We're looking at an interface that may contain a JSO. If the object - * is a JSO, replace it with the canonical instance by calling into - * the interface's SingleJsoImpl adjunct class's castToObject() - * method. - */ - String supportType = topType + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX; - - // Stack is: interfaceType - super.visitMethodInsn(Opcodes.INVOKESTATIC, supportType, - SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD, "(L" + topType - + ";)Ljava/lang/Object;"); - // Stack is: object (possible canonical JSO) - return true; - - } else if ("java/lang/Object".equals(topType)) { - /* - * This is a situation that we'll run into when dealing with JRE code. - * See JsoTest.testArrayJreInteractions for the canonical example of - * loss of type information. - */ - super.visitMethodInsn(Opcodes.INVOKESTATIC, - SINGLE_JSO_IMPL_SUPPORT_CLASS, "ensureCanonical", - "(Ljava/lang/Object;)Ljava/lang/Object;"); - // Stack is: canonical object - return true; - - } else { - // Casting some non-JSO type to Object - return false; - } - - } else if (internalName.equals(topType)) { - // Casting an object to its own type - return false; - - } else if (!rewriterOracle.couldContainJso(internalName)) { - // It's a cast to something that could never contain a JSO - return false; - - } else if (rewriterOracle.isInterface(internalName) - && rewriterOracle.couldContainJso(internalName)) { - /* - * Casting to a SingleJsoImpl interface. Need to use the SingleJsoImpl - * adjunct to possibly create a new wrapper type. - */ - - // Stack is: something - super.visitMethodInsn(Opcodes.INVOKESTATIC, internalName - + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_CAST_METHOD, - "(Ljava/lang/Object;)Ljava/lang/Object;"); - // Stack is: something (maybe JSO wrapper type) - super.visitTypeInsn(Opcodes.CHECKCAST, internalName); - // Stack is: desiredType (maybe JSO wrapper type) - - return true; - - } else if (rewriterOracle.isJsoOrSubtype(internalName)) { - /* - * Casting to a JavaScriptObject subtype. - */ - - /* - * Change the cast to JavaScriptObject to ensure we get a - * ClassCastException for something like (JsoSubclass) "foo". - */ - // Stack is: something - super.visitTypeInsn(Opcodes.CHECKCAST, JAVASCRIPTOBJECT_DESC); - // Stack is: jso - - /* - * Put the canonical object onto the stack by calling - * JsoSubclass.$rewrap(). - */ - super.visitMethodInsn(Opcodes.INVOKESTATIC, internalName, - REWRAP_METHOD, "(L" + JAVASCRIPTOBJECT_DESC + ";)L" + internalName - + ";"); - // Stack is: wrapperObject - return true; - } - - /* - * Fail definitively, since getting this wrong is going to break the user - * in all kinds of unpredictable ways. - */ - throw new RuntimeException( - "generateCast was called with an unhandled configuration. topType: " - + topType + " internalName: " + internalName); - } - - /** - * If <code>type</code> is a JSO subtype or JSO subtype array, return the - * JSO type or JSO array type to which <code>type</code> can be assigned, or - * <code>null</code>. - */ - private Type upcastJsoType(Type type) { - StringBuilder sb = new StringBuilder(); - if (type.getSort() == Type.ARRAY) { - Type elementType = type.getElementType(); - if (elementType.getSort() != Type.OBJECT - || !rewriterOracle.isJsoOrSubtype(elementType.getInternalName())) { - return null; - } - for (int i = 0, j = type.getDimensions(); i < j; i++) { - sb.append("["); - } - } else if (type.getSort() == Type.OBJECT) { - if (type.getInternalName().equals(JAVASCRIPTOBJECT_DESC) - || !rewriterOracle.isJsoOrSubtype(type.getInternalName())) { - return null; - } - } else { - return null; - } - sb.append("L" + JAVASCRIPTOBJECT_DESC + ";"); - return Type.getType(sb.toString()); - } - } - - /** - * Decodes the UTF8-as-octets output from javap. - */ - public static void main(String args[]) throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - Pattern p = Pattern.compile("[0-9A-F]{2}"); - String line; - while ((line = reader.readLine()) != null) { - if (line.length() > 0) { - Matcher m = p.matcher(line); - while (m.find()) { - bytes.write(Byte.parseByte(m.group(), 16)); - } - } else { - String message = new String(bytes.toByteArray()); - System.out.println(message); - bytes = new ByteArrayOutputStream(); - } - } - } - - private String currentClass; - private final RewriterOracle rewriterOracle; - - public RewriteJsoCasts(ClassVisitor v, RewriterOracle rewriterOracle) { - super(v); - this.rewriterOracle = rewriterOracle; - } - - @Override - public void visit(int version, int access, String name, String signature, - String superName, String[] interfaces) { - currentClass = name; - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, - String signature, String[] exceptions) { - MethodVisitor mv = super.visitMethod(access, name, desc, signature, - exceptions); - if (mv != null && !REWRAP_METHOD.equals(name)) { - mv = new MyMethodAdapter(access, name, desc, mv); - } - return mv; - } -}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteObjectComparisons.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteObjectComparisons.java deleted file mode 100644 index f7d2377..0000000 --- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteObjectComparisons.java +++ /dev/null
@@ -1,108 +0,0 @@ -/* - * 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.shell.rewrite; - -import com.google.gwt.dev.asm.ClassAdapter; -import com.google.gwt.dev.asm.ClassVisitor; -import com.google.gwt.dev.asm.Label; -import com.google.gwt.dev.asm.MethodVisitor; -import com.google.gwt.dev.asm.Opcodes; -import com.google.gwt.dev.asm.commons.AnalyzerAdapter; -import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.RewriterOracle; - -/** - * This injects artificial casts to Object which will be replaced by - * {@link RewriteJsoCasts}. - */ -class RewriteObjectComparisons extends ClassAdapter { - - private class MyMethodAdapter extends AnalyzerAdapter { - public MyMethodAdapter(String owner, int access, String name, String desc, - MethodVisitor mv) { - super(owner, access, name, desc, mv); - } - - /** - * All object equality comparisons in the JVM are performed via a jump - * opcode. Even something as simple as <code>boolean x = a == b;</code> is - * implemented as - * - * <pre> - * ALOAD 1; - * ALOAD 2; - * IF_ACMPEQ label; - * PUSH false; - * GOTO: done; - * label: PUSH true; - * done: ASTORE 3; - * </pre> - */ - @Override - public void visitJumpInsn(int opcode, Label label) { - switch (opcode) { - case Opcodes.IF_ACMPEQ: - case Opcodes.IF_ACMPNE: - Object type1 = stack.get(stack.size() - 2); - boolean jso1 = type1 instanceof String - && rewriterOracle.couldContainJso((String) type1); - Object type2 = stack.get(stack.size() - 1); - boolean jso2 = type2 instanceof String - && rewriterOracle.couldContainJso((String) type2); - - if (jso1 || jso2) { - if (jso2) { - // Stack: something, something - super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Object"); - } - if (jso1) { - // Stack: something, object2 - super.visitInsn(Opcodes.SWAP); - // Stack: object2, something - super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Object"); - // Stack: object2, object1 - } - } - } - super.visitJumpInsn(opcode, label); - } - } - - private final RewriterOracle rewriterOracle; - private String currentClass; - - public RewriteObjectComparisons(ClassVisitor v, RewriterOracle rewriterOracle) { - super(v); - this.rewriterOracle = rewriterOracle; - } - - @Override - public void visit(int version, int access, String name, String signature, - String superName, String[] interfaces) { - currentClass = name; - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, - String signature, String[] exceptions) { - MethodVisitor mv = super.visitMethod(access, name, desc, signature, - exceptions); - if (mv != null) { - mv = new MyMethodAdapter(currentClass, access, name, desc, mv); - } - return mv; - } -}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteRefsToJsoClasses.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteRefsToJsoClasses.java new file mode 100644 index 0000000..3a45363 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteRefsToJsoClasses.java
@@ -0,0 +1,150 @@ +/* + * Copyright 2008 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.shell.rewrite; + +import com.google.gwt.dev.asm.ClassAdapter; +import com.google.gwt.dev.asm.ClassVisitor; +import com.google.gwt.dev.asm.MethodAdapter; +import com.google.gwt.dev.asm.MethodVisitor; +import com.google.gwt.dev.asm.Opcodes; +import com.google.gwt.dev.asm.commons.Remapper; +import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle; + +import java.util.Set; + +/** + * Rewrites references to modified JSO subtypes. + * + * <ol> + * <li>Changes the owner type for instructions that reference items in a JSO + * class to the implementation class.</li> + * <li>Rewrites instance calls to JSO classes into static calls.</li> + * <li>Updates the descriptor for such call sites to includes a synthetic + * <code>this</code> parameter. This modified method has same stack behavior + * as the original instance method.</li> + * </ol> + */ +class RewriteRefsToJsoClasses extends ClassAdapter { + + /** + * A method body rewriter to actually rewrite call sites. + */ + private class MyMethodAdapter extends MethodAdapter { + + private Remapper remapper = new Remapper() { + @Override + public String map(String typeName) { + if (jsoDescriptors.contains(typeName)) { + return HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC; + } + return typeName; + } + }; + + public MyMethodAdapter(MethodVisitor mv) { + super(mv); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, + String desc) { + if (jsoDescriptors.contains(owner)) { + // Change the owner to the rewritten class. + owner += "$"; + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitLdcInsn(Object cst) { + cst = remapper.mapValue(cst); + super.visitLdcInsn(cst); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if (jsoDescriptors.contains(owner)) { + // Find the class that actually declared the method. + if (opcode == Opcodes.INVOKEVIRTUAL) { + owner = mapper.findOriginalDeclaringClass(owner, name + desc); + } + if (!owner.equals("java/lang/Object")) { + if (opcode == Opcodes.INVOKEVIRTUAL + || opcode == Opcodes.INVOKESPECIAL) { + // Instance/super call to JSO; rewrite as static. + opcode = Opcodes.INVOKESTATIC; + desc = HostedModeClassRewriter.addSyntheticThisParam(owner, desc); + name += "$"; + } + // Change the owner to implementation class. + owner += "$"; + } + } + super.visitMethodInsn(opcode, owner, name, desc); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + desc = remapper.mapType(desc); + super.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + if (opcode == Opcodes.ANEWARRAY) { + type = remapper.mapType(type); + } + super.visitTypeInsn(opcode, type); + } + } + + /** + * An unmodifiable set of descriptors containing <code>JavaScriptObject</code> + * and all subclasses. + */ + protected final Set<String> jsoDescriptors; + + /** + * Maps methods to the class in which they are declared. + */ + private InstanceMethodOracle mapper; + + /** + * Construct a new rewriter instance. + * + * @param cv the visitor to chain to + * @param jsoDescriptors an unmodifiable set of descriptors containing + * <code>JavaScriptObject</code> and all subclasses + * @param mapper maps methods to the class in which they are declared + */ + public RewriteRefsToJsoClasses(ClassVisitor cv, Set<String> jsoDescriptors, + InstanceMethodOracle mapper) { + super(cv); + this.jsoDescriptors = jsoDescriptors; + this.mapper = mapper; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + // Wrap the returned method visitor in my own. + MethodVisitor mv = super.visitMethod(access, name, desc, signature, + exceptions); + return new MyMethodAdapter(mv); + } + +}
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 new file mode 100644 index 0000000..b6c7c1e --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
@@ -0,0 +1,349 @@ +/* + * Copyright 2009 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.shell.rewrite; + +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.dev.asm.ClassAdapter; +import com.google.gwt.dev.asm.ClassVisitor; +import com.google.gwt.dev.asm.MethodAdapter; +import com.google.gwt.dev.asm.MethodVisitor; +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; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Effects the renaming of {@code @SingleJsoImpl} methods from their original + * name to their mangled name. Let us call the original method an "unmangled + * method" and the new method a "mangled method". There are three steps in this + * process: + * <ol> + * <li>Within {@code @SingleJsoImpl} interfaces rename all unmangled methods to + * become mangled methods.</li> + * <li>Within non-JSO classes containing a concrete implementation of an + * unmangled method, add a mangled method which is implemented as a simple + * trampoline to the unmangled method. (We don't do this in JSO classes here + * because the one-and-only trampoline lives in JavaScriptObject$ and is emitted + * in {@link WriteJsoImpl}). + * <li>Update all call sites targeting unmangled methods to target mangled + * methods instead, provided the caller is binding to the interface rather than + * a concrete type.</li> + * </ol> + */ +public class RewriteSingleJsoImplDispatches extends ClassAdapter { + private class MyMethodVisitor extends MethodAdapter { + public MyMethodVisitor(MethodVisitor mv) { + super(mv); + } + + /* + * Implements objective #3: updates call sites to unmangled methods. + */ + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if (opcode == Opcodes.INVOKEINTERFACE) { + if (jsoData.getSingleJsoIntfTypes().contains(owner)) { + // Simple case; referring directly to a SingleJso interface. + name = owner.replace('/', '_') + "_" + name; + assert jsoData.getMangledNames().contains(name) : "Missing " + name; + + } else { + /* + * Might be referring to a subtype of a SingleJso interface: + * + * interface IA { void foo() } + * + * interface JA extends JSO implements IA; + * + * interface IB extends IA {} + * + * void bar() { ((IB) object).foo(); } + */ + outer : for (String intf : computeAllInterfaces(owner)) { + 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 + * there are two super-interfaces that define methods with + * identical names and descriptors, the choice of implementation + * is undefined. + */ + String maybeMangled = intf.replace('/', '_') + "_" + name; + List<Method> methods = jsoData.getImplementations(maybeMangled); + if (methods != null) { + for (Method method : methods) { + /* + * Found a method with the right name, but we need to check + * the parameters and the return type. In order to do this, + * we'll look at the arguments and return type of the target + * method, removing the first argument, which is the instance. + */ + assert method.getArgumentTypes().length >= 1; + Type[] argumentTypes = new Type[method.getArgumentTypes().length - 1]; + System.arraycopy(method.getArgumentTypes(), 1, argumentTypes, + 0, argumentTypes.length); + String maybeDescriptor = Type.getMethodDescriptor( + method.getReturnType(), argumentTypes); + if (maybeDescriptor.equals(desc)) { + name = maybeMangled; + break outer; + } + } + } + } + } + } + } + + super.visitMethodInsn(opcode, owner, name, desc); + } + } + + private String currentTypeName; + private final Set<String> implementedMethods = new HashSet<String>(); + private boolean inSingleJsoImplInterfaceType; + private Map<String, Set<String>> intfNamesToAllInterfaces = Maps.create(); + private final SingleJsoImplData jsoData; + private final TypeOracle typeOracle; + + public RewriteSingleJsoImplDispatches(ClassVisitor v, TypeOracle typeOracle, + SingleJsoImplData jsoData) { + super(v); + this.typeOracle = typeOracle; + this.jsoData = jsoData; + } + + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + assert currentTypeName == null; + super.visit(version, access, name, signature, superName, interfaces); + + /* + * This visitor would mangle JSO$ since it acts as a roll-up of all + * SingleJso types and the result would be repeated method definitions due + * to the trampoline methods this visitor would create. + */ + if (name.equals(HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC)) { + return; + } + + currentTypeName = name; + inSingleJsoImplInterfaceType = jsoData.getSingleJsoIntfTypes().contains( + name); + + /* + * Implements objective #2: non-JSO types that implement a SingleJsoImpl + * interface don't have their original instance methods altered. Instead, we + * add trampoline methods with mangled names that simply call over to the + * original methods. + */ + if (interfaces != null && (access & Opcodes.ACC_INTERFACE) == 0) { + Set<String> toStub = computeAllInterfaces(interfaces); + toStub.retainAll(jsoData.getSingleJsoIntfTypes()); + + for (String stubIntr : toStub) { + writeTrampoline(stubIntr); + } + } + } + + @Override + public void visitEnd() { + /* + * Add any missing methods that are defined by a super-interface, but that + * may be referenced via a more specific interface. + */ + if (inSingleJsoImplInterfaceType) { + for (Map.Entry<String, List<Method>> entry : toImplement(currentTypeName).entrySet()) { + for (Method method : entry.getValue()) { + writeEmptyMethod(entry.getKey(), method); + } + } + } + super.visitEnd(); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + + /* + * Implements objective #2: Rename unmangled methods in a @SingleJsoImpl + * into mangled methods (except for clinit, LOL). + */ + if (inSingleJsoImplInterfaceType && !"<clinit>".equals(name)) { + name = currentTypeName.replace('/', '_') + "_" + name; + implementedMethods.add(name); + } + + MethodVisitor mv = super.visitMethod(access, name, desc, signature, + exceptions); + if (mv == null) { + return null; + } + + return new MyMethodVisitor(mv); + } + + private Set<String> computeAllInterfaces(String intfName) { + Set<String> toReturn = intfNamesToAllInterfaces.get(intfName); + if (toReturn != null) { + return toReturn; + } + + toReturn = Sets.create(); + List<JClassType> q = new LinkedList<JClassType>(); + JClassType intf = typeOracle.findType(intfName.replace('/', '.').replace( + '$', '.')); + assert intf != null : "Could not find interface " + intfName; + q.add(intf); + + while (!q.isEmpty()) { + intf = q.remove(0); + String resourceName = getResourceName(intf); + if (!toReturn.contains(resourceName)) { + toReturn = Sets.add(toReturn, resourceName); + Collections.addAll(q, intf.getImplementedInterfaces()); + } + } + + intfNamesToAllInterfaces = Maps.put(intfNamesToAllInterfaces, intfName, + toReturn); + return toReturn; + } + + private Set<String> computeAllInterfaces(String[] interfaces) { + Set<String> toReturn = new HashSet<String>(); + for (String intfName : interfaces) { + toReturn.addAll(computeAllInterfaces(intfName)); + } + return toReturn; + } + + private String getResourceName(JClassType type) { + if (type.getEnclosingType() != null) { + return getResourceName(type.getEnclosingType()) + "$" + + type.getSimpleSourceName(); + } + return type.getQualifiedSourceName().replace('.', '/'); + } + + /** + * Given a resource name of a class, find all mangled method names that must + * be implemented. + */ + private SortedMap<String, List<Method>> toImplement(String typeName) { + String name = typeName.replace('/', '_'); + String prefix = name + "_"; + String suffix = name + "`"; + SortedMap<String, List<Method>> toReturn = new TreeMap<String, List<Method>>(); + + for (String mangledName : jsoData.getMangledNames().subSet(prefix, suffix)) { + toReturn.put(mangledName, jsoData.getImplementations(mangledName)); + } + toReturn.keySet().removeAll(implementedMethods); + return toReturn; + } + + private void writeEmptyMethod(String mangledMethodName, Method method) { + assert method.getArgumentTypes().length > 0; + // Remove the first argument, which would be the implementing JSO type + String descriptor = "(" + + method.getDescriptor().substring( + 1 + method.getArgumentTypes()[0].getDescriptor().length()); + + // Create the stub method entry in the interface + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC + | Opcodes.ACC_ABSTRACT, mangledMethodName, descriptor, null, null); + mv.visitEnd(); + } + + /** + * For regular Java objects that implement a SingleJsoImpl interface, write + * instance trampoline dispatchers for mangled method names to the + * implementing method. + */ + private void writeTrampoline(String stubIntr) { + /* + * This is almost the same kind of trampoline as the ones generated in + * WriteJsoImpl, however there are enough small differences between the + * semantics of the dispatches that would make a common implementation far + * more awkward than the duplication of code. + */ + for (Map.Entry<String, List<Method>> entry : toImplement(stubIntr).entrySet()) { + for (Method method : entry.getValue()) { + String mangledName = entry.getKey(); + + String descriptor = "(" + + method.getDescriptor().substring( + 1 + method.getArgumentTypes()[0].getDescriptor().length()); + String localName = method.getName().substring(0, + method.getName().length() - 1); + Method toCall = new Method(localName, descriptor); + + // Must not be final + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC + | Opcodes.ACC_SYNTHETIC, mangledName, descriptor, null, null); + if (mv != null) { + mv.visitCode(); + + /* + * It just so happens that the stack and local variable sizes are the + * same, but they're kept distinct to aid in clarity should the + * dispatch logic change. + * + * These start at 1 because we need to load "this" onto the stack + */ + int var = 1; + int size = 1; + + // load this + mv.visitVarInsn(Opcodes.ALOAD, 0); + + // then the rest of the arguments + for (Type t : toCall.getArgumentTypes()) { + size += t.getSize(); + mv.visitVarInsn(t.getOpcode(Opcodes.ILOAD), var); + var += t.getSize(); + } + + // Make sure there's enough room for the return value + size = Math.max(size, toCall.getReturnType().getSize()); + + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, currentTypeName, + toCall.getName(), toCall.getDescriptor()); + mv.visitInsn(toCall.getReturnType().getOpcode(Opcodes.IRETURN)); + mv.visitMaxs(size, var); + mv.visitEnd(); + } + } + } + } +}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/SingleJsoImplSupport.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/SingleJsoImplSupport.java deleted file mode 100644 index 9c0b794..0000000 --- a/dev/core/src/com/google/gwt/dev/shell/rewrite/SingleJsoImplSupport.java +++ /dev/null
@@ -1,252 +0,0 @@ -/* - * 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.shell.rewrite; - -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REWRAP_METHOD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_ADJUNCT_SUFFIX; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_FIELD; - -import com.google.gwt.dev.shell.JsValueGlue; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * Utility methods that are called by code generated by - * {@link WriteSingleJsoSupportCode}. - */ -public class SingleJsoImplSupport { - - /** - * Stash a reference to the JavaScriptObject Class object. - */ - private static final ThreadLocal<Class<?>> JSO_CLASS_OBJECT = new ThreadLocal<Class<?>>(); - - /** - * Called from JsValueGlue to ensure that a JSO instance can be cast to the - * desired type. If the desired type is a regular JSO subclass, this is a - * simple rewrap call. Otherwise, assume that it's a SingleJsoImpl interface - * type and use {@link #cast(Object, Class, Class)}. - */ - public static Object cast(Object o, Class<?> jsoOrIntfType) { - assert o != null : "o is null"; - assert jsoOrIntfType != Object.class : "Not expecting Object"; - - if (jsoOrIntfType.isInterface()) { - // Casting to an interface type - Class<?> targetJsoType = getTargetJsoType(jsoOrIntfType); - - checkTarget(jsoOrIntfType, targetJsoType); - - // Let the no-implementing-type code in cast(3) handle the error - return rewrap(targetJsoType, o); - } - - if (getJsoClass(o).isAssignableFrom(jsoOrIntfType)) { - // A JSO type - return rewrap(jsoOrIntfType, o); - } - - // Something else, let the call-site hit CCE - return o; - } - - /** - * Called by synthetic code to cast an object <code>o</code> to some concrete - * type that implements the interface <code>intfType</code>. If a - * JavaScriptObject subtype has been loaded that implements the interface, - * that type will be provided in <code>targetJsoType</code>. - * - * @return an object that implements <code>intfType</code> - * @throws ClassCastException if <code>o</code> is a JSO subtype and there is - * no SingleJsoImpl type for <code>intfType</code> - */ - public static Object cast(Object o, Class<?> intfType, Class<?> targetJsoType) { - if (o == null || intfType.isInstance(o) || !getJsoClass(o).isInstance(o)) { - // Let class cast exception happen at callsite - return o; - } - - checkTarget(intfType, targetJsoType); - - return rewrap(targetJsoType, o); - } - - public static Object ensureCanonical(Object o) { - if (o == null) { - return null; - } - - if (getJsoClass(o).isInstance(o)) { - return rewrap(getJsoClass(o), o); - } - - return o; - } - - /** - * Returns the JSO implementation type declared on the interface via a - * SingleJsoImpl annotation. - */ - @SuppressWarnings("unchecked") - public static Class<?> getDeclaredSingleJsoImplType(Class<?> intfType) { - Exception ex; - try { - Class singleJsoImplType = intfType.getClassLoader().loadClass( - "com.google.gwt.core.client.SingleJsoImpl"); - Object annotation = intfType.getAnnotation(singleJsoImplType); - if (annotation != null) { - Method m = singleJsoImplType.getMethod("value"); - Class<?> value = (Class<?>) m.invoke(annotation); - return value; - } - - // Try the by-name annotation - singleJsoImplType = intfType.getClassLoader().loadClass( - "com.google.gwt.core.client.SingleJsoImplName"); - annotation = intfType.getAnnotation(singleJsoImplType); - if (annotation != null) { - Method m = singleJsoImplType.getMethod("value"); - String name = (String) m.invoke(annotation); - - return Class.forName(name, true, intfType.getClassLoader()); - } - - return null; - } catch (ClassNotFoundException e) { - ex = e; - } catch (IllegalArgumentException e) { - ex = e; - } catch (IllegalAccessException e) { - ex = e; - } catch (InvocationTargetException e) { - ex = e; - } catch (SecurityException e) { - ex = e; - } catch (NoSuchMethodException e) { - ex = e; - } - throw new RuntimeException("Unable to determine SingleJsoImpl type", ex); - } - - /** - * Called by synthetic code to implement instanceof tests. - * - * @param o the object to test - * @param intf the interface type that <code>o</code> is being tested against - * @param implJsoType the JavaScriptObject subtype that implements - * <code>intf</code> if it has been loaded - * @return <code>true</code> if <code>o</code> implements <code>intf</code> or - * if <code>o</code> is a JSO and some JSO type that implements - * <code>intf</code> has been loaded - */ - public static boolean instanceOf(Object o, Class<?> intf, Class<?> implJsoType) { - return intf.isInstance(o) - || (implJsoType != null && getJsoClass(o).isInstance(o)); - } - - /** - * Throw a specialized ClassCastException if <code>targetJsoType</code> is - * null. - */ - private static void checkTarget(Class<?> intfType, Class<?> targetJsoType) { - if (targetJsoType == null) { - throw new ClassCastException( - "There is no known JavaScriptObject subtype that implements " - + intfType.getCanonicalName() - + ". Fix by adding an @SingleJsoImpl annotation to " - + intfType.getSimpleName() - + " or by referencing the concrete JSO type."); - } - } - - /** - * Load the JavaScriptObject Class object from a client object's isolated - * ClassLoader. - */ - private static Class<?> getJsoClass(Object jso) { - Class<?> toReturn = JSO_CLASS_OBJECT.get(); - if (toReturn != null) { - return toReturn; - } - try { - toReturn = jso.getClass().getClassLoader().loadClass( - JsValueGlue.JSO_CLASS); - } catch (ClassNotFoundException e) { - throw new RuntimeException("No JavaScriptObject class", e); - } - JSO_CLASS_OBJECT.set(toReturn); - return toReturn; - } - - private static Class<?> getTargetJsoType(Class<?> intfType) { - Field f; - Exception ex; - try { - intfType = intfType.getClassLoader().loadClass( - intfType.getName() + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX); - f = intfType.getField(SINGLE_JSO_IMPL_FIELD); - return (Class<?>) f.get(null); - } catch (SecurityException e) { - ex = e; - } catch (NoSuchFieldException e) { - ex = e; - } catch (IllegalArgumentException e) { - ex = e; - } catch (IllegalAccessException e) { - ex = e; - } catch (ClassNotFoundException e) { - ex = e; - } - throw new RuntimeException("Unable to query singleJsoImpl type", ex); - } - - /** - * Reflectively invoke a JSO subtype's rewrap method to create a new wrapper - * instance for a JavaScriptObject. - * - * @param jsoClass the desired wrapper type - * @param jso a JavaScriptObject - * @return a JavaScriptObject wrapper that encloses <code>jso</code>'s - * canonical JSO identity - */ - private static Object rewrap(Class<?> jsoClass, Object jso) { - Method m; - Exception ex; - try { - m = jsoClass.getMethod(REWRAP_METHOD, getJsoClass(jsoClass)); - return m.invoke(null, jso); - } catch (NoSuchMethodException e) { - throw new RuntimeException("Missing expected synthetic method", e); - } catch (SecurityException e) { - ex = e; - } catch (IllegalArgumentException e) { - ex = e; - } catch (IllegalAccessException e) { - ex = e; - } catch (InvocationTargetException e) { - ex = e; - } - throw new RuntimeException(ex); - } - - /** - * Utility class. - */ - private SingleJsoImplSupport() { - } -}
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 92acc1b..4eafd89 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
@@ -15,79 +15,94 @@ */ package com.google.gwt.dev.shell.rewrite; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.CANONICAL_FIELD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.JAVASCRIPTOBJECT_DESC; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REFERENCE_FIELD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REWRAP_METHOD; - import com.google.gwt.dev.asm.ClassAdapter; import com.google.gwt.dev.asm.ClassVisitor; import com.google.gwt.dev.asm.FieldVisitor; -import com.google.gwt.dev.asm.Label; import com.google.gwt.dev.asm.MethodVisitor; 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.InstanceMethodOracle; +import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; /** * Writes the implementation classes for JSO and its subtypes. + * + * Changes made by the base class: + * <ol> + * <li>The new type has the same name as the old type with a '$' appended.</li> + * <li>All instance methods in the original type become static methods taking an + * explicit <code>this</code> parameter. Such methods have the same stack + * behavior as the original.</li> + * </ol> */ -class WriteJsoImpl { +abstract class WriteJsoImpl extends ClassAdapter { /** * This type implements JavaScriptObject. * - * <ul> + * <ol> * <li>JavaScriptObject itself gets a new synthetic field to store the * underlying hosted mode reference.</li> - * <li>It also receives a field to retain the canonical JavaScriptObject when - * creating wrapper subclasses.</li> - * <li>A rewrap method is added that simply returns the input object's - * canonical object.</li> - * <li>The zero-arg constructor is made public and makes the JavaScriptObject - * its own canonical object.</li> - * <li>A one-arg constructor is added for use by subclasses that copies the - * hosted mode reference and canonical identity object.</li> - * </ul> + * <li>Instance methods are added so that JavaScriptObject implements all + * SingleJsoImpl interfaces.</li> + * </ol> * */ - private static class ForJso extends ClassAdapter { - public ForJso(ClassVisitor cv) { - super(cv); + private static class ForJsoDollar extends WriteJsoImpl { + /** + * An unmodifiable set of descriptors containing + * <code>JavaScriptObject</code> and all subclasses. + */ + private final Set<String> jsoDescriptors; + private final SingleJsoImplData jsoData; + + public ForJsoDollar(ClassVisitor cv, Set<String> jsoDescriptors, + InstanceMethodOracle mapper, SingleJsoImplData jsoData) { + super(cv, mapper); + this.jsoDescriptors = jsoDescriptors; + this.jsoData = jsoData; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC - | Opcodes.ACC_SYNTHETIC, name, signature, superName, interfaces); + ArrayList<String> jsoDescList = new ArrayList<String>(); + jsoDescList.addAll(jsoDescriptors); + interfaces = jsoDescList.toArray(new String[jsoDescList.size()]); - // Generate JavaScriptObject.rewrap$() - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC - | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, REWRAP_METHOD, "(L" - + JAVASCRIPTOBJECT_DESC + ";)L" + name + ";", null, null); - if (mv != null) { - writeRewrapMethod(mv); - } + super.visit(version, access, name, signature, superName, interfaces); /* * Generate the synthetic "hostedModeReferece" field to contain the * underlying real reference to the JavaScript object. */ - FieldVisitor fv = visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC - | Opcodes.ACC_FINAL, REFERENCE_FIELD, "Ljava/lang/Object;", null, + FieldVisitor fv = visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, + HostedModeClassRewriter.REFERENCE_FIELD, "Ljava/lang/Object;", null, null); if (fv != null) { fv.visitEnd(); } - /* - * Generate a synthetic "canonical" field. - */ - fv = visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC - | Opcodes.ACC_FINAL, CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC - + ";", null, null); - if (fv != null) { - fv.visitEnd(); + // Implement the trampoline methods + for (String mangledName : jsoData.getMangledNames()) { + List<Method> declarations = jsoData.getDeclarations(mangledName); + List<Method> implementations = jsoData.getImplementations(mangledName); + assert declarations.size() == implementations.size() : "Declaration / implementation size mismatch"; + + Iterator<Method> declIterator = declarations.iterator(); + Iterator<Method> implIterator = implementations.iterator(); + + while (declIterator.hasNext()) { + assert implIterator.hasNext(); + writeTrampoline(mangledName, declIterator.next(), implIterator.next()); + } } } @@ -95,364 +110,123 @@ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (isCtor(name)) { - writeConstructors(name); - return null; - } else if ("equals".equals(name) && "(Ljava/lang/Object;)Z".equals(desc)) { - writeEquals(access, name, desc, signature, exceptions); - return null; + // make the JavaScriptObject$ constructor public + access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); + access |= Opcodes.ACC_PUBLIC; } return super.visitMethod(access, name, desc, signature, exceptions); } /** - * Generates a method to return the canonical object. + * JSO methods are implemented as flyweight style, with the instance being + * passed as the first parameter. This loop create instance methods on JSO$ + * for all of the mangled SingleJsoImpl interface method names. These + * instance methods simply turn around and call the static-dispatch methods. + * In Java, it might look like: * * <pre> - * public JavaScriptObject rewrap$(JavaScriptObject o) { - * if (o == null) { - * return null; + * 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); * } - * return o.canonical; * } * </pre> - */ - protected void writeRewrapMethod(MethodVisitor mv) { - Label start = new Label(); - Label end = new Label(); - - mv.visitCode(); - mv.visitLabel(start); - - mv.visitVarInsn(Opcodes.ALOAD, 0); - // Stack is: jso - mv.visitInsn(Opcodes.DUP); - // Stack is: jso, jso - - Label ret = new Label(); - mv.visitJumpInsn(Opcodes.IFNULL, ret); - // Stack is: jso - - mv.visitFieldInsn(Opcodes.GETFIELD, JAVASCRIPTOBJECT_DESC, - CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC + ";"); - // Stack is: canonical - - mv.visitLabel(ret); - mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {JAVASCRIPTOBJECT_DESC}, 1, - new Object[] {JAVASCRIPTOBJECT_DESC}); - mv.visitInsn(Opcodes.ARETURN); - - mv.visitMaxs(2, 1); - mv.visitLabel(end); - mv.visitLocalVariable("jso", "L" + JAVASCRIPTOBJECT_DESC + ";", null, - start, end, 0); - mv.visitEnd(); - } - - /** - * Write JavaScriptObject's constructors. * - * <pre> - * public JavaScriptObject(Object hostedModeReference) { - * this.canonical = this; - * this.hostedModeReference = hostedModeReference; - * } - * protected JavaScriptObject(JavaScriptObject jso) { - * this.canonical = jso.canonical; - * this.hostedModeReference = jso.hostedModeReference; - * } - * </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 writeConstructors(String name) { - // Write the zero-arg constructor - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC - | Opcodes.ACC_SYNTHETIC, name, "(Ljava/lang/Object;)V", null, null); + private void writeTrampoline(String mangledName, Method interfaceMethod, + Method implementingMethod) { + assert implementingMethod.getArgumentTypes().length > 0; + + /* + * 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); + + /* + * We also use the first argument to know which type to statically + * dispatch to. + */ + Type implementingType = Type.getType("L" + + implementingMethod.getArgumentTypes()[0].getInternalName() + "$;"); + + // Maybe create the method. This is marked final as a sanity check + MethodVisitor mv = visitMethodNoRewrite(Opcodes.ACC_PUBLIC + | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC, localMethod.getName(), + localMethod.getDescriptor(), null, null); + if (mv != null) { mv.visitCode(); - // Call Object's constructor - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitInsn(Opcodes.DUP); - // Stack: this, this - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", - "()V"); - // Stack: this - // this.canonical = this; - mv.visitInsn(Opcodes.DUP); - mv.visitInsn(Opcodes.DUP); - mv.visitVarInsn(Opcodes.ALOAD, 1); - // Stack: this, this, this, hostedModeReference - mv.visitFieldInsn(Opcodes.PUTFIELD, JAVASCRIPTOBJECT_DESC, - REFERENCE_FIELD, "Ljava/lang/Object;"); - // Stack: this, this - mv.visitFieldInsn(Opcodes.PUTFIELD, JAVASCRIPTOBJECT_DESC, - CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC + ";"); - // Stack: <empty> + /* + * It just so happens that the stack and local variable sizes are the + * same, but they're kept distinct to aid in clarity should the dispatch + * logic change. + */ + int var = 0; + int size = 0; - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(4, 2); + for (Type t : implementingMethod.getArgumentTypes()) { + size += t.getSize(); + mv.visitVarInsn(t.getOpcode(Opcodes.ILOAD), var); + var += t.getSize(); + } + + // Make sure there's enough room for the return value + size = Math.max(size, implementingMethod.getReturnType().getSize()); + + mv.visitMethodInsn(Opcodes.INVOKESTATIC, + implementingType.getInternalName(), implementingMethod.getName(), + implementingMethod.getDescriptor()); + mv.visitInsn(localMethod.getReturnType().getOpcode(Opcodes.IRETURN)); + mv.visitMaxs(size, var); mv.visitEnd(); } - - // Write the protected one-arg constructor - mv = super.visitMethod(Opcodes.ACC_PROTECTED | Opcodes.ACC_SYNTHETIC, - name, "(L" + JAVASCRIPTOBJECT_DESC + ";)V", null, null); - if (mv != null) { - Label start = new Label(); - Label end = new Label(); - - // Call Object's constructor - mv.visitCode(); - mv.visitLabel(start); - mv.visitVarInsn(Opcodes.ALOAD, 0); - // Stack: this - mv.visitInsn(Opcodes.DUP); - // Stack: this, this - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", - "()V"); - // Stack: this - - // this.canonical = otherJso; - mv.visitVarInsn(Opcodes.ALOAD, 1); - // Stack: this, otherJso - mv.visitInsn(Opcodes.DUP2); - // Stack: this, otherJso, this, otherJso - mv.visitFieldInsn(Opcodes.GETFIELD, JAVASCRIPTOBJECT_DESC, - CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC + ";"); - // Stack: this, otherJso, this, canonical - mv.visitFieldInsn(Opcodes.PUTFIELD, JAVASCRIPTOBJECT_DESC, - CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC + ";"); - // Stack: this, otherJso - - // this.hostedModeReference = otherJso.hostedModeReference - mv.visitFieldInsn(Opcodes.GETFIELD, JAVASCRIPTOBJECT_DESC, - REFERENCE_FIELD, "Ljava/lang/Object;"); - // Stack: this, hostedModeReference - mv.visitFieldInsn(Opcodes.PUTFIELD, JAVASCRIPTOBJECT_DESC, - REFERENCE_FIELD, "Ljava/lang/Object;"); - // Stack: <empty> - - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(4, 2); - mv.visitLabel(end); - mv.visitLocalVariable("this", "L" + JAVASCRIPTOBJECT_DESC + ";", null, - start, end, 0); - mv.visitLocalVariable("jso", "L" + JAVASCRIPTOBJECT_DESC + ";", null, - start, end, 1); - mv.visitEnd(); - } - } - - /** - * Write the implementation of JSO.equals() to use a regular object-identity - * comparison that can be rewritten further. - * - * <pre> - * public boolean equals(Object other) { - * return this == other; - * } - * </pre> - */ - private void writeEquals(int access, String name, String desc, - String signature, String[] exceptions) { - MethodVisitor mv = super.visitMethod(access, name, desc, signature, - exceptions); - if (mv == null) { - return; - } - mv.visitCode(); - - Label returnTrue = new Label(); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitVarInsn(Opcodes.ALOAD, 1); - // Stack: this, other - - mv.visitJumpInsn(Opcodes.IF_ACMPEQ, returnTrue); - mv.visitInsn(Opcodes.ICONST_0); - // Stack: 0 - mv.visitInsn(Opcodes.IRETURN); - - mv.visitLabel(returnTrue); - mv.visitFrame(Opcodes.F_NEW, 2, new Object[] { - JAVASCRIPTOBJECT_DESC, "java/lang/Object"}, 0, new Object[0]); - mv.visitInsn(Opcodes.ICONST_1); - // Stack: 1 - mv.visitInsn(Opcodes.IRETURN); - - mv.visitMaxs(2, 2); - mv.visitEnd(); } } /** * This type is used to implement subtypes of JSO. * - * <ul> - * <li>The type's zero-arg constructor is replaced with a one-arg copy - * constructor that delegates to the one-arg super-constructor.</li> - * <li>A static rewrap method is added</li> - * </ul> + * <ol> + * <li>The new type's superclass is mangled by adding $.</li> + * <li>Constructors are deleted.</li> + * </ol> */ - private static class ForJsoSubclass extends ClassAdapter { - private String superName; - private String typeName; - - public ForJsoSubclass(ClassVisitor cv) { - super(cv); + private static class ForJsoInterface extends WriteJsoImpl { + public ForJsoInterface(ClassVisitor cv, InstanceMethodOracle mapper) { + super(cv, mapper); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - this.superName = superName; - this.typeName = name; - super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC - | Opcodes.ACC_SYNTHETIC, name, signature, superName, interfaces); + // Reference the old superclass's implementation class. + superName += '$'; + interfaces = null; - // Generate JsoSubtype.rewrap$() - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC - | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, REWRAP_METHOD, "(L" - + JAVASCRIPTOBJECT_DESC + ";)L" + name + ";", null, null); - if (mv != null) { - writeRewrapMethod(mv); - } + super.visit(version, access, name, signature, superName, interfaces); } - /** - * Rewrite the JSO's constructor. - * - * <pre> - * protected SomeJso(JavaScriptObject other) { - * super(other); - * } - * </pre> - */ @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { boolean isCtor = isCtor(name); if (isCtor) { - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PROTECTED - | Opcodes.ACC_SYNTHETIC, name, - "(L" + JAVASCRIPTOBJECT_DESC + ";)V", null, null); - if (mv == null) { - return null; - } - - Label start = new Label(); - Label end = new Label(); - mv.visitCode(); - mv.visitLabel(start); - // super(otherJso) - mv.visitVarInsn(Opcodes.ALOAD, 0); - // Stack: this - mv.visitVarInsn(Opcodes.ALOAD, 1); - // Stack: this, other - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "<init>", "(L" - + JAVASCRIPTOBJECT_DESC + ";)V"); - // Stack: <empty> - mv.visitInsn(Opcodes.RETURN); - - mv.visitMaxs(2, 2); - mv.visitLabel(end); - - // For debugging - mv.visitLocalVariable("this", "L" + typeName + ";", null, start, end, 0); - mv.visitLocalVariable("jso", "L" + JAVASCRIPTOBJECT_DESC + ";", null, - start, end, 1); - mv.visitEnd(); - + // Don't copy over constructors except for JavaScriptObject itself. return null; } return super.visitMethod(access, name, desc, signature, exceptions); } - - /** - * Constructs a type-specific rewrap method. - * - * <pre> - * public static JsoSubclass rewrap$(JavaScriptObject jso) { - * start: - * if (jso == null) { - * topOfStack = null; - * goto doReturn; - * } - * - * notNull: if (jso instanceof JsoSubclass) { - * topOfStack = (JsoSubclass) jso; - * goto doReturn; - * } - * - * notMySubclass: topOfStack = new JsoSubclass(jso); - * - * doReturn: return topOfStack; - * end: - * } - * </pre> - */ - protected void writeRewrapMethod(MethodVisitor mv) { - Label start = new Label(); - Label notNull = new Label(); - Label notMySubclass = new Label(); - Label doReturn = new Label(); - Label end = new Label(); - - mv.visitCode(); - mv.visitLabel(start); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitInsn(Opcodes.DUP); - // Stack is: jso, jso - - mv.visitJumpInsn(Opcodes.IFNONNULL, notNull); - // Stack is: jso - // Push a null instead of using dup so that we don't need a useless cast - mv.visitInsn(Opcodes.POP); - mv.visitInsn(Opcodes.ACONST_NULL); - // Stack is: null - mv.visitJumpInsn(Opcodes.GOTO, doReturn); - - mv.visitLabel(notNull); - mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {JAVASCRIPTOBJECT_DESC}, 1, - new Object[] {JAVASCRIPTOBJECT_DESC}); - mv.visitInsn(Opcodes.DUP); - // Stack is: jso, jso - mv.visitTypeInsn(Opcodes.INSTANCEOF, typeName); - // Stack is: jso, boolean - mv.visitJumpInsn(Opcodes.IFEQ, notMySubclass); - // Stack is: jso - mv.visitTypeInsn(Opcodes.CHECKCAST, typeName); - mv.visitJumpInsn(Opcodes.GOTO, doReturn); - - mv.visitLabel(notMySubclass); - mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {JAVASCRIPTOBJECT_DESC}, 1, - new Object[] {JAVASCRIPTOBJECT_DESC}); - // Stack is: jso - - // Allocate the new wrapper instance. - mv.visitTypeInsn(Opcodes.NEW, typeName); - // Stack is: jso, wrapper - - mv.visitInsn(Opcodes.DUP_X1); - // Stack is: wrapper, jso, wrapper - mv.visitInsn(Opcodes.SWAP); - // Stack is: wrapper, wrapper, jso - - // Invoke the constructor, which will access the canonical object - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, typeName, "<init>", "(L" - + JAVASCRIPTOBJECT_DESC + ";)V"); - // Stack is: wrapper - - mv.visitLabel(doReturn); - mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {JAVASCRIPTOBJECT_DESC}, 1, - new Object[] {JAVASCRIPTOBJECT_DESC}); - // Stack is: toReturn - mv.visitInsn(Opcodes.ARETURN); - - mv.visitLabel(end); - mv.visitMaxs(3, 1); - mv.visitLocalVariable("jso", "L" + JAVASCRIPTOBJECT_DESC + ";", null, - start, end, 0); - mv.visitEnd(); - } } /** @@ -460,21 +234,89 @@ * select between a simple implementation for user-defined JSO subtypes and * the complex implementation for implementing JavaScriptObject$. */ - public static ClassVisitor create(ClassVisitor cv, String classDescriptor) { - if (classDescriptor.equals(JAVASCRIPTOBJECT_DESC)) { - return new ForJso(cv); + public static ClassVisitor create(ClassVisitor cv, String classDescriptor, + Set<String> jsoDescriptors, InstanceMethodOracle mapper, + SingleJsoImplData singleJsoImplData) { + + if (classDescriptor.equals(HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC)) { + return new ForJsoDollar(cv, jsoDescriptors, mapper, singleJsoImplData); } else { - return new ForJsoSubclass(cv); + return new ForJsoInterface(cv, mapper); } } - private static boolean isCtor(String name) { - return "<init>".equals(name); + /** + * Maps methods to the class in which they are declared. + */ + private final InstanceMethodOracle mapper; + + /** + * The original name of the class being visited. + */ + private String originalName; + + /** + * Construct a new rewriter instance. + * + * @param cv the visitor to chain to + * @param jsoDescriptors an unmodifiable set of descriptors containing + * <code>JavaScriptObject</code> and all subclasses + * @param mapper maps methods to the class in which they are declared + */ + private WriteJsoImpl(ClassVisitor cv, InstanceMethodOracle mapper) { + super(cv); + this.mapper = mapper; } /** - * Utility class. + * Records the original name and resets access opcodes. */ - private WriteJsoImpl() { + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + originalName = name; + super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC + | Opcodes.ACC_SYNTHETIC, name + '$', signature, superName, interfaces); + } + + /** + * Mangle all instance methods declared in JavaScriptObject types. + */ + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + boolean isCtor = isCtor(name); + if (!isCtor && !isStatic(access) && !isObjectMethod(name + desc)) { + access |= Opcodes.ACC_STATIC; + desc = HostedModeClassRewriter.addSyntheticThisParam(getOriginalName(), + desc); + name = name + "$"; + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + + protected String getOriginalName() { + return originalName; + } + + protected boolean isCtor(String name) { + return "<init>".equals(name); + } + + protected boolean isObjectMethod(String signature) { + return "java/lang/Object".equals(mapper.findOriginalDeclaringClass( + originalName, signature)); + } + + protected boolean isStatic(int access) { + return (access & Opcodes.ACC_STATIC) != 0; + } + + /** + * Allows access to an unmodified visitMethod call. + */ + protected MethodVisitor visitMethodNoRewrite(int access, String name, + String desc, String signature, String[] exceptions) { + return super.visitMethod(access, name, desc, signature, exceptions); } }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteSingleJsoSupportCode.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteSingleJsoSupportCode.java deleted file mode 100644 index 05532a3..0000000 --- a/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteSingleJsoSupportCode.java +++ /dev/null
@@ -1,424 +0,0 @@ -/* - * 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.shell.rewrite; - -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.JAVASCRIPTOBJECT_DESC; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REWRAP_METHOD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_ADJUNCT_SUFFIX; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_CAST_METHOD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_FIELD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_INSTANCEOF_METHOD; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_SUPPORT_CLASS; -import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SYSTEM_CLASS_VERSION; - -import com.google.gwt.dev.asm.ClassAdapter; -import com.google.gwt.dev.asm.ClassVisitor; -import com.google.gwt.dev.asm.ClassWriter; -import com.google.gwt.dev.asm.FieldVisitor; -import com.google.gwt.dev.asm.Label; -import com.google.gwt.dev.asm.MethodAdapter; -import com.google.gwt.dev.asm.MethodVisitor; -import com.google.gwt.dev.asm.Opcodes; -import com.google.gwt.dev.asm.Type; -import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.RewriterOracle; - -/** - * Adds code to JavaScriptObject subtypes to register themselves as the - * implementation type for a given interface. This class also contains a utility - * method for generating an interface's adjunct type to support SingleJsoImpl - * dispatch. - */ -class WriteSingleJsoSupportCode extends ClassAdapter { - public static String SINGLE_JSO_IMPL_ASSIGNMENT_METHOD = "assignSingleJso$"; - - private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; - - /** - * Create an adjunct class for every interface type that a JavaScriptObject - * might implement. It will contain methods for assisting with casts and - * instanceof checks. Given the interface <code>IFoo</code> the following type - * will be generated: - * - * <pre> - * class IFoo$singleJsoImpl { - * //Initialized by the JSO subtype - * public static Class jsoImplType; - * - * static { - * jsoImplType = SingleJsoImplSupport.getDeclaredSingleJsoImplType(IFoo.class) - * } - * - * public static Object cast(Object o) { - * return SingleJsoImplSupport.cast(o, IFoo.class, jsoImplType); - * } - * - * public static Object castToObject$(InterfaceType o) { - * if (o instanceof JavaScriptObject) { - * o = JavaScriptObject.rewrap$((JavaScriptObject o)); - * } - * return o; - * } - * public static boolean instanceOf(Object o) { - * return SingleJsoImplSupport.instanceOf(o, IFoo.class, jsoImplType); - * } - * } - * </pre> - */ - static byte[] writeSingleJsoImplAdjunct(String className) { - assert className.endsWith(SINGLE_JSO_IMPL_ADJUNCT_SUFFIX) : "Bad className " - + className; - String internalName = toInternalName(className); - String intfName = internalName.substring(0, internalName.length() - - SINGLE_JSO_IMPL_ADJUNCT_SUFFIX.length()); - - ClassWriter writer = new ClassWriter(0); - - writer.visit(SYSTEM_CLASS_VERSION, Opcodes.ACC_PUBLIC - | Opcodes.ACC_SYNTHETIC, internalName, null, "java/lang/Object", null); - - // Create jsoImplType field - FieldVisitor fv = writer.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC - | Opcodes.ACC_SYNTHETIC, SINGLE_JSO_IMPL_FIELD, "Ljava/lang/Class;", - null, null); - if (fv != null) { - fv.visitEnd(); - } - - // Write static initializer - MethodVisitor mv = writer.visitMethod(Opcodes.ACC_STATIC, "<clinit>", - "()V", null, null); - if (mv != null) { - mv.visitCode(); - mv.visitLdcInsn(Type.getObjectType(intfName)); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, SINGLE_JSO_IMPL_SUPPORT_CLASS, - "getDeclaredSingleJsoImplType", - "(Ljava/lang/Class;)Ljava/lang/Class;"); - mv.visitFieldInsn(Opcodes.PUTSTATIC, internalName, SINGLE_JSO_IMPL_FIELD, - "Ljava/lang/Class;"); - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(1, 0); - mv.visitEnd(); - } - - // Write cast method - mv = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC - | Opcodes.ACC_SYNTHETIC, SINGLE_JSO_IMPL_CAST_METHOD, - "(Ljava/lang/Object;)Ljava/lang/Object;", null, null); - if (mv != null) { - Label start = new Label(); - Label end = new Label(); - - mv.visitCode(); - mv.visitLabel(start); - // Stack is: empty - mv.visitVarInsn(Opcodes.ALOAD, 0); - // Stack is: object - mv.visitLdcInsn(Type.getType("L" + intfName + ";")); - // Stack is: object, interfaceType - mv.visitFieldInsn(Opcodes.GETSTATIC, internalName, SINGLE_JSO_IMPL_FIELD, - "Ljava/lang/Class;"); - // Stack is: object, interfaceType, jsoType (may be null) - - mv.visitMethodInsn(Opcodes.INVOKESTATIC, SINGLE_JSO_IMPL_SUPPORT_CLASS, - "cast", - "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/Object;"); - // Stack is: object (maybe JSO wrapper) - - mv.visitInsn(Opcodes.ARETURN); - mv.visitLabel(end); - mv.visitMaxs(3, 1); - mv.visitLocalVariable("obj", "Ljava/lang/Object;", null, start, end, 0); - mv.visitEnd(); - } - - // Write castToObject method - mv = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC - | Opcodes.ACC_SYNTHETIC, SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD, "(L" - + intfName + ";)Ljava/lang/Object;", null, null); - if (mv != null) { - Label start = new Label(); - Label beforeReturn = new Label(); - Label end = new Label(); - - mv.visitCode(); - mv.visitLabel(start); - // Stack is: empty - mv.visitVarInsn(Opcodes.ALOAD, 0); - // Stack is: object - mv.visitTypeInsn(Opcodes.INSTANCEOF, JAVASCRIPTOBJECT_DESC); - // Stack is: int - mv.visitJumpInsn(Opcodes.IFEQ, beforeReturn); - // Stack is: empty - - mv.visitVarInsn(Opcodes.ALOAD, 0); - // Stack is: object - mv.visitTypeInsn(Opcodes.CHECKCAST, JAVASCRIPTOBJECT_DESC); - // Stack is: JSO - mv.visitMethodInsn(Opcodes.INVOKESTATIC, JAVASCRIPTOBJECT_DESC, - REWRAP_METHOD, "(L" + JAVASCRIPTOBJECT_DESC + ";)L" - + JAVASCRIPTOBJECT_DESC + ";"); - // Stack is: canonical JSO - mv.visitVarInsn(Opcodes.ASTORE, 0); - // Stack is: empty (local 0 contains canonical object) - - mv.visitLabel(beforeReturn); - mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {"java/lang/Object"}, 0, - EMPTY_OBJECT_ARRAY); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitInsn(Opcodes.ARETURN); - mv.visitLabel(end); - mv.visitMaxs(1, 1); - mv.visitLocalVariable("obj", "Ljava/lang/Object;", null, start, end, 0); - mv.visitEnd(); - } - - // Write instanceOf method - mv = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC - | Opcodes.ACC_SYNTHETIC, SINGLE_JSO_IMPL_INSTANCEOF_METHOD, - "(Ljava/lang/Object;)Z", null, null); - if (mv != null) { - Label start = new Label(); - Label end = new Label(); - - mv.visitCode(); - mv.visitLabel(start); - // Stack is: empty - mv.visitVarInsn(Opcodes.ALOAD, 0); - // Stack is: object - mv.visitLdcInsn(Type.getType("L" + intfName + ";")); - // Stack is: object, interfaceType - mv.visitFieldInsn(Opcodes.GETSTATIC, internalName, SINGLE_JSO_IMPL_FIELD, - "Ljava/lang/Class;"); - // Stack is: object, interfaceType, jsoType (may be null) - - mv.visitMethodInsn(Opcodes.INVOKESTATIC, SINGLE_JSO_IMPL_SUPPORT_CLASS, - "instanceOf", - "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/Class;)Z"); - // Stack is: boolean - - mv.visitInsn(Opcodes.IRETURN); - mv.visitLabel(end); - mv.visitMaxs(3, 1); - mv.visitLocalVariable("obj", "Ljava/lang/Object;", null, start, end, 0); - mv.visitEnd(); - } - - writer.visitEnd(); - return writer.toByteArray(); - } - - private static String toInternalName(String jsoSubtype) { - return jsoSubtype.replace('.', '/'); - } - - private String className; - private boolean hasClinit; - private String[] interfaces; - private final RewriterOracle oracle; - - public WriteSingleJsoSupportCode(ClassVisitor cv, RewriterOracle oracle) { - super(cv); - this.oracle = oracle; - } - - @Override - public void visit(int version, int access, String name, String signature, - String superName, String[] interfaces) { - className = name; - if (oracle.isJsoOrSubtype(name)) { - this.interfaces = oracle.getAllSuperInterfaces(interfaces); - } else { - this.interfaces = null; - } - super.visit(version, access, name, signature, superName, interfaces); - } - - /** - * Write the interface assignment code and possibly introduce a static - * initializer. - */ - @Override - public void visitEnd() { - if (interfaces != null) { - writeSingleJsoImplAssignments(); - - if (!hasClinit) { - MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, "<clinit>", - "()V", null, null); - if (mv != null) { - mv.visitCode(); - mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0, - EMPTY_OBJECT_ARRAY); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, - SINGLE_JSO_IMPL_ASSIGNMENT_METHOD, "()V"); - mv.visitInsn(Opcodes.RETURN); - mv.visitEnd(); - } - } - } - - super.visitEnd(); - } - - /** - * Possibly update the existing static initializer to call the assignment - * code. - */ - @Override - public MethodVisitor visitMethod(int access, String name, String desc, - String signature, String[] exceptions) { - if (interfaces != null && "<clinit>".equals(name)) { - // Disable code in visitEnd() - hasClinit = true; - MethodVisitor mv = super.visitMethod(access, name, desc, signature, - exceptions); - - if (mv == null) { - return null; - } - - return new MethodAdapter(mv) { - /** - * Write the call to the assignment method as the first code in the - * static initializer. - */ - @Override - public void visitCode() { - super.visitCode(); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, - SINGLE_JSO_IMPL_ASSIGNMENT_METHOD, "()V"); - } - }; - } - return super.visitMethod(access, name, desc, signature, exceptions); - } - - /** - * Generate code to register the JSO subtype as the implementation type for - * its interfaces. For every interface (and super-interface) implemented by - * the JSO type, we'll register the JSO type in the interfaces' adjunct types. - * <p> - * For tag interfaces: - * - * <pre> - * if (IFoo$singleJsoImpl.singleJsoImpl$ == null) { - * IFoo$singleJsoImpl.singleJsoImpl$ = JsoFoo.class; - * } - * </pre> - * </p> - * <p> - * For non-trivial interfaces, we check to see if any existing type is a - * supertype of this JSO: - * - * <pre> - * if (IFoo$singleJsoImpl.singleJsoImpl$ == null) { - * IFoo$singleJsoImpl.singleJsoImpl$ = JsoFoo.class; - * } else if (!IFoo$singleJsoImpl.singleJsoImpl$.isAssignableFrom(JsoFoo.class) { - * throw new RuntimeException(); - * } - * </pre> - * </p> - */ - private void writeSingleJsoImplAssignments() { - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PRIVATE - | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, - SINGLE_JSO_IMPL_ASSIGNMENT_METHOD, "()V", null, null); - if (mv == null) { - return; - } - - int stack = 0; - mv.visitCode(); - mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0, EMPTY_OBJECT_ARRAY); - for (String intf : interfaces) { - - mv.visitFieldInsn(Opcodes.GETSTATIC, intf - + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_FIELD, - "Ljava/lang/Class;"); - // Stack is: classLit (may be null) - - if (oracle.isTagInterface(intf)) { - Label noActionNeeded = new Label(); - /* - * Multiple JSO types may implement tag interfaces, so we'll ignore any - * existing type. - */ - mv.visitJumpInsn(Opcodes.IFNONNULL, noActionNeeded); - mv.visitLdcInsn(Type.getObjectType(className)); - mv.visitFieldInsn(Opcodes.PUTSTATIC, intf - + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_FIELD, - "Ljava/lang/Class;"); - mv.visitLabel(noActionNeeded); - mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0, - EMPTY_OBJECT_ARRAY); - stack = Math.max(stack, 1); - } else { - /* - * Otherwise, throw an exception if the existing JSO implementation is - * not a supertype of the current class. - */ - Label noPreviousClass = new Label(); - Label noActionNeeded = new Label(); - mv.visitJumpInsn(Opcodes.IFNULL, noPreviousClass); - // Stack is: empty - - // Ensure the existing type is one of my supertypes - mv.visitFieldInsn(Opcodes.GETSTATIC, intf - + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_FIELD, - "Ljava/lang/Class;"); - mv.visitLdcInsn(Type.getObjectType(className)); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", - "isAssignableFrom", "(Ljava/lang/Class;)Z"); - mv.visitJumpInsn(Opcodes.IFNE, noActionNeeded); - // Stack is: empty - - mv.visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException"); - // Stack is: uninitialized - mv.visitInsn(Opcodes.DUP); - // Stack is: uninitialized, uninitialized - mv.visitLdcInsn("Multiple JavaScriptObject subclasses implement an " - + "interface declared on this type"); - // Stack is: uninitialized, uninitialized, string - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException", - "<init>", "(Ljava/lang/String;)V"); - // Stack is: RuntimeException - mv.visitInsn(Opcodes.ATHROW); - - mv.visitLabel(noPreviousClass); - // Stack is: empty - mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0, - EMPTY_OBJECT_ARRAY); - mv.visitLdcInsn(Type.getObjectType(className)); - // Stack is: class literal - mv.visitFieldInsn(Opcodes.PUTSTATIC, intf - + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_FIELD, - "Ljava/lang/Class;"); - // Stack is: empty - - mv.visitLabel(noActionNeeded); - // Stack is: empty - mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0, - EMPTY_OBJECT_ARRAY); - - stack = Math.max(stack, 3); - } - } - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(stack, 0); - mv.visitEnd(); - } -}
diff --git a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java index 50c49cc..5bf60b0 100644 --- a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
@@ -15,13 +15,9 @@ */ package com.google.gwt.dev.jjs.test; -import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.junit.client.GWTTestCase; -import java.util.Arrays; -import java.util.List; - /** * Tests {@link JavaScriptObject} and subclasses. */ @@ -56,7 +52,7 @@ public static native String staticNative() /*-{ return "nativeFoo"; }-*/; - + /** * Ensure that a supertype can refer to members of a subtype. */ @@ -84,7 +80,7 @@ static String staticValueSub() { return "FooSub"; } - + protected FooSub() { } @@ -211,20 +207,6 @@ } } - public static void assertSame(Object o1, Object o2) { - assertTrue("Failed double-equals", o1 == o2); - assertTrue("Failed o1.equals(o2)", o1.equals(o2)); - assertTrue("Failed o2.equals(o1)", o2.equals(o1)); - assertEquals("Hashcode mismatch", o1.hashCode(), o2.hashCode()); - } - - public static void assertSame(JavaScriptObject o1, JavaScriptObject o2) { - assertTrue("Failed double-equals", o1 == o2); - assertTrue("Failed o1.equals(o2)", o1.equals(o2)); - assertTrue("Failed o2.equals(o1)", o2.equals(o1)); - assertEquals("Hashcode mismatch", o1.hashCode(), o2.hashCode()); - } - private static native Bar makeBar() /*-{ return { toString:function() { @@ -275,8 +257,6 @@ return jso; }-*/; - private Object aField; - @Override public String getModuleName() { return "com.google.gwt.dev.jjs.CompilerSuite"; @@ -289,29 +269,6 @@ assertFalse(array[2] instanceof JavaScriptObject); } - public void testArrayJreInteractions() { - JavaScriptObject obj = makeJSO(); - Foo[] foo = new Foo[1]; - foo[0] = (Foo) obj; - - Bar[] bar = new Bar[1]; - bar[0] = (Bar) obj; - - assertSame(foo[0], bar[0]); - - List<Foo> fooList = Arrays.asList(foo); - List<Bar> barList = Arrays.asList(bar); - assertSame(fooList.get(0), barList.get(0)); - assertSame(fooList.iterator().next(), barList.iterator().next()); - - @SuppressWarnings("unchecked") - List fooRawList = Arrays.asList(foo); - @SuppressWarnings("unchecked") - List barRawList = Arrays.asList(bar); - assertSame(fooRawList.get(0), barRawList.get(0)); - assertSame(fooRawList.iterator().next(), barRawList.iterator().next()); - } - public void testArrayStore() { JavaScriptObject[] jsoArray = new JavaScriptObject[1]; jsoArray[0] = makeJSO(); @@ -417,16 +374,6 @@ fail("Expected ClassCastException"); } catch (ClassCastException expected) { } - - bar = null; - assertFalse(bar instanceof JavaScriptObject); - try { - assertNull(bar); - foo = bar.cast(); - assertNull(foo); - } catch (RuntimeException e) { - fail("Should not have thrown exception " + e.getMessage()); - } } @SuppressWarnings("cast") @@ -483,10 +430,7 @@ assertEquals(JavaScriptObject.class, Bar.class); assertEquals(Foo.class, Bar.class); - if (!GWT.isScript()) { - assertEquals("com.google.gwt.core.client.JavaScriptObject", - JavaScriptObject.class.getName()); - } else if (!JavaScriptObject.class.getName().startsWith("Class$")) { + if (!JavaScriptObject.class.getName().startsWith("Class$")) { // Class metadata could be disabled assertEquals("com.google.gwt.core.client.JavaScriptObject$", JavaScriptObject.class.getName()); @@ -510,10 +454,7 @@ assertEquals(JavaScriptObject[][].class, Bar[][].class); assertEquals(Foo[][].class, Bar[][].class); - if (!GWT.isScript()) { - assertEquals("[[Lcom.google.gwt.core.client.JavaScriptObject;", - JavaScriptObject[][].class.getName()); - } else if (!JavaScriptObject.class.getName().startsWith("Class$")) { + if (!JavaScriptObject.class.getName().startsWith("Class$")) { // Class metadata could be disabled assertEquals("[[Lcom.google.gwt.core.client.JavaScriptObject$;", JavaScriptObject[][].class.getName()); @@ -525,19 +466,11 @@ assertEquals(jso, jso); JavaScriptObject jso2 = makeJSO(); - assertNotSame(jso, jso2); assertFalse(jso.equals(jso2)); assertFalse(jso2.equals(jso)); jso2 = returnMe(jso); - assertSame(jso, jso2); - - Object aLocal = (Bar) jso; - Object aLocal2 = (Foo) jso2; - aField = aLocal; - assertSame(aLocal, aField); - assertSame(jso, aField); - assertSame(aLocal, aLocal2); + assertEquals(jso, jso2); } public void testGenericsJsos() { @@ -629,30 +562,6 @@ jso2 = returnMe(jso); assertSame(jso, jso2); - - { - JavaScriptObject[] arr = new JavaScriptObject[] {jso, jso2}; - assertSame(arr[0], arr[1]); - } - - { - JavaScriptObject[] arr = new JavaScriptObject[2]; - arr[0] = jso; - arr[1] = jso2; - assertSame(arr[0], arr[1]); - } - - { - JavaScriptObject[] arr = new JavaScriptObject[] {(Foo) jso, (Bar) jso2}; - assertSame(arr[0], arr[1]); - } - - { - JavaScriptObject[] arr = new JavaScriptObject[2]; - arr[0] = (Foo) jso; - arr[1] = (Bar) jso2; - assertSame(arr[0], arr[1]); - } } public void testInheritance() {
diff --git a/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java b/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java index ebc7195..ae07751 100644 --- a/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
@@ -16,20 +16,12 @@ package com.google.gwt.dev.jjs.test; import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.SingleJsoImpl; import com.google.gwt.dev.jjs.test.SingleJsoImplTest.JsoHasInnerJsoType.InnerType; import com.google.gwt.dev.jjs.test.jsointfs.JsoInterfaceWithUnreferencedImpl; import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.user.client.rpc.AsyncCallback; import java.io.IOException; -import java.util.AbstractMap; -import java.util.Arrays; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; /** * Ensures that JavaScriptObjects may implement interfaces with methods. @@ -75,12 +67,10 @@ String call(int a, int b); } - @SingleJsoImpl(JsoCreatedWithCast.class) interface CreatedWithCast { String foo(); } - @SingleJsoImpl(JsoCreatedWithCastToTag.class) interface CreatedWithCastToTag { } @@ -519,19 +509,12 @@ return {}; }-*/; - private Object asObject; - - private Simple asSimple; - - private Adder asAdder; - @Override public String getModuleName() { return "com.google.gwt.dev.jjs.CompilerSuite"; } public void testCallsToInnerTypes() { - JsoHasInnerJsoType o = JavaScriptObject.createObject().cast(); CallsMethodInInnerType a = (CallsMethodInInnerType) JavaScriptObject.createObject(); InnerType i = (InnerType) JavaScriptObject.createObject(); assertEquals(5, a.call(i, 5).get()); @@ -539,7 +522,6 @@ } public void testCallsWithArrays() { - JsoUsesArrays toss = JavaScriptObject.createObject().cast(); UsesArrays a = JavaScriptObject.createObject().<JsoUsesArrays> cast(); a.acceptIntArray(a.returnIntArray()); a.acceptInt3Array(a.returnInt3Array()); @@ -557,12 +539,6 @@ a.acceptObject3Array(a.returnString3Array()); } - public void testClassLiterals() { - JavaScriptObject jso = makeSimple(); - assertEquals(JavaScriptObject.class, jso.getClass()); - assertEquals(JavaScriptObject.class, asSimple((JsoSimple) jso).getClass()); - } - /** * Ensure that SingleJSO types that are referred to only via a cast to the * interface type are retained. If the JsoCreatedWithCast type isn't rescued @@ -570,7 +546,6 @@ * compiler would assume there are types that implement the interface. */ public void testCreatedWithCast() { - // This can't work in hosted mode, we need something to load the JSO try { Object a = (CreatedWithCast) JavaScriptObject.createObject(); } catch (ClassCastException e) { @@ -629,7 +604,7 @@ assertTrue(a instanceof JsoAdder); assertFalse(a instanceof JavaAdder); // NB: This is unexpected until you consider JSO$ as a roll-up type - // assertTrue(a instanceof Tag); + assertTrue(a instanceof Tag); try { ((JavaAdder) a).add(1, 1); fail("Should have thrown CCE"); @@ -661,269 +636,6 @@ }, "Hello"); } - /** - * Test identity with arrays. - */ - public void testIdentityArrays() { - // XXX Really need @SingleJsoImpl annotation here - makeDivider(1); - makeAdder(1); - JavaScriptObject obj = makeSimple(); - Adder[] adders = {(Adder) obj}; - Divider[] dividers = {(Divider) obj}; - assertSame(adders[0], dividers[0]); - - Object[] arr = {adders[0], dividers[0]}; - assertSame(arr[0], arr[1]); - - List<Adder> adderList = Arrays.asList(adders); - List<Divider> dividerList = Arrays.asList(dividers); - assertSame(adderList.get(0), dividerList.get(0)); - - @SuppressWarnings("unchecked") - List adderListRaw = Arrays.asList(adders); - @SuppressWarnings("unchecked") - List dividerListRaw = Arrays.asList(dividers); - assertSame(adderListRaw.get(0), dividerListRaw.get(0)); - } - - /** - * Pass some SingleJsoImpl types into JRE collections. - */ - public void testIdentityJRE() { - IdentityHashMap<JavaScriptObject, Boolean> jMap = new IdentityHashMap<JavaScriptObject, Boolean>(); - IdentityHashMap<Simple, Boolean> sMap = new IdentityHashMap<Simple, Boolean>(); - - JsoSimple jso = makeSimple(); - jMap.put(jso, true); - sMap.put(jso, true); - - assertTrue(jMap.get(jso)); - assertTrue(sMap.get(jso)); - - assertTrue(jMap.get(asSimple(jso))); - assertTrue(sMap.get(asSimple(jso))); - assertTrue(jMap.get(asSimple)); - assertTrue(sMap.get(asSimple)); - - assertTrue(jMap.get(asObject(jso))); - assertTrue(sMap.get(asObject(jso))); - assertTrue(jMap.get(asObject)); - assertTrue(sMap.get(asObject)); - - assertNull(jMap.get(JavaScriptObject.createObject())); - assertNull(sMap.get(JavaScriptObject.createObject())); - } - - /** - * Pass some SingleJsoImpl types into an IdentityHashMap. - */ - public void testIdentityJRE2() { - final JavaScriptObject jso = JavaScriptObject.createObject(); - IdentityHashMap<Object, Boolean> map = new IdentityHashMap<Object, Boolean>(); - - // First with a proper JSO subtype - map.putAll(new AbstractMap<JsoAdder, Boolean>() { - @Override - public Set<Entry<JsoAdder, Boolean>> entrySet() { - Entry<JsoAdder, Boolean> entry = new Entry<JsoAdder, Boolean>() { - public JsoAdder getKey() { - return (JsoAdder) jso; - } - - public Boolean getValue() { - return true; - } - - public Boolean setValue(Boolean value) { - throw new RuntimeException("unimplemented"); - } - }; - return Collections.singleton(entry); - } - }); - - assertEquals(1, map.size()); - assertNotNull(map.get(jso)); - assertTrue(map.get(jso)); - assertTrue(map.get((Adder) jso)); - assertTrue(map.get(asAdder((Adder) jso))); - assertTrue(map.get((JsoAdder) jso)); - assertTrue(map.get((JsoSimple) jso)); - - // Now with an interface type - map.putAll(new AbstractMap<Adder, Boolean>() { - @Override - public Set<Entry<Adder, Boolean>> entrySet() { - Entry<Adder, Boolean> entry = new Entry<Adder, Boolean>() { - public Adder getKey() { - return (Adder) jso; - } - - public Boolean getValue() { - return false; - } - - public Boolean setValue(Boolean value) { - throw new RuntimeException("unimplemented"); - } - }; - return Collections.singleton(entry); - } - }); - - assertEquals(1, map.size()); - assertNotNull(map.get(jso)); - assertFalse(map.get(jso)); - - assertNull(map.get(JavaScriptObject.createObject())); - - map.put(JavaScriptObject.createObject(), true); - assertFalse(map.remove(jso)); - assertEquals(1, map.size()); - assertTrue(map.values().iterator().next()); - } - - public void testIdentityMethodsAndFields() { - JsoSimple jso = makeSimple(); - JsoSimple jso2 = makeSimple(); - assertNotSame(jso, jso2); - assertTrue(jso != jso2); - assertFalse(jso == jso2); - - Object o = asObject(jso); - Object o2 = asObject(jso2); - assertNotSame(o, o2); - assertTrue(o != o2); - assertFalse(o == o2); - - assertSame(jso, o); - assertTrue(jso == o); - assertFalse(jso != o); - - assertSame(jso2, o2); - assertTrue(jso2 == o2); - assertFalse(jso2 != o2); - - assertSame(asObject, jso2); - assertTrue(asObject == jso2); - assertFalse(asObject != jso2); - - assertSame(asObject, o2); - assertTrue(asObject == o2); - assertFalse(asObject != o2); - - Simple s = asSimple(jso); - Simple s2 = asSimple(jso2); - assertNotSame(s, s2); - assertTrue(s != s2); - assertFalse(s == s2); - - assertSame(jso, s); - assertTrue(jso == s); - assertFalse(jso != s); - - assertSame(s, o); - assertTrue(s == o); - assertFalse(s != o); - - assertSame(jso2, s2); - assertTrue(jso2 == s2); - assertFalse(jso2 != s2); - - assertSame(o2, s2); - assertTrue(o2 == s2); - assertFalse(o2 != s2); - - assertSame(asSimple, jso2); - assertTrue(asSimple == jso2); - assertFalse(asSimple != jso2); - - assertSame(asSimple, o2); - assertTrue(asSimple == o2); - assertFalse(asSimple != o2); - - assertSame(asSimple, s2); - assertTrue(asSimple == s2); - assertFalse(asSimple != s2); - - JsoRandom r = (JsoRandom) o2; - assertSame(r, jso2); - // Can't legally compare r == jso2 - - assertSame(r, o2); - assertTrue(r == o2); - assertFalse(r != o2); - - assertSame(r, asObject); - assertTrue(r == asObject); - assertFalse(r != asObject); - - assertSame(r, s2); - // Can't legally compare r == s2 - - assertSame(r, asSimple); - // Can't legally compare r == asSimple - } - - public void testIdentityWithDualTypes() { - JsoAdder jso = makeAdder(0); - JavaAdder java = new JavaAdder(); - - assertNotSame(jso, java); - - IdentityHashMap<Adder, Integer> map = new IdentityHashMap<Adder, Integer>(); - map.put(jso, 0); - map.put(java, 1); - assertEquals(2, map.size()); - - assertEquals(new Integer(0), map.get(jso)); - assertEquals(new Integer(1), map.get(java)); - - // Use unambiguous dispatch - assertEquals(new Integer(0), map.get(asAdder(jso))); - assertEquals(new Integer(0), map.get(asAdder)); - assertEquals(new Integer(1), map.get(asAdder(java))); - assertEquals(new Integer(1), map.get(asAdder)); - - // Use ambiguous dispatch - assertEquals(new Integer(0), map.get(asAdder((Adder) jso))); - assertEquals(new Integer(0), map.get(asAdder)); - assertEquals(new Integer(1), map.get(asAdder((Adder) java))); - assertEquals(new Integer(1), map.get(asAdder)); - - // Test with plain Object - assertEquals(new Integer(0), map.get(asObject(jso))); - assertEquals(new Integer(0), map.get(asObject)); - assertEquals(new Integer(1), map.get(asObject(java))); - assertEquals(new Integer(1), map.get(asObject)); - - // Behavior of keys - for (Map.Entry<Adder, Integer> entry : map.entrySet()) { - if (entry.getKey() == jso) { - assertEquals(new Integer(0), entry.getValue()); - } - if (entry.getKey() == java) { - assertEquals(new Integer(1), entry.getValue()); - } - } - } - - public void testIdentityWithNativeMethods() { - JsoSimple s = makeSimple(); - Simple s2 = asSimpleNative(s); - assertSame(s, s2); - assertSame(s, asSimple); - assertSame(s2, asSimple); - } - - /** - * Called from asSimpleNative to continue the above test. - */ - public void testIdentityWithNativeMethods(JsoSimple o) { - assertSame(o, asSimple); - } - @SuppressWarnings("cast") public void testSimpleCase() { { @@ -999,7 +711,6 @@ } public void testStaticCallsToSubclasses() { - JsoCallsStaticMethodInSubclass a = JavaScriptObject.createObject().cast(); Object o = "String"; assertEquals(String.class, o.getClass()); o = JavaScriptObject.createObject(); @@ -1080,59 +791,9 @@ } } - /** - * This test relies on the SingleJsoimpl annotation in dev mode. - */ public void testUnreferencedType() { JsoInterfaceWithUnreferencedImpl o = (JsoInterfaceWithUnreferencedImpl) JavaScriptObject.createObject(); assertNotNull(o); assertTrue(o.isOk()); } - - protected Adder asAdder(Adder o) { - asAdder = o; - return o; - } - - /* - * These asXYZ methods are to test cast-via-return and cast-via-assignment - * rewriting. - */ - - protected Adder asAdder(JavaAdder o) { - asAdder = o; - return o; - } - - protected Adder asAdder(JsoAdder o) { - asAdder = o; - return o; - } - - protected Object asObject(JavaAdder jso) { - asObject = jso; - return jso; - } - - protected Object asObject(JsoAdder jso) { - asObject = jso; - return jso; - } - - protected Object asObject(JsoSimple jso) { - asObject = jso; - return jso; - } - - protected Simple asSimple(JsoSimple jso) { - asSimple = jso; - return jso; - } - - private native Simple asSimpleNative(Object o) /*-{ - // Make sure glue code does the interface cast - this.@com.google.gwt.dev.jjs.test.SingleJsoImplTest::asSimple = o; - this.@com.google.gwt.dev.jjs.test.SingleJsoImplTest::testIdentityWithNativeMethods(Lcom/google/gwt/dev/jjs/test/SingleJsoImplTest$JsoSimple;)(o); - return o; - }-*/; }