This patch expands Gwt's ability to successfully create classMappings, necessary when running Emma. The following types of classes
can now be handled correctly:
(a) A top level class that is not the main type in a java file (e.g., Test$1Foo produced from Bar.java)
(b) A class that is nested multiple-levels deep (e.g., Test$Foo$1Bar)
(c) An anonmyous class that extends a named local class (e.g., Test$1Foo)
Also adds a test target to enable running all hosted mode tests in Emma mode. All tests pass except CoverageTest.java that fails
because of a bug in javac in both OpenJDK and Sun's Java6.
Patch by: amitmanjhi
Review by: jat (desk review)
git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4627 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
index e2653a1..e3c2f19 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
@@ -15,11 +15,13 @@
*/
package com.google.gwt.dev.javac;
+import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.asm.ClassReader;
import com.google.gwt.dev.asm.Opcodes;
import com.google.gwt.dev.asm.commons.EmptyVisitor;
import com.google.gwt.dev.jdt.TypeRefVisitor;
import com.google.gwt.dev.shell.CompilingClassLoader;
+import com.google.gwt.dev.util.Util;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
@@ -30,6 +32,9 @@
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -46,25 +51,129 @@
*/
public abstract class CompilationUnit {
- static class AnonymousClassVisitor extends EmptyVisitor {
- /*
- * array of classNames of inner clases that aren't synthetic classes.
- */
- List<String> classNames = new ArrayList<String>();
+ /**
+ * Encapsulates the functionality to find all nested classes of this class
+ * that have compiler-generated names. All class bytes are loaded from the
+ * disk and then analyzed using ASM.
+ */
+ static class GeneratedClassnameFinder {
+ private static class AnonymousClassVisitor extends EmptyVisitor {
+ /*
+ * array of classNames of inner clases that aren't synthetic classes.
+ */
+ List<String> classNames = new ArrayList<String>();
- public List<String> getInnerClassNames() {
- return classNames;
- }
+ public List<String> getInnerClassNames() {
+ return classNames;
+ }
- @Override
- public void visitInnerClass(String name, String outerName,
- String innerName, int access) {
- if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
- classNames.add(name);
+ @Override
+ public void visitInnerClass(String name, String outerName,
+ String innerName, int access) {
+ if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
+ classNames.add(name);
+ }
}
}
- }
+ private final List<String> classesToScan;
+ private final TreeLogger logger;
+ private final String mainClass;
+ private String mainUrlBase = null;
+
+ GeneratedClassnameFinder(TreeLogger logger, String mainClass) {
+ assert mainClass != null;
+ this.mainClass = mainClass;
+ classesToScan = new ArrayList<String>();
+ classesToScan.add(mainClass);
+ this.logger = logger;
+ }
+
+ List<String> getClassNames() {
+ // using a list because presumably there will not be many generated
+ // classes
+ List<String> allGeneratedClasses = new ArrayList<String>();
+ for (int i = 0; i < classesToScan.size(); i++) {
+ String lookupName = classesToScan.get(i);
+ byte classBytes[] = getClassBytes(lookupName);
+ if (classBytes == null) {
+ /*
+ * Weird case: javac might generate a name and reference the class in
+ * the bytecode but decide later that the class is unnecessary. In the
+ * bytecode, a null is passed for the class.
+ */
+ continue;
+ }
+
+ /*
+ * Add the class to the list only if it can be loaded to get around the
+ * javac weirdness issue where javac refers a class but does not
+ * generate it.
+ */
+ if (CompilingClassLoader.isClassnameGenerated(lookupName)
+ && !allGeneratedClasses.contains(lookupName)) {
+ allGeneratedClasses.add(lookupName);
+ }
+ AnonymousClassVisitor cv = new AnonymousClassVisitor();
+ new ClassReader(classBytes).accept(cv, 0);
+ List<String> innerClasses = cv.getInnerClassNames();
+ for (String innerClass : innerClasses) {
+ // The innerClass has to be an inner class of the lookupName
+ if (!innerClass.startsWith(mainClass + "$")) {
+ continue;
+ }
+ /*
+ * TODO (amitmanjhi): consider making this a Set if necessary for
+ * performance
+ */
+ // add the class to classes
+ if (!classesToScan.contains(innerClass)) {
+ classesToScan.add(innerClass);
+ }
+ }
+ }
+ Collections.sort(allGeneratedClasses, new GeneratedClassnameComparator());
+ return allGeneratedClasses;
+ }
+
+ /*
+ * Load classBytes from disk. Check if the classBytes are loaded from the
+ * same location as the location of the mainClass.
+ */
+ private byte[] getClassBytes(String slashedName) {
+ URL url = Thread.currentThread().getContextClassLoader().getResource(
+ slashedName + ".class");
+ if (url == null) {
+ logger.log(TreeLogger.DEBUG, "Unable to find " + slashedName
+ + " on the classPath");
+ return null;
+ }
+ String urlStr = url.toExternalForm();
+ if (slashedName.equals(mainClass)) {
+ // initialize the mainUrlBase for later use.
+ mainUrlBase = urlStr.substring(0, urlStr.lastIndexOf('/'));
+ } else {
+ assert mainUrlBase != null;
+ if (!mainUrlBase.equals(urlStr.substring(0, urlStr.lastIndexOf('/')))) {
+ logger.log(TreeLogger.DEBUG, "Found " + slashedName + " at " + urlStr
+ + " The base location is different from that of " + mainUrlBase
+ + " Not loading");
+ return null;
+ }
+ }
+
+ // url != null, we found it on the class path.
+ try {
+ URLConnection conn = url.openConnection();
+ return Util.readURLConnectionAsBytes(conn);
+ } catch (IOException ignored) {
+ logger.log(TreeLogger.DEBUG, "Unable to load " + urlStr
+ + ", in trying to load " + slashedName);
+ // Fall through.
+ }
+ return null;
+ }
+ }
/**
* Tracks the state of a compilation unit through the compile and recompile
* process.
@@ -167,26 +276,31 @@
private State state = State.FRESH;
/*
- * Check if the unit has one or more anonymous classes. 'javac' below refers
- * to the compiler that was used to compile the java files on disk. Returns
- * true if our heuristic for constructing the anonymous class mappings worked.
+ * Check if the unit has one or more classes with generated names. 'javac'
+ * below refers to the compiler that was used to compile the java files on
+ * disk. Returns true if our heuristic for constructing the anonymous class
+ * mappings worked.
*/
- public boolean constructAnonymousClassMappings(byte classBytes[]) {
+ public boolean constructAnonymousClassMappings(TreeLogger logger) {
// map from the name in javac to the name in jdt
anonymousClassMap = new HashMap<String, String>();
- List<String> javacClasses = getJavacClassNames(classBytes);
- List<String> jdtClasses = getJdtClassNames();
- if (javacClasses.size() == jdtClasses.size()) {
+ for (String topLevelClass : getTopLevelClasses()) {
+ // Generate a mapping for each top-level class separately
+ List<String> javacClasses = new GeneratedClassnameFinder(logger,
+ topLevelClass).getClassNames();
+ List<String> jdtClasses = getJdtClassNames(topLevelClass);
+ if (javacClasses.size() != jdtClasses.size()) {
+ anonymousClassMap = Collections.emptyMap();
+ return false;
+ }
int size = javacClasses.size();
for (int i = 0; i < size; i++) {
if (!javacClasses.get(i).equals(jdtClasses.get(i))) {
anonymousClassMap.put(javacClasses.get(i), jdtClasses.get(i));
}
}
- return true;
}
- anonymousClassMap = Collections.emptyMap();
- return false;
+ return true;
}
public boolean createdClassMapping() {
@@ -383,25 +497,11 @@
}
}
- private List<String> getJavacClassNames(byte classBytes[]) {
- AnonymousClassVisitor cv = new AnonymousClassVisitor();
- new ClassReader(classBytes).accept(cv, 0);
- List<String> classNames = cv.getInnerClassNames();
- List<String> namesToRemove = new ArrayList<String>();
- for (String className : classNames) {
- if (!CompilingClassLoader.isClassnameGenerated(className)) {
- namesToRemove.add(className);
- }
- }
- classNames.removeAll(namesToRemove);
- Collections.sort(classNames, new GeneratedClassnameComparator());
- return classNames;
- }
-
- private List<String> getJdtClassNames() {
+ private List<String> getJdtClassNames(String topLevelClass) {
List<String> classNames = new ArrayList<String>();
for (CompiledClass cc : getCompiledClasses()) {
- if (isAnonymousClass(cc)) {
+ if (isAnonymousClass(cc)
+ && cc.getBinaryName().startsWith(topLevelClass + "$")) {
classNames.add(cc.getBinaryName());
}
}
@@ -409,6 +509,16 @@
return classNames;
}
+ private List<String> getTopLevelClasses() {
+ List<String> topLevelClasses = new ArrayList<String>();
+ for (CompiledClass cc : getCompiledClasses()) {
+ if (cc.getEnclosingClass() == null) {
+ topLevelClasses.add(cc.binaryName);
+ }
+ }
+ return topLevelClasses;
+ }
+
/**
* Removes all accumulated state associated with compilation.
*/
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 143a3c3..d773404 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -361,7 +361,7 @@
*/
private static byte[] javaScriptHostBytes;
- private static final Pattern GENERATED_CLASSNAME_PATTERN = Pattern.compile(".+\\$\\d+(\\$.*)?");
+ private static final Pattern GENERATED_CLASSNAME_PATTERN = Pattern.compile(".+\\$\\d.*");
static {
for (Class<?> c : BRIDGE_CLASSES) {
@@ -386,9 +386,10 @@
/**
* Checks if the class names is generated. Accepts any classes whose names
- * match .+$\d+($.*)? (handling named classes within anonymous classes).
- * Checks if the class or any of its enclosing classes are anonymous or
- * synthetic.
+ * match .+$\d.* (handling named classes within anonymous classes and
+ * multiple named classes of the same name in a class, but in different
+ * methods). Checks if the class or any of its enclosing classes are anonymous
+ * or synthetic.
* <p>
* If new compilers have different conventions for anonymous and synthetic
* classes, this code needs to be updated.
@@ -670,7 +671,7 @@
lookupClassName);
CompilationUnit unit = (compiledClass == null)
- ? getUnitForClassName(className) : compiledClass.getUnit();
+ ? getUnitForClassName(lookupClassName) : compiledClass.getUnit();
if (emmaAvailable) {
/*
* build the map for anonymous classes. Do so only if unit has anonymous
@@ -682,20 +683,12 @@
if (unit != null && !unit.isSuperSource() && unit.hasAnonymousClasses()
&& jsniMethods != null && jsniMethods.size() > 0
&& !unit.createdClassMapping()) {
- String mainLookupClassName = unit.getTypeName().replace('.', '/');
- byte mainClassBytes[] = emmaStrategy.getEmmaClassBytes(null,
- mainLookupClassName, 0);
- if (mainClassBytes != null) {
- if (!unit.constructAnonymousClassMappings(mainClassBytes)) {
- logger.log(TreeLogger.ERROR,
- "Our heuristic for mapping anonymous classes between compilers "
- + "failed. Unsafe to continue because the wrong jsni code "
- + "could end up running. className = " + className);
- return null;
- }
- } else {
- logger.log(TreeLogger.ERROR, "main class bytes is null for unit = "
- + unit + ", mainLookupClassName = " + mainLookupClassName);
+ if (!unit.constructAnonymousClassMappings(logger)) {
+ logger.log(TreeLogger.ERROR,
+ "Our heuristic for mapping anonymous classes between compilers "
+ + "failed. Unsafe to continue because the wrong jsni code "
+ + "could end up running. className = " + className);
+ return null;
}
}
}
@@ -717,7 +710,8 @@
* find it on disk. Typically this is a synthetic class added by the
* compiler.
*/
- if (typeHasCompilationUnit(className) && isClassnameGenerated(className)) {
+ if (typeHasCompilationUnit(lookupClassName)
+ && isClassnameGenerated(className)) {
/*
* modification time = 0 ensures that whatever is on the disk is always
* loaded.
@@ -758,17 +752,19 @@
/**
* Returns the compilationUnit corresponding to the className. For nested
* classes, the unit corresponding to the top level type is returned.
+ *
+ * Since a file might have several top-level types, search using classFileMap.
*/
private CompilationUnit getUnitForClassName(String className) {
String mainTypeName = className;
int index = mainTypeName.length();
- CompilationUnit unit = null;
- while (unit == null && index != -1) {
+ CompiledClass cc = null;
+ while (cc == null && index != -1) {
mainTypeName = mainTypeName.substring(0, index);
- unit = compilationState.getCompilationUnitMap().get(mainTypeName);
+ cc = compilationState.getClassFileMap().get(mainTypeName);
index = mainTypeName.lastIndexOf('$');
}
- return unit;
+ return cc == null ? null : cc.getUnit();
}
private void injectJsniMethods(CompilationUnit unit) {
diff --git a/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java
index 630d438..3af1a6a 100644
--- a/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java
@@ -1,3 +1,18 @@
+/*
+ * 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.javac;
import junit.framework.TestCase;
@@ -38,8 +53,21 @@
}
public void testMixedNames() {
- String original[] = {"Foo", "Foo$1", "Foo$1xyz", "Foo$2", "Foo$xyz"};
- String expected[] = {"Foo", "Foo$1", "Foo$2", "Foo$1xyz", "Foo$xyz"};
+ String original[] = {
+ "Foo", "Foo$1", "Foo$1Bar", "Foo$2Bar", "Foo$2", "Foo$xyz"};
+ String expected[] = {
+ "Foo", "Foo$1", "Foo$2", "Foo$1Bar", "Foo$2Bar", "Foo$xyz"};
+ Arrays.sort(original, new GeneratedClassnameComparator());
+ for (int i = 0; i < original.length; i++) {
+ assertEquals("index = " + i, expected[i], original[i]);
+ }
+ }
+
+ public void testMultipleToplevelClasses() {
+ String original[] = {
+ "Foo$1", "Foo$2", "Bar$1", "Bar$3", "Foo$2$1", "Bar$2$1"};
+ String expected[] = {
+ "Bar$1", "Bar$3", "Foo$1", "Foo$2", "Bar$2$1", "Foo$2$1"};
Arrays.sort(original, new GeneratedClassnameComparator());
for (int i = 0; i < original.length; i++) {
assertEquals("index = " + i, expected[i], original[i]);
diff --git a/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java
new file mode 100644
index 0000000..0f428e2
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java
@@ -0,0 +1,371 @@
+/*
+ * 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.javac;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.javac.CompilationUnit.GeneratedClassnameFinder;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Test-cases to check that we indeed obtain the correct list of nested types
+ * with generated classNames by examining bytecodes using ASM.
+ *
+ */
+public class GeneratedClassnameFinderTest extends TestCase {
+ enum EnumClass {
+ A, B, C,
+ }
+
+ static class MainClass {
+ static class NestedClass {
+ void foo() {
+ TestInterface c = new TestInterface() {
+ public void foo() {
+ }
+ };
+ EnumClass et = EnumClass.A;
+ switch (et) {
+ case A:
+ break;
+ }
+ TestInterface d = new TestInterface() {
+ public void foo() {
+ }
+ };
+ }
+ }
+
+ void foo() {
+ TestInterface a = new TestInterface() {
+ public void foo() {
+ }
+ };
+ EnumClass et = EnumClass.A;
+ switch (et) {
+ case A:
+ break;
+ }
+ TestInterface b = new TestInterface() {
+ public void foo() {
+ }
+ };
+ }
+ }
+ interface TestInterface {
+ void foo();
+ }
+
+ static final TreeLogger logger = new PrintWriterTreeLogger();
+
+ public void test() {
+ String mainClassName = this.getClass().getName().replace('.', '/');
+ assertEquals(
+ 4,
+ new GeneratedClassnameFinder(logger, mainClassName).getClassNames().size());
+ assertEquals(0, new GeneratedClassnameFinder(logger, mainClassName
+ + "$EnumClass").getClassNames().size());
+ assertEquals(0, new GeneratedClassnameFinder(logger, mainClassName
+ + "$TestInterface").getClassNames().size());
+ assertEquals(4, new GeneratedClassnameFinder(logger, mainClassName
+ + "$MainClass").getClassNames().size());
+ assertEquals(2, new GeneratedClassnameFinder(logger, mainClassName
+ + "$MainClass$NestedClass").getClassNames().size());
+ }
+
+ public void testAnonymous() {
+ assertEquals(1, new AnonymousTester().getGeneratedClasses().size());
+ }
+
+ public void testEnum() {
+ assertEquals(0, new EnumTester().getGeneratedClasses().size());
+ }
+
+ public void testJavacWeirdness() {
+ List<String> classNames = new JavacWeirdnessTester().getGeneratedClasses();
+ assertEquals(3, classNames.size());
+ assertTrue(classNames.get(0) + " should not contain Foo",
+ classNames.get(0).indexOf("Foo") == -1);
+ assertTrue(classNames.get(1) + " should contain Foo",
+ classNames.get(1).indexOf("Foo") != -1);
+ assertTrue(classNames.get(2) + " should contain Foo",
+ classNames.get(2).indexOf("Foo") != -1);
+ }
+
+ public void testNamedLocal() {
+ assertEquals(2, new NamedLocalTester().getGeneratedClasses().size());
+ }
+
+ public void testNested() {
+ assertEquals(2, new NestedTester().getGeneratedClasses().size());
+ }
+
+ public void testStatic() {
+ assertEquals(0, new StaticTester().getGeneratedClasses().size());
+ }
+
+ public void testTopLevel() {
+ assertEquals(1, new TopLevelTester().getGeneratedClasses().size());
+ }
+
+}
+
+/**
+ * For testing a class containing an anonymous inner class.
+ */
+class AnonymousTester {
+ interface TestInterface {
+ void foo();
+ }
+
+ void foo() {
+ TestInterface a = new TestInterface() {
+ public void foo() {
+ }
+ };
+ a.foo();
+ }
+
+ List<String> getGeneratedClasses() {
+ return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+ this.getClass().getName().replace('.', '/'))).getClassNames();
+ }
+}
+
+/**
+ * For testing a class with an Enum (for which javac generates a synthetic
+ * class).
+ */
+class EnumTester {
+ enum EnumClass {
+ A, B, C,
+ }
+
+ void foo() {
+ EnumClass et = EnumClass.A;
+ switch (et) {
+ case A:
+ break;
+ }
+ }
+
+ List<String> getGeneratedClasses() {
+ return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+ this.getClass().getName().replace('.', '/'))).getClassNames();
+ }
+}
+
+/**
+ * Javac generates weird code for the following class. It passes a synthetic
+ * class ...Tester$1 as a first parameter to constructors of Fuji and Granny.
+ * Normally, it generates the synthetic class, but in this case, it decides not
+ * to generate the class. However, the bytecode still has reference to the
+ * synthetic class -- it just passes null for the synthetic class.
+ *
+ * This code also tests for an anonymous class extending a named local class.
+ */
+class JavacWeirdnessTester {
+ private abstract static class Apple implements Fruit {
+ }
+
+ private static interface Fruit {
+ }
+
+ private static class Fuji extends Apple {
+ }
+
+ private static class Granny extends Apple {
+ }
+
+ private static volatile boolean TRUE = true;
+
+ List<String> getGeneratedClasses() {
+ return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+ this.getClass().getName().replace('.', '/'))).getClassNames();
+ }
+
+ private void assertEquals(Object a, Object b) {
+ }
+
+ private void testArrayStore() {
+ Apple[] apple = TRUE ? new Granny[3] : new Apple[3];
+ Apple g = TRUE ? (Apple) new Granny() : (Apple) new Fuji();
+ Apple a = apple[0] = g;
+ assertEquals(g, a);
+ }
+
+ private void testDeadTypes() {
+ if (false) {
+ new Object() {
+ }.toString();
+
+ class Foo {
+ void a() {
+ }
+ }
+ new Foo().a();
+ }
+ }
+
+ private void testLocalClasses() {
+ class Foo {
+ public Foo(int j) {
+ assertEquals(1, j);
+ };
+ }
+ final int i;
+ new Foo(i = 1) {
+ {
+ assertEquals(1, i);
+ }
+ };
+ assertEquals(1, i);
+ }
+
+ private void testReturnStatementInCtor() {
+ class Foo {
+ int i;
+
+ Foo(int i) {
+ this.i = i;
+ if (i == 0) {
+ return;
+ } else if (i == 1) {
+ return;
+ }
+ return;
+ }
+ }
+ assertEquals(new Foo(0).i, 0);
+ }
+}
+
+/**
+ * For testing a class with a generated name like $1Foo.
+ */
+class NamedLocalTester {
+ void foo1() {
+ if (false) {
+ class Foo {
+ void foo() {
+ }
+ }
+ new Foo().foo();
+ }
+ }
+
+ void foo2() {
+ class Foo {
+ void foo() {
+ }
+ }
+ new Foo().foo();
+ }
+
+ void foo3() {
+ class Foo {
+ void foo() {
+ }
+ }
+ new Foo().foo();
+ }
+
+ List<String> getGeneratedClasses() {
+ return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+ this.getClass().getName().replace('.', '/'))).getClassNames();
+ }
+}
+
+/**
+ * For testing that nested classes are examined recursively for classes with
+ * generated names.
+ */
+class NestedTester {
+ class MainClass {
+ class NestedClass {
+ void foo() {
+ class Foo {
+ void bar() {
+ }
+ }
+ new Foo().bar();
+ }
+ }
+
+ void foo() {
+ class Foo {
+ void bar() {
+ }
+ }
+ new Foo().bar();
+ }
+ }
+
+ List<String> getGeneratedClasses() {
+ return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+ this.getClass().getName().replace('.', '/'))).getClassNames();
+ }
+}
+
+/**
+ * For testing classes with private static members (javac generates a synthetic
+ * class here but the jdt does not).
+ */
+class StaticTester {
+ private abstract static class Apple implements Fruit {
+ }
+
+ private static interface Fruit {
+ void bar();
+ }
+
+ private static class Fuji extends Apple {
+ public void bar() {
+ }
+ }
+
+ private static class Granny extends Apple {
+ public void bar() {
+ }
+ }
+
+ List<String> getGeneratedClasses() {
+ return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+ this.getClass().getName().replace('.', '/'))).getClassNames();
+ }
+
+}
+
+/**
+ * For testing that a class with a generated name inside another top-level class
+ * is found.
+ */
+class TopLevelTester {
+ public void foo() {
+ GeneratedClassnameFinderTest.TestInterface a = new GeneratedClassnameFinderTest.TestInterface() {
+ public void foo() {
+ }
+ };
+ }
+
+ List<String> getGeneratedClasses() {
+ return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+ this.getClass().getName().replace('.', '/'))).getClassNames();
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java b/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java
index b5040c1..ecf7087 100644
--- a/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java
+++ b/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java
@@ -11,8 +11,8 @@
public void testGeneratedClassnames() {
String namesToAccept[] = {
"Test$1", "Test$10", "Test$Foo$1", "Test$1$Foo", "Test$10$Foo",
- "$$345", "Test$1$Foo$"};
- String namesToReject[] = {"Test1", "$345", "Test$2Foo", "Test$Foo$1Bar"};
+ "$$345", "Test$1$Foo$", "Test$1Foo", "Test$2Foo", "Test$Foo$1Bar"};
+ String namesToReject[] = {"Test1", "TestFoo", "Test$Foo$Bar", "$345"};
for (String name : namesToAccept) {
assertTrue("className = " + name + " should have been accepted",
diff --git a/user/build.xml b/user/build.xml
index dcd2c99..67c8337 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -12,6 +12,11 @@
<fileset id="default.emma.tests" dir="${javac.junit.out}"
includes="**/EmmaClassLoadingTest.class" />
+
+ <fileset id="default.hosted.emma.tests" dir="${javac.junit.out}"
+ excludes="**/*jjs.test.CoverageTest.class" includes="**/*Test.class" />
+ <!-- everything succeeds except CoverageTest.java. It fails due to a javac bug in sun/OpenJDK's Java. See the file contents for details -->
+
<!--
Default web mode test cases
-->
@@ -96,6 +101,15 @@
</gwt.junit>
</target>
+ <target name="test.hosted.emma" depends="compile, compile.tests" description="Run all hosted-mode tests in emma mode.">
+ <gwt.junit test.args="${test.args}" test.out="${junit.out}/${build.host.platform}-hosted-mode" test.cases="default.hosted.emma.tests" >
+ <extraclasspaths>
+ <pathelement location="${gwt.build}/out/dev/core/bin-test" />
+ <pathelement location="${gwt.tools.redist}/emma/emma.jar" />
+ </extraclasspaths>
+ </gwt.junit>
+ </target>
+
<target name="test.hosted" depends="compile, compile.tests" description="Run only hosted-mode tests for this project.">
<gwt.junit test.args="${test.args}" test.out="${junit.out}/${build.host.platform}-hosted-mode" test.cases="default.hosted.tests" >
<extraclasspaths>
@@ -107,7 +121,7 @@
<pathelement location="${gwt.build}/out/dev/core/bin-test" />
<pathelement location="${gwt.tools.redist}/emma/emma.jar" />
</extraclasspaths>
- </gwt.junit>
+ </gwt.junit>
</target>
<target name="test.web" depends="compile, compile.tests" description="Run only web-mode tests for this project.">
diff --git a/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java b/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java
index a975fc4..9d161ce 100644
--- a/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java
@@ -95,6 +95,12 @@
new InnerSub().new InnerSubSub().fda();
new SecondMain().new FunkyInner();
+ /*
+ * The statement below causes a javac bug in openJdk and sun's java 6. It
+ * produces incorrect bytecode that fails with a java.lang.VerifyError --
+ * see Google's internal issue 1628473. This is likely to be an hindrance
+ * if and when GWT attempts to read bytecode directly.
+ */
new NamedLocal().new NamedLocalSub().foo();
}
diff --git a/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java b/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
index 35250a3..8d6eb5b 100644
--- a/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
+++ b/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
@@ -16,6 +16,9 @@
package com.google.gwt.dev.shell.rewrite.client;
import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Timer;
+
+import junit.framework.TestCase;
/**
* Test-case to check if the jsni blocks are mapped correctly between the
@@ -31,7 +34,7 @@
*/
public class EmmaClassLoadingTest extends GWTTestCase {
- enum EnumTest {
+ enum EnumClass {
A, B, C,
}
@@ -40,7 +43,7 @@
}
private static String messages[] = {
- "a foo", "b foo", "enum A", "d foo", "e foo"};
+ "1a foo", "1b foo", "1enum A", "1d foo", "1e foo"};
private static int logCount = 0;
@@ -56,14 +59,14 @@
public void test1() {
TestInterface a = new TestInterface() {
public void foo() {
- log("a foo");
+ log("1a foo");
}
};
a.foo();
TestInterface b = new TestInterface() {
public native void foo() /*-{
- @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("b foo");
+ @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1b foo");
}-*/;
};
b.foo();
@@ -75,10 +78,10 @@
}-*/;
};
}
- EnumTest et = EnumTest.A;
+ EnumClass et = EnumClass.A;
switch (et) {
case A:
- log("enum A");
+ log("1enum A");
break;
case B:
log("ANY_FOO_2");
@@ -90,7 +93,7 @@
TestInterface d = new TestInterface() {
public native void foo() /*-{
- @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("d foo");
+ @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1d foo");
}-*/;
};
d.foo();
@@ -103,8 +106,193 @@
*/
TestInterface e = new TestInterface() {
public native void foo() /*-{
- @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("e foo");
+ @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1e foo");
}-*/;
};
- }
+ }
+
+ public void test2() {
+ SecondTopLevelClass second = new SecondTopLevelClass();
+ SecondTopLevelClass.InnerClass.test();
+ }
+
+ public void test3() {
+ ThirdTopLevelClass third = new ThirdTopLevelClass();
+ third.test();
+ }
+
+ public void test4() {
+ FourthTopLevelClass fourth = new FourthTopLevelClass();
+ fourth.test();
+ }
+
+}
+
+/**
+ * Check that the algorithm correctly maps named inner classes. In this example,
+ * jdt generates $1Foo and $2Foo whereas javac generates $2Foo and $3Foo.
+ *
+ */
+class FourthTopLevelClass extends TestCase {
+ private static String messages[] = {"4a foo"};
+
+ private static int logCount = 0;
+
+ private static void log(String msg) {
+ assertEquals(messages[logCount++], msg);
+ }
+
+ void test() {
+ test1();
+ test2();
+ };
+
+ private void test1() {
+ if (false) {
+ class Foo {
+ public native void foo() /*-{
+ @com.google.gwt.dev.shell.rewrite.client.FourthTopLevelClass::log(Ljava/lang/String;)("ANY_FOO");
+ }-*/;
+ }
+ new Foo().foo();
+ }
+ }
+
+ private void test2() {
+ class Foo {
+ public native void foo() /*-{
+ @com.google.gwt.dev.shell.rewrite.client.FourthTopLevelClass::log(Ljava/lang/String;)("4a foo");
+ }-*/;
+ }
+ new Foo().foo();
+ }
+
+ /*
+ * Added a test3() method so that when the test fails, it fails quickly.
+ * Instead of timing out, the AssertEquals fails
+ */
+ private void test3() {
+ class Foo {
+ public native void foo() /*-{
+ @com.google.gwt.dev.shell.rewrite.client.FourthTopLevelClass::log(Ljava/lang/String;)("4b foo");
+ }-*/;
+ }
+ new Foo().foo();
+ }
+}
+
+/**
+ * Check if GWT is able to correctly compile cases when there are multiple
+ * top-level classes and when there is a need to traverse inner classes. This
+ * class's test method simply mirrors the test methods of EmmaClassLoadingTest.
+ *
+ */
+class SecondTopLevelClass extends TestCase {
+
+ static class InnerClass {
+ /**
+ * Test that mapping is constructed for something which is not in the main
+ * unit as well.
+ */
+ static void test() {
+ EmmaClassLoadingTest.TestInterface a = new EmmaClassLoadingTest.TestInterface() {
+ public void foo() {
+ log("2a foo");
+ }
+ };
+ a.foo();
+
+ EmmaClassLoadingTest.TestInterface b = new EmmaClassLoadingTest.TestInterface() {
+ public native void foo() /*-{
+ @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2b foo");
+ }-*/;
+ };
+ b.foo();
+
+ if (false) {
+ EmmaClassLoadingTest.TestInterface c = new EmmaClassLoadingTest.TestInterface() {
+ public native void foo() /*-{
+ @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("ANY_FOO_1");
+ }-*/;
+ };
+ }
+ EmmaClassLoadingTest.EnumClass et = EmmaClassLoadingTest.EnumClass.A;
+ switch (et) {
+ case A:
+ log("2enum A");
+ break;
+ case B:
+ log("ANY_FOO_2");
+ break;
+ case C:
+ log("ANY_FOO_3");
+ break;
+ }
+
+ EmmaClassLoadingTest.TestInterface d = new EmmaClassLoadingTest.TestInterface() {
+ public native void foo() /*-{
+ @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2d foo");
+ }-*/;
+ };
+ d.foo();
+
+ /*
+ * jdt generates $1 (a), $2 (b), $3 (d), $4 (e). javac generates $1 (a),
+ * $2 (b), $3 (c), $4 (d), $5 (e), $6 (synthetic). Added e so that the
+ * test fails quickly. Otherwise, it had to wait for a time-out (in
+ * looking through jdt generated code for non-existent jsni methods of $4)
+ * to fail.
+ */
+ EmmaClassLoadingTest.TestInterface e = new EmmaClassLoadingTest.TestInterface() {
+ public native void foo() /*-{
+ @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2e foo");
+ }-*/;
+ };
+ }
+ }
+
+ private static String messages[] = {
+ "2a foo", "2b foo", "2enum A", "2d foo", "2e foo"};
+
+ private static int logCount = 0;
+
+ private static void log(String msg) {
+ assertEquals(messages[logCount++], msg);
+ }
+}
+
+/**
+ * Check that the mapping algorithm is not confused by the presence of other
+ * inner classes.
+ */
+class ThirdTopLevelClass extends TestCase {
+
+ private static String messages[] = {"3a foo"};
+
+ private static int logCount = 0;
+
+ private static void log(String msg) {
+ assertEquals(messages[logCount++], msg);
+ }
+
+ void test() {
+ Timer t1 = new Timer() {
+ @Override
+ public void run() {
+ }
+ };
+
+ EmmaClassLoadingTest.TestInterface a = new EmmaClassLoadingTest.TestInterface() {
+ public native void foo() /*-{
+ @com.google.gwt.dev.shell.rewrite.client.ThirdTopLevelClass::log(Ljava/lang/String;)("3a foo");
+ }-*/;
+ };
+ a.foo();
+
+ Timer t2 = new Timer() {
+ @Override
+ public void run() {
+ }
+ };
+ }
}