Always preserve source file information.
Bug: b/214018111
Change-Id: Icd1155686188ebf7fa0d7000742bbcbaeb9315c1
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index d9158fc..8cb3ed0 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -36,7 +36,6 @@
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.synthesis.SyntheticMarker;
import com.android.tools.r8.utils.AsmUtils;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -116,16 +115,6 @@
ClassReader reader = new ClassReader(bytes);
int parsingOptions = SKIP_FRAMES | SKIP_CODE;
-
- // If the source-file and source-debug-extension attributes are not kept we can skip all debug
- // related attributes when parsing the class structure.
- if (application.options.getProguardConfiguration() != null) {
- ProguardKeepAttributes keep =
- application.options.getProguardConfiguration().getKeepAttributes();
- if (!keep.sourceFile && !keep.sourceDebugExtension && !keep.methodParameters) {
- parsingOptions |= SKIP_DEBUG;
- }
- }
if (classKind != ClassKind.PROGRAM) {
parsingOptions |= SKIP_DEBUG;
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 3d2be09..5264b18 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -9,7 +9,6 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
@@ -117,6 +116,14 @@
"classmerging.SuperClass"
);
+ private static void assertAbsentOrIdentity(ClassNameMapper mapper, String className) {
+ // The mapping should be absent or identity.
+ ClassNamingForNameMapper classNaming = mapper.getClassNaming(className);
+ if (classNaming != null) {
+ assertEquals(classNaming.originalName, classNaming.renamedName);
+ }
+ }
+
@Test
public void testClassesHaveNotBeenMerged() throws Throwable {
runR8(DONT_OPTIMIZE, null);
@@ -526,7 +533,7 @@
ClassNameMapper mappingWithClassMerging =
ClassNameMapper.mapperFromString(compileResultWithClassMerging.getProguardMap());
- assertNull(mappingWithClassMerging.getClassNaming("classmerging.ProguardFieldMapTest$A"));
+ assertAbsentOrIdentity(mappingWithClassMerging, "classmerging.ProguardFieldMapTest$A");
ClassNamingForNameMapper mappingsForClassBWithClassMerging =
mappingWithClassMerging.getClassNaming("classmerging.ProguardFieldMapTest$B");
@@ -607,7 +614,7 @@
ClassNameMapper mappingWithClassMerging =
ClassNameMapper.mapperFromString(compileResultWithClassMerging.getProguardMap());
- assertNull(mappingWithClassMerging.getClassNaming("classmerging.ProguardMethodMapTest$A"));
+ assertAbsentOrIdentity(mappingWithClassMerging, "classmerging.ProguardMethodMapTest$A");
ClassNamingForNameMapper mappingsForClassBWithClassMerging =
mappingWithClassMerging.getClassNaming("classmerging.ProguardMethodMapTest$B");
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoKeepSourceFileAttributeTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoKeepSourceFileAttributeTest.java
new file mode 100644
index 0000000..9a68a09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoKeepSourceFileAttributeTest.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2023, 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.debuginfo;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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.StackTraceLine;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NoKeepSourceFileAttributeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+ }
+
+ public NoKeepSourceFileAttributeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public boolean isRuntimeWithPcAsLineNumberSupport() {
+ return parameters.isDexRuntime()
+ && parameters
+ .getRuntime()
+ .maxSupportedApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(NoKeepSourceFileAttributeTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .inspectOriginalStackTrace(
+ stacktrace -> {
+ List<StackTraceLine> stackTraceLines = stacktrace.getStackTraceLines();
+ assertEquals(1, stackTraceLines.size());
+ StackTraceLine stackTraceLine = stackTraceLines.get(0);
+ if (!isRuntimeWithPcAsLineNumberSupport()) {
+ assertEquals("SourceFile", stackTraceLine.fileName);
+ } else {
+ // VMs with native PC support and no debug info print "Unknown Source".
+ // TODO(b/146565491): This will need a check for new VMs once fixed.
+ assertEquals("Unknown Source", stackTraceLine.fileName);
+ }
+ })
+ .inspectStackTrace(
+ stacktrace -> {
+ List<StackTraceLine> stackTraceLines = stacktrace.getStackTraceLines();
+ assertEquals(1, stackTraceLines.size());
+ StackTraceLine stackTraceLine = stackTraceLines.get(0);
+ assertEquals("NoKeepSourceFileAttributeTest.java", stackTraceLine.fileName);
+ });
+ }
+
+ @Test
+ public void testNoOpt() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(NoKeepSourceFileAttributeTest.class)
+ .addKeepMainRule(TestClass.class)
+ // Keeping lines and not obfuscating or optimizing will retain original lines.
+ .addDontObfuscate()
+ .addDontOptimize()
+ .addKeepAttributeLineNumberTable()
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .inspectOriginalStackTrace(
+ stacktrace -> {
+ List<StackTraceLine> stackTraceLines = stacktrace.getStackTraceLines();
+ assertEquals(1, stackTraceLines.size());
+ StackTraceLine stackTraceLine = stackTraceLines.get(0);
+ assertEquals("SourceFile", stackTraceLine.fileName);
+ // The non-optimizing/obfuscating build will retain the original line info.
+ assertTrue(stackTraceLine.lineNumber > 50);
+ })
+ .inspectStackTrace(
+ stacktrace -> {
+ List<StackTraceLine> stackTraceLines = stacktrace.getStackTraceLines();
+ assertEquals(1, stackTraceLines.size());
+ StackTraceLine stackTraceLine = stackTraceLines.get(0);
+ assertEquals("NoKeepSourceFileAttributeTest.java", stackTraceLine.fileName);
+ assertTrue(stackTraceLine.lineNumber > 50);
+ });
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ throw new RuntimeException("My Exception!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java b/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java
index 21ece10..6bffb78 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.naming;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DiagnosticsHandler;
@@ -51,11 +51,20 @@
assertThat(mapping, containsString("# {\"id\":\"com.android.tools.r8.mapping\",\"version\":"));
assertThat(mapping, containsString("# pg_map_id: "));
assertThat(mapping, containsString("# pg_map_hash: SHA-256 "));
- // Check the mapping is the identity, e.g., only comments are defined.
- // Note, this could change if the mapping is ever changed to be complete, in which case the
- // mapping will have actual identity mappings.
+ // Check the mapping is the identity, e.g., only comments and identity entries are defined.
for (String line : StringUtils.splitLines(mapping)) {
- assertThat(line, startsWith("#"));
+ if (line.startsWith("#")) {
+ continue;
+ }
+ String[] parts = line.split(" -> ");
+ if (parts.length == 2 && line.endsWith(":")) {
+ String left = parts[0];
+ String right = parts[1];
+ if (left.equals(right.substring(0, right.length() - 1))) {
+ continue;
+ }
+ }
+ fail("Expected comment or identity, got: " + line);
}
}