Change non-kept SourceFile from empty string to SourceFile
This change will re-use the already existing string SourceFile and not
add a new empty string. For DEX this possibly saves a single empty
string but in CF this will save an empty-string per class file.
Bug: 129362602
Bug: 147571342
Change-Id: I71809cb8c14654e33551559d2cf562bbd745b01b
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 8f4330c..cfc3878 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -29,16 +29,21 @@
public void run() {
ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
String renameSourceFile = proguardConfiguration.getRenameSourceFileAttribute();
+ boolean hasRenameSourceFileAttribute = renameSourceFile != null;
// Return early if a user wants to keep the current source file attribute as-is.
- if (renameSourceFile == null && proguardConfiguration.getKeepAttributes().sourceFile) {
+ if (!hasRenameSourceFileAttribute
+ && proguardConfiguration.getKeepAttributes().sourceFile
+ && appView.options().forceProguardCompatibility) {
return;
}
- // Now, the user wants either to remove source file attribute or to rename it.
- DexString dexRenameSourceFile =
- renameSourceFile == null
- ? appView.dexItemFactory().createString("")
- : appView.dexItemFactory().createString(renameSourceFile);
+ // Now, the user wants either to remove source file attribute or to rename it for non-kept
+ // classes.
+ DexString dexRenameSourceFile = getSourceFileRenaming(proguardConfiguration);
for (DexClass clazz : appView.appInfo().classes()) {
+ if (!hasRenameSourceFileAttribute
+ && appView.rootSet().mayNotBeMinified(clazz.type, appView)) {
+ continue;
+ }
clazz.sourceFile = dexRenameSourceFile;
clazz.forEachMethod(encodedMethod -> {
// Abstract methods do not have code_item.
@@ -66,4 +71,23 @@
});
}
}
+
+ private DexString getSourceFileRenaming(ProguardConfiguration proguardConfiguration) {
+ // If we should not be keeping the source file, null it out.
+ if (!proguardConfiguration.getKeepAttributes().sourceFile) {
+ return null;
+ }
+
+ String renamedSourceFileAttribute = proguardConfiguration.getRenameSourceFileAttribute();
+ if (renamedSourceFileAttribute != null) {
+ return appView.dexItemFactory().createString(renamedSourceFileAttribute);
+ }
+
+ // Otherwise, take the smallest size depending on platform. We cannot use NULL since the jvm
+ // and art will write at foo.bar.baz(Unknown Source) without a line-number. Newer version of ART
+ // will report the DEX PC.
+ return appView
+ .dexItemFactory()
+ .createString(appView.options().isGeneratingClassFiles() ? "SourceFile" : "");
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 2ec28b8..9fe91d1 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -59,6 +59,16 @@
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());
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 46bf0a1..bf3bb9d 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -142,6 +142,15 @@
return self();
}
+ public <E extends Throwable> RR inspectFailure(ThrowingConsumer<CodeInspector, E> consumer)
+ throws IOException, ExecutionException, E {
+ assertFailure();
+ assertNotNull(app);
+ CodeInspector inspector = new CodeInspector(app);
+ consumer.accept(inspector);
+ return self();
+ }
+
public <E extends Throwable> RR inspectStackTrace(ThrowingConsumer<StackTrace, E> consumer)
throws E {
consumer.accept(getStackTrace());
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
new file mode 100644
index 0000000..324df58
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2020, 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.containsLinePositions;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.testclasses.ClassToBeMinified;
+import com.android.tools.r8.naming.testclasses.Main;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RenameSourceFileRetraceTest extends TestBase {
+
+ private static final String FILENAME_RENAME = "FOO";
+ private static final String FILENAME_MAIN = "Main.java";
+ private static final String FILENAME_CLASS_TO_BE_MINIFIED = "ClassToBeMinified.java";
+
+ private final TestParameters parameters;
+ private final boolean isCompat;
+ private final boolean keepSourceFile;
+
+ @Parameters(name = "{0}, is compat: {1}, keep source file attribute: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
+ }
+
+ public RenameSourceFileRetraceTest(
+ TestParameters parameters, Boolean isCompat, Boolean keepSourceFile) {
+ this.parameters = parameters;
+ this.isCompat = isCompat;
+ this.keepSourceFile = keepSourceFile;
+ }
+
+ @Test
+ public void testR8()
+ throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+ R8TestBuilder<? extends R8TestBuilder<?>> r8TestBuilder =
+ isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend());
+ if (keepSourceFile) {
+ r8TestBuilder.addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
+ }
+ String minifiedFileName =
+ (keepSourceFile && isCompat)
+ ? FILENAME_CLASS_TO_BE_MINIFIED
+ : (parameters.getBackend() == Backend.CF ? "SourceFile" : "");
+ r8TestBuilder
+ .addProgramClasses(ClassToBeMinified.class, Main.class)
+ .addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("ClassToBeMinified.foo()"))
+ .inspectFailure(inspector -> inspectOutput(inspector, minifiedFileName, FILENAME_MAIN))
+ .inspectStackTrace(stackTrace -> inspectStackTrace(stackTrace, FILENAME_MAIN));
+ }
+
+ @Test
+ public void testRenameSourceFileR8()
+ throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+ R8TestBuilder<? extends R8TestBuilder<?>> r8TestBuilder =
+ isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend());
+ if (keepSourceFile) {
+ r8TestBuilder.addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
+ }
+ r8TestBuilder
+ .addProgramClasses(ClassToBeMinified.class, Main.class)
+ .addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE)
+ .addKeepRules("-renamesourcefileattribute " + FILENAME_RENAME)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString("ClassToBeMinified.foo()"))
+ .inspectFailure(inspector -> inspectOutput(inspector, FILENAME_RENAME, FILENAME_RENAME))
+ .inspectStackTrace(stackTrace -> inspectStackTrace(stackTrace, FILENAME_RENAME));
+ }
+
+ private void inspectOutput(
+ CodeInspector inspector, String classToBeMinifiedFilename, String mainClassFilename) {
+ ClassSubject mainSubject = inspector.clazz(Main.class);
+ assertThat(mainSubject, isPresent());
+ DexClass mainClazz = mainSubject.getDexClass();
+ ClassSubject classToBeMinifiedSubject = inspector.clazz(ClassToBeMinified.class);
+ assertThat(classToBeMinifiedSubject, isPresent());
+ DexClass minifiedClazz = classToBeMinifiedSubject.getDexClass();
+ if (keepSourceFile) {
+ assertEquals(mainClassFilename, mainClazz.sourceFile.toString());
+ assertEquals(classToBeMinifiedFilename, minifiedClazz.sourceFile.toString());
+ } else {
+ assertNull(mainClazz.sourceFile);
+ assertNull(minifiedClazz.sourceFile);
+ }
+ }
+
+ private void inspectStackTrace(StackTrace stackTrace, String mainFileName)
+ throws NoSuchMethodException {
+ if (!keepSourceFile) {
+ return;
+ }
+ assertEquals(2, stackTrace.getStackTraceLines().size());
+ MethodReference classToBeMinifiedFoo =
+ Reference.methodFromMethod(ClassToBeMinified.class.getDeclaredMethod("foo"));
+ MethodReference mainMain =
+ Reference.methodFromMethod(Main.class.getDeclaredMethod("main", String[].class));
+ LinePosition expectedStack =
+ LinePosition.stack(
+ LinePosition.create(classToBeMinifiedFoo, 1, 13, FILENAME_CLASS_TO_BE_MINIFIED),
+ LinePosition.create(mainMain, 1, 10, mainFileName));
+ assertThat(stackTrace, containsLinePositions(expectedStack));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/ClassToBeMinified.java b/src/test/java/com/android/tools/r8/naming/testclasses/ClassToBeMinified.java
new file mode 100644
index 0000000..da6f9c0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/testclasses/ClassToBeMinified.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.testclasses;
+
+import com.android.tools.r8.NeverInline;
+
+public class ClassToBeMinified {
+
+ @NeverInline
+ public static void foo() {
+ throw new RuntimeException("ClassToBeMinified.foo()");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/Main.java b/src/test/java/com/android/tools/r8/naming/testclasses/Main.java
new file mode 100644
index 0000000..1e37d4c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/testclasses/Main.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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.testclasses;
+
+public class Main {
+
+ public static void main(String[] args) {
+ ClassToBeMinified.foo();
+ }
+}