Merge "Apply name reflection optimization to seemingly top-level classes only."
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
new file mode 100644
index 0000000..20faecf
--- /dev/null
+++ b/PRESUBMIT.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+def CheckDoNotMerge(input_api, output_api):
+  for l in input_api.change.FullDescriptionText().splitlines():
+    if l.lower().startswith('do not merge'):
+      msg = 'Your cl contains: \'Do not merge\' - this will break WIP bots'
+      return [output_api.PresubmitPromptWarning(msg, [])]
+  return []
+
+def CheckChangeOnUpload(input_api, output_api):
+  results = []
+  results.extend(CheckDoNotMerge(input_api, output_api))
+  return results
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6d00ebb..7f77c6b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
@@ -527,6 +528,8 @@
         return;
       }
 
+      assert application.classes().stream().allMatch(DexClass::isValid);
+
       // Generate the resulting application resources.
       writeApplication(
           executorService,
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index d79276e..5a4b204 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -524,4 +524,9 @@
     return getKotlinInfo() != null;
   }
 
+  public boolean isValid() {
+    assert !isInterface()
+        || Arrays.stream(virtualMethods()).noneMatch(method -> method.accessFlags.isFinal());
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 952038a..e9966f5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -68,7 +68,7 @@
   // ReferenceTypeLattice canonicalization.
   private final ConcurrentHashMap<DexType, ReferenceTypeLatticeElement>
       referenceTypeLatticeElements = new ConcurrentHashMap<>();
-  public static final LRUCacheTable<Set<DexType>, Set<DexType>, Set<DexType>>
+  public final LRUCacheTable<Set<DexType>, Set<DexType>, Set<DexType>>
       leastUpperBoundOfInterfacesTable = LRUCacheTable.create(8, 8);
 
   boolean sorted = false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 281d41b..20c04ab 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -56,6 +56,7 @@
 import com.android.tools.r8.ir.optimize.Outliner;
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
@@ -904,7 +905,7 @@
 
     if (appInfo.hasLiveness()) {
       // Reflection optimization 1. getClass() -> const-class
-      codeRewriter.rewriteGetClass(code);
+      ReflectionOptimizer.rewriteGetClass(appInfo.withLiveness(), code);
     }
 
     if (!isDebugMode) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 6580b29..0b54a4d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -52,7 +52,6 @@
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
-import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
@@ -1962,67 +1961,6 @@
     return converter.definitionFor(type);
   }
 
-  // Rewrite getClass() call to const-class if the type of the given instance is effectively final.
-  public void rewriteGetClass(IRCode code) {
-    InstructionIterator it = code.instructionIterator();
-    while (it.hasNext()) {
-      Instruction current = it.next();
-      // Conservatively bail out if the containing block has catch handlers.
-      // TODO(b/118509730): unless join of all catch types is ClassNotFoundException ?
-      if (current.getBlock().hasCatchHandlers()) {
-        continue;
-      }
-      if (!current.isInvokeVirtual()) {
-        continue;
-      }
-      InvokeVirtual invoke = current.asInvokeVirtual();
-      DexMethod invokedMethod = invoke.getInvokedMethod();
-      // Class<?> Object#getClass() is final and cannot be overridden.
-      if (invokedMethod != appInfo.dexItemFactory.objectMethods.getClass) {
-        continue;
-      }
-      Value in = invoke.getReceiver();
-      if (in.hasLocalInfo()) {
-        continue;
-      }
-      TypeLatticeElement inType = in.getTypeLattice();
-      // Check the receiver is either class type or array type. Also make sure it is not nullable.
-      if (!(inType.isClassType() || inType.isArrayType())
-          || inType.isNullable()) {
-        continue;
-      }
-      DexType type = inType.isClassType()
-          ? inType.asClassTypeLatticeElement().getClassType()
-          : inType.asArrayTypeLatticeElement().getArrayType(appInfo.dexItemFactory);
-      DexType baseType = type.toBaseType(appInfo.dexItemFactory);
-      // Make sure base type is a class type.
-      if (!baseType.isClassType()) {
-        continue;
-      }
-      // Only consider program class, e.g., platform can introduce sub types in different versions.
-      DexClass clazz = appInfo.definitionFor(baseType);
-      if (clazz == null || !clazz.isProgramClass()) {
-        continue;
-      }
-      // Only consider effectively final class. Exception: new Base().getClass().
-      if (!baseType.hasSubtypes()
-          || !appInfo.withLiveness().isInstantiatedIndirectly(baseType)
-          || (!in.isPhi() && in.definition.isCreatingInstanceOrArray())) {
-        // Make sure the target (base) type is visible.
-        ConstraintWithTarget constraints =
-            ConstraintWithTarget.classIsVisible(code.method.method.getHolder(), baseType, appInfo);
-        if (constraints == ConstraintWithTarget.NEVER) {
-          continue;
-        }
-        TypeLatticeElement typeLattice = TypeLatticeElement.classClassType(appInfo);
-        Value value = code.createValue(typeLattice, invoke.getLocalInfo());
-        ConstClass constClass = new ConstClass(value, type);
-        it.replaceCurrentInstruction(constClass);
-      }
-    }
-    assert code.isConsistentSSA();
-  }
-
   public void removeTrivialCheckCastAndInstanceOfInstructions(
       IRCode code, boolean enableWholeProgramOptimizations) {
     if (!enableWholeProgramOptimizations) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index ecd3a7a..496e113 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -9,8 +9,19 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.base.Strings;
 
 public class ReflectionOptimizer {
@@ -57,6 +68,67 @@
     }
   }
 
+  // Rewrite getClass() call to const-class if the type of the given instance is effectively final.
+  public static void rewriteGetClass(AppInfoWithLiveness appInfo, IRCode code) {
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      Instruction current = it.next();
+      // Conservatively bail out if the containing block has catch handlers.
+      // TODO(b/118509730): unless join of all catch types is ClassNotFoundException ?
+      if (current.getBlock().hasCatchHandlers()) {
+        continue;
+      }
+      if (!current.isInvokeVirtual()) {
+        continue;
+      }
+      InvokeVirtual invoke = current.asInvokeVirtual();
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      // Class<?> Object#getClass() is final and cannot be overridden.
+      if (invokedMethod != appInfo.dexItemFactory.objectMethods.getClass) {
+        continue;
+      }
+      Value in = invoke.getReceiver();
+      if (in.hasLocalInfo()) {
+        continue;
+      }
+      TypeLatticeElement inType = in.getTypeLattice();
+      // Check the receiver is either class type or array type. Also make sure it is not nullable.
+      if (!(inType.isClassType() || inType.isArrayType())
+          || inType.isNullable()) {
+        continue;
+      }
+      DexType type = inType.isClassType()
+          ? inType.asClassTypeLatticeElement().getClassType()
+          : inType.asArrayTypeLatticeElement().getArrayType(appInfo.dexItemFactory);
+      DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+      // Make sure base type is a class type.
+      if (!baseType.isClassType()) {
+        continue;
+      }
+      // Only consider program class, e.g., platform can introduce sub types in different versions.
+      DexClass clazz = appInfo.definitionFor(baseType);
+      if (clazz == null || !clazz.isProgramClass()) {
+        continue;
+      }
+      // Only consider effectively final class. Exception: new Base().getClass().
+      if (!baseType.hasSubtypes()
+          || !appInfo.isInstantiatedIndirectly(baseType)
+          || (!in.isPhi() && in.definition.isCreatingInstanceOrArray())) {
+        // Make sure the target (base) type is visible.
+        ConstraintWithTarget constraints =
+            ConstraintWithTarget.classIsVisible(code.method.method.getHolder(), baseType, appInfo);
+        if (constraints == ConstraintWithTarget.NEVER) {
+          continue;
+        }
+        TypeLatticeElement typeLattice = TypeLatticeElement.classClassType(appInfo);
+        Value value = code.createValue(typeLattice, invoke.getLocalInfo());
+        ConstClass constClass = new ConstClass(value, type);
+        it.replaceCurrentInstruction(constClass);
+      }
+    }
+    assert code.isConsistentSSA();
+  }
+
   public static String computeClassName(
       DexString descriptor, DexClass holder, ClassNameComputationInfo classNameComputationInfo) {
     return computeClassName(
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 3a2f4e2..d1e0606 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
-import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
@@ -127,13 +126,11 @@
   }
 
   public GraphLense run() {
-    // Visit the program classes in a top-down order according to the class hierarchy.
-    Iterable<DexProgramClass> classes = appView.appInfo().app.classesWithDeterministicOrder();
-    TopDownClassHierarchyTraversal.visit(appView, classes, clazz -> {
+    for (DexProgramClass clazz : appView.appInfo().app.classesWithDeterministicOrder()) {
       if (satisfiesMergeCriteria(clazz)) {
         merge(clazz);
       }
-    });
+    }
     if (Log.ENABLED) {
       Log.info(
           getClass(),
@@ -204,6 +201,11 @@
     return true;
   }
 
+  private boolean isValidRepresentative(DexProgramClass clazz) {
+    // Disallow interfaces from being representatives, since interface methods require desugaring.
+    return !clazz.isInterface();
+  }
+
   private boolean merge(DexProgramClass clazz) {
     assert satisfiesMergeCriteria(clazz);
 
@@ -216,8 +218,12 @@
   private boolean mergeGlobally(DexProgramClass clazz, String pkg) {
     Representative globalRepresentative = representatives.get(GLOBAL);
     if (globalRepresentative == null) {
-      // Make the current class the global representative.
-      setRepresentative(GLOBAL, getOrCreateRepresentative(clazz, pkg));
+      if (isValidRepresentative(clazz)) {
+        // Make the current class the global representative.
+        setRepresentative(GLOBAL, getOrCreateRepresentative(clazz, pkg));
+      } else {
+        clearRepresentative(GLOBAL);
+      }
 
       // Do not attempt to merge this class inside its own package, because that could lead to
       // an increase in the global representative, which is not desirable.
@@ -227,8 +233,12 @@
       globalRepresentative.include(clazz);
 
       if (globalRepresentative.isFull()) {
-        // Make the current class the global representative instead.
-        setRepresentative(GLOBAL, getOrCreateRepresentative(clazz, pkg));
+        if (isValidRepresentative(clazz)) {
+          // Make the current class the global representative instead.
+          setRepresentative(GLOBAL, getOrCreateRepresentative(clazz, pkg));
+        } else {
+          clearRepresentative(GLOBAL);
+        }
 
         // Do not attempt to merge this class inside its own package, because that could lead to
         // an increase in the global representative, which is not desirable.
@@ -244,7 +254,8 @@
   private boolean mergeInsidePackage(DexProgramClass clazz, String pkg) {
     Representative packageRepresentative = representatives.get(pkg);
     if (packageRepresentative != null) {
-      if (clazz.accessFlags.isMoreVisibleThan(packageRepresentative.clazz.accessFlags)) {
+      if (isValidRepresentative(clazz)
+          && clazz.accessFlags.isMoreVisibleThan(packageRepresentative.clazz.accessFlags)) {
         // Use `clazz` as a representative for this package instead.
         Representative newRepresentative = getOrCreateRepresentative(clazz, pkg);
         newRepresentative.include(packageRepresentative.clazz);
@@ -271,7 +282,9 @@
     }
 
     // We were unable to use the current representative for this package (if any).
-    setRepresentative(pkg, getOrCreateRepresentative(clazz, pkg));
+    if (isValidRepresentative(clazz)) {
+      setRepresentative(pkg, getOrCreateRepresentative(clazz, pkg));
+    }
     return false;
   }
 
@@ -288,6 +301,7 @@
   }
 
   private void setRepresentative(String pkg, Representative representative) {
+    assert isValidRepresentative(representative.clazz);
     if (Log.ENABLED) {
       if (pkg.equals(GLOBAL)) {
         Log.info(
@@ -305,6 +319,17 @@
     representatives.put(pkg, representative);
   }
 
+  private void clearRepresentative(String pkg) {
+    if (Log.ENABLED) {
+      if (pkg.equals(GLOBAL)) {
+        Log.info(getClass(), "Removing the global representative");
+      } else {
+        Log.info(getClass(), "Removing the representative for package %s", pkg);
+      }
+    }
+    representatives.remove(pkg);
+  }
+
   private boolean mayMergeAcrossPackageBoundaries(DexProgramClass clazz) {
     // Check that the class is public. Otherwise, accesses to `clazz` from within its current
     // package may become illegal.
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerInterfaceTest.java
new file mode 100644
index 0000000..294eca0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StaticClassMergerInterfaceTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticClassMergerInterfaceTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public StaticClassMergerInterfaceTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("In A.a()", "In B.b()", "In C.c()");
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(StaticClassMergerInterfaceTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules("-dontobfuscate")
+            .enableInliningAnnotations()
+            .enableClassInliningAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    // Check that A has not been merged into B. The static class merger visits classes in alpha-
+    // betical order. By the time A is processed, there is no merge representative and A is not
+    // a valid merge representative itself, because it is an interface.
+    if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V6_0_1) || backend == Backend.CF) {
+      assertThat(inspector.clazz(A.class), isPresent());
+    } else {
+      assertThat(inspector.clazz(A.class.getTypeName() + "$-CC"), isPresent());
+    }
+
+
+    // By the time B is processed, there is no merge representative, so it should be present.
+    assertThat(inspector.clazz(B.class), isPresent());
+
+    // By the time C is processed, B should be merge candidate. Therefore, we should allow C.c() to
+    // be moved to B *although C is an interface*.
+    assertThat(inspector.clazz(C.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A.a();
+      B.b();
+      C.c();
+    }
+  }
+
+  @NeverClassInline
+  interface A {
+
+    @NeverInline
+    static void a() {
+      System.out.println("In A.a()");
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    @NeverInline
+    static void b() {
+      System.out.println("In B.b()");
+    }
+  }
+
+  @NeverClassInline
+  interface C {
+
+    @NeverInline
+    static void c() {
+      System.out.println("In C.c()");
+    }
+  }
+}