Compatibility tests for source file attribute.
Bug: 202367773
Bug: 201269335
Change-Id: Icba97d678f05540c08b9b695e50e14fb73c5aa80
diff --git a/src/test/java/com/android/tools/r8/L8TestRunResult.java b/src/test/java/com/android/tools/r8/L8TestRunResult.java
index a9a9a78..8f6bab8 100644
--- a/src/test/java/com/android/tools/r8/L8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/L8TestRunResult.java
@@ -49,6 +49,12 @@
}
@Override
+ public <E extends Throwable> L8TestRunResult inspectFailure(
+ ThrowingConsumer<CodeInspector, E> consumer) {
+ throw new Unimplemented();
+ }
+
+ @Override
public L8TestRunResult disassemble() throws IOException, ExecutionException {
throw new Unimplemented();
}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
index 3c2bbfc..c5cccbe 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
public class ProguardTestRunResult extends SingleTestRunResult<ProguardTestRunResult> {
@@ -34,14 +33,8 @@
return super.getStackTrace().retrace(proguardMap);
}
- public StackTrace getOriginalStackTrace() {
- return super.getStackTrace();
- }
-
@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);
}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 3ef4901..716971d 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.naming.retrace.StackTrace;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ThrowingBiConsumer;
-import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import java.io.IOException;
@@ -52,10 +51,6 @@
return super.getStackTrace().retraceAllowExperimentalMapping(proguardMap);
}
- public StackTrace getOriginalStackTrace() {
- return super.getStackTrace();
- }
-
@Override
protected CodeInspector internalGetCodeInspector() throws IOException {
assertNotNull(app);
@@ -63,12 +58,6 @@
}
public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
- ThrowingConsumer<StackTrace, E> consumer) throws E {
- consumer.accept(getOriginalStackTrace());
- return self();
- }
-
- public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer) throws E, IOException {
consumer.accept(getOriginalStackTrace(), 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 17546ee..726acbd 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -60,6 +60,10 @@
}
public StackTrace getStackTrace() {
+ return getOriginalStackTrace();
+ }
+
+ public StackTrace getOriginalStackTrace() {
if (runtime.isDex()) {
return StackTrace.extractFromArt(getStdErr(), runtime.asDex().getVm());
} else {
@@ -129,6 +133,12 @@
return self();
}
+ public <E extends Throwable> RR inspectOriginalStackTrace(
+ ThrowingConsumer<StackTrace, E> consumer) throws E {
+ consumer.accept(getOriginalStackTrace());
+ return self();
+ }
+
public RR disassemble(PrintStream ps) throws IOException, ExecutionException {
ToolHelper.disassemble(app, ps);
return self();
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 90d4f75..bd5ad83 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -38,6 +38,9 @@
public abstract <E extends Throwable> RR inspect(ThrowingConsumer<CodeInspector, E> consumer)
throws IOException, ExecutionException, E;
+ public abstract <E extends Throwable> RR inspectFailure(
+ ThrowingConsumer<CodeInspector, E> consumer) throws IOException, E;
+
public abstract RR disassemble() throws IOException, ExecutionException;
public <E extends Throwable> RR apply(ThrowingConsumer<RR, E> fn) throws E {
diff --git a/src/test/java/com/android/tools/r8/TestRunResultCollection.java b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
index e166be4..c5421ac 100644
--- a/src/test/java/com/android/tools/r8/TestRunResultCollection.java
+++ b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -57,6 +58,12 @@
return inspectIf(Predicates.alwaysTrue(), consumer);
}
+ @Override
+ public <E extends Throwable> RR inspectFailure(ThrowingConsumer<CodeInspector, E> consumer)
+ throws IOException, E {
+ throw new Unimplemented();
+ }
+
public RR applyIf(Predicate<C> filter, Consumer<TestRunResult<?>> thenConsumer) {
return applyIf(filter, thenConsumer, r -> {});
}
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
new file mode 100644
index 0000000..ac17f3c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileAttributeCompatTest.java
@@ -0,0 +1,197 @@
+// Copyright (c) 2021, 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.naming.sourcefile;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ProguardVersion;
+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.TestShrinkerBuilder;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.function.Supplier;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SourceFileAttributeCompatTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withSystemRuntime().build();
+ }
+
+ public SourceFileAttributeCompatTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private String getOriginalSourceFile() {
+ return new Exception().getStackTrace()[0].getFileName();
+ }
+
+ private void commonSetUp(TestShrinkerBuilder<?, ?, ?, ?, ?> builder) {
+ builder
+ .addProgramClasses(TestClass.class, SemiKept.class, NonKept.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-keep,allowshrinking class " + SemiKept.class.getName() + " { *; }");
+ }
+
+ private void checkSourceFileIsRemoved(SingleTestRunResult<?> result) throws Exception {
+ // TODO(b/202368282): We should likely emit a "default" source file attribute rather than strip.
+ checkSourceFile(result, null, null, null);
+ }
+
+ private void checkSourceFileIsOriginal(SingleTestRunResult<?> result) throws Exception {
+ String originalSourceFile = getOriginalSourceFile();
+ checkSourceFile(result, originalSourceFile, originalSourceFile, originalSourceFile);
+ }
+
+ private void checkSourceFile(
+ SingleTestRunResult<?> result, String keptValue, String semiKeptValue, String nonKeptValue)
+ throws Exception {
+ result.assertFailure();
+ result.inspectOriginalStackTrace(
+ stackTrace -> {
+ StackTraceLine nonKeptLine = stackTrace.get(0);
+ StackTraceLine semiKeptLine = stackTrace.get(1);
+ StackTraceLine keptLine = stackTrace.get(4);
+ assertEquals(getExpectedSourceFile(nonKeptValue), nonKeptLine.fileName);
+ assertEquals(getExpectedSourceFile(semiKeptValue), semiKeptLine.fileName);
+ assertEquals(getExpectedSourceFile(keptValue), keptLine.fileName);
+ });
+ result.inspectFailure(
+ inspector -> {
+ ClassSubject testClass = inspector.clazz(TestClass.class);
+ ClassSubject semiKept = inspector.clazz(SemiKept.class);
+ ClassSubject nonKept = inspector.clazz(NonKept.class);
+ assertEquals(keptValue, getSourceFileString(testClass));
+ assertEquals(semiKeptValue, getSourceFileString(semiKept));
+ assertEquals(nonKeptValue, getSourceFileString(nonKept));
+ });
+ }
+
+ private String getSourceFileString(ClassSubject subject) {
+ DexString sourceFile = subject.getDexProgramClass().getSourceFile();
+ return sourceFile == null ? null : sourceFile.toString();
+ }
+
+ private String getExpectedSourceFile(String expectedSourceFileValue) {
+ return expectedSourceFileValue == null ? "Unknown Source" : expectedSourceFileValue;
+ }
+
+ private <RR extends SingleTestRunResult<RR>> void testJustKeepMain(
+ TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+ // If the source file attribute is not kept then all compilers will strip it throughout.
+ commonSetUp(builder);
+ builder.run(parameters.getRuntime(), TestClass.class).apply(this::checkSourceFileIsRemoved);
+ }
+
+ private <RR extends SingleTestRunResult<RR>> void testDontObfuscate(
+ TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+ // If minification is off then compat compilers retain it, full mode will remove it.
+ commonSetUp(builder);
+ builder
+ .addKeepRules("-dontobfuscate")
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(fullMode, this::checkSourceFileIsRemoved, this::checkSourceFileIsOriginal);
+ }
+
+ private <RR extends SingleTestRunResult<RR>> void testDontOptimize(
+ TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+ // No effect from -dontoptimize
+ commonSetUp(builder);
+ builder
+ .addKeepRules("-dontoptimize")
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkSourceFileIsRemoved);
+ }
+
+ private <RR extends SingleTestRunResult<RR>> void testDontShrink(
+ TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+ // No effect from -dontshrink
+ commonSetUp(builder);
+ builder
+ .addKeepRules("-dontshrink")
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkSourceFileIsRemoved);
+ }
+
+ private <RR extends SingleTestRunResult<RR>> void testKeepSourceFileAttribute(
+ TestShrinkerBuilder<?, ?, ?, RR, ?> builder, boolean fullMode) throws Exception {
+ // If the source file attribute is kept, then PG and compat R8 will preserve it in original
+ // form for every input class. R8 will only preserve it for (soft) pinned classes. Others will
+ // be replaced by 'SourceFile'. The use of 'SourceFile' is to ensure VMs still print lines.
+ // TODO(b/202367773): R8 (non-compat) should rather replace it for all classes like line opt.
+ String originalSourceFile = getOriginalSourceFile();
+ String residualSourceFile = fullMode ? "SourceFile" : originalSourceFile;
+ commonSetUp(builder);
+ builder
+ .addKeepAttributeSourceFile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(
+ result ->
+ checkSourceFile(
+ result, originalSourceFile, originalSourceFile, residualSourceFile));
+ }
+
+ private <RR extends SingleTestRunResult<RR>> void runAllTests(
+ Supplier<TestShrinkerBuilder<?, ?, ?, RR, ?>> builder, boolean fullMode) throws Exception {
+ testJustKeepMain(builder.get(), fullMode);
+ testDontObfuscate(builder.get(), fullMode);
+ testDontOptimize(builder.get(), fullMode);
+ testDontShrink(builder.get(), fullMode);
+ testKeepSourceFileAttribute(builder.get(), fullMode);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runAllTests(() -> testForR8(parameters.getBackend()), true);
+ }
+
+ @Test
+ public void testCompatR8() throws Exception {
+ runAllTests(() -> testForR8Compat(parameters.getBackend()), false);
+ }
+
+ @Test
+ public void testPG() throws Exception {
+ runAllTests(() -> testForProguard(ProguardVersion.V7_0_0).addDontWarn(getClass()), false);
+ }
+
+ static class NonKept {
+ @Override
+ public String toString() {
+ throw new RuntimeException("BOOM!");
+ }
+ }
+
+ static class SemiKept {
+ final Object o;
+
+ public SemiKept(Object o) {
+ this.o = o;
+ }
+
+ @Override
+ public String toString() {
+ return o.toString();
+ }
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ System.out.println(
+ System.nanoTime() > 0
+ ? new SemiKept(System.nanoTime() > 0 ? new NonKept() : null)
+ : null);
+ }
+ }
+}