Fixes a ClassCircularityError for real. Note: this work may be temporary pending redesign of JSNI injection.
Review by: jat (postmortem)
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2615 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 9b4cd4b..79ec9cf 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -42,6 +42,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -356,8 +357,21 @@
private final TreeLogger logger;
+ /**
+ * Stores a list of classes needing JSNI injection. This list will be cleared
+ * when the {@link #stackDepth} is <code>0</code>.
+ */
+ private final List<Class<?>> pendingJsniInjectionClasses = new ArrayList<Class<?>>();
+
private ShellJavaScriptHost shellJavaScriptHost;
+ /**
+ * Used to guard against {@link ClassCircularityError}. Attempting to read
+ * class annotations for the purpose of JSNI injection while defining a class
+ * can lead to circularities; we must wait until we're at the "top of stack".
+ */
+ private int stackDepth = 0;
+
private final TypeOracle typeOracle;
@SuppressWarnings("unchecked")
@@ -524,6 +538,8 @@
// Get the bytes, compiling if necessary.
byte[] classBytes;
try {
+ ++stackDepth;
+
// A JSO impl class needs the class bytes for the original class.
String lookupClassName = className;
if (classRewriter != null && classRewriter.isJsoImpl(className)) {
@@ -544,6 +560,8 @@
return newClass;
} catch (UnableToCompleteException e) {
throw new ClassNotFoundException(className);
+ } finally {
+ --stackDepth;
}
}
@@ -554,20 +572,34 @@
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> newClass = super.loadClass(name, resolve);
- JsniMethods jsniMethods = newClass.getAnnotation(JsniMethods.class);
- if (jsniMethods != null) {
- for (JsniMethod jsniMethod : jsniMethods.value()) {
- String[] bodyParts = jsniMethod.body();
- int size = 0;
- for (String bodyPart : bodyParts) {
- size += bodyPart.length();
+
+ // Only real, non-local classes can have JSNI method annotations.
+ if (!newClass.isInterface() && !newClass.isLocalClass()) {
+ pendingJsniInjectionClasses.add(newClass);
+ }
+
+ if (stackDepth == 0 && !pendingJsniInjectionClasses.isEmpty()) {
+ // Save a copy because this can re-enter.
+ Class<?>[] toCheck = pendingJsniInjectionClasses.toArray(new Class<?>[pendingJsniInjectionClasses.size()]);
+ pendingJsniInjectionClasses.clear();
+ for (Class<?> checkClass : toCheck) {
+ JsniMethods jsniMethods = checkClass.getAnnotation(JsniMethods.class);
+ if (jsniMethods != null) {
+ for (JsniMethod jsniMethod : jsniMethods.value()) {
+ String[] bodyParts = jsniMethod.body();
+ int size = 0;
+ for (String bodyPart : bodyParts) {
+ size += bodyPart.length();
+ }
+ StringBuilder body = new StringBuilder(size);
+ for (String bodyPart : bodyParts) {
+ body.append(bodyPart);
+ }
+ shellJavaScriptHost.createNative(jsniMethod.file(),
+ jsniMethod.line(), jsniMethod.name(), jsniMethod.paramNames(),
+ body.toString());
+ }
}
- StringBuilder body = new StringBuilder(size);
- for (String bodyPart : bodyParts) {
- body.append(bodyPart);
- }
- shellJavaScriptHost.createNative(jsniMethod.file(), jsniMethod.line(),
- jsniMethod.name(), jsniMethod.paramNames(), body.toString());
}
}
return newClass;