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