This patch improves the correctness of SingleJso types by following all super-interfaces when calculating singleJso data. Additionally, casts to singleJso interfaces will trigger a rescue of the concrete JSO type.
Without these changes, singleJso types that are referred to only via cast operations would generate incorrect code at the cast site, as a Cast.throwClassCastExceptionUnlessNull() call would be made instead of a dynamicCastJso() as the compiler would decide there are no concrete implementations of the singleJso interface type.
Patch by: bobv, mtsui
Review by: spoon
Reported by: macpherson, mtsui
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5466 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
index 5cc2fbd..337b94f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -183,8 +183,8 @@
*
* @param type any type
* @param instantiatedTypes a set of types assumed to be instantiated. If
- * <code>null</code>, then there are no assumptions about which
- * types are instantiated.
+ * <code>null</code>, then there are no assumptions about which types
+ * are instantiated.
* @return whether the type is instantiated
*/
private static boolean isInstantiatedType(JReferenceType type,
@@ -536,6 +536,25 @@
}
/**
+ * Computes the set of all interfaces implemented by a type.
+ */
+ private Set<JInterfaceType> allSuperInterfaces(JDeclaredType type) {
+ Set<JInterfaceType> toReturn = new IdentityHashSet<JInterfaceType>();
+ List<JInterfaceType> q = new LinkedList<JInterfaceType>();
+ q.addAll(type.getImplements());
+
+ while (!q.isEmpty()) {
+ JInterfaceType t = q.remove(0);
+
+ if (toReturn.add(t)) {
+ q.addAll(t.getImplements());
+ }
+ }
+
+ return toReturn;
+ }
+
+ /**
* Compute all of the things I might conceivably implement, either through
* super types or sub types.
*/
@@ -653,7 +672,7 @@
for (JDeclaredType type : program.getDeclaredTypes()) {
if (!program.isJavaScriptObject(type)) {
if (type instanceof JClassType) {
- dualImpl.addAll(type.getImplements());
+ dualImpl.addAll(allSuperInterfaces(type));
}
continue;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
index 383835a..8e08a11 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -175,7 +175,23 @@
JType targetType = x.getCastType();
if (program.isJavaScriptObject(targetType)) {
rescue((JReferenceType) targetType, true, true);
+ } else {
+ /*
+ * If there's a cast to a SingleJso interface, rescue the implementing
+ * JSO type. If the JSO type isn't rescued (and there's no other regular
+ * Java type implementing the interface), then the cast operation will
+ * be replaced with a throwCCEUnlessNull() call, since there's no type
+ * left in the type system that implements the interface. If there is an
+ * implementing Java type, then a dynamicCast() will be emitted which
+ * will throw a CCE if it hits a JSO type.
+ */
+ JClassType maybeSingleJso = program.typeOracle.getSingleJsoImpls().get(
+ targetType);
+ if (maybeSingleJso != null) {
+ rescue(maybeSingleJso, true, true);
+ }
}
+
return true;
}
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 8ed2db3..2e99313 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -993,7 +993,7 @@
if (unit != null) {
anonymousClassMap = unit.getAnonymousClassMap();
}
- byte[] newBytes = classRewriter.rewrite(className, classBytes,
+ byte[] newBytes = classRewriter.rewrite(this, className, classBytes,
anonymousClassMap);
if (CLASS_DUMP) {
if (!Arrays.equals(classBytes, newBytes)) {
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 7b88173..6f1621a 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
@@ -183,12 +183,13 @@
/**
* Performs rewriting transformations on a class.
*
+ * @param ccl the ClassLoader requesting the rewrite
* @param className the name of the class
* @param classBytes the bytes of the class
* @param anonymousClassMap a map between the anonymous class names of java
* compiler used to compile code and jdt. Emma-specific.
*/
- public byte[] rewrite(String className, byte[] classBytes,
+ public byte[] rewrite(ClassLoader ccl, String className, byte[] classBytes,
Map<String, String> anonymousClassMap) {
String desc = toDescriptor(className);
assert (!jsoIntfDescs.contains(desc));
@@ -200,7 +201,7 @@
// v = new CheckClassAdapter(v);
// v = new TraceClassVisitor(v, new PrintWriter(System.out));
- v = new RewriteSingleJsoImplDispatches(v, singleJsoImplTypes,
+ v = new RewriteSingleJsoImplDispatches(v, ccl, singleJsoImplTypes,
mangledNamesToImplementations);
v = new RewriteRefsToJsoClasses(v, jsoIntfDescs, mapper);
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
index b08e667..94bb354 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
@@ -23,9 +23,9 @@
import com.google.gwt.dev.asm.Type;
import com.google.gwt.dev.asm.commons.Method;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -62,8 +62,57 @@
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
- if (singleJsoImplTypes.contains(owner)) {
- name = owner.replace('/', '_') + "_" + name;
+ if (opcode == Opcodes.INVOKEINTERFACE) {
+ if (singleJsoImplTypes.contains(owner)) {
+ // Simple case; referring directly to a SingleJso interface.
+ name = owner.replace('/', '_') + "_" + name;
+ assert mangledNamesToImplementations.containsKey(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(); }
+ */
+ for (String intf : computeAllInterfaces(owner)) {
+ if (singleJsoImplTypes.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;
+ Method method = mangledNamesToImplementations.get(maybeMangled);
+ if (method != null) {
+ /*
+ * 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;
+ }
+ }
+ }
+ }
+ }
}
super.visitMethodInsn(opcode, owner, name, desc);
@@ -76,10 +125,13 @@
private final Set<String> singleJsoImplTypes;
private boolean inSingleJsoImplInterfaceType;
- public RewriteSingleJsoImplDispatches(ClassVisitor v,
+ private final ClassLoader ccl;
+
+ public RewriteSingleJsoImplDispatches(ClassVisitor v, ClassLoader ccl,
Set<String> singleJsoImplTypes,
SortedMap<String, Method> mangledNamesToImplementations) {
super(v);
+ this.ccl = ccl;
this.singleJsoImplTypes = Collections.unmodifiableSet(singleJsoImplTypes);
this.mangledNamesToImplementations = Collections.unmodifiableSortedMap(mangledNamesToImplementations);
}
@@ -90,6 +142,15 @@
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 = singleJsoImplTypes.contains(name);
@@ -100,8 +161,7 @@
* original methods.
*/
if (interfaces != null && (access & Opcodes.ACC_INTERFACE) == 0) {
- List<String> toStub = new ArrayList<String>();
- Collections.addAll(toStub, interfaces);
+ Set<String> toStub = computeAllInterfaces(interfaces);
toStub.retainAll(singleJsoImplTypes);
for (String stubIntr : toStub) {
@@ -146,6 +206,29 @@
return new MyMethodVisitor(mv);
}
+ private Set<String> computeAllInterfaces(String... interfaces) {
+ Set<String> toReturn = new HashSet<String>();
+ List<String> q = new LinkedList<String>();
+ Collections.addAll(q, interfaces);
+
+ while (!q.isEmpty()) {
+ String intf = q.remove(0);
+ if (toReturn.add(intf)) {
+ try {
+ Class<?> intfClass = Class.forName(intf.replace('/', '.'), false, ccl);
+ for (Class<?> i : intfClass.getInterfaces()) {
+ q.add(i.getName().replace('.', '/'));
+ }
+ } catch (ClassNotFoundException e) {
+ assert false : intf;
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return toReturn;
+ }
+
/**
* Given a resource name of a class, find all mangled method names that must
* be implemented.
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
index db89ae3..a2d754d 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -44,6 +44,7 @@
import com.google.gwt.dev.jjs.test.SingleJsoImplTest;
import com.google.gwt.dev.jjs.test.UnstableGeneratorTest;
import com.google.gwt.dev.jjs.test.VarargsTest;
+import com.google.gwt.dev.jjs.test.singlejso.TypeHierarchyTest;
import com.google.gwt.junit.tools.GWTTestSuite;
import junit.framework.Test;
@@ -84,6 +85,7 @@
suite.addTestSuite(RunAsyncFailureTest.class);
suite.addTestSuite(RunAsyncTest.class);
suite.addTestSuite(SingleJsoImplTest.class);
+ suite.addTestSuite(TypeHierarchyTest.class);
suite.addTestSuite(UnstableGeneratorTest.class);
suite.addTestSuite(VarargsTest.class);
// $JUnit-END$
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 3926e4e..4847899 100644
--- a/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
@@ -66,6 +66,10 @@
String call(int a, int b);
}
+ interface CreatedWithCast {
+ String foo();
+ }
+
interface Divider extends Multiplier {
int divide(int a, int b);
}
@@ -81,6 +85,16 @@
}
/**
+ * Even though this type is never instantiated, it's necessary to ensure that
+ * the CreatedWithCast test isn't short-circuited due to type tightening.
+ */
+ static class JavaCreatedWithCast implements CreatedWithCast {
+ public String foo() {
+ return "foo";
+ }
+ }
+
+ /**
* The extra declaration of implementing Multiplier should still be legal.
*/
static class JavaDivider extends JavaMultiplier implements Divider,
@@ -189,6 +203,16 @@
}
}
+ static class JsoCreatedWithCast extends JavaScriptObject implements
+ CreatedWithCast {
+ protected JsoCreatedWithCast() {
+ }
+
+ public final String foo() {
+ return "foo";
+ }
+ }
+
static class JsoDivider extends JsoMultiplier implements Divider, Tag {
protected JsoDivider() {
}
@@ -452,6 +476,16 @@
a.acceptObject3Array(a.returnString3Array());
}
+ /**
+ * 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
+ * correctly, the cast in this test will throw a ClassCastException since the
+ * compiler would assume there are types that implement the interface.
+ */
+ public void testCreatedWithCast() {
+ Object o = (CreatedWithCast) JavaScriptObject.createObject();
+ }
+
public void testDualCase() {
// Direct dispatch
{
diff --git a/user/test/com/google/gwt/dev/jjs/test/singlejso/A.java b/user/test/com/google/gwt/dev/jjs/test/singlejso/A.java
new file mode 100644
index 0000000..068fb94
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/singlejso/A.java
@@ -0,0 +1,34 @@
+/*
+ * 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.jjs.test.singlejso;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * A class.
+ */
+public class A extends JavaScriptObject implements IA {
+ protected A() {
+ }
+
+ public static A create() {
+ return JavaScriptObject.createObject().cast();
+ }
+
+ public final String whoAmI() {
+ return "A";
+ }
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/singlejso/B1.java b/user/test/com/google/gwt/dev/jjs/test/singlejso/B1.java
new file mode 100644
index 0000000..f337cea
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/singlejso/B1.java
@@ -0,0 +1,29 @@
+/*
+ * 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.jjs.test.singlejso;
+
+/**
+ * B1 class.
+ */
+public class B1 implements IB {
+ public String sayHello() {
+ return "Hey, I'm " + whoAmI();
+ }
+
+ public String whoAmI() {
+ return "B1";
+ }
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/singlejso/B2.java b/user/test/com/google/gwt/dev/jjs/test/singlejso/B2.java
new file mode 100644
index 0000000..6dc39fa
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/singlejso/B2.java
@@ -0,0 +1,29 @@
+/*
+ * 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.jjs.test.singlejso;
+
+/**
+ * B2 class.
+ */
+public class B2 implements IB {
+ public String sayHello() {
+ return "Hi, I'm " + whoAmI();
+ }
+
+ public String whoAmI() {
+ return "B2";
+ }
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/singlejso/IA.java b/user/test/com/google/gwt/dev/jjs/test/singlejso/IA.java
new file mode 100644
index 0000000..faaf611
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/singlejso/IA.java
@@ -0,0 +1,23 @@
+/*
+ * 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.jjs.test.singlejso;
+
+/**
+ * Interface A.
+ */
+public interface IA {
+ String whoAmI();
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/singlejso/IB.java b/user/test/com/google/gwt/dev/jjs/test/singlejso/IB.java
new file mode 100644
index 0000000..d11422b
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/singlejso/IB.java
@@ -0,0 +1,23 @@
+/*
+ * 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.jjs.test.singlejso;
+
+/**
+ * Interface B.
+ */
+public interface IB extends IA {
+ String sayHello();
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java b/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java
new file mode 100644
index 0000000..f50be8b
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/singlejso/TypeHierarchyTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.jjs.test.singlejso;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests SingleJso semantics in non-trivial type hierarchies.
+ */
+public class TypeHierarchyTest extends GWTTestCase {
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.dev.jjs.CompilerSuite";
+ }
+
+ public void testCase1() {
+ A a = A.create();
+ assertEquals("A", a.whoAmI());
+
+ B1 b1 = new B1();
+ assertEquals("B1", b1.whoAmI());
+
+ B2 b2 = new B2();
+ assertEquals("B2", b2.whoAmI());
+ }
+
+ public void testCase2() {
+ IA a = A.create();
+ assertEquals("A", a.whoAmI());
+
+ IA b1 = new B1();
+ assertEquals("B1", b1.whoAmI());
+
+ IA b2 = new B2();
+ assertEquals("B2", b2.whoAmI());
+ }
+
+ public void testCase3() {
+ IA a = A.create();
+ assertEquals("A", a.whoAmI());
+
+ IB b1 = new B1();
+ assertEquals("B1", b1.whoAmI());
+
+ IB b2 = new B2();
+ assertEquals("B2", b2.whoAmI());
+ }
+
+}