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. *