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 ->