[ApiModel] Enable stubbing in D8

Bug: 213552119
Change-Id: I399fd40edbbd34234abcea191013c0a5c9dc7d64
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index b54e34d..217a787 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.AssertionUtils.forTesting;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
+import com.android.tools.r8.androidapi.ApiReferenceStubber;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
@@ -265,7 +266,7 @@
       namingLens = RecordRewritingNamingLens.createRecordRewritingNamingLens(appView, namingLens);
 
       if (options.isGeneratingClassFiles()) {
-        finalizeApplication(inputApp, appView, executor, namingLens);
+        finalizeApplication(appView, executor);
         new CfApplicationWriter(appView, marker, GraphLens.getIdentityLens(), namingLens)
             .write(options.getClassFileConsumer(), inputApp);
       } else {
@@ -308,7 +309,12 @@
                       executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
           appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
         }
-        finalizeApplication(inputApp, appView, executor, namingLens);
+
+        finalizeApplication(appView, executor);
+
+        if (options.apiModelingOptions().enableStubbingOfClasses && !appView.options().debug) {
+          new ApiReferenceStubber(appView).run(executor);
+        }
 
         new ApplicationWriter(
                 appView,
@@ -330,11 +336,7 @@
     }
   }
 
-  private static void finalizeApplication(
-      AndroidApp inputApp,
-      AppView<AppInfo> appView,
-      ExecutorService executorService,
-      NamingLens namingLens)
+  private static void finalizeApplication(AppView<AppInfo> appView, ExecutorService executorService)
       throws ExecutionException {
     SyntheticFinalization.finalize(appView, executorService);
   }
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index ef86f73..e0852c5 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.androidapi;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
 import com.android.tools.r8.graph.DexClass;
@@ -106,13 +106,13 @@
     }
   }
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final Map<DexLibraryClass, Set<DexMethod>> libraryClassesToMock =
       new ConcurrentHashMap<>();
   private final Set<DexType> seenTypes = Sets.newConcurrentHashSet();
   private final AndroidApiLevelCompute apiLevelCompute;
 
-  public ApiReferenceStubber(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public ApiReferenceStubber(AppView<?> appView) {
     this.appView = appView;
     apiLevelCompute = appView.apiLevelCompute();
   }
@@ -138,10 +138,18 @@
       AppView<AppInfoWithLiveness> appInfoWithLivenessAppView = appView.withLiveness();
       appInfoWithLivenessAppView.setAppInfo(
           appInfoWithLivenessAppView.appInfo().rebuildWithLiveness(committedItems));
-    } else {
+    } else if (appView.hasClassHierarchy()) {
       appView
           .withClassHierarchy()
-          .setAppInfo(appView.appInfo().rebuildWithClassHierarchy(committedItems));
+          .setAppInfo(
+              appView.appInfo().withClassHierarchy().rebuildWithClassHierarchy(committedItems));
+    } else {
+      appView
+          .withoutClassHierarchy()
+          .setAppInfo(
+              new AppInfo(
+                  appView.appInfo().getSyntheticItems().commit(appView.app()),
+                  appView.appInfo().getMainDexInfo()));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
index 0462290..784fce6 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -55,22 +57,40 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Add support for stubbing
         .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
   }
 
   @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
+  }
+
+  @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
@@ -84,9 +104,7 @@
         .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector ->
-                verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel));
+        .inspect(this::inspect);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
@@ -102,6 +120,10 @@
         result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
   }
 
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+  }
+
   // Only present from api level 23.
   public static class LibraryClass {
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
index 6cf2ea6..8651a23 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
@@ -6,9 +6,11 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
@@ -16,6 +18,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -50,15 +53,15 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
-        // TODO(b/213552119): Add support for stubbing
         .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
         .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
@@ -66,6 +69,24 @@
   }
 
   @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeFalse(
@@ -75,11 +96,16 @@
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
         .compile()
+        .inspect(this::inspect)
         .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput);
   }
 
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+  }
+
   private void checkOutput(SingleTestRunResult<?> runResult) {
     runResult.assertSuccessWithOutputLinesIf(isGreaterOrEqualToMockLevel(), "Hello World");
     runResult.assertFailureWithErrorThatThrowsIf(
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
index 8d6f816..1397da7 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -59,19 +61,37 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Add support for stubbing
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(this::inspect);
   }
 
   @Test
@@ -88,9 +108,11 @@
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector ->
-                verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel));
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
index 8209ef0..3d6d30c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -58,19 +60,37 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Add support for stubbing
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(this::inspect);
   }
 
   @Test
@@ -87,9 +107,11 @@
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector ->
-                verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel));
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
index cb9a7aa..c2a6d4d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -68,12 +70,36 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(
+            addLibraryClassesToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryInterface.class))
+        .applyIf(
+            addOtherLibraryClassesToBootClasspath(),
+            b -> b.addBootClasspathClasses(OtherLibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(
@@ -84,8 +110,7 @@
             b -> b.addBootClasspathClasses(OtherLibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Add support for stubbing
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(this::inspect);
   }
 
   @Test
@@ -106,14 +131,13 @@
             addOtherLibraryClassesToBootClasspath(),
             b -> b.addBootClasspathClasses(OtherLibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
-        .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(lowerMockApiLevel);
-              verifyThat(inspector, parameters, LibraryInterface.class)
-                  .stubbedUntil(lowerMockApiLevel);
-              verifyThat(inspector, parameters, OtherLibraryClass.class).stubbedUntil(mockApiLevel);
-            });
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(lowerMockApiLevel);
+    verifyThat(inspector, parameters, LibraryInterface.class).stubbedUntil(lowerMockApiLevel);
+    verifyThat(inspector, parameters, OtherLibraryClass.class).stubbedUntil(mockApiLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {