The DevMode JSO changes are too complex to integrate as a single change. Need to find a better strategy.
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8297 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
index b80057c..1ceeea4 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
@@ -328,7 +328,7 @@
* @return <code>null</code> if the type is not found
*/
public JClassType findType(String name) {
- assert Name.isSourceName(name) : name + " is not a source name";
+ assert Name.isSourceName(name);
return allTypes.get(name);
}
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 a6ff29d..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,21 +19,29 @@
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;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.javac.CompilationUnit;
import com.google.gwt.dev.javac.CompiledClass;
import com.google.gwt.dev.javac.JsniMethod;
+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.OriginalJsniSignature;
-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.BinaryName;
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;
@@ -56,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
@@ -136,6 +146,23 @@
DispatchClassInfo dispClassInfo = getClassInfoFromClassName(className);
if (dispClassInfo != null) {
String memberName = parsed.memberSignature();
+
+ /*
+ * Disallow the use of JSNI references to SingleJsoImpl interface
+ * methods. This policy is due to web-mode dispatch implementation
+ * details; resolving the JSNI reference wouldn't be just be a name
+ * replacement, instead it would be necessary to significantly alter the
+ * semantics of the hand-written JS.
+ */
+ if (singleJsoImplTypes.contains(canonicalizeClassName(className))) {
+ logger.log(TreeLogger.WARN,
+ "Invalid JSNI reference to SingleJsoImpl interface (" + className
+ + "); consider using a trampoline. "
+ + "Expect subsequent failures.", new NoSuchFieldError(
+ jsniMemberRef));
+ return -1;
+ }
+
int memberId = dispClassInfo.getMemberId(memberName);
if (memberId < 0) {
if (!className.startsWith("java.")) {
@@ -267,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
@@ -323,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<?>>();
@@ -332,8 +683,7 @@
* space (thus, they bridge across the spaces).
*/
private static final Class<?>[] BRIDGE_CLASSES = new Class<?>[] {
- ShellJavaScriptHost.class, GWTBridge.class, OriginalJsniSignature.class,
- SingleJsoImplSupport.class};
+ ShellJavaScriptHost.class, GWTBridge.class};
private static final boolean CLASS_DUMP = Boolean.getBoolean("gwt.dev.classDump");
@@ -480,6 +830,8 @@
private ShellJavaScriptHost shellJavaScriptHost;
+ private final Set<String> singleJsoImplTypes = new HashSet<String>();
+
/**
* Used by {@link #findClass(String)} to prevent reentrant JSNI injection.
*/
@@ -509,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;
@@ -642,8 +1020,8 @@
* when loading a JSO interface class; just wait until the implementation
* class is loaded.
*/
- {
- CompilationUnit unit = getUnitForClassName(BinaryName.toInternalName(className));
+ if (!classRewriter.isJsoIntf(className)) {
+ CompilationUnit unit = getUnitForClassName(canonicalizeClassName(className));
if (unit != null) {
toInject.push(unit);
}
@@ -686,27 +1064,37 @@
dispClassInfoOracle.clear();
}
+ /**
+ * Convert a binary class name into a resource-like name.
+ */
+ private String canonicalizeClassName(String className) {
+ 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")
private byte[] findClassBytes(String className) {
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;
- } else if (className.startsWith(HostedModeClassRewriter.DISAMBIGUATOR_TYPE_NAME)) {
- byte[] bytes = classRewriter.writeConstructorDisambiguationType(className);
- if (CLASS_DUMP) {
- classDump(className, bytes);
- }
- return bytes;
+ return newBytes;
}
// A JSO impl class needs the class bytes for the original class.
- String lookupClassName = BinaryName.toInternalName(className);
+ String lookupClassName = canonicalizeClassName(className);
CompiledClass compiledClass = compilationState.getClassFileMap().get(
lookupClassName);
@@ -767,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);
@@ -779,6 +1167,29 @@
return classBytes;
}
+ private String getBinaryName(JClassType type) {
+ String name = type.getPackage().getName() + '.';
+ name += type.getName().replace('.', '$');
+ return name;
+ }
+
+ private String getBinaryOrPrimitiveName(JType type) {
+ JArrayType asArray = type.isArray();
+ JClassType asClass = type.isClassOrInterface();
+ JPrimitiveType asPrimitive = type.isPrimitive();
+ if (asClass != null) {
+ return getBinaryName(asClass);
+ } else if (asPrimitive != null) {
+ return asPrimitive.getQualifiedSourceName();
+ } else if (asArray != null) {
+ JType componentType = asArray.getComponentType();
+ return getBinaryOrPrimitiveName(componentType) + "[]";
+ } else {
+ throw new InternalCompilerException("Cannot create binary name for "
+ + type.getQualifiedSourceName());
+ }
+ }
+
/**
* Returns the compilationUnit corresponding to the className. For nested
* classes, the unit corresponding to the top level type is returned.
@@ -801,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/DispatchClassInfo.java b/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
index 9fecfa2..27819b3 100644
--- a/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
+++ b/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
@@ -15,10 +15,8 @@
*/
package com.google.gwt.dev.shell;
-import com.google.gwt.dev.shell.rewrite.OriginalJsniSignature;
import com.google.gwt.dev.util.JsniRef;
-import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
@@ -155,21 +153,6 @@
String name;
Class<?>[] paramTypes;
- if (member instanceof AccessibleObject) {
- AccessibleObject accessibleObject = (AccessibleObject) member;
- OriginalJsniSignature signature = accessibleObject.getAnnotation(OriginalJsniSignature.class);
- if (signature != null) {
- StringBuilder sb = new StringBuilder();
- sb.append(signature.name());
- if (wildcardParamList) {
- sb.append("(*)");
- } else {
- sb.append(signature.paramList());
- }
- return sb.toString();
- }
- }
-
if (member instanceof Field) {
return member.getName();
} else if (member instanceof SyntheticClassMember) {
@@ -185,7 +168,7 @@
+ member.getClass().getName());
}
- StringBuilder sb = new StringBuilder();
+ StringBuffer sb = new StringBuffer();
sb.append(name);
sb.append("(");
if (wildcardParamList) {
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/DebugAnalyzerAdapter.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/DebugAnalyzerAdapter.java
deleted file mode 100644
index 44bbf8a..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/DebugAnalyzerAdapter.java
+++ /dev/null
@@ -1,130 +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.EXTRA_DEBUG_DATA;
-
-import com.google.gwt.dev.asm.Attribute;
-import com.google.gwt.dev.asm.ByteVector;
-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.commons.AnalyzerAdapter;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A utility base class that allows rewriting passes to record label-based
- * messages in the emitted bytecode.
- */
-class DebugAnalyzerAdapter extends AnalyzerAdapter {
- /**
- * Used to record debugging data generated by {@link #recordDebugData}.
- *
- * @see #visitEnd()
- */
- private static class DebugAttribute extends Attribute {
- private final LinkedHashMap<Label, String> debugData;
-
- private DebugAttribute(String name, LinkedHashMap<Label, String> debugData) {
- super(name);
- this.debugData = debugData;
- }
-
- @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;
- }
- }
-
- /**
- * 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 final LinkedHashMap<Label, String> debugData = EXTRA_DEBUG_DATA
- ? new LinkedHashMap<Label, String>() : null;
-
- protected DebugAnalyzerAdapter(String owner, int access, String name,
- String desc, MethodVisitor mv) {
- super(owner, access, name, desc, mv);
- }
-
- /**
- * Record debugging data, if any, in an extra attribute on the method.
- */
- @Override
- public void visitEnd() {
- if (EXTRA_DEBUG_DATA && !debugData.isEmpty()) {
- super.visitAttribute(new DebugAttribute(getClass().getCanonicalName(),
- debugData));
- }
- super.visitEnd();
- }
-
- /**
- * 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.
- */
- protected void recordDebugData(String message) {
- if (EXTRA_DEBUG_DATA) {
- Label castLocation = new Label();
- debugData.put(castLocation, message);
- super.visitLabel(castLocation);
- }
- }
-}
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 ca1f488..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,26 +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.Type;
+import com.google.gwt.dev.asm.commons.Method;
import com.google.gwt.dev.shell.JsValueGlue;
-import com.google.gwt.dev.util.Name.BinaryName;
-import com.google.gwt.dev.util.Name.InternalName;
-import com.google.gwt.dev.util.collect.Lists;
-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
@@ -42,168 +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 {
- /**
- * Used by {@link #getConstructorDisambiguator}.
- */
- private static final String[] EMPTY_STRING = new String[0];
-
- private final Map<String, String[]> arrayDisambiguations = new HashMap<String, String[]>();
-
- 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(BinaryName.toInternalName(intf.getQualifiedBinaryName()));
- queue.addAll(Arrays.asList(intf.getImplementedInterfaces()));
- }
-
- String[] array = toReturn.toArray(new String[toReturn.size()]);
- Arrays.sort(array);
- return array;
- }
-
- /**
- * In order to be able to upcast all JSO subtype arrays to JavaScriptObject
- * arrays, it is necessary to be able to maintain distinguishing method
- * descriptors. If the given descriptor will be affected by the array upcast
- * transformation, this method will return an array containing the internal
- * names of any additional types that should be appended to the descriptor.
- */
- public String[] getArrayDisambiguator(String desc) {
- String[] cached = arrayDisambiguations.get(desc);
- if (cached != null) {
- return cached;
- }
-
- List<String> toReturn = Lists.create();
- for (Type t : Type.getArgumentTypes(desc)) {
- if (t.getSort() == Type.ARRAY
- && t.getElementType().getSort() == Type.OBJECT) {
- String leafName = t.getElementType().getInternalName();
- if (isJsoOrSubtype(leafName)) {
- String disambiguatotr = DISAMBIGUATOR_TYPE_INTERNAL_NAME + "$"
- + InternalName.toIdentifier(leafName);
- toReturn = Lists.add(toReturn, disambiguatotr);
- }
- }
- }
-
- String[] array;
- if (toReturn.isEmpty()) {
- array = EMPTY_STRING;
- } else {
- array = toReturn.toArray(new String[toReturn.size()]);
- }
- arrayDisambiguations.put(desc, 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
@@ -212,68 +65,160 @@
*/
/**
- * Used in CompilingClassLoader to trigger a call to
- * {@link #writeConstructorDisambiguationType}.
+ * 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 DISAMBIGUATOR_TYPE_NAME = "com.google.gwt.dev.shell.rewrite.$Disambiguator";
+ public interface InstanceMethodOracle {
+
+ /**
+ * 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);
+ }
/**
- * Used in CompilingClassLoader to trigger a call to
- * {@link #writeSingleJsoImplAdjunct}.
+ * Contains data about how SingleJsoImpl methods are to be dispatched.
*/
- public static final String SINGLE_JSO_IMPL_ADJUNCT_SUFFIX = "$$singleJsoImpl";
+ 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);
- static final String CANONICAL_FIELD = "canonicalJso";
+ /**
+ * 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);
- static final String DISAMBIGUATOR_TYPE_INTERNAL_NAME = BinaryName.toInternalName(DISAMBIGUATOR_TYPE_NAME);
+ /**
+ * Returns all of the mangled method names for SingleJsoImpl methods.
+ */
+ SortedSet<String> getMangledNames();
- static final boolean EXTRA_DEBUG_DATA = Boolean.getBoolean("gwt.dev.classDump");
+ /**
+ * Returns the internal names of all interface types implemented by JSOs.
+ */
+ Set<String> getSingleJsoIntfTypes();
+ }
- static final String JAVASCRIPTOBJECT_DESC = BinaryName.toInternalName(JsValueGlue.JSO_CLASS);
+ static final String JAVASCRIPTOBJECT_DESC = JsValueGlue.JSO_CLASS.replace(
+ '.', '/');
+
+ static final String JAVASCRIPTOBJECT_IMPL_DESC = JsValueGlue.JSO_IMPL_CLASS.replace(
+ '.', '/');
static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE;
- static final String REWRAP_METHOD = "$rewrap";
+ static String addSyntheticThisParam(String owner, String methodDescriptor) {
+ return "(L" + owner + ";" + methodDescriptor.substring(1);
+ }
- 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 = Type.getInternalName(SingleJsoImplSupport.class);
-
- static final int SYSTEM_CLASS_VERSION = Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_6
- ? Opcodes.V1_5 : Opcodes.V1_6;
+ 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}.
+ * An unmodifiable set of descriptors containing the interface form of
+ * <code>JavaScriptObject</code> and all subclasses.
+ */
+ 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 typeOracle The TypeOracle for the GWT module that is being rewritten
+ * @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(TypeOracle typeOracle) {
- rewriterOracle = new RewriterOracle(typeOracle);
+ 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 = BinaryName.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);
@@ -282,75 +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 RewriteJsoArrays(v, rewriterOracle);
-
- 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 is used to disambiguate constructors that have
- * JSO subtype array parameters that have been upcast to JavaScriptObject
- * arrays.
- */
- public byte[] writeConstructorDisambiguationType(String className) {
- // Keep the synthetic class creation code next to where its consumed
- return RewriteJsoArrays.writeConstructorDisambiguationType(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);
- /**
- * 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);
- }
+ // The ASM model is to chain a bunch of visitors together.
+ ClassWriter writer = new ClassWriter(0);
+ ClassVisitor v = writer;
- /**
- * 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;
+ // v = new CheckClassAdapter(v);
+ // v = new TraceClassVisitor(v, new PrintWriter(System.out));
- // 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);
- }
- };
-
- 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/OriginalJsniSignature.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/OriginalJsniSignature.java
deleted file mode 100644
index 74d924a..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/OriginalJsniSignature.java
+++ /dev/null
@@ -1,39 +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 java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * This annotation will be applied to a method or constructor by
- * {@link HostedModeClassRewriter} if the method signature of a rewritten method
- * differs from the original source. This annotation is necessary because
- * dispatch id allocation is performed reflectively, after the class rewriting
- * may have altered the method.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(value = {ElementType.CONSTRUCTOR, ElementType.METHOD})
-public @interface OriginalJsniSignature {
- String name();
-
- /**
- * This is the method descriptor, minus the return type.
- */
- String paramList();
-}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoArrays.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoArrays.java
deleted file mode 100644
index db716e3..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoArrays.java
+++ /dev/null
@@ -1,388 +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.DISAMBIGUATOR_TYPE_INTERNAL_NAME;
-import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SYSTEM_CLASS_VERSION;
-
-import com.google.gwt.dev.asm.AnnotationVisitor;
-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.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.RewriterOracle;
-import com.google.gwt.dev.util.Name.InternalName;
-
-import java.util.Arrays;
-
-/**
- * The object of this pass is to turn all JSO subtype arrays into
- * JavaScriptObject arrays. This allows JSO arrays to participate in arbitrary
- * cross-casting.
- */
-public class RewriteJsoArrays extends ClassAdapter {
-
- private static final String ORIGINAL_JSNI_SIGNATURE_DESC = Type.getDescriptor(OriginalJsniSignature.class);
-
- /**
- * Performs upcasts on JSO subtype arrays.
- */
- private class UpcastAdapter extends DebugAnalyzerAdapter {
-
- private final int minLocals;
- private int minStack;
-
- public UpcastAdapter(String owner, int access, String name, String desc,
- MethodVisitor mv, int minLocals) {
- super(owner, access, name, desc, mv);
- this.minLocals = minLocals;
- }
-
- /**
- * Upcast field access.
- */
- @Override
- public void visitFieldInsn(int opcode, String owner, String name,
- String desc) {
- Type t = Type.getType(desc);
- if (t.getSort() == Type.ARRAY) {
- t = upcastJsoType(t);
- if (t != null) {
- desc = t.getDescriptor();
- }
- }
- super.visitFieldInsn(opcode, owner, name, desc);
- }
-
- /**
- * Fix JSO array types in framing data to keep the verifier and other
- * AnalyzerAdapters synced up.
- */
- @Override
- public void visitFrame(int type, int nLocal, Object[] local, int nStack,
- Object[] stack) {
- fixFrameData(local);
- fixFrameData(stack);
- super.visitFrame(type, nLocal, local, nStack, stack);
- }
-
- /**
- * Keep the debugger from going off the rails.
- */
- @Override
- public void visitLocalVariable(String name, String desc, String signature,
- Label start, Label end, int index) {
- Type t = Type.getType(desc);
- if (t.getSort() == Type.ARRAY) {
- t = upcastJsoType(t);
- if (t != null) {
- desc = t.getInternalName();
- }
- }
- super.visitLocalVariable(name, desc, signature, start, end, index);
- }
-
- /**
- * We need to override the number of locals when rewriting a constructor to
- * account for synthetic disambiguation parameters. The null argument to the
- * disambiguated constructor might require adjustment of the stack size.
- */
- @Override
- public void visitMaxs(int maxStack, int maxLocals) {
- super.visitMaxs(Math.max(minStack, maxStack), Math.max(minLocals,
- maxLocals));
- }
-
- /**
- * Fix the descriptors used by method invocations to account for the upcast
- * array types. Initializers are handled specially, since we can't simply
- * change their name to provide disambiguation. Instead, we add an
- * additional null argument of a synthetic type.
- */
- @Override
- public void visitMethodInsn(int opcode, String owner, String name,
- String desc) {
- switch (opcode) {
- case Opcodes.INVOKESPECIAL: {
- if ("<init>".equals(name)) {
- String[] disambiguator = rewriterOracle.getArrayDisambiguator(desc);
- // Widen any JSO arrays.
- Method m = upcastMethod(name, desc);
- desc = m == null ? desc : m.getDescriptor();
- if (disambiguator.length != 0) {
- desc = addDisambiguator(desc, disambiguator);
- // Add bogus values to the stack
- recordDebugData("Constructor disambiguation "
- + Arrays.asList(disambiguator));
- for (int i = 0, j = disambiguator.length; i < j; i++) {
- super.visitInsn(Opcodes.ACONST_NULL);
- }
- minStack = Math.max(minStack, stack.size());
- }
- break;
- }
- // Intentional fallthrough for non-init methods
- }
- case Opcodes.INVOKEINTERFACE:
- case Opcodes.INVOKESTATIC:
- case Opcodes.INVOKEVIRTUAL: {
- Method m = upcastMethod(name, desc);
- if (m != null) {
- recordDebugData("Replaced method call " + name + " " + desc
- + " with " + m.toString());
- name = m.getName();
- desc = m.getDescriptor();
- }
- break;
- }
- default:
- throw new RuntimeException("Unhandled method instruction " + opcode);
- }
- super.visitMethodInsn(opcode, owner, name, desc);
- }
-
- /**
- * Fix the types of multidimensional arrays allocations.
- */
- @Override
- public void visitMultiANewArrayInsn(String desc, int dims) {
- Type replacement = upcastJsoType(Type.getObjectType(desc));
- if (replacement != null) {
- recordDebugData("Widened JSO array allocation");
- desc = replacement.getInternalName();
- }
- super.visitMultiANewArrayInsn(desc, dims);
- }
-
- /**
- * Convert all JSO subtype array allocations into an allocation of a
- * JavaScriptObject array.
- */
- @Override
- public void visitTypeInsn(int opcode, String internalName) {
- if (opcode == Opcodes.ANEWARRAY) {
- Type replacement = upcastJsoType(Type.getObjectType(internalName));
- if (replacement != null) {
- recordDebugData("Widened JSO array allocation");
- internalName = replacement.getInternalName();
- }
- }
- super.visitTypeInsn(opcode, internalName);
- }
-
- /**
- * Utility method used by {@link #visitFrame} to process the stack and local
- * arrays.
- */
- private void fixFrameData(Object[] data) {
- for (int i = 0, j = data.length; i < j; i++) {
- if (data[i] instanceof String) {
- Type t = Type.getObjectType((String) data[i]);
- if (t.getSort() == Type.ARRAY) {
- t = upcastJsoType(t);
- if (t != null) {
- data[i] = t.getInternalName();
- }
- }
- }
- }
- }
- }
-
- /**
- * Creates an empty interface type.
- */
- static byte[] writeConstructorDisambiguationType(String className) {
- String internalName = className.replace('.', '/');
- assert internalName.startsWith(DISAMBIGUATOR_TYPE_INTERNAL_NAME) : "Bad className "
- + className;
-
- ClassWriter writer = new ClassWriter(0);
-
- writer.visit(SYSTEM_CLASS_VERSION, Opcodes.ACC_PUBLIC
- | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE, internalName, null,
- "java/lang/Object", null);
-
- writer.visitEnd();
- return writer.toByteArray();
- }
-
- /**
- * Records the class currently being processed.
- */
- private String owner;
- private final RewriterOracle rewriterOracle;
-
- public RewriteJsoArrays(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) {
- super.visit(version, access, name, signature, superName, interfaces);
- owner = name;
- }
-
- /**
- * Widen all fields of a JSO array type to JSO[].
- */
- @Override
- public FieldVisitor visitField(int access, String name, String desc,
- String signature, Object value) {
- Type t = Type.getType(desc);
- if (t.getSort() == Type.ARRAY) {
- Type newType = upcastJsoType(t);
- if (newType != null) {
- desc = newType.getDescriptor();
- }
- }
- return super.visitField(access, name, desc, signature, value);
- }
-
- /**
- * Widen all JSO array paramaters to JSO[]. Constructors may have a
- * disambiguator type added to their parameter list.
- */
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
- int minLocals = 0;
- String originalJsniName = name;
- String originalJsniDesc = desc;
- Method upcast = upcastMethod(name, desc);
-
- if (upcast != null) {
- if ("<init>".equals(name)) {
- String[] disambiguator = rewriterOracle.getArrayDisambiguator(desc);
- desc = upcast.getDescriptor();
- if (disambiguator.length != 0) {
- desc = addDisambiguator(desc, disambiguator);
- // +1 for this
- minLocals = Type.getArgumentTypes(desc).length + 1;
- }
- originalJsniName = "new";
- } else {
- name = upcast.getName();
- desc = upcast.getDescriptor();
- }
- }
-
- MethodVisitor mv = super.visitMethod(access, name, desc, signature,
- exceptions);
- if (mv != null) {
- // Record the original JSNI signature if we've mangled the method
- if (upcast != null) {
- AnnotationVisitor av = mv.visitAnnotation(ORIGINAL_JSNI_SIGNATURE_DESC,
- true);
- if (av != null) {
- av.visit("name", originalJsniName);
- // Strip the return type
- av.visit("paramList", originalJsniDesc.substring(0,
- originalJsniDesc.indexOf(')') + 1));
- av.visitEnd();
- }
- }
- mv = new UpcastAdapter(owner, access, name, desc, mv, minLocals);
- }
- return mv;
- }
-
- /**
- * Add disambiguator types as the last parameters of a method descriptor.
- */
- private String addDisambiguator(String desc, String[] disambiguator) {
- int idx = desc.indexOf(')');
- StringBuilder sb = new StringBuilder();
- sb.append(desc.substring(0, idx));
- for (String d : disambiguator) {
- sb.append("L").append(d).append(";");
- }
- sb.append(desc.substring(idx));
- return sb.toString();
- }
-
- /**
- * Utility method for constructing a descriptor. If the given type is a JSO
- * subtype array, the appropriate JavaScriptObject array type will be appended
- * to the descriptor and this method will return <code>true</code>. Otherwise,
- * this method will simply append the type to the descriptor and return
- * <code>false</code>.
- */
- private boolean appendTypeMaybeUpcast(StringBuilder newDesc, Type type) {
- if (type.getSort() == Type.ARRAY) {
- Type newType = upcastJsoType(type);
- if (newType != null) {
- newDesc.append(newType.getDescriptor());
- return true;
- }
- }
- newDesc.append(type.getDescriptor());
- return false;
- }
-
- /**
- * Calls {@link RewriteJsoCasts#upcastJsoType} with the instance's
- * {@link #rewriterOracle}.
- */
- private Type upcastJsoType(Type type) {
- return RewriteJsoCasts.upcastJsoType(rewriterOracle, type);
- }
-
- /**
- * Determine if a descriptor contains references to JSO subtype arrays. If so,
- * returns an upcast descriptor and a guaranteed-unique name for the method.
- * Otherwise, this method returns <code>null</code>.
- */
- private Method upcastMethod(String name, String desc) {
- boolean didChange = false;
- StringBuilder newName = new StringBuilder(name);
- StringBuilder newDesc = new StringBuilder("(");
-
- for (Type arg : Type.getArgumentTypes(desc)) {
- // Add the arguments, one at a time, to the new descriptor
- if (appendTypeMaybeUpcast(newDesc, arg)) {
- didChange = true;
- /*
- * Add the original type to the method name. We don't need to worry
- * about the number of parameters or their relative positions when
- * constructing the name, because that information is still in the
- * method descriptor.
- */
- newName.append("_").append(
- InternalName.toIdentifier(arg.getElementType().getInternalName())).append(
- "_").append(arg.getDimensions());
- }
- }
- newDesc.append(")");
-
- Type returnType = Type.getReturnType(desc);
- if (appendTypeMaybeUpcast(newDesc, returnType)) {
- didChange = true;
- }
-
- if (!didChange) {
- return null;
- }
- return new Method(newName.toString(), newDesc.toString());
- }
-}
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 267fe42..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoCasts.java
+++ /dev/null
@@ -1,557 +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.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.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.Type;
-import com.google.gwt.dev.asm.commons.Method;
-import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.RewriterOracle;
-
-/**
- * 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 DebugAnalyzerAdapter {
- /*
- * 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 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();
- if (returnType.getSort() == Type.OBJECT) {
- String internalName = returnType.getInternalName();
- boolean maybeCanonicalize = rewriterOracle.jsoAssignmentRequiresCanonicalization(internalName);
- boolean maybeRewrap = rewriterOracle.isJsoOrSubtype(internalName);
- returnNeedsCanonical = maybeCanonicalize || maybeRewrap;
- } else {
- returnNeedsCanonical = false;
- }
- }
-
- @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.
- */
- recordDebugData("Canonicalized object for getClass");
- 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 ("cast".equals(name)
- && ("()L" + JAVASCRIPTOBJECT_DESC + ";").equals(desc)
- && rewriterOracle.isJsoOrSubtype(owner)) {
- // Remove calls to cast, since they trigger NPEs in older code
- recordDebugData("Removed call to JSO.cast()");
- 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);
- boolean isInstance = opcode == Opcodes.INVOKEINTERFACE
- || opcode == Opcodes.INVOKEVIRTUAL;
- Type[] args;
- if (isInstance) {
- /*
- * Treat invocations of non-static dispatch as though they were static
- * methods that have an extra "this" argument that is the type of the
- * instance that's expected.
- */
- Type[] actualArgs = m.getArgumentTypes();
- args = new Type[actualArgs.length + 1];
- args[0] = Type.getObjectType(owner);
- System.arraycopy(actualArgs, 0, args, 1, actualArgs.length);
- } else {
- 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 or
- * rewrapping.
- */
- if (arg.getSort() != Type.OBJECT) {
- continue;
- }
- boolean maybeCanonicalize = rewriterOracle.jsoAssignmentRequiresCanonicalization(arg.getInternalName());
- boolean maybeRewrap = rewriterOracle.couldContainJso(arg.getInternalName());
- if (!maybeCanonicalize && !maybeRewrap) {
- 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) {
- recordDebugData("Fixed arguments to method invocation");
- // 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);
- 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());
- }
-
- // 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) {
- recordDebugData("Upcast JSO array checkcast from "
- + parsed.getInternalName());
- type = t.getInternalName();
- }
- }
- // Intentional fall-through to super invocation below
- break;
-
- case Opcodes.INSTANCEOF: {
- String internalName = parsed.getInternalName();
- if (rewriterOracle.isInterface(internalName)
- && rewriterOracle.couldContainJso(internalName)) {
- recordDebugData("SingleJsoImpl instanceof check for "
- + internalName);
- /*
- * 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
- recordDebugData("Upcast instanceof from " + internalName);
- 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;
-
- recordDebugData(topType + " -> " + internalName);
-
- 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>.
- */
- static Type upcastJsoType(RewriterOracle rewriterOracle, 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());
- }
-
- 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;
- }
-
- /**
- * Convenience method.
- */
- private Type upcastJsoType(Type type) {
- return upcastJsoType(rewriterOracle, type);
- }
-}
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 36a182e..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/SingleJsoImplSupport.java
+++ /dev/null
@@ -1,270 +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.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);
- }
-
- /**
- * Called by synthetic code when the object on the stack might be a JSO
- * wrapper and we want the canonical object.
- */
- public static Object ensureCanonical(Object o) {
- if (o == null) {
- return null;
- }
-
- Class<?> jsoClass = getJsoClass(o);
- if (jsoClass.isInstance(o)) {
- Exception ex;
- try {
- return jsoClass.getField(CANONICAL_FIELD).get(o);
- } catch (IllegalArgumentException e) {
- ex = e;
- } catch (SecurityException e) {
- ex = e;
- } catch (IllegalAccessException e) {
- ex = e;
- } catch (NoSuchFieldException e) {
- ex = e;
- }
- throw new RuntimeException("Unable to determine canonical object", ex);
- }
-
- 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/dev/core/src/com/google/gwt/dev/util/Name.java b/dev/core/src/com/google/gwt/dev/util/Name.java
index 647a9f9..279a744 100644
--- a/dev/core/src/com/google/gwt/dev/util/Name.java
+++ b/dev/core/src/com/google/gwt/dev/util/Name.java
@@ -165,12 +165,7 @@
assert isInternalName(internalName);
return internalName.replace('/', '.');
}
-
- public static String toIdentifier(String internalName) {
- assert isInternalName(internalName);
- return internalName.replace("_", "_1").replace('/', '_');
- }
-
+
public static String toSourceName(String internalName) {
assert isInternalName(internalName);
// don't change a trailing $ or slash to a .
diff --git a/user/src/com/google/gwt/i18n/client/CurrencyData.java b/user/src/com/google/gwt/i18n/client/CurrencyData.java
index 2b11211..e4eda01 100644
--- a/user/src/com/google/gwt/i18n/client/CurrencyData.java
+++ b/user/src/com/google/gwt/i18n/client/CurrencyData.java
@@ -15,13 +15,9 @@
*/
package com.google.gwt.i18n.client;
-import com.google.gwt.core.client.SingleJsoImpl;
-import com.google.gwt.i18n.client.impl.CurrencyDataImpl;
-
/**
* Information about a currency.
*/
-@SingleJsoImpl(CurrencyDataImpl.class)
public interface CurrencyData {
/**
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 93f9e3f..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,54 +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());
-
- // Test Collection.toArray(), since the behavior depends on the array type
- Object[] objectArray = fooList.toArray(new Object[0]);
- assertSame(foo[0], objectArray[0]);
- JavaScriptObject[] jsArray = fooList.toArray(new JavaScriptObject[0]);
- assertSame(foo[0], jsArray[0]);
- Foo[] fooArray = fooList.toArray(new Foo[0]);
- assertSame(foo[0], fooArray[0]);
- acceptsFooArray(fooArray, 1);
-
- // Test System.arrayCopy to make sure cross-JSO cast appears to work
- Bar[] bar2 = new Bar[1];
- System.arraycopy(foo, 0, bar2, 0, 1);
- assertSame(foo[0], bar[0]);
-
- try {
- String[] bad = new String[1];
- System.arraycopy(foo, 0, bad, 0, 1);
- fail("Should have thrown ArrayStoreException");
- } catch (ArrayStoreException expected) {
- }
- }
-
- private void acceptsFooArray(Foo[] array, int length) {
- assertEquals(length, array.length);
- }
-
public void testArrayStore() {
JavaScriptObject[] jsoArray = new JavaScriptObject[1];
jsoArray[0] = makeJSO();
@@ -442,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")
@@ -508,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());
@@ -535,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());
@@ -550,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() {
@@ -654,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 b7d4927..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) {
@@ -582,10 +557,6 @@
fail("b");
}
}
-
- public void testDispatchWithWidenedArrays() {
- acceptsSimpleArrayAndInvokes(makeSimple(), makeSimple(), makeSimple());
- }
public void testDualCase() {
// Direct dispatch
@@ -633,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");
@@ -665,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() {
{
@@ -1003,7 +711,6 @@
}
public void testStaticCallsToSubclasses() {
- JsoCallsStaticMethodInSubclass a = JavaScriptObject.createObject().cast();
Object o = "String";
assertEquals(String.class, o.getClass());
o = JavaScriptObject.createObject();
@@ -1084,66 +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 void acceptsSimpleArrayAndInvokes(Simple... simples) {
- assertEquals(3, simples.length);
- for (Simple s : simples) {
- assertEquals("a", s.a());
- }
- }
-
- 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;
- }-*/;
}
diff --git a/user/test/com/google/gwt/dev/jjs/test/jsointfs/JsoInterfaceWithUnreferencedImpl.java b/user/test/com/google/gwt/dev/jjs/test/jsointfs/JsoInterfaceWithUnreferencedImpl.java
index 8a281e0..42a7e26 100644
--- a/user/test/com/google/gwt/dev/jjs/test/jsointfs/JsoInterfaceWithUnreferencedImpl.java
+++ b/user/test/com/google/gwt/dev/jjs/test/jsointfs/JsoInterfaceWithUnreferencedImpl.java
@@ -15,14 +15,10 @@
*/
package com.google.gwt.dev.jjs.test.jsointfs;
-import com.google.gwt.core.client.SingleJsoImpl;
-import com.google.gwt.dev.jjs.test.jsoimpls.UnreferencedImplOfJsoInterface;
-
/**
* This class exists for the purpose of testing JSO implementation types that
* aren't specifically referenced in any Java source.
*/
-@SingleJsoImpl(UnreferencedImplOfJsoInterface.class)
public interface JsoInterfaceWithUnreferencedImpl {
boolean isOk();
}