Do not compute optimization info summary for kept methods

Bug: 131619590
Change-Id: Ia8ba0b16c0801a05db6d4a9bac080c9bc63d7413
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 7395d2b..2d8a5de 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
@@ -1115,22 +1115,6 @@
     previous =
         printMethod(code, "IR after idempotent function call canonicalization (SSA)", previous);
 
-    codeRewriter.identifyReturnsArgument(method, code, feedback);
-    if (options.enableInlining && inliner != null) {
-      codeRewriter.identifyInvokeSemanticsForInlining(method, code, appView, feedback);
-    }
-
-    if (appView.enableWholeProgramOptimizations()) {
-      // Track usage of parameters and compute their nullability and possibility of NPE.
-      if (method.getOptimizationInfo().getNonNullParamOrThrow() == null) {
-        computeNonNullParamHints(feedback, method, code);
-      }
-
-      computeDynamicReturnType(feedback, method, code);
-      computeInitializedClassesOnNormalExit(feedback, method, code);
-      computeMayHaveSideEffects(feedback, method, code);
-    }
-
     // Insert code to log arguments if requested.
     if (options.methodMatchesLogArgumentsFilter(method)) {
       codeRewriter.logArgumentTypes(method, code);
@@ -1139,19 +1123,32 @@
 
     previous = printMethod(code, "IR after argument type logging (SSA)", previous);
 
-    // Analysis must be done after method is rewritten by logArgumentTypes()
-    codeRewriter.identifyClassInlinerEligibility(method, code, feedback);
-
-    previous = printMethod(code, "IR after class inliner eligibility (SSA)", previous);
-
-    if (method.isInstanceInitializer() || method.isClassInitializer()) {
-      codeRewriter.identifyTrivialInitializer(method, code, feedback);
-    }
-    codeRewriter.identifyParameterUsages(method, code, feedback);
     if (classStaticizer != null) {
       classStaticizer.examineMethodCode(method, code);
     }
 
+    // Compute optimization info summary for the current method unless it is pinned (in that case,
+    // we should not be making any assumptions about the behavior of the method).
+    if (appView.enableWholeProgramOptimizations()
+        && !appView.appInfo().withLiveness().isPinned(method.method)) {
+      codeRewriter.identifyClassInlinerEligibility(method, code, feedback);
+      codeRewriter.identifyParameterUsages(method, code, feedback);
+      codeRewriter.identifyReturnsArgument(method, code, feedback);
+      codeRewriter.identifyTrivialInitializer(method, code, feedback);
+
+      if (options.enableInlining && inliner != null) {
+        codeRewriter.identifyInvokeSemanticsForInlining(method, code, appView, feedback);
+      }
+
+      computeDynamicReturnType(feedback, method, code);
+      computeInitializedClassesOnNormalExit(feedback, method, code);
+      computeMayHaveSideEffects(feedback, method, code);
+      computeNonNullParamOrThrow(feedback, method, code);
+    }
+
+    previous =
+        printMethod(code, "IR after computation of optimization info summary (SSA)", previous);
+
     if (options.canHaveNumberConversionRegisterAllocationBug()) {
       codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
     }
@@ -1171,8 +1168,13 @@
     finalizeIR(method, code, feedback);
   }
 
-  private void computeNonNullParamHints(
-    OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+  // Track usage of parameters and compute their nullability and possibility of NPE.
+  private void computeNonNullParamOrThrow(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+    if (method.getOptimizationInfo().getNonNullParamOrThrow() != null) {
+      return;
+    }
+
     List<Value> arguments = code.collectArguments();
     BitSet paramsCheckedForNull = new BitSet();
     for (int index = 0; index < arguments.size(); index++) {
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 87c28bf..f214373 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
@@ -11,7 +11,6 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -1169,22 +1168,23 @@
 
   public void identifyTrivialInitializer(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    boolean isInstanceInitializer = method.isInstanceInitializer();
-    boolean isClassInitializer = method.isClassInitializer();
-    assert isInstanceInitializer || isClassInitializer;
+    if (!method.isInstanceInitializer() && !method.isClassInitializer()) {
+      return;
+    }
+
     if (method.accessFlags.isNative()) {
       return;
     }
 
-    AppInfo appInfo = appView.appInfo();
-    DexClass clazz = appInfo.definitionFor(method.method.holder);
+    DexClass clazz = appView.appInfo().definitionFor(method.method.holder);
     if (clazz == null) {
       return;
     }
 
-    feedback.setTrivialInitializer(method,
-        isInstanceInitializer
-            ? computeInstanceInitializerInfo(code, clazz, appInfo::definitionFor)
+    feedback.setTrivialInitializer(
+        method,
+        method.isInstanceInitializer()
+            ? computeInstanceInitializerInfo(code, clazz, appView::definitionFor)
             : computeClassInitializerInfo(code, clazz));
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/OptimizationSummaryForKeptMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/OptimizationSummaryForKeptMethodTest.java
new file mode 100644
index 0000000..7dc327c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/OptimizationSummaryForKeptMethodTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2019, 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.ir.optimize;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+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 OptimizationSummaryForKeptMethodTest extends TestBase {
+
+  private final boolean enableExtraKeepRule;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, enable extra keep rule: {0}")
+  public static List<Object[]> params() {
+    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+  }
+
+  public OptimizationSummaryForKeptMethodTest(
+      boolean enableExtraKeepRule, TestParameters parameters) {
+    this.enableExtraKeepRule = enableExtraKeepRule;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(OptimizationSummaryForKeptMethodTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(
+            (Class<?>[]) (enableExtraKeepRule ? new Class<?>[] {KeptClass.class} : new Class<?>[0]))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(this::verifyOutput);
+  }
+
+  private void verifyOutput(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+
+    // Main method invokes noNormalExits() since it is not allowed to be inlined.
+    assertThat(
+        mainMethodSubject,
+        invokesMethod(inspector.clazz(KeptClass.class).uniqueMethodWithName("noNormalExits")));
+
+    // The fact that noNormalExits() never returns normally has only been exploited if it is not
+    // kept.
+    assertNotEquals(
+        enableExtraKeepRule,
+        mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isThrow));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      try {
+        KeptClass.noNormalExits();
+        System.out.println(" world!");
+      } catch (Exception e) {
+        System.out.println("Caught " + e.getClass().getName());
+      }
+    }
+  }
+
+  static class KeptClass {
+
+    @NeverInline
+    public static void noNormalExits() {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index d6ca6e1..9df1d87 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -12,20 +12,24 @@
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CfInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.DexInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Iterator;
+import java.util.List;
 import java.util.function.BiConsumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,15 +39,17 @@
 @RunWith(Parameterized.class)
 public class NeverReturnsNormallyTest extends TestBase {
 
-  private Backend backend;
+  private boolean keepOuterTrivial;
+  private TestParameters parameters;
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{1}, keep outerTrivial: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
   }
 
-  public NeverReturnsNormallyTest(Backend backend) {
-    this.backend = backend;
+  public NeverReturnsNormallyTest(boolean keepOuterTrivial, TestParameters parameters) {
+    this.keepOuterTrivial = keepOuterTrivial;
+    this.parameters = parameters;
   }
 
   private void runTest(
@@ -52,53 +58,65 @@
     R8Command.Builder builder = R8Command.builder();
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(ForceInline.class));
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
-    builder.setProgramConsumer(emptyConsumer(backend));
-    builder.addLibraryFiles(runtimeJar(backend));
+    builder.setProgramConsumer(emptyConsumer(parameters.getBackend()));
+    builder.addLibraryFiles(runtimeJar(parameters.getBackend()));
     builder.setMode(mode);
     builder.addProguardConfiguration(
         ImmutableList.of(
             "-forceinline class * { @com.android.tools.r8.ForceInline *; }",
-            "-keep class " + TestClass.class.getCanonicalName() + "{",
+            "-keep class " + TestClass.class.getTypeName() + " {",
             "  public static void main(java.lang.String[]);",
             "  *** test*(...);",
-            "  *** outerTrivial(...);",
-            "}",
-            "",
-            "-checkdiscard class " + TestClass.class.getCanonicalName() + "{",
-            "  *** assertRemoved(...);",
             "}",
             "",
             "-dontobfuscate",
             "-allowaccessmodification"),
         Origin.unknown());
+    if (keepOuterTrivial) {
+      builder.addProguardConfiguration(
+          ImmutableList.of(
+              "-keep class " + TestClass.class.getTypeName() + " {",
+              "  *** outerTrivial(...);",
+              "}"),
+          Origin.unknown());
+    } else {
+      builder.addProguardConfiguration(
+          ImmutableList.of(
+              "-checkdiscard class " + TestClass.class.getTypeName() + " {",
+              "  *** assertRemoved(...);",
+              "}"),
+          Origin.unknown());
+    }
     ToolHelper.allowTestProguardOptions(builder);
     AndroidApp app =
         ToolHelper.runR8(builder.build(), opts -> opts.enableClassInlining = enableClassInliner);
     inspection.accept(new CodeInspector(app), mode);
 
-    if (backend == Backend.DEX) {
+    if (parameters.isDexRuntime()) {
       // Run on Art to check generated code against verifier.
       runOnArt(app, TestClass.class);
     } else {
-      assert backend == Backend.CF;
+      assert parameters.isCfRuntime();
       runOnJava(app, TestClass.class);
     }
   }
 
   private void validate(CodeInspector inspector, CompilationMode mode) {
-    assert (backend == Backend.DEX || backend == Backend.CF);
+    assert parameters.isCfRuntime() || parameters.isDexRuntime();
     ClassSubject clazz = inspector.clazz(TestClass.class);
     assertTrue(clazz.isPresent());
 
     // All calls to 'assertRemoved' are to be removed.
-    clazz.forAllMethods(method -> {
-      Iterator<InstructionSubject> instructions =
-          method.iterateInstructions(InstructionSubject::isInvoke);
-      while (instructions.hasNext()) {
-        InvokeInstructionSubject invoke = (InvokeInstructionSubject) instructions.next();
-        assertFalse(invoke.invokedMethod().name.toString().equals("assertRemoved"));
-      }
-    });
+    int numberOfCallsToAssertRemoved = 0;
+    for (FoundMethodSubject method : clazz.allMethods()) {
+      numberOfCallsToAssertRemoved +=
+          method
+              .streamInstructions()
+              .filter(InstructionSubject::isInvoke)
+              .filter(invoke -> invoke.getMethod().name.toString().equals("assertRemoved"))
+              .count();
+    }
+    assertEquals(keepOuterTrivial ? 2 : 0, numberOfCallsToAssertRemoved);
 
     // Check the instruction used for testInlinedIntoVoidMethod
     MethodSubject methodThrowToBeInlined =
@@ -140,16 +158,17 @@
     verifyTrailingPattern(instructions);
 
     // Check the instruction used for outerTrivial
-    MethodSubject methodOuterTrivial =
-        clazz.method("int", "outerTrivial", ImmutableList.of());
-    assertTrue(methodOuterTrivial.isPresent());
-    instructions = methodOuterTrivial.iterateInstructions();
-    // Call, followed by [nop, goto]
-    insn = nextInstructionSkippingCfPositionAndLabel(instructions);
-    assertTrue(insn.isInvoke());
-    assertTrue(((InvokeInstructionSubject) insn)
-        .invokedMethod().name.toString().equals("innerNotReachable"));
-    verifyTrailingPattern(instructions);
+    MethodSubject methodOuterTrivial = clazz.method("int", "outerTrivial", ImmutableList.of());
+    assertEquals(keepOuterTrivial || mode == CompilationMode.DEBUG, methodOuterTrivial.isPresent());
+
+    if (methodOuterTrivial.isPresent()) {
+      instructions = methodOuterTrivial.iterateInstructions();
+      // Call, followed by [nop, goto]
+      insn = nextInstructionSkippingCfPositionAndLabel(instructions);
+      assertTrue(insn.isInvoke());
+      assertEquals("innerNotReachable", insn.getMethod().name.toString());
+      verifyTrailingPattern(instructions);
+    }
   }
 
   private InstructionSubject nextInstruction(Iterator<InstructionSubject> instructions) {
@@ -175,7 +194,7 @@
 
   private void verifyTrailingPattern(Iterator<InstructionSubject> instructions) {
     InstructionSubject insn = nextInstruction(instructions);
-    if (backend == Backend.DEX) {
+    if (parameters.isDexRuntime()) {
       assertTrue(
           insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
     } else {