Merge "Support for keepattributes LineNumberTable and LocalVariableTable."
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 400e5ba..0f0b5e5 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import static org.objectweb.asm.ClassReader.SKIP_CODE;
+import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
import static org.objectweb.asm.Opcodes.ASM6;
@@ -27,6 +28,7 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.FieldSignatureEquivalence;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -93,9 +95,18 @@
input.reset();
ClassReader reader = new ClassReader(input);
+
+ 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.
+ ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
+ if (!keep.sourceFile && !keep.sourceDebugExtension) {
+ parsingOptions |= SKIP_DEBUG;
+ }
reader.accept(
new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
- SKIP_FRAMES | SKIP_CODE);
+ parsingOptions);
}
private static int cleanAccessFlags(int access) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index b978408..0668d05 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.io.PrintWriter;
@@ -163,7 +164,7 @@
InternalOptions options,
ValueNumberGenerator generator,
Position callerPosition) {
- if (!options.debug || options.testing.removeLocalsTable) {
+ if (!options.debug || !options.proguardConfiguration.getKeepAttributes().localVariableTable) {
node.localVariables.clear();
}
JarSourceCode source =
@@ -241,9 +242,16 @@
}
private void parseCode(ReparseContext context, boolean useJsrInliner) {
+ // If the keep attributes do not specify keeping LocalVariableTable, LocalVariableTypeTable or
+ // LineNumberTable, then we can skip parsing all the debug related attributes during code read.
+ int parsingOptions = ClassReader.SKIP_FRAMES;
+ ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
+ if (!keep.localVariableTable && !keep.localVariableTypeTable && !keep.lineNumberTable) {
+ parsingOptions |= ClassReader.SKIP_DEBUG;
+ }
SecondVisitor classVisitor = new SecondVisitor(createCodeLocator(context), useJsrInliner);
try {
- new ClassReader(context.classCache).accept(classVisitor, ClassReader.SKIP_FRAMES);
+ new ClassReader(context.classCache).accept(classVisitor, parsingOptions);
} catch (Exception exception) {
throw new CompilationError(
"Unable to parse method `" + method.toSourceString() + "`", exception);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index fe31b14..49f99dc 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -16,6 +16,9 @@
public static final String ENCLOSING_METHOD = "EnclosingMethod";
public static final String SIGNATURE = "Signature";
public static final String EXCEPTIONS = "Exceptions";
+ public static final String LINE_NUMBER_TABLE = "LineNumberTable";
+ public static final String LOCAL_VARIABLE_TABLE = "LocalVariableTable";
+ public static final String LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable";
public static final String SOURCE_DEBUG_EXTENSION = "SourceDebugExtension";
public static final String RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations";
public static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations";
@@ -36,6 +39,9 @@
public boolean enclosingMethod = false;
public boolean signature = false;
public boolean exceptions = false;
+ public boolean lineNumberTable = false;
+ public boolean localVariableTable = false;
+ public boolean localVariableTypeTable = false;
public boolean sourceDebugExtension = false;
public boolean runtimeVisibleAnnotations = false;
public boolean runtimeInvisibleAnnotations = false;
@@ -108,6 +114,9 @@
innerClasses = update(innerClasses, INNER_CLASSES, patterns);
enclosingMethod = update(enclosingMethod, ENCLOSING_METHOD, patterns);
signature = update(signature, SIGNATURE, patterns);
+ lineNumberTable = update(lineNumberTable, LINE_NUMBER_TABLE, patterns);
+ localVariableTable = update(localVariableTable, LOCAL_VARIABLE_TABLE, patterns);
+ localVariableTypeTable = update(localVariableTypeTable, LOCAL_VARIABLE_TYPE_TABLE, patterns);
exceptions = update(exceptions, EXCEPTIONS, patterns);
sourceDebugExtension = update(sourceDebugExtension, SOURCE_DEBUG_EXTENSION, patterns);
runtimeVisibleAnnotations = update(runtimeVisibleAnnotations, RUNTIME_VISIBLE_ANNOTATIONS,
@@ -146,6 +155,18 @@
throw new CompilationError("Attribute Signature requires InnerClasses attribute. Check "
+ "-keepattributes directive.");
}
+ if (forceProguardCompatibility && localVariableTable && !lineNumberTable) {
+ // If locals are kept, assume line numbers should be kept too.
+ lineNumberTable = true;
+ compatibility.addKeepAttributePatterns(
+ ImmutableList.of(ProguardKeepAttributes.LINE_NUMBER_TABLE));
+ }
+ if (localVariableTable && !lineNumberTable) {
+ throw new CompilationError(
+ "Attribute " + LOCAL_VARIABLE_TABLE
+ + " requires " + LINE_NUMBER_TABLE
+ + ". Check -keepattributes directive.");
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index e0e4227..93b379d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -462,7 +462,6 @@
public boolean nondeterministicCycleElimination = false;
public Set<Inliner.Reason> validInliningReasons = null;
public boolean suppressExperimentalCfBackendWarning = false;
- public boolean removeLocalsTable = false;
}
public boolean canUseInvokePolymorphicOnVarHandle() {
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 97be613..5e70808 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -2,6 +2,9 @@
# 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.
+# Keep line numbers to ensure method mappings in the map file.
+-keepattributes LineNumberTable
+
# Keep the application entry point. Get rid of everything that is not
# reachable from there.
-keep public class classmerging.Test {
@@ -57,6 +60,3 @@
}
-printmapping
-
-# TODO(herhut): Consider supporting merging of inner-class attributes.
-# -keepattributes *
\ No newline at end of file
diff --git a/src/test/examples/shaking1/keep-rules.txt b/src/test/examples/shaking1/keep-rules.txt
index 66cf1c6..82786d5 100644
--- a/src/test/examples/shaking1/keep-rules.txt
+++ b/src/test/examples/shaking1/keep-rules.txt
@@ -2,6 +2,8 @@
# 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.
+-keepattributes LineNumberTable
+
# Keep the application entry point. Get rid of everything that is not
# reachable from there.
-keep public class shaking1.Shaking {
diff --git a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
index 308e8d7..e0b953a 100644
--- a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
@@ -27,7 +27,7 @@
DebugTestConfig d8NoLocals = new D8DebugTestConfig().compileAndAdd(
temp,
Collections.singletonList(ToolHelper.getClassFileForTestClass(CLASS)),
- options -> options.testing.removeLocalsTable = true);
+ options -> options.proguardConfiguration.getKeepAttributes().localVariableTable = false);
new DebugStreamComparator()
.add("CF", streamDebugTest(cf, NAME, NO_FILTER))
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index caa697b..e65ebca 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
@@ -429,10 +430,11 @@
}
private String getProguardConfig(String main) {
- return keepMainProguardConfiguration(main)
- + "\n"
- + "-dontobfuscate\n"
- + "-allowaccessmodification";
+ return StringUtils.joinLines(
+ keepMainProguardConfiguration(main),
+ "-dontobfuscate",
+ "-allowaccessmodification",
+ "-keepattributes LineNumberTable");
}
private void configure(InternalOptions options) {
diff --git a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
index 9ccc94b..5127a59 100644
--- a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
@@ -112,6 +112,7 @@
+ " <methods>;"
+ "}",
"-printmapping",
+ "-keepattributes LineNumberTable",
reflectionRules),
Origin.unknown())
.setOutput(out, outputMode(backend));
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
new file mode 100644
index 0000000..275d2e9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2018, 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.shaking;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.forceproguardcompatibility.keepattributes.TestKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class KeepAttributesTest extends TestBase {
+
+ public static final Class CLASS = TestKeepAttributes.class;
+
+ @Test
+ public void discardAllAttributes()
+ throws CompilationFailedException, IOException, ExecutionException {
+ List<String> keepRules = ImmutableList.of(
+ "-keep class ** { *; }"
+ );
+ CodeInspector inspector = compile(keepRules);
+ DebugInfoInspector debugInfo = debugInfoForMain(inspector);
+ checkLineNumbers(false, debugInfo);
+ checkLocals(false, debugInfo);
+ }
+
+ @Test
+ public void keepLineNumberTable()
+ throws CompilationFailedException, IOException, ExecutionException {
+ List<String> keepRules = ImmutableList.of(
+ "-keep class ** { *; }",
+ "-keepattributes " + ProguardKeepAttributes.LINE_NUMBER_TABLE
+ );
+ CodeInspector inspector = compile(keepRules);
+ DebugInfoInspector debugInfo = debugInfoForMain(inspector);
+ checkLineNumbers(true, debugInfo);
+ checkLocals(false, debugInfo);
+ }
+
+ @Test
+ public void keepLineNumberTableAndLocalVariableTable()
+ throws CompilationFailedException, IOException, ExecutionException {
+ List<String> keepRules = ImmutableList.of(
+ "-keep class ** { *; }",
+ "-keepattributes "
+ + ProguardKeepAttributes.LINE_NUMBER_TABLE
+ + ", "
+ + ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
+ );
+ CodeInspector inspector = compile(keepRules);
+ DebugInfoInspector debugInfo = debugInfoForMain(inspector);
+ checkLineNumbers(true, debugInfo);
+ checkLocals(true, debugInfo);
+ }
+
+ @Test
+ public void keepLocalVariableTable() throws IOException, ExecutionException {
+ List<String> keepRules = ImmutableList.of(
+ "-keep class ** { *; }",
+ "-keepattributes " + ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
+ );
+ // Compiling with a keep rule for locals but no line results in an error in R8.
+ try {
+ compile(keepRules);
+ } catch (CompilationFailedException e) {
+ assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LOCAL_VARIABLE_TABLE));
+ assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LINE_NUMBER_TABLE));
+ return;
+ }
+ fail("Expected error");
+ }
+
+ private CodeInspector compile(List<String> keepRules)
+ throws CompilationFailedException, IOException, ExecutionException {
+ Path dexOut = temp.getRoot().toPath().resolve("dex.zip");
+ R8.run(R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(CLASS))
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addProguardConfiguration(keepRules, Origin.unknown())
+ .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(dexOut))
+ .build());
+ ToolHelper.runArtRaw(dexOut.toString(), CLASS.getCanonicalName());
+ return new CodeInspector(dexOut);
+ }
+
+ private DebugInfoInspector debugInfoForMain(CodeInspector inspector) {
+ return new DebugInfoInspector(
+ inspector,
+ CLASS.getCanonicalName(),
+ new MethodSignature("main", "void", Collections.singleton("java.lang.String[]")));
+ }
+
+ private void checkLineNumbers(boolean expected, DebugInfoInspector debugInfo) {
+ assertEquals("Expected " + (expected ? "line entries" : "no line entries"),
+ expected, debugInfo.getEntries().stream().anyMatch(e -> e.lineEntry));
+ }
+
+ private void checkLocals(boolean expected, DebugInfoInspector debugInfo) {
+ assertEquals("Expected " + (expected ? "locals" : "no locals"),
+ expected, debugInfo.getEntries().stream().anyMatch(e -> !e.locals.isEmpty()));
+ }
+}