Make NativeSubclass.class.getSuperclass() return Object.class.

There is an assumption on the ordering of the output that code for
a super class is emitted before that of the subclass in the JS
output. Because of the replacement of Native class literals for
JavaScriptObject class literals, the invariant was broken and
in some cases the produced output was errorneous.

Change-Id: Id0f5b8a536379d7307a59cac8a4bd2c8fab34d94
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 eabb035..11d373a 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
@@ -328,7 +328,8 @@
         return;
       }
 
-      JsniMethodBody newBody = new JsniMethodBody(jsniMethodBody.getSourceInfo(), jsniMethodBody.getFunc(),
+      JsniMethodBody newBody =
+          new JsniMethodBody(jsniMethodBody.getSourceInfo(), jsniMethodBody.getFunc(),
           Lists.newArrayList(newClassRefs), jsniMethodBody.getJsniFieldRefs(),
           jsniMethodBody.getJsniMethodRefs(), jsniMethodBody.getUsedStrings());
 
@@ -356,7 +357,8 @@
   private ImplementClassLiteralsAsFields(JProgram program, boolean shouldOptimize) {
     this.program = program;
     this.typeClassLiteralHolder = program.getTypeClassLiteralHolder();
-    this.classLiteralHolderClinitBody = (JMethodBody) typeClassLiteralHolder.getClinitMethod().getBody();
+    this.classLiteralHolderClinitBody =
+        (JMethodBody) typeClassLiteralHolder.getClinitMethod().getBody();
     this.shouldOptimize = shouldOptimize;
     assert program.getDeclaredTypes().contains(typeClassLiteralHolder);
   }
@@ -365,7 +367,17 @@
     if (!(type instanceof JClassType) ||  ((JClassType) type).getSuperClass() == null) {
       return JNullLiteral.INSTANCE;
     }
-    return createDependentClassLiteral(info, ((JClassType) type).getSuperClass());
+
+    JClassType superClass = ((JClassType) type).getSuperClass();
+
+    if (superClass.isJsNative()) {
+      // Class object for subclasses of native JsType will return Object.class as their super class
+      // class literal; this is done so that in invariant that "super" class literals are literals
+      // of a actual superclass.
+      superClass = program.getTypeJavaLangObject();
+    }
+
+    return createDependentClassLiteral(info, superClass);
   }
 
   private JClassLiteral createDependentClassLiteral(SourceInfo info, JType type) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JavaScriptVerifier.java b/dev/core/src/com/google/gwt/dev/js/ast/JavaScriptVerifier.java
index 834d70f..221658e 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JavaScriptVerifier.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JavaScriptVerifier.java
@@ -18,6 +18,10 @@
 
 import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
 import com.google.gwt.dev.js.JsUtils;
+import com.google.gwt.dev.js.ast.JsVars.JsVar;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import java.util.Set;
 
 /**
  * Verifies that the JavaScript AST and the map are consistent.
@@ -28,6 +32,68 @@
     if (!JavaScriptVerifier.class.desiredAssertionStatus()) {
       return;
     }
+    verifyTopLevelMethodMapping(jsProgram, map);
+    verifyGlobalNameOrdering(jsProgram, map);
+  }
+
+  private static void verifyGlobalNameOrdering(JsProgram jsProgram, final JavaToJavaScriptMap map) {
+    final Set<JsName> declaredEntities = Sets.newHashSet();
+    new JsVisitor() {
+      @Override
+      public boolean visit(JsFunction x, JsContext ctx) {
+        declaredEntities.add(x.getName());
+        // Do not examine function bodies.
+        return false;
+      }
+    }.accept(jsProgram);
+
+    new JsVisitor() {
+      @Override
+      public boolean visit(JsFunction x, JsContext ctx) {
+        // Do not examine function bodies.
+        return false;
+      }
+
+      @Override
+      public boolean visit(JsVars x, JsContext ctx) {
+        for (JsVar var : x) {
+          declaredEntities.add(var.getName());
+        }
+        return true;
+      }
+
+      @Override
+      public boolean visit(JsBinaryOperation x, JsContext ctx) {
+        if (x.getOperator().isAssignment()) {
+          JsNameRef nameRef = (JsNameRef) x.getArg1();
+          if (nameRef.getQualifier() == null) {
+            declaredEntities.add(nameRef.getName());
+          }
+        }
+        return true;
+      }
+
+      @Override
+      public boolean visit(JsObjectLiteral x, JsContext ctx) {
+        for (JsPropertyInitializer propertyInitializer : x.getPropertyInitializers()) {
+          accept(propertyInitializer.getValueExpr());
+        }
+        return false;
+      }
+
+      @Override
+      public void endVisit(JsNameRef x, JsContext ctx) {
+        if (x.getQualifier() != null || !x.getName().isObfuscatable()) {
+          return;
+        }
+        assert declaredEntities.contains(x.getName()) : x.getName() + " reference found before " +
+            " definition.";
+        map.nameToField(x.getName());
+      }
+    }.accept(jsProgram);
+  }
+
+  public static void verifyTopLevelMethodMapping(JsProgram jsProgram, JavaToJavaScriptMap map) {
     for (JsProgramFragment fragment : jsProgram.getFragments()) {
       for (JsStatement statement : fragment.getGlobalBlock().getStatements()) {
         JsFunction function = JsUtils.isFunctionDeclaration(statement);
diff --git a/dev/core/test/com/google/gwt/dev/CompilerTest.java b/dev/core/test/com/google/gwt/dev/CompilerTest.java
index d5f5848..8ee48b2 100644
--- a/dev/core/test/com/google/gwt/dev/CompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/CompilerTest.java
@@ -1028,7 +1028,7 @@
         Lists.newArrayList(moduleResource, testEntryPoint, someJsFunction,
             jsFunctionInterfaceImplementation, someInterface),
         new MinimalRebuildCache(), emptySet, JsOutputOption.DETAILED);
-    // Make sure the referenced class literals ends up beign included in the resulting JS.
+    // Make sure the referenced class literals ends up being included in the resulting JS.
     String classliteralHolderVarName =
         JjsUtils.mangleMemberName("com.google.gwt.lang.ClassLiteralHolder",
             JjsUtils.classLiteralFieldNameFromJavahTypeSignatureName(
@@ -1422,6 +1422,55 @@
     assertDeterministicBuild(HELLO_MODULE, 9);
   }
 
+  public void testSuccessfulCompile_jsoClassLiteralOrder() throws Exception {
+    // Crafted resource to make sure the a native subclass is compiled before the JSO class,
+    // In the case of native sublcasses the class hierarchy does not match the class literal
+    // hierarchy.
+    MockJavaResource nativeClassAndSubclass =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.MyNativeSubclass",
+            "package com.foo;",
+            "import jsinterop.annotations.JsType;",
+            "@JsType(isNative=true)",
+            "class NativeClass {",
+            "}",
+            "public class MyNativeSubclass extends NativeClass {",
+            "}");
+
+    MockJavaResource testEntryPoint =
+        JavaResourceBase.createMockJavaResource(
+            "com.foo.MyEntryPoint",
+            "package com.foo;",
+            "import com.foo.MyNativeSubclass;",
+            "public class MyEntryPoint extends MyNativeSubclass {",
+            "  public void onModuleLoad() {",
+            "    Object o = new Object();",
+            "    if (MyNativeSubclass.class.getName() == null) ",
+            "      o = new MyNativeSubclass();",
+            // Make .clazz reachable so that class literals are emmitted with the respective
+            // classses.
+            "    o.getClass();",
+            "  }",
+            "}");
+
+    MockResource moduleResource =
+        JavaResourceBase.createMockResource(
+            "com/foo/MyEntryPoint.gwt.xml",
+            "<module>",
+            "  <source path=''/>",
+            "  <entry-point class='com.foo.MyEntryPoint'/>",
+            "</module>");
+
+    CompilerOptions compilerOptions = new CompilerOptionsImpl();
+    // Make sure it compiles successfully with no assertions
+    compilerOptions.setEnableAssertions(true);
+    compilerOptions.setGenerateJsInteropExports(true);
+    compilerOptions.setOutput(JsOutputOption.PRETTY);
+    compilerOptions.setOptimizationLevel(9);
+    assertCompileSucceeds(compilerOptions, testEntryPoint.getTypeName(),
+        Lists.newArrayList(moduleResource, nativeClassAndSubclass, testEntryPoint));
+  }
+
   // TODO(stalcup): add recompile tests for file deletion.
 
   public void testIncrementalRecompile_noop() throws UnableToCompleteException, IOException,
@@ -2245,6 +2294,64 @@
             "com.foo.FooReplacementTwo"), outputOption);
   }
 
+  private void assertCompileSucceeds(CompilerOptions options, String moduleName,
+      List<MockResource> applicationResources) throws Exception {
+    File compileWorkDir = Utility.makeTemporaryDirectory(null, moduleName);
+    final CompilerOptionsImpl compilerOptions = new CompilerOptionsImpl(options);
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
+    logger.setMaxDetail(TreeLogger.ERROR);
+
+    String oldPersistentUnitCacheValue =
+        System.setProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE, "false");
+    try {
+
+      File applicationDir = Files.createTempDir();
+      Thread.sleep(1001);
+      // We might be reusing the same application dir but we want to make sure that the output dir
+      // is clean to avoid confusion when returning the output JS.
+      File outputDir = new File(applicationDir.getPath() + File.separator + moduleName);
+      if (outputDir.exists()) {
+        Util.recursiveDelete(outputDir, true);
+      }
+
+      // Fake out the resource loader to read resources both from the normal classpath as well as
+      // this new application directory.
+      ResourceLoader resourceLoader = ResourceLoaders.forClassLoader(Thread.currentThread());
+      resourceLoader =
+          ResourceLoaders.forPathAndFallback(ImmutableList.of(applicationDir), resourceLoader);
+
+      // Setup options to perform a per-file compile, output to this new application directory and
+      // compile the given module.
+      compilerOptions.setGenerateJsInteropExports(true);
+      compilerOptions.setWarDir(applicationDir);
+      compilerOptions.setModuleNames(ImmutableList.of(moduleName));
+
+      // Write the Java/XML/etc resources that make up the test application.
+      for (MockResource applicationResource : applicationResources) {
+        writeResourceTo(applicationResource, applicationDir);
+      }
+
+      // Cause the module to be cached with a reference to the prefixed resource loader so that the
+      // compile process will see those resources.
+      ModuleDefLoader.clearModuleCache();
+      ModuleDefLoader.loadFromResources(logger, moduleName, resourceLoader, true);
+
+      options.addModuleName(moduleName);
+      options.setWarDir(new File(compileWorkDir, "war"));
+      options.setExtraDir(new File(compileWorkDir, "extra"));
+
+      // Run the compiler once here.
+      new Compiler(options).run(logger);
+    } finally {
+      Util.recursiveDelete(compileWorkDir, false);
+      if (oldPersistentUnitCacheValue == null) {
+        System.clearProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE);
+      } else {
+        System.setProperty(UnitCacheSingleton.GWT_PERSISTENTUNITCACHE, oldPersistentUnitCacheValue);
+      }
+    }
+  }
+
   private void assertDeterministicBuild(String topLevelModule, int optimizationLevel)
       throws UnableToCompleteException, IOException {