Fix reference error for class literals in SDM.

If during the initial SDM compile a class literal was not referenced,
the class literal would not be part of the JS for that class. Instead
it would be generated in the epilogue in the compile were a reference
is introduced.

The epilogue does NOT have memory, so if a subsequent recompile did
not see a reference to the mentioned class literal it would not
be part of the epilogue and a reference error would occur.

Given that UnifyAST did not prune anything and class literal references
are synthezised at each class getClass(), the only class literals for
interfaces could cause the error (making this error a rare occurrence).

When compiling without optimizations (a requirement for SDM), this
patch creates class literals for all types in the current compile.

Change-Id: Ie5f287794bf66a2988ae07f4fda0cd6468c030f4
diff --git a/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java b/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java
index 0531200..183bf12 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java
@@ -114,7 +114,7 @@
       }
     }
 
-    ImplementClassLiteralsAsFields.exec(jprogram);
+    ImplementClassLiteralsAsFields.exec(jprogram, true);
     return jprogram;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 0f11fbe..03a1c26 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -1179,7 +1179,7 @@
         ConfigurationProperties config = new ConfigurationProperties(module);
         CodeSplitters.pickInitialLoadSequence(logger, jprogram, config);
       }
-      ImplementClassLiteralsAsFields.exec(jprogram);
+      ImplementClassLiteralsAsFields.exec(jprogram, shouldOptimize());
 
       // TODO(stalcup): hide metrics gathering in a callback or subclass
       logAstTypeMetrics(precompilationMetrics);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JPrimitiveType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JPrimitiveType.java
index dbbe272..0fffbb7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JPrimitiveType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JPrimitiveType.java
@@ -17,6 +17,8 @@
 
 import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.util.StringInterner;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableCollection;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
 
 import java.util.Map;
@@ -49,6 +51,9 @@
   public static final JPrimitiveType VOID =
       new JPrimitiveType("void", "V", "java.lang.VOID", null, Coercion.TO_VOID);
 
+  public static final ImmutableCollection<JPrimitiveType> types = ImmutableList.of(BOOLEAN, BYTE,
+      CHAR, DOUBLE, FLOAT, INT, LONG, SHORT, VOID);
+
   private final transient JValueLiteral defaultValue;
   private final transient String signatureName;
   private final transient String wrapperTypeName;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 7ba6c73..0c8b94f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -172,6 +172,7 @@
 import com.google.gwt.thirdparty.guava.common.base.Joiner;
 import com.google.gwt.thirdparty.guava.common.base.Predicate;
 import com.google.gwt.thirdparty.guava.common.base.Predicates;
+import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedSet;
 import com.google.gwt.thirdparty.guava.common.collect.Iterables;
@@ -1872,6 +1873,7 @@
       Set<JDeclaredType> alreadyProcessed =
           Sets.<JDeclaredType>newLinkedHashSet(program.immortalCodeGenTypes);
       alreadyProcessed.add(program.getTypeClassLiteralHolder());
+      alreadyRan.addAll(alreadyProcessed);
 
       List<JDeclaredType> classLiteralSupportClasses =
           computeClassLiteralsSupportClasses(program, alreadyProcessed);
@@ -1977,9 +1979,8 @@
     }
 
     private void generateEpilogue(List<JsStatement> globalStmts) {
-      // Emit all the class literals for classes that where pruned.
-      generateClassLiterals(globalStmts, Iterables.filter(classLiteralDeclarationsByType.keySet(),
-          Predicates.not(Predicates.<JType>in(alreadyRan))));
+
+      generateRemainingClassLiterals(globalStmts);
 
       // add all @JsExport assignments
       generateExports(globalStmts);
@@ -2000,6 +2001,33 @@
       }
     }
 
+    private void generateRemainingClassLiterals(List<JsStatement> globalStmts) {
+      if (!incremental) {
+        // Emit classliterals that are references but whose classes are not live.
+        generateClassLiterals(globalStmts, Iterables.filter(classLiteralDeclarationsByType.keySet(),
+            Predicates.not(Predicates.<JType>in(alreadyRan))));
+        return;
+      }
+
+      // In incremental, class literal references to class literals that were not generated
+      // as part of the current compile have to be from reference only classes.
+      assert FluentIterable.from(classLiteralDeclarationsByType.keySet())
+          .filter(Predicates.instanceOf(JDeclaredType.class))
+          .filter(Predicates.not(Predicates.<JType>in(alreadyRan)))
+          .filter(
+              new Predicate<JType>() {
+                @Override
+                public boolean apply(JType type) {
+                  return !program.isReferenceOnly((JDeclaredType) type);
+                }
+              })
+          .isEmpty();
+
+      // In incremental only the class literals for the primitive types should be part of the
+      // epilogue.
+      generateClassLiterals(globalStmts, JPrimitiveType.types);
+    }
+
     private void generateClassLiterals(List<JsStatement> globalStmts,
         Iterable<? extends JType> orderedTypes) {
       JsVars vars = new JsVars(jsProgram.getSourceInfo());
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
index 5ea70b4..8220385 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
@@ -108,32 +108,31 @@
             (JReferenceType) type));
         call.addArg(superclassLiteral);
 
-        maybeAddStandardMethodAsArg(info, program, type, "values()", call);
-        maybeAddStandardMethodAsArg(info, program, type, "valueOf(Ljava/lang/String;)", call);
+        call.addArg(getStandardMethodAsArg(info, program, type, "values()"));
+        call.addArg(getStandardMethodAsArg(info, program, type, "valueOf(Ljava/lang/String;)"));
         return call;
       }
 
-      private void maybeAddStandardMethodAsArg(SourceInfo info, JProgram program, JType type,
-          String methodSignature, JMethodCall call) {
+      private JExpression getStandardMethodAsArg(SourceInfo info, JProgram program, JType type,
+          String methodSignature) {
         JEnumType enumType = type.isEnumOrSubclass();
-        JMethod standardMethod = null;
 
-        if (enumType == type) {
-          for (JMethod method : enumType.getMethods()) {
-            if (method.isStatic() && method.getSignature().startsWith(methodSignature)) {
-              standardMethod = method;
-              break;
-            }
+        if (enumType != type) {
+          // The type is an anonymous subclass that represents one of the enum values
+          return JNullLiteral.INSTANCE;
+        }
+
+        // This type is the base enum type not one of the anonymous classes that represent
+        // enum values.
+        for (JMethod method : enumType.getMethods()) {
+          if (method.isStatic() && method.getSignature().startsWith(methodSignature)) {
+            return new JsniMethodRef(info, method.getJsniSignature(true, false),
+                method, program.getJavaScriptObject());
           }
         }
-        assert enumType != type || standardMethod != null
-            : "Could not find enum " + methodSignature + " method";
-        if (standardMethod == null) {
-          call.addArg(JNullLiteral.INSTANCE);
-        } else {
-          call.addArg(new JsniMethodRef(info, standardMethod.getJsniSignature(true, false),
-              standardMethod, program.getJavaScriptObject()));
-        }
+
+        // The method was pruned.
+        return JNullLiteral.INSTANCE;
       }
     },
     CREATE_FOR_CLASS() {
@@ -325,16 +324,11 @@
 
       ctx.replaceMe(newBody);
     }
-
-    private void resolveClassLiteral(JClassLiteral x) {
-      JField field = resolveClassLiteralField(x.getRefType());
-      x.setField(field);
-    }
   }
 
-  public static void exec(JProgram program) {
+  public static void exec(JProgram program, boolean shouldOptimize) {
     Event normalizerEvent = SpeedTracerLogger.start(CompilerEventType.NORMALIZER);
-    new ImplementClassLiteralsAsFields(program).execImpl();
+    new ImplementClassLiteralsAsFields(program, shouldOptimize).execImpl();
     normalizerEvent.end();
   }
 
@@ -342,11 +336,13 @@
   private final JMethodBody classLiteralHolderClinitBody;
   private final JProgram program;
   private final JClassType typeClassLiteralHolder;
+  private final boolean shouldOptimize;
 
-  private ImplementClassLiteralsAsFields(JProgram program) {
+  private ImplementClassLiteralsAsFields(JProgram program, boolean shouldOptimize) {
     this.program = program;
     this.typeClassLiteralHolder = program.getTypeClassLiteralHolder();
     this.classLiteralHolderClinitBody = (JMethodBody) typeClassLiteralHolder.getClinitMethod().getBody();
+    this.shouldOptimize = shouldOptimize;
     assert program.getDeclaredTypes().contains(typeClassLiteralHolder);
   }
 
@@ -375,6 +371,15 @@
   }
 
   private void execImpl() {
+    if (!shouldOptimize) {
+      // Create all class literals regardless of whether they are referenced or not.
+      for (JPrimitiveType type : JPrimitiveType.types) {
+        resolveClassLiteralField(type);
+      }
+      for (JType type : program.getDeclaredTypes()) {
+        resolveClassLiteralField(type);
+      }
+    }
     NormalizeVisitor visitor = new NormalizeVisitor();
     visitor.accept(program);
     program.recordClassLiteralFields(classLiteralFields);
@@ -432,6 +437,11 @@
         getSuperclassClassLiteral(info, type));
   }
 
+  private void resolveClassLiteral(JClassLiteral x) {
+    JField field = resolveClassLiteralField(x.getRefType());
+    x.setField(field);
+  }
+
   /**
    * Resolve a class literal field. Takes the form:
    *
diff --git a/dev/core/test/com/google/gwt/dev/CompilerTest.java b/dev/core/test/com/google/gwt/dev/CompilerTest.java
index ff26e75..e90eb55 100644
--- a/dev/core/test/com/google/gwt/dev/CompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/CompilerTest.java
@@ -1071,9 +1071,20 @@
         relinkMinimalRebuildCache.getProcessedStaleTypeNames().contains("com.foo.Bottom$Value"));
   }
 
-  public void testIncrementalRecompile_superClassOrder() throws UnableToCompleteException,
-      IOException,
-      InterruptedException {
+  public void testIncrementalRecompile_classLiteralNewReference()
+      throws UnableToCompleteException, IOException, InterruptedException {
+    checkIncrementalRecompile_classLiteralNewReference(JsOutputOption.OBFUSCATED);
+    checkIncrementalRecompile_classLiteralNewReference(JsOutputOption.DETAILED);
+  }
+
+  public void testIncrementalRecompile_primitiveClassLiteralReference()
+      throws UnableToCompleteException, IOException, InterruptedException {
+    checkIncrementalRecompile_primitiveClassLiteralReference(JsOutputOption.OBFUSCATED);
+    checkIncrementalRecompile_primitiveClassLiteralReference(JsOutputOption.DETAILED);
+  }
+
+  public void testIncrementalRecompile_superClassOrder()
+      throws UnableToCompleteException, IOException, InterruptedException {
     // Linked output is sorted alphabetically except that super-classes come before sub-classes. If
     // on recompile a sub-class -> super-class relationship is lost then a sub-class with an
     // alphabetically earlier name might start linking out before the super-class.
@@ -1081,9 +1092,8 @@
     checkIncrementalRecompile_superClassOrder(JsOutputOption.DETAILED);
   }
 
-  public void testIncrementalRecompile_superFromStaleInner() throws UnableToCompleteException,
-      IOException,
-      InterruptedException {
+  public void testIncrementalRecompile_superFromStaleInner()
+      throws UnableToCompleteException, IOException, InterruptedException {
     checkIncrementalRecompile_superFromStaleInner(JsOutputOption.OBFUSCATED);
     checkIncrementalRecompile_superFromStaleInner(JsOutputOption.DETAILED);
   }
@@ -1440,6 +1450,113 @@
         output);
   }
 
+  public void checkIncrementalRecompile_classLiteralNewReference(JsOutputOption output)
+      throws UnableToCompleteException, IOException, InterruptedException {
+    // Tests that when a superclass has a "inherits" a default method,
+    MockJavaResource interfaceA =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.A",
+            "package com.foo;",
+            "interface A {",
+            " static String b = \"\";",
+            "}");
+
+    MockJavaResource classBSansLiteralReference =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.B",
+            "package com.foo;",
+            "public class B {",
+            "}");
+
+    MockJavaResource classBWithLiteralReference =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.B",
+            "package com.foo;",
+            "public class B {",
+            "  public B() { Class c = A.class; }",
+            "}");
+
+    MockJavaResource classC =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.C",
+            "package com.foo;",
+            "import com.google.gwt.core.client.EntryPoint;",
+            "public class C implements EntryPoint {",
+            "  public void onModuleLoad() {",
+            "    if (A.b == null) ",
+            "      new B();",
+            "  }",
+            "}");
+
+    MockResource moduleResource =
+        JavaResourceBase.createMockResource(
+            "com/foo/ClassLiteralReference.gwt.xml",
+            "<module>",
+            "  <source path=''/>",
+            "  <entry-point class='com.foo.C'/>",
+            "</module>");
+
+    checkRecompiledModifiedApp("com.foo.ClassLiteralReference", Lists.newArrayList(
+            moduleResource, interfaceA, classC),
+        classBSansLiteralReference, classBWithLiteralReference,
+        stringSet("com.foo.B", "com.foo.C"), output);
+  }
+
+  public void checkIncrementalRecompile_primitiveClassLiteralReference(JsOutputOption output)
+      throws UnableToCompleteException, IOException, InterruptedException {
+    // Tests that when a superclass has a "inherits" a default method,
+    MockJavaResource classA =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.A",
+            "package com.foo;",
+            "public class A {",
+            "  public A() { ",
+            "    Class c = void.class; ",
+            "    c = int.class; ",
+            "    c = boolean.class;",
+            "    c = short.class;",
+            "    c = byte.class;",
+            "    c = long.class;",
+            "    c = float.class;",
+            "    c = double.class;",
+            "  }",
+            "}");
+
+    MockJavaResource classB =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.B",
+            "package com.foo;",
+            "public class B {",
+            "  public B() {  }",
+            "  public void m () { new A(); }",
+            "}");
+
+    MockJavaResource classC =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.C",
+            "package com.foo;",
+            "import com.google.gwt.core.client.EntryPoint;",
+            "public class C implements EntryPoint {",
+            "  public void onModuleLoad() {",
+            "      new B().m();",
+            "  }",
+            "}");
+
+    MockResource moduleResource =
+        JavaResourceBase.createMockResource(
+            "com/foo/PrimitiveClassLiteralReference.gwt.xml",
+            "<module>",
+            "  <source path=''/>",
+            "  <entry-point class='com.foo.C'/>",
+            "</module>");
+
+    checkRecompiledModifiedApp("com.foo.PrimitiveClassLiteralReference", Lists.newArrayList(
+            moduleResource, classA, classB),
+        classC, classC,
+        stringSet(getEntryMethodHolderTypeName("com.foo.PrimitiveClassLiteralReference"),
+            "com.foo.C"), output);
+  }
+
   private void checkIncrementalRecompile_dateStampChange(JsOutputOption output)
       throws UnableToCompleteException, IOException, InterruptedException {
     MinimalRebuildCache relinkMinimalRebuildCache = new MinimalRebuildCache();