This patch fixes the NoClassDefFoundError which occurred when a class loaded
from the disk (under the emma strategy) had references to synthetic classes the
TypeOracle did not know anything about.
Patch by: amitmanjhi
Review (and suggestions) by: jat
git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4357 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 d1df21c..777c868 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -349,13 +349,15 @@
private static final String CLASS_DUMP_PATH = "rewritten-classes";
+ private static boolean emmaIsAvailable = false;
+
+ private static EmmaStrategy emmaStrategy;
+
/**
* Caches the byte code for {@link JavaScriptHost}.
*/
private static byte[] javaScriptHostBytes;
- private static EmmaStrategy emmaStrategy;
-
static {
for (Class<?> c : BRIDGE_CLASSES) {
BRIDGE_CLASS_NAMES.put(c.getName(), c);
@@ -363,8 +365,10 @@
/*
* Specific support for bridging to Emma since the user classloader is
* generally completely isolated.
+ *
+ * We are looking for a specific emma class "com.vladium.emma.rt.RT". If
+ * that changes in the future, this code would need to be updated as well.
*/
- boolean emmaIsAvailable = false;
try {
Class<?> emmaBridge = Class.forName(EmmaStrategy.EMMA_RT_CLASSNAME,
false, Thread.currentThread().getContextClassLoader());
@@ -648,29 +652,52 @@
CompiledClass compiledClass = compilationState.getClassFileMap().get(
lookupClassName);
- if (compiledClass != null) {
- injectJsniFor(compiledClass);
- byte[] classBytes = compiledClass.getBytes();
+ byte classBytes[] = null;
+ if (compiledClass != null) {
+
+ injectJsniFor(compiledClass);
+ classBytes = compiledClass.getBytes();
if (!compiledClass.getUnit().isSuperSource()) {
+ /*
+ * It is okay if Emma throws away the old classBytes since the actual
+ * jsni injection happens in the rewriter. The injectJsniFor method
+ * above simply defines the native methods in the browser.
+ */
classBytes = emmaStrategy.getEmmaClassBytes(classBytes,
lookupClassName, compiledClass.getUnit().getLastModified());
} else {
logger.log(TreeLogger.SPAM, "no emma instrumentation for "
+ lookupClassName + " because it is from super-source");
}
- if (classRewriter != null) {
- byte[] newBytes = classRewriter.rewrite(className, classBytes);
- if (CLASS_DUMP) {
- if (!Arrays.equals(classBytes, newBytes)) {
- classDump(className, newBytes);
- }
- }
- classBytes = newBytes;
+ } else if (emmaIsAvailable) {
+ /*
+ * TypeOracle does not know about this class. Most probably, this class
+ * was referenced in one of the classes loaded from disk. Check if we can
+ * find it on disk. Typically this is a synthetic class added by the
+ * compiler. If the synthetic class contains native methods, it will fail
+ * later.
+ */
+ if (isSynthetic(className)) {
+ /*
+ * modification time = 0 ensures that whatever is on the disk is always
+ * loaded.
+ */
+ logger.log(TreeLogger.SPAM, "EmmaStrategy: loading " + lookupClassName
+ + " from disk even though TypeOracle does not know about it");
+ classBytes = emmaStrategy.getEmmaClassBytes(null, lookupClassName, 0);
}
- return classBytes;
}
- return null;
+ if (classBytes != null && classRewriter != null) {
+ byte[] newBytes = classRewriter.rewrite(className, classBytes);
+ if (CLASS_DUMP) {
+ if (!Arrays.equals(classBytes, newBytes)) {
+ classDump(className, newBytes);
+ }
+ }
+ classBytes = newBytes;
+ }
+ return classBytes;
}
private String getBinaryName(JClassType type) {
@@ -708,6 +735,30 @@
}
/**
+ * For safety, we only allow synthetic classes to be loaded this way. Accepts
+ * any classes whose names match .+$\d+
+ * <p>
+ * If new compilers have different conventions for synthetic classes, this
+ * code needs to be updated.
+ * </p>
+ *
+ * @param className class to be loaded from disk.
+ * @return true iff class should be loaded from disk
+ */
+ private boolean isSynthetic(String className) {
+ int index = className.lastIndexOf("$");
+ if (index <= 0 || index == className.length() - 1) {
+ return false;
+ }
+ for (int i = index + 1; i < className.length(); i++) {
+ if (!Character.isDigit(className.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Tricky one, this. Reaches over into this modules's JavaScriptHost class and
* sets its static 'host' field to our module space.
*