Internal support for emitting map-file info from D8.

Bug: 172014416
Bug: 183125319
Change-Id: Ic509ee2284560a9772de6310a812875f5a392b98
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index cdbcdd8..9b3f92c 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -27,8 +27,10 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
+import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.naming.RecordRewritingNamingLens;
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.origin.CommandLineOrigin;
@@ -41,6 +43,8 @@
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.LineNumberOptimizer;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -268,9 +272,10 @@
         namingLens = RecordRewritingNamingLens.createRecordRewritingNamingLens(appView, namingLens);
       }
       if (options.isGeneratingClassFiles()) {
-        // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
-        SyntheticFinalization.finalize(appView);
-        new CfApplicationWriter(appView, marker, GraphLens.getIdentityLens(), namingLens, null)
+        ProguardMapSupplier proguardMapSupplier =
+            finalizeApplication(inputApp, appView, namingLens);
+        new CfApplicationWriter(
+                appView, marker, GraphLens.getIdentityLens(), namingLens, proguardMapSupplier)
             .write(options.getClassFileConsumer());
       } else {
         if (!hasDexResources || !hasClassResources || !appView.rewritePrefix.isRewriting()) {
@@ -311,9 +316,8 @@
                       executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
           appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
         }
-
-        // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
-        SyntheticFinalization.finalize(appView);
+        ProguardMapSupplier proguardMapSupplier =
+            finalizeApplication(inputApp, appView, namingLens);
 
         new ApplicationWriter(
                 appView,
@@ -321,7 +325,7 @@
                 appView.graphLens(),
                 InitClassLens.getDefault(),
                 namingLens,
-                null)
+                proguardMapSupplier)
             .write(executor);
       }
       options.printWarnings();
@@ -336,6 +340,19 @@
     }
   }
 
+  private static ProguardMapSupplier finalizeApplication(
+      AndroidApp inputApp, AppView<AppInfo> appView, NamingLens namingLens) {
+    SyntheticFinalization.finalize(appView);
+    // TODO(b/37830524): Once D8 supports PC mapping this will need to be run for that too.
+    assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF;
+    if (appView.options().proguardMapConsumer == null) {
+      return null;
+    }
+    ClassNameMapper classNameMapper =
+        LineNumberOptimizer.run(appView, appView.appInfo().app(), inputApp, namingLens);
+    return ProguardMapSupplier.create(classNameMapper, appView.options());
+  }
+
   private static DexApplication rewriteNonDexInputs(
       AppView<AppInfo> appView,
       AndroidApp inputApp,
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 05c2e8e..7e20368 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -459,6 +460,7 @@
     internal.readCompileTimeAnnotations = intermediate;
     internal.desugarGraphConsumer = desugarGraphConsumer;
     internal.mainDexKeepRules = mainDexKeepRules;
+    internal.lineNumberOptimization = LineNumberOptimization.OFF;
 
     // Assert and fixup defaults.
     assert !internal.isShrinking();
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index f8ad5bb..6019425 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -265,10 +265,7 @@
   }
 
   public static ClassNameMapper run(
-      AppView<AppInfoWithClassHierarchy> appView,
-      DexApplication application,
-      AndroidApp inputApp,
-      NamingLens namingLens) {
+      AppView<?> appView, DexApplication application, AndroidApp inputApp, NamingLens namingLens) {
     // For finding methods in kotlin files based on SourceDebugExtensions, we use a line method map.
     // We create it here to ensure it is only reading class files once.
     CfLineToMethodMapper cfLineToMethodMapper = new CfLineToMethodMapper(inputApp);
@@ -462,10 +459,11 @@
   }
 
   private static boolean verifyMethodsAreKeptDirectlyOrIndirectly(
-      AppView<AppInfoWithClassHierarchy> appView, List<DexEncodedMethod> methods) {
-    if (appView.options().isGeneratingClassFiles()) {
+      AppView<?> appView, List<DexEncodedMethod> methods) {
+    if (appView.options().isGeneratingClassFiles() || !appView.appInfo().hasClassHierarchy()) {
       return true;
     }
+    AppInfoWithClassHierarchy appInfo = appView.appInfo().withClassHierarchy();
     KeepInfoCollection keepInfo = appView.getKeepInfo();
     boolean allSeenAreInstanceInitializers = true;
     DexString originalName = null;
@@ -487,7 +485,7 @@
       // We use the same name for interface names even if it has different types.
       DexProgramClass clazz = appView.definitionForProgramType(method.getHolderType());
       DexClassAndMethod lookupResult =
-          appView.appInfo().lookupMaximallySpecificMethod(clazz, method.getReference());
+          appInfo.lookupMaximallySpecificMethod(clazz, method.getReference());
       if (lookupResult == null) {
         // We cannot rename methods we cannot look up.
         continue;
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 67eeeb0..d299224 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -28,6 +28,8 @@
     return new D8TestBuilder(state, D8Command.builder(state.getDiagnosticsHandler()), backend);
   }
 
+  private StringBuilder proguardMapOutputBuilder = null;
+
   @Override
   D8TestBuilder self() {
     return this;
@@ -65,7 +67,12 @@
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
     ToolHelper.runD8(builder, optionsConsumer);
-    return new D8TestCompileResult(getState(), app.get(), minApiLevel, getOutputMode());
+    return new D8TestCompileResult(
+        getState(), app.get(), minApiLevel, getOutputMode(), getMapContent());
+  }
+
+  private String getMapContent() {
+    return proguardMapOutputBuilder == null ? null : proguardMapOutputBuilder.toString();
   }
 
   public D8TestBuilder setIntermediate(boolean intermediate) {
@@ -106,4 +113,14 @@
     }
     return self();
   }
+
+  // TODO(b/183125319): Make this the default as part of API support in D8.
+  public D8TestBuilder internalEnableMappingOutput() {
+    assert proguardMapOutputBuilder == null;
+    proguardMapOutputBuilder = new StringBuilder();
+    // TODO(b/183125319): Use the API once supported in D8.
+    addOptionsModification(
+        o -> o.proguardMapConsumer = (s, h) -> proguardMapOutputBuilder.append(s));
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index b5fccb8..e36ff2d 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -9,8 +9,12 @@
 import java.util.Set;
 
 public class D8TestCompileResult extends TestCompileResult<D8TestCompileResult, D8TestRunResult> {
-  D8TestCompileResult(TestState state, AndroidApp app, int minApiLevel, OutputMode outputMode) {
+  private final String proguardMap;
+
+  D8TestCompileResult(
+      TestState state, AndroidApp app, int minApiLevel, OutputMode outputMode, String proguardMap) {
     super(state, app, minApiLevel, outputMode);
+    this.proguardMap = proguardMap;
   }
 
   @Override
@@ -38,8 +42,12 @@
     return state.getStderr();
   }
 
+  public String getProguardMap() {
+    return proguardMap;
+  }
+
   @Override
   public D8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
-    return new D8TestRunResult(app, runtime, result);
+    return new D8TestRunResult(app, runtime, result, proguardMap);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/D8TestRunResult.java b/src/test/java/com/android/tools/r8/D8TestRunResult.java
index 4e13e54..d0fe6fb 100644
--- a/src/test/java/com/android/tools/r8/D8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestRunResult.java
@@ -4,17 +4,31 @@
 
 package com.android.tools.r8;
 
+import static org.junit.Assert.assertNotNull;
+
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
 
 public class D8TestRunResult extends SingleTestRunResult<D8TestRunResult> {
 
-  public D8TestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
+  private final String proguardMap;
+
+  public D8TestRunResult(
+      AndroidApp app, TestRuntime runtime, ProcessResult result, String proguardMap) {
     super(app, runtime, result);
+    this.proguardMap = proguardMap;
   }
 
   @Override
   protected D8TestRunResult self() {
     return this;
   }
+
+  @Override
+  protected CodeInspector internalGetCodeInspector() throws IOException {
+    assertNotNull(app);
+    return proguardMap == null ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 028ed02..f35f686 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -57,23 +57,11 @@
   }
 
   @Override
-  public CodeInspector inspector() throws IOException, ExecutionException {
-    // See comment in base class.
-    assertSuccess();
+  protected CodeInspector internalGetCodeInspector() throws IOException {
     assertNotNull(app);
     return new CodeInspector(app, proguardMap);
   }
 
-  @Override
-  public <E extends Throwable> R8TestRunResult inspectFailure(
-      ThrowingConsumer<CodeInspector, E> consumer) throws IOException, ExecutionException, E {
-    assertFailure();
-    assertNotNull(app);
-    CodeInspector codeInspector = new CodeInspector(app, proguardMap);
-    consumer.accept(codeInspector);
-    return self();
-  }
-
   public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
       ThrowingConsumer<StackTrace, E> consumer) throws E {
     consumer.accept(getOriginalStackTrace());
@@ -81,9 +69,8 @@
   }
 
   public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
-      ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer)
-      throws E, IOException, ExecutionException {
-    consumer.accept(getOriginalStackTrace(), new CodeInspector(app, proguardMap));
+      ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer) throws E, IOException {
+    consumer.accept(getOriginalStackTrace(), internalGetCodeInspector());
     return self();
   }
 
@@ -100,7 +87,7 @@
 
   public <E extends Throwable> R8TestRunResult inspectStackTrace(
       ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer) throws E, IOException {
-    consumer.accept(getStackTrace(), new CodeInspector(app, proguardMap));
+    consumer.accept(getStackTrace(), internalGetCodeInspector());
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index d532547..07e0736 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -90,12 +90,16 @@
     return self();
   }
 
+  protected CodeInspector internalGetCodeInspector() throws IOException {
+    assertNotNull(app);
+    return new CodeInspector(app);
+  }
+
   public CodeInspector inspector() throws IOException, ExecutionException {
     // Inspection post run implies success. If inspection of an invalid program is needed it should
     // be done on the compilation result or on the input.
     assertSuccess();
-    assertNotNull(app);
-    return new CodeInspector(app);
+    return internalGetCodeInspector();
   }
 
   @Override
@@ -107,10 +111,9 @@
   }
 
   public <E extends Throwable> RR inspectFailure(ThrowingConsumer<CodeInspector, E> consumer)
-      throws IOException, ExecutionException, E {
+      throws IOException, E {
     assertFailure();
-    assertNotNull(app);
-    CodeInspector inspector = new CodeInspector(app);
+    CodeInspector inspector = internalGetCodeInspector();
     consumer.accept(inspector);
     return self();
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
index 41d1c3f..9e24498 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
@@ -13,14 +13,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
+import java.io.IOException;
 import java.util.Collection;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,10 +54,33 @@
 
   @Test
   public void testReference() throws Exception {
-    testForRuntime(parameters)
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
         .addInnerClasses(getClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .apply(this::checkRunResult)
+        .apply(this::checkNoOutputSynthetics)
+        .inspectStackTrace(
+            stackTrace ->
+                assertThat(
+                    stackTrace,
+                    isSameExceptForFileNameAndLineNumber(
+                        StackTrace.builder()
+                            .addWithoutFileNameAndLineNumber(Main.class, JAVAC_LAMBDA_METHOD)
+                            .addWithoutFileNameAndLineNumber(Main.class, "runIt")
+                            .addWithoutFileNameAndLineNumber(Main.class, "main")
+                            .build())));
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .internalEnableMappingOutput()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkRunResult)
+        .apply(this::checkOneOutputSynthetic)
         .inspectStackTrace(
             stackTrace ->
                 assertThat(
@@ -63,45 +89,14 @@
                         StackTrace.builder()
                             .addWithoutFileNameAndLineNumber(Main.class, JAVAC_LAMBDA_METHOD)
                             // TODO(b/172014416): Support a D8 mapping and prune the synthetic.
-                            .applyIf(
-                                parameters.isDexRuntime(),
-                                b ->
-                                    b.addWithoutFileNameAndLineNumber(
-                                        SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0),
-                                        "run"))
+                            .addWithoutFileNameAndLineNumber(
+                                SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0), "run")
                             .addWithoutFileNameAndLineNumber(Main.class, "runIt")
                             .addWithoutFileNameAndLineNumber(Main.class, "main")
                             .build())));
   }
 
   @Test
-  public void testMappingInformation() throws Exception {
-    assumeTrue("R8/CF does not desugar", parameters.isDexRuntime());
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .addKeepAttributeSourceFile()
-        .addKeepAttributeLineNumberTable()
-        .noTreeShaking()
-        .noMinification()
-        .setMinApi(parameters.getApiLevel())
-        .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
-        .inspectFailure(
-            inspector -> {
-              Collection<ClassReference> inputs =
-                  ImmutableList.of(classFromClass(MyRunner.class), classFromClass(Main.class));
-              for (FoundClassSubject clazz : inspector.allClasses()) {
-                if (inputs.contains(clazz.getFinalReference())) {
-                  assertThat(clazz, not(isCompilerSynthesized()));
-                } else {
-                  assertThat(clazz, isCompilerSynthesized());
-                }
-              }
-              assertEquals(inputs.size() + 1, inspector.allClasses().size());
-            });
-  }
-
-  @Test
   public void testEverythingInlined() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
@@ -110,17 +105,23 @@
         .addKeepAttributeLineNumberTable()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .apply(this::checkRunResult)
+        .inspectFailure(
+            inspector ->
+                assertEquals(parameters.isCfRuntime() ? 2 : 1, inspector.allClasses().size()))
         .inspectStackTrace(
             stackTrace -> {
               int frames = parameters.isCfRuntime() ? 2 : 1;
               checkRawStackTraceFrameCount(stackTrace, frames, "Expected everything to be inlined");
-              checkCurrentlyIncorrectStackTrace(stackTrace, JAVAC_LAMBDA_METHOD);
+              checkCurrentlyIncorrectStackTrace(stackTrace);
             });
   }
 
   @Test
   public void testNothingInlined() throws Exception {
+    assumeTrue(
+        "Skip R8/CF for min-api > 1 (R8/CF does not desugar)",
+        parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
@@ -130,15 +131,46 @@
         .addKeepAttributeLineNumberTable()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .apply(this::checkRunResult)
+        .applyIf(
+            parameters.isCfRuntime(), this::checkNoOutputSynthetics, this::checkOneOutputSynthetic)
         .inspectStackTrace(
             stackTrace -> {
               int frames = parameters.isCfRuntime() ? 3 : 5;
               checkRawStackTraceFrameCount(stackTrace, frames, "Expected nothing to be inlined");
-              checkCurrentlyIncorrectStackTrace(stackTrace, "lambda$main$0");
+              checkCurrentlyIncorrectStackTrace(stackTrace);
             });
   }
 
+  private void checkRunResult(SingleTestRunResult<?> runResult) {
+    runResult.assertFailureWithErrorThatMatches(containsString("Hello World!"));
+  }
+
+  private void checkNoOutputSynthetics(SingleTestRunResult<?> runResult) throws IOException {
+    checkOutputSynthetics(runResult, 0);
+  }
+
+  private void checkOneOutputSynthetic(SingleTestRunResult<?> runResult) throws IOException {
+    checkOutputSynthetics(runResult, 1);
+  }
+
+  private void checkOutputSynthetics(SingleTestRunResult<?> runResult, int expectedSyntheticsCount)
+      throws IOException {
+    runResult.inspectFailure(
+        inspector -> {
+          Collection<ClassReference> inputs =
+              ImmutableList.of(classFromClass(MyRunner.class), classFromClass(Main.class));
+          for (FoundClassSubject clazz : inspector.allClasses()) {
+            if (inputs.contains(clazz.getOriginalReference())) {
+              assertThat(clazz, not(isCompilerSynthesized()));
+            } else {
+              assertThat(clazz, isCompilerSynthesized());
+            }
+          }
+          assertEquals(inputs.size() + expectedSyntheticsCount, inspector.allClasses().size());
+        });
+  }
+
   private void checkRawStackTraceFrameCount(
       StackTrace stackTrace, int expectedFrames, String message) {
     int linesFromTest = 0;
@@ -150,12 +182,12 @@
     assertEquals(message + stackTrace.getOriginalStderr(), expectedFrames, linesFromTest);
   }
 
-  private void checkCurrentlyIncorrectStackTrace(StackTrace stackTrace, String javacLambdaMethod) {
+  private void checkCurrentlyIncorrectStackTrace(StackTrace stackTrace) {
     assertThat(
         stackTrace,
         isSameExceptForFileNameAndLineNumber(
             StackTrace.builder()
-                .addWithoutFileNameAndLineNumber(Main.class, javacLambdaMethod)
+                .addWithoutFileNameAndLineNumber(Main.class, RetraceLambdaTest.JAVAC_LAMBDA_METHOD)
                 .applyIf(
                     parameters.isDexRuntime(),
                     b ->