Reintroduces DevMode JSO rewrite after correcting VerificationErrors.
http://gwt-code-reviews.appspot.com/473801/show
Patch by: bobv
Review by: scottb
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8295 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 1ceeea4..b80057c 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);
+ assert Name.isSourceName(name) : name + " is not a source 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 a789ac0..a6ff29d 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -19,29 +19,21 @@
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.HostedModeClassRewriter.InstanceMethodOracle;
-import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
+import com.google.gwt.dev.shell.rewrite.OriginalJsniSignature;
+import com.google.gwt.dev.shell.rewrite.SingleJsoImplSupport;
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;
@@ -64,9 +56,7 @@
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
@@ -146,23 +136,6 @@
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.")) {
@@ -294,11 +267,6 @@
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
@@ -355,325 +323,6 @@
}
/**
- * 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<?>>();
@@ -683,7 +332,8 @@
* space (thus, they bridge across the spaces).
*/
private static final Class<?>[] BRIDGE_CLASSES = new Class<?>[] {
- ShellJavaScriptHost.class, GWTBridge.class};
+ ShellJavaScriptHost.class, GWTBridge.class, OriginalJsniSignature.class,
+ SingleJsoImplSupport.class};
private static final boolean CLASS_DUMP = Boolean.getBoolean("gwt.dev.classDump");
@@ -830,8 +480,6 @@
private ShellJavaScriptHost shellJavaScriptHost;
- private final Set<String> singleJsoImplTypes = new HashSet<String>();
-
/**
* Used by {@link #findClass(String)} to prevent reentrant JSNI injection.
*/
@@ -861,36 +509,10 @@
ensureJavaScriptHostBytes(logger);
- // Create a class rewriter based on all the subtypes of the JSO class.
+ // Create a class rewriter based on availability of JSO class.
JClassType jsoType = typeOracle.findType(JsValueGlue.JSO_CLASS);
if (jsoType != null) {
-
- // 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);
+ classRewriter = new HostedModeClassRewriter(typeOracle);
} else {
// If we couldn't find the JSO class, we don't need to do any rewrites.
classRewriter = null;
@@ -1020,8 +642,8 @@
* when loading a JSO interface class; just wait until the implementation
* class is loaded.
*/
- if (!classRewriter.isJsoIntf(className)) {
- CompilationUnit unit = getUnitForClassName(canonicalizeClassName(className));
+ {
+ CompilationUnit unit = getUnitForClassName(BinaryName.toInternalName(className));
if (unit != null) {
toInject.push(unit);
}
@@ -1064,37 +686,27 @@
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;
- }
-
- if (classRewriter != null && classRewriter.isJsoIntf(className)) {
- // Generate a synthetic JSO interface class.
- byte[] newBytes = classRewriter.writeJsoIntf(className);
+ } else if (className.endsWith(HostedModeClassRewriter.SINGLE_JSO_IMPL_ADJUNCT_SUFFIX)) {
+ byte[] bytes = classRewriter.writeSingleJsoImplAdjunct(className);
if (CLASS_DUMP) {
- classDump(className, newBytes);
+ classDump(className, bytes);
}
- return newBytes;
+ return bytes;
+ } else if (className.startsWith(HostedModeClassRewriter.DISAMBIGUATOR_TYPE_NAME)) {
+ byte[] bytes = classRewriter.writeConstructorDisambiguationType(className);
+ if (CLASS_DUMP) {
+ classDump(className, bytes);
+ }
+ return bytes;
}
// A JSO impl class needs the class bytes for the original class.
- String lookupClassName = canonicalizeClassName(className);
+ String lookupClassName = BinaryName.toInternalName(className);
CompiledClass compiledClass = compilationState.getClassFileMap().get(
lookupClassName);
@@ -1155,8 +767,8 @@
if (unit != null) {
anonymousClassMap = unit.getAnonymousClassMap();
}
- byte[] newBytes = classRewriter.rewrite(typeOracle, className,
- classBytes, anonymousClassMap);
+ byte[] newBytes = classRewriter.rewrite(className, classBytes,
+ anonymousClassMap);
if (CLASS_DUMP) {
if (!Arrays.equals(classBytes, newBytes)) {
classDump(className, newBytes);
@@ -1167,29 +779,6 @@
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.
@@ -1212,6 +801,8 @@
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 27819b3..9fecfa2 100644
--- a/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
+++ b/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
@@ -15,8 +15,10 @@
*/
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;
@@ -153,6 +155,21 @@
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) {
@@ -168,7 +185,7 @@
+ member.getClass().getName());
}
- StringBuffer sb = new StringBuffer();
+ StringBuilder sb = new StringBuilder();
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 02e2cdd..fdefaed 100644
--- a/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java
+++ b/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java
@@ -16,6 +16,7 @@
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;
@@ -29,11 +30,13 @@
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.
+ * Create a JavaScriptObject instance referring to this JavaScript object,
+ * optionally wrapping the object in the desired JSO facade type.
*
+ * @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
*/
@@ -41,23 +44,18 @@
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) {
- return jso;
+ if (jso == null) {
+ // Instantiate the JSO instance.
+ Constructor<?> ctor = jsoType.getDeclaredConstructor(Object.class);
+ jso = ctor.newInstance(value);
+
+ classLoader.putCachedJso(value.getJavaScriptObjectPointer(), 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;
@@ -73,8 +71,6 @@
caught = e;
} catch (ClassNotFoundException e) {
caught = e;
- } catch (NoSuchFieldException e) {
- caught = e;
}
throw new RuntimeException("Error creating JavaScript object", caught);
}
@@ -88,7 +84,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,
@@ -166,7 +162,11 @@
return type.cast(value.getString());
}
if (value.isJavaScriptObject()) {
- return type.cast(createJavaScriptObject(value, cl));
+ Object jso = createJavaScriptObject(value, cl);
+ if (type != Object.class) {
+ jso = SingleJsoImplSupport.cast(jso, type);
+ }
+ return type.cast(jso);
}
// Just don't know what do to with this.
@@ -217,8 +217,8 @@
} else {
// not a boxed primitive
try {
- Class<?> jsoType = Class.forName(JSO_IMPL_CLASS, false, cl);
- if (jsoType == obj.getClass()) {
+ Class<?> jsoType = Class.forName(JSO_CLASS, false, cl);
+ if (jsoType.isInstance(obj)) {
JsValue jsObject = getUnderlyingObject(obj);
value.setValue(jsObject);
return;
@@ -258,9 +258,10 @@
}
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
new file mode 100644
index 0000000..44bbf8a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/DebugAnalyzerAdapter.java
@@ -0,0 +1,130 @@
+/*
+ * 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 319aeb3..ca1f488 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
@@ -15,22 +15,26 @@
*/
package com.google.gwt.dev.shell.rewrite;
+import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.asm.ClassAdapter;
import com.google.gwt.dev.asm.ClassReader;
import com.google.gwt.dev.asm.ClassVisitor;
import com.google.gwt.dev.asm.ClassWriter;
import com.google.gwt.dev.asm.Opcodes;
-import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.asm.Type;
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.Collections;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
-import java.util.SortedSet;
/**
* This class performs any and all byte code rewriting needed to make hosted
@@ -38,25 +42,168 @@
* <ol>
* <li>Rewrites all native methods into non-native thunks to call JSNI via
* {@link com.google.gwt.dev.shell.JavaScriptHost}.</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>
+ * <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>
* </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
@@ -65,160 +212,68 @@
*/
/**
- * Maps instance methods to the class in which they are declared. This must be
- * provided by the caller since it requires global program state.
+ * Used in CompilingClassLoader to trigger a call to
+ * {@link #writeConstructorDisambiguationType}.
*/
- 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);
- }
+ public static final String DISAMBIGUATOR_TYPE_NAME = "com.google.gwt.dev.shell.rewrite.$Disambiguator";
/**
- * Contains data about how SingleJsoImpl methods are to be dispatched.
+ * Used in CompilingClassLoader to trigger a call to
+ * {@link #writeSingleJsoImplAdjunct}.
*/
- 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);
+ public static final String SINGLE_JSO_IMPL_ADJUNCT_SUFFIX = "$$singleJsoImpl";
- /**
- * 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 CANONICAL_FIELD = "canonicalJso";
- /**
- * Returns all of the mangled method names for SingleJsoImpl methods.
- */
- SortedSet<String> getMangledNames();
+ static final String DISAMBIGUATOR_TYPE_INTERNAL_NAME = BinaryName.toInternalName(DISAMBIGUATOR_TYPE_NAME);
- /**
- * Returns the internal names of all interface types implemented by JSOs.
- */
- Set<String> getSingleJsoIntfTypes();
- }
+ static final boolean EXTRA_DEBUG_DATA = Boolean.getBoolean("gwt.dev.classDump");
- static final String JAVASCRIPTOBJECT_DESC = JsValueGlue.JSO_CLASS.replace(
- '.', '/');
-
- static final String JAVASCRIPTOBJECT_IMPL_DESC = JsValueGlue.JSO_IMPL_CLASS.replace(
- '.', '/');
+ static final String JAVASCRIPTOBJECT_DESC = BinaryName.toInternalName(JsValueGlue.JSO_CLASS);
static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE;
- static String addSyntheticThisParam(String owner, String methodDescriptor) {
- return "(L" + owner + ";" + methodDescriptor.substring(1);
- }
+ static final String REWRAP_METHOD = "$rewrap";
- private static String toDescriptor(String jsoSubtype) {
- return jsoSubtype.replace('.', '/');
- }
+ 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;
/**
- * An unmodifiable set of descriptors containing the implementation form of
- * <code>JavaScriptObject</code> and all subclasses.
+ * Passed into the rewriting visitors.
*/
- private final Set<String> jsoImplDescs;
+ private final RewriterOracle rewriterOracle;
/**
- * 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.
+ * Creates a new {@link HostedModeClassRewriter}.
*
- * @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
+ * @param typeOracle The TypeOracle for the GWT module that is being rewritten
*/
- 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));
+ public HostedModeClassRewriter(TypeOracle typeOracle) {
+ rewriterOracle = new RewriterOracle(typeOracle);
}
/**
* 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(TypeOracle typeOracle, String className,
- byte[] classBytes, Map<String, String> anonymousClassMap) {
- String desc = toDescriptor(className);
- assert (!jsoIntfDescs.contains(desc));
+ public byte[] rewrite(String className, byte[] classBytes,
+ Map<String, String> anonymousClassMap) {
+ classBytes = maybeUpgradeBytecode(classBytes);
+ String desc = BinaryName.toInternalName(className);
// The ASM model is to chain a bunch of visitors together.
ClassWriter writer = new ClassWriter(0);
@@ -227,49 +282,75 @@
// v = new CheckClassAdapter(v);
// v = new TraceClassVisitor(v, new PrintWriter(System.out));
- v = new RewriteSingleJsoImplDispatches(v, typeOracle, jsoData);
+ v = new WriteSingleJsoSupportCode(v, rewriterOracle);
- v = new RewriteRefsToJsoClasses(v, jsoIntfDescs, mapper);
+ v = new RewriteJsoCasts(v, rewriterOracle);
- if (jsoImplDescs.contains(desc)) {
- v = WriteJsoImpl.create(v, desc, jsoIntfDescs, mapper, jsoData);
+ v = new RewriteJsoArrays(v, rewriterOracle);
+
+ v = new RewriteObjectComparisons(v, rewriterOracle);
+
+ if (rewriterOracle.isJsoOrSubtype(desc)) {
+ v = WriteJsoImpl.create(v, desc);
}
v = new RewriteJsniMethods(v, anonymousClassMap);
- if (Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_6) {
+ if (SYSTEM_CLASS_VERSION < Opcodes.V1_6) {
v = new ForceClassVersion15(v);
}
- new ClassReader(classBytes).accept(v, 0);
+ // We need EXPAND_FRAMES here for RewriteJsoCasts
+ new ClassReader(classBytes).accept(v, ClassReader.EXPAND_FRAMES);
return writer.toByteArray();
}
- 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 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);
+ }
- // The ASM model is to chain a bunch of visitors together.
- ClassWriter writer = new ClassWriter(0);
- ClassVisitor v = writer;
+ /**
+ * 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);
+ }
- // v = new CheckClassAdapter(v);
- // v = new TraceClassVisitor(v, new PrintWriter(System.out));
+ /**
+ * 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;
- String[] interfaces;
- // TODO(bov): something better than linear?
- if (superDescs.contains("java/lang/Object")) {
- interfaces = null;
- } else {
- interfaces = superDescs.toArray(new String[superDescs.size()]);
+ // 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();
}
- v.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, desc,
- null, "java/lang/Object", interfaces);
- v.visitEnd();
- return writer.toByteArray();
+ return classBytes;
}
}
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
new file mode 100644
index 0000000..74d924a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/OriginalJsniSignature.java
@@ -0,0 +1,39 @@
+/*
+ * 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
new file mode 100644
index 0000000..db716e3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoArrays.java
@@ -0,0 +1,388 @@
+/*
+ * 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
new file mode 100644
index 0000000..267fe42
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoCasts.java
@@ -0,0 +1,557 @@
+/*
+ * 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
new file mode 100644
index 0000000..f7d2377
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteObjectComparisons.java
@@ -0,0 +1,108 @@
+/*
+ * 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
deleted file mode 100644
index 3a45363..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteRefsToJsoClasses.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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
deleted file mode 100644
index b6c7c1e..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..36a182e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/SingleJsoImplSupport.java
@@ -0,0 +1,270 @@
+/*
+ * 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 4eafd89..92acc1b 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,94 +15,79 @@
*/
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>
*/
-abstract class WriteJsoImpl extends ClassAdapter {
+class WriteJsoImpl {
/**
* This type implements JavaScriptObject.
*
- * <ol>
+ * <ul>
* <li>JavaScriptObject itself gets a new synthetic field to store the
* underlying hosted mode reference.</li>
- * <li>Instance methods are added so that JavaScriptObject implements all
- * SingleJsoImpl interfaces.</li>
- * </ol>
+ * <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>
*
*/
- 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;
+ private static class ForJso extends ClassAdapter {
+ public ForJso(ClassVisitor cv) {
+ super(cv);
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
- ArrayList<String> jsoDescList = new ArrayList<String>();
- jsoDescList.addAll(jsoDescriptors);
- interfaces = jsoDescList.toArray(new String[jsoDescList.size()]);
+ super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
+ | Opcodes.ACC_SYNTHETIC, name, signature, superName, interfaces);
- super.visit(version, access, name, signature, superName, interfaces);
+ // 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);
+ }
/*
* Generate the synthetic "hostedModeReferece" field to contain the
* underlying real reference to the JavaScript object.
*/
- FieldVisitor fv = visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
- HostedModeClassRewriter.REFERENCE_FIELD, "Ljava/lang/Object;", null,
+ FieldVisitor fv = visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC
+ | Opcodes.ACC_FINAL, REFERENCE_FIELD, "Ljava/lang/Object;", 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());
- }
+ /*
+ * 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();
}
}
@@ -110,123 +95,364 @@
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if (isCtor(name)) {
- // make the JavaScriptObject$ constructor public
- access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
- access |= Opcodes.ACC_PUBLIC;
+ writeConstructors(name);
+ return null;
+ } else if ("equals".equals(name) && "(Ljava/lang/Object;)Z".equals(desc)) {
+ writeEquals(access, name, desc, signature, exceptions);
+ return null;
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
/**
- * 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:
+ * Generates a method to return the canonical object.
*
* <pre>
- * 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);
+ * public JavaScriptObject rewrap$(JavaScriptObject o) {
+ * if (o == null) {
+ * return null;
* }
+ * return o.canonical;
* }
* </pre>
- *
- * @param mangledName {@code com_google_gwt_sample_hello_client_Interface_a}
- * @param interfaceMethod {@code java.lang.String a(int, double)}
- * @param implementingMethod {@code static final java.lang.String
- * a$(com.google.gwt.sample.hello.client.Jso, ...);}
*/
- private void writeTrampoline(String mangledName, Method interfaceMethod,
- Method implementingMethod) {
- assert implementingMethod.getArgumentTypes().length > 0;
+ protected void writeRewrapMethod(MethodVisitor mv) {
+ Label start = new Label();
+ Label end = new Label();
- /*
- * 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);
+ mv.visitCode();
+ mv.visitLabel(start);
- /*
- * We also use the first argument to know which type to statically
- * dispatch to.
- */
- Type implementingType = Type.getType("L"
- + implementingMethod.getArgumentTypes()[0].getInternalName() + "$;");
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ // Stack is: jso
+ mv.visitInsn(Opcodes.DUP);
+ // Stack is: jso, jso
- // 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);
+ 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>
+ */
+ 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);
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
- /*
- * 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;
+ // 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>
- 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.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(4, 2);
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.
*
- * <ol>
- * <li>The new type's superclass is mangled by adding $.</li>
- * <li>Constructors are deleted.</li>
- * </ol>
+ * <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>
*/
- private static class ForJsoInterface extends WriteJsoImpl {
- public ForJsoInterface(ClassVisitor cv, InstanceMethodOracle mapper) {
- super(cv, mapper);
+ private static class ForJsoSubclass extends ClassAdapter {
+ private String superName;
+ private String typeName;
+
+ public ForJsoSubclass(ClassVisitor cv) {
+ super(cv);
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
- // Reference the old superclass's implementation class.
- superName += '$';
- interfaces = null;
+ this.superName = superName;
+ this.typeName = name;
+ super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
+ | Opcodes.ACC_SYNTHETIC, name, signature, superName, interfaces);
- super.visit(version, access, name, signature, superName, interfaces);
+ // 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);
+ }
}
+ /**
+ * 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) {
- // Don't copy over constructors except for JavaScriptObject itself.
+ 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();
+
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();
+ }
}
/**
@@ -234,89 +460,21 @@
* 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,
- Set<String> jsoDescriptors, InstanceMethodOracle mapper,
- SingleJsoImplData singleJsoImplData) {
-
- if (classDescriptor.equals(HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC)) {
- return new ForJsoDollar(cv, jsoDescriptors, mapper, singleJsoImplData);
+ public static ClassVisitor create(ClassVisitor cv, String classDescriptor) {
+ if (classDescriptor.equals(JAVASCRIPTOBJECT_DESC)) {
+ return new ForJso(cv);
} else {
- return new ForJsoInterface(cv, mapper);
+ return new ForJsoSubclass(cv);
}
}
- /**
- * 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;
- }
-
- /**
- * Records the original name and resets access opcodes.
- */
- @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) {
+ private static 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.
+ * Utility class.
*/
- protected MethodVisitor visitMethodNoRewrite(int access, String name,
- String desc, String signature, String[] exceptions) {
- return super.visitMethod(access, name, desc, signature, exceptions);
+ private WriteJsoImpl() {
}
}
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
new file mode 100644
index 0000000..05532a3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteSingleJsoSupportCode.java
@@ -0,0 +1,424 @@
+/*
+ * 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 279a744..647a9f9 100644
--- a/dev/core/src/com/google/gwt/dev/util/Name.java
+++ b/dev/core/src/com/google/gwt/dev/util/Name.java
@@ -165,7 +165,12 @@
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 e4eda01..2b11211 100644
--- a/user/src/com/google/gwt/i18n/client/CurrencyData.java
+++ b/user/src/com/google/gwt/i18n/client/CurrencyData.java
@@ -15,9 +15,13 @@
*/
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 5bf60b0..93f9e3f 100644
--- a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
@@ -15,9 +15,13 @@
*/
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.
*/
@@ -52,7 +56,7 @@
public static native String staticNative() /*-{
return "nativeFoo";
}-*/;
-
+
/**
* Ensure that a supertype can refer to members of a subtype.
*/
@@ -80,7 +84,7 @@
static String staticValueSub() {
return "FooSub";
}
-
+
protected FooSub() {
}
@@ -207,6 +211,20 @@
}
}
+ 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() {
@@ -257,6 +275,8 @@
return jso;
}-*/;
+ private Object aField;
+
@Override
public String getModuleName() {
return "com.google.gwt.dev.jjs.CompilerSuite";
@@ -269,6 +289,54 @@
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();
@@ -374,6 +442,16 @@
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")
@@ -430,7 +508,10 @@
assertEquals(JavaScriptObject.class, Bar.class);
assertEquals(Foo.class, Bar.class);
- if (!JavaScriptObject.class.getName().startsWith("Class$")) {
+ if (!GWT.isScript()) {
+ assertEquals("com.google.gwt.core.client.JavaScriptObject",
+ JavaScriptObject.class.getName());
+ } else if (!JavaScriptObject.class.getName().startsWith("Class$")) {
// Class metadata could be disabled
assertEquals("com.google.gwt.core.client.JavaScriptObject$",
JavaScriptObject.class.getName());
@@ -454,7 +535,10 @@
assertEquals(JavaScriptObject[][].class, Bar[][].class);
assertEquals(Foo[][].class, Bar[][].class);
- if (!JavaScriptObject.class.getName().startsWith("Class$")) {
+ if (!GWT.isScript()) {
+ assertEquals("[[Lcom.google.gwt.core.client.JavaScriptObject;",
+ JavaScriptObject[][].class.getName());
+ } else if (!JavaScriptObject.class.getName().startsWith("Class$")) {
// Class metadata could be disabled
assertEquals("[[Lcom.google.gwt.core.client.JavaScriptObject$;",
JavaScriptObject[][].class.getName());
@@ -466,11 +550,19 @@
assertEquals(jso, jso);
JavaScriptObject jso2 = makeJSO();
+ assertNotSame(jso, jso2);
assertFalse(jso.equals(jso2));
assertFalse(jso2.equals(jso));
jso2 = returnMe(jso);
- assertEquals(jso, jso2);
+ assertSame(jso, jso2);
+
+ Object aLocal = (Bar) jso;
+ Object aLocal2 = (Foo) jso2;
+ aField = aLocal;
+ assertSame(aLocal, aField);
+ assertSame(jso, aField);
+ assertSame(aLocal, aLocal2);
}
public void testGenericsJsos() {
@@ -562,6 +654,30 @@
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 ae07751..b7d4927 100644
--- a/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
@@ -16,12 +16,20 @@
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.
@@ -67,10 +75,12 @@
String call(int a, int b);
}
+ @SingleJsoImpl(JsoCreatedWithCast.class)
interface CreatedWithCast {
String foo();
}
+ @SingleJsoImpl(JsoCreatedWithCastToTag.class)
interface CreatedWithCastToTag {
}
@@ -509,12 +519,19 @@
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());
@@ -522,6 +539,7 @@
}
public void testCallsWithArrays() {
+ JsoUsesArrays toss = JavaScriptObject.createObject().cast();
UsesArrays a = JavaScriptObject.createObject().<JsoUsesArrays> cast();
a.acceptIntArray(a.returnIntArray());
a.acceptInt3Array(a.returnInt3Array());
@@ -539,6 +557,12 @@
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
@@ -546,6 +570,7 @@
* 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) {
@@ -557,6 +582,10 @@
fail("b");
}
}
+
+ public void testDispatchWithWidenedArrays() {
+ acceptsSimpleArrayAndInvokes(makeSimple(), makeSimple(), makeSimple());
+ }
public void testDualCase() {
// Direct dispatch
@@ -604,7 +633,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");
@@ -636,6 +665,269 @@
}, "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() {
{
@@ -711,6 +1003,7 @@
}
public void testStaticCallsToSubclasses() {
+ JsoCallsStaticMethodInSubclass a = JavaScriptObject.createObject().cast();
Object o = "String";
assertEquals(String.class, o.getClass());
o = JavaScriptObject.createObject();
@@ -791,9 +1084,66 @@
}
}
+ /**
+ * 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 42a7e26..8a281e0 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,10 +15,14 @@
*/
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();
}