Overwrite SourceFile if -renamesourcefileattribute is set.
SourceFile appears at class_def_item and debug_info_item as indices.
When -renamesourcefileattribute is set, SourceFileRewriter will
1) visit all program classes and overwrite sourceFile; and
2) visit all method definitions in a class and set sourceFile in
debug_info_item directly.
Bug: 36799675
Change-Id: I6b1e3afe0c41e51cdc8f32564209d45b54b62e94
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3d0cee7..45af2b3 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.optimize.SwitchMapCollector;
import com.android.tools.r8.naming.Minifier;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.naming.SourceFileRewriter;
import com.android.tools.r8.optimize.BridgeMethodAnalysis;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.VisibilityBridgeRemover;
@@ -272,6 +273,11 @@
application = optimize(application, appInfo, graphLense, executorService);
+ // Overwrite SourceFile if specified. This step should be done after IR conversion.
+ timing.begin("Rename SourceFile");
+ new SourceFileRewriter(appInfo, options).run();
+ timing.end();
+
if (!options.mainDexKeepRules.isEmpty()) {
appInfo = new AppInfoWithSubtyping(application);
Enqueuer enqueuer = new Enqueuer(appInfo);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index e350c33..5de6538 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -22,7 +22,7 @@
public final DexAccessFlags accessFlags;
public DexType superType;
public DexTypeList interfaces;
- public final DexString sourceFile;
+ public DexString sourceFile;
protected DexEncodedField[] staticFields;
protected DexEncodedField[] instanceFields;
protected DexEncodedMethod[] directMethods;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index db99359..9381c90 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -294,12 +294,20 @@
public static class SetFile extends DexDebugEvent {
- final DexString fileName;
+ DexString fileName;
SetFile(DexString fileName) {
this.fileName = fileName;
}
+ public DexString getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(DexString fileName) {
+ this.fileName = fileName;
+ }
+
public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
writer.putByte(Constants.DBG_SET_FILE);
writer.putString(fileName);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index d2c9b3b..5236d12 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -12,7 +12,7 @@
public final int startLine;
public final DexString[] parameters;
- public final DexDebugEvent[] events;
+ public DexDebugEvent[] events;
public DexDebugInfo(int startLine, DexString[] parameters, DexDebugEvent[] events) {
assert startLine >= 0;
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
new file mode 100644
index 0000000..38f8cb3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2017, 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 com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Arrays;
+
+/**
+ * Visit program {@link DexClass}es and replace their sourceFile with the given string.
+ *
+ * If -keepattribute SourceFile is not set, we rather remove that attribute.
+ */
+public class SourceFileRewriter {
+
+ private final AppInfo appInfo;
+ private final InternalOptions options;
+
+ public SourceFileRewriter(AppInfo appInfo, InternalOptions options) {
+ this.appInfo = appInfo;
+ this.options = options;
+ }
+
+ public void run() {
+ String renameSourceFile = options.proguardConfiguration.getRenameSourceFileAttribute();
+ // Return early if a user wants to keep the current source file attribute as-is.
+ if (renameSourceFile == null && options.keepAttributes.sourceFile) {
+ return;
+ }
+ // Now, the user wants either to remove source file attribute or to rename it.
+ DexString dexRenameSourceFile =
+ renameSourceFile == null
+ ? appInfo.dexItemFactory.createString("")
+ : appInfo.dexItemFactory.createString(renameSourceFile);
+ for (DexClass clazz : appInfo.classes()) {
+ clazz.sourceFile = dexRenameSourceFile;
+ clazz.forEachMethod(encodedMethod -> {
+ // Abstract methods do not have code_item.
+ if (encodedMethod.accessFlags.isAbstract()) {
+ return;
+ }
+ Code code = encodedMethod.getCode();
+ // Other kinds of {@link Code} do not have debug_info_item.
+ if (code == null || !code.isDexCode()) {
+ return;
+ }
+ if (code.asDexCode().getDebugInfo() == null) {
+ return;
+ }
+ // Thanks to a single global source file, we can safely remove DBG_SET_FILE entirely.
+ DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
+ dexDebugInfo.events =
+ Arrays.stream(dexDebugInfo.events)
+ .filter(dexDebugEvent -> !(dexDebugEvent instanceof SetFile))
+ .toArray(DexDebugEvent[]::new);
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 47e809c..3eaa2be 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -26,6 +26,7 @@
public static final String JAR_EXTENSION = ".jar";
public static final String ZIP_EXTENSION = ".zip";
public static final String DEFAULT_DEX_FILENAME = "classes.dex";
+ public static final String JAVA_EXTENSION = ".java";
public static boolean isDexFile(Path path) {
String name = path.getFileName().toString().toLowerCase();
diff --git a/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java b/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java
index 3c40b74..6f6b7d0 100644
--- a/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java
@@ -19,7 +19,7 @@
public static void setUp() throws Exception {
// Force inversion of all conditionals to reliably construct a regression test for incorrect
// line information when reording blocks.
- setUp(options -> options.testing.invertConditionals = true);
+ setUp(options -> options.testing.invertConditionals = true, null);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 1cd5c71..1f9101c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -6,10 +6,13 @@
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.D8Command;
+import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
@@ -29,6 +32,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -104,6 +108,7 @@
public static TemporaryFolder temp = new TemporaryFolder();
private static Path jdwpDexD8 = null;
private static Path debuggeeDexD8 = null;
+ private static Path debuggeeDexR8 = null;
private static Path debuggeeJava8DexD8 = null;
private static Path debuggeeKotlinDexD8 = null;
@@ -112,25 +117,30 @@
@BeforeClass
public static void setUp() throws Exception {
- setUp(null);
+ setUp(null, null);
}
- protected static void setUp(Consumer<InternalOptions> optionsConsumer) throws Exception {
+ protected static void setUp(
+ Consumer<InternalOptions> optionsConsumer,
+ Consumer<ProguardConfiguration.Builder> pgConsumer)
+ throws Exception {
// Convert jar to dex with d8 with debug info
jdwpDexD8 = compileToDex(null, JDWP_JAR);
+ // TODO(zerny): supply a set of compilers to run with.
debuggeeDexD8 = compileToDex(optionsConsumer, DEBUGGEE_JAR);
+ debuggeeDexR8 = compileToDexViaR8(optionsConsumer, pgConsumer, DEBUGGEE_JAR);
debuggeeJava8DexD8 = compileToDex(options -> {
- // Enable desugaring for preN runtimes
- options.interfaceMethodDesugaring = OffOrAuto.Auto;
- if (optionsConsumer != null) {
- optionsConsumer.accept(options);
- }
- }, DEBUGGEE_JAVA8_JAR);
+ // Enable desugaring for preN runtimes
+ options.interfaceMethodDesugaring = OffOrAuto.Auto;
+ if (optionsConsumer != null) {
+ optionsConsumer.accept(options);
+ }
+ }, DEBUGGEE_JAVA8_JAR);
debuggeeKotlinDexD8 = compileToDex(optionsConsumer, DEBUGGEE_KOTLIN_JAR);
}
- protected static Path compileToDex(Consumer<InternalOptions> optionsConsumer,
- Path jarToCompile) throws IOException, CompilationException {
+ static Path compileToDex(Consumer<InternalOptions> optionsConsumer, Path jarToCompile)
+ throws IOException, CompilationException {
int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
assert jarToCompile.toFile().exists();
Path dexOutputDir = temp.newFolder().toPath();
@@ -144,7 +154,31 @@
.build(),
optionsConsumer);
return dexOutputDir.resolve("classes.dex");
+ }
+ static Path compileToDexViaR8(
+ Consumer<InternalOptions> optionsConsumer,
+ Consumer<ProguardConfiguration.Builder> pgConsumer,
+ Path jarToCompile)
+ throws IOException, CompilationException, ExecutionException, ProguardRuleParserException {
+ int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+ assert jarToCompile.toFile().exists();
+ Path dexOutputDir = temp.newFolder().toPath();
+ R8Command.Builder builder =
+ R8Command.builder()
+ .addProgramFiles(jarToCompile)
+ .setOutputPath(dexOutputDir)
+ .setMinApiLevel(minSdk)
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(minSdk)));
+ if (pgConsumer != null) {
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"))
+ .addProguardConfigurationConsumer(pgConsumer);
+ }
+ ToolHelper.runR8(builder.build(), optionsConsumer);
+ return dexOutputDir.resolve("classes.dex");
}
protected final boolean supportsDefaultMethod() {
@@ -200,6 +234,16 @@
runDebugTest(Collections.emptyList(), LanguageFeatures.KOTLIN, debuggeeClass, commands);
}
+ protected final void runDebugTestR8(String debuggeeClass, JUnit3Wrapper.Command... commands)
+ throws Throwable {
+ runDebugTestR8(debuggeeClass, Arrays.asList(commands));
+ }
+
+ protected final void runDebugTestR8(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
+ throws Throwable {
+ runDebugTest(Collections.emptyList(), LanguageFeatures.R8, debuggeeClass, commands);
+ }
+
protected enum LanguageFeatures {
JAVA_7(DEBUGGEE_JAR) {
@Override
@@ -218,6 +262,12 @@
public Path getDexPath() {
return debuggeeKotlinDexD8;
}
+ },
+ R8(DEBUGGEE_JAR) {
+ @Override
+ public Path getDexPath() {
+ return debuggeeDexR8;
+ }
};
private final Path jarPath;
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
new file mode 100644
index 0000000..d0ea94c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2017, 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 com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.shaking.ProguardKeepRule;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests -renamesourcefileattribute.
+ */
+public class RenameSourceFileDebugTest extends DebugTestBase {
+
+ private static final String TEST_FILE = "TestFile.java";
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ setUp(null, pg -> {
+ pg.addRule(ProguardKeepRule.defaultKeepAllRule());
+ pg.setRenameSourceFileAttribute(TEST_FILE);
+ });
+ }
+
+ /**
+ * replica of {@link ClassInitializationTest#testBreakpointInEmptyClassInitializer}
+ */
+ @Test
+ public void testBreakpointInEmptyClassInitializer() throws Throwable {
+ final String CLASS = "ClassInitializerEmpty";
+ runDebugTestR8(CLASS,
+ breakpoint(CLASS, "<clinit>"),
+ run(),
+ checkLine(TEST_FILE, 8),
+ run());
+ }
+
+ /**
+ * replica of {@link LocalsTest#testNoLocal}, except for checking overwritten class file.
+ */
+ @Test
+ public void testNoLocal() throws Throwable {
+ final String className = "Locals";
+ final String methodName = "noLocals";
+ runDebugTestR8(className,
+ breakpoint(className, methodName),
+ run(),
+ checkMethod(className, methodName),
+ checkLine(TEST_FILE, 8),
+ checkNoLocal(),
+ stepOver(),
+ checkMethod(className, methodName),
+ checkLine(TEST_FILE, 9),
+ checkNoLocal(),
+ run());
+ }
+
+ /**
+ * replica of {@link MultipleReturnsTest#testMultipleReturns}
+ */
+ @Test
+ public void testMultipleReturns() throws Throwable {
+ runDebugTestR8("MultipleReturns",
+ breakpoint("MultipleReturns", "multipleReturns"),
+ run(),
+ stepOver(),
+ checkLine(TEST_FILE, 16), // this should be the 1st return statement
+ run(),
+ stepOver(),
+ checkLine(TEST_FILE, 18), // this should be the 2nd return statement
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
new file mode 100644
index 0000000..4506310
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
@@ -0,0 +1,123 @@
+package com.android.tools.r8.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests -renamesourcefileattribute.
+ */
+@RunWith(Parameterized.class)
+public class RenameSourceFileSmaliTest extends SmaliTestBase {
+
+ private static final String TEST_FILE = "TestFile.java";
+
+ private static final List<String> DEFAULT_PG_CONFIGS =
+ ImmutableList.of(
+ "-keep class *** { *; }",
+ "-dontoptimize",
+ "-keepattributes SourceFile,LineNumberTable");
+
+ private void configure(ProguardConfiguration.Builder pg) {
+ if (renaming) {
+ pg.setRenameSourceFileAttribute(TEST_FILE);
+ }
+ }
+
+ @Parameter
+ public boolean renaming;
+
+ @Parameters(name="renaming:{0}")
+ public static Object[] parameters() {
+ return new Object[] {true, false};
+ }
+
+ /**
+ * replica of {@link RunArtSmokeTest#test}
+ */
+ @Test
+ public void artSmokeTest() throws Exception {
+ // Build simple "Hello, world!" application.
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+ String originalSourceFile = DEFAULT_CLASS_NAME + FileUtils.JAVA_EXTENSION;
+ builder.setSourceFile(originalSourceFile);
+ MethodSignature mainSignature = builder.addMainMethod(
+ 2,
+ ".line 1",
+ " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ " const-string v1, \"Hello, world!\"",
+ ".source \"PrintStream.java\"",
+ ".line 337",
+ " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ ".source \"" + originalSourceFile + "\"",
+ ".line 2",
+ " return-void"
+ );
+ Path processedApp = runR8(builder, DEFAULT_PG_CONFIGS, this::configure, null);
+
+ DexClass mainClass = getClass(processedApp, DEFAULT_CLASS_NAME);
+ verifySourceFileInCodeItem(mainClass, originalSourceFile, TEST_FILE);
+
+ DexEncodedMethod mainMethod = getMethod(processedApp, mainSignature);
+ assertNotNull(mainMethod);
+
+ DexCode code = mainMethod.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof SgetObject);
+ assertTrue(code.instructions[1] instanceof ConstString);
+ assertTrue(code.instructions[2] instanceof InvokeVirtual);
+ assertTrue(code.instructions[3] instanceof ReturnVoid);
+
+ // Run the generated code in Art.
+ String result = runArt(processedApp, DEFAULT_MAIN_CLASS_NAME);
+ assertEquals(StringUtils.lines("Hello, world!"), result);
+
+ verifySourceFileInDebugInfo(code);
+ }
+
+ private void verifySourceFileInCodeItem(DexClass clazz, String original, String rename) {
+ String processedSourceFile = clazz.sourceFile.toString();
+ if (renaming) {
+ assertEquals(rename, processedSourceFile);
+ } else {
+ assertEquals(original, processedSourceFile);
+ }
+ }
+
+ private void verifySourceFileInDebugInfo(DexCode code) {
+ assertNotNull(code.getDebugInfo());
+ assertNotEquals(0, code.getDebugInfo().events.length);
+ long setFileCount =
+ Arrays.stream(code.getDebugInfo().events)
+ .filter(dexDebugEvent -> dexDebugEvent instanceof SetFile)
+ .count();
+ if (renaming) {
+ assertEquals(0, setFileCount);
+ } else {
+ assertNotEquals(0, setFileCount);
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
index 6a39c6d..214a3b4 100644
--- a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
@@ -5,9 +5,9 @@
package com.android.tools.r8.smali;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.ReturnVoid;
@@ -41,7 +41,7 @@
// Return the processed method for inspection.
DexEncodedMethod main = getMethod(processedApplication, mainSignature);
- assert main != null;
+ assertNotNull(main);
DexCode code = main.getCode().asDexCode();
assertTrue(code.instructions[0] instanceof SgetObject);
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index c580f74..f2f1220 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -7,7 +7,9 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
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.dex.ApplicationReader;
@@ -24,6 +26,8 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.DexInspector;
@@ -32,11 +36,13 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Smali;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -45,6 +51,7 @@
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
+import java.util.function.Consumer;
import org.antlr.runtime.RecognitionException;
public class SmaliTestBase extends TestBase {
@@ -71,6 +78,12 @@
public static MethodSignature staticInitializer(String clazz) {
return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
}
+
+ @Override
+ public String toString() {
+ return returnType + " " + clazz + "." + name
+ + "(" + StringUtils.join(parameterTypes, ",") + ")";
+ }
}
public static class SmaliBuilder {
@@ -468,6 +481,30 @@
}
}
+ protected Path runR8(
+ SmaliBuilder builder,
+ List<String> proguardConfigurations,
+ Consumer<ProguardConfiguration.Builder> pgConsumer,
+ Consumer<InternalOptions> optionsConsumer) {
+ try {
+ Path dexOutputDir = temp.newFolder().toPath();
+ R8Command command =
+ R8Command.builder()
+ .addDexProgramData(builder.compile())
+ .setOutputPath(dexOutputDir)
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
+ .addProguardConfiguration(proguardConfigurations)
+ .addProguardConfigurationConsumer(pgConsumer)
+ .build();
+ ToolHelper.runR8WithFullResult(command, optionsConsumer);
+ return dexOutputDir.resolve("classes.dex");
+ } catch (CompilationException | IOException | RecognitionException | ExecutionException
+ | ProguardRuleParserException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
protected DexClass getClass(DexApplication application, String className) {
DexInspector inspector = new DexInspector(application);
ClassSubject clazz = inspector.clazz(className);
@@ -479,9 +516,23 @@
return getClass(application, signature.clazz);
}
- protected DexEncodedMethod getMethod(DexApplication application, String className,
- String returnType, String methodName, List<String> parameters) {
- DexInspector inspector = new DexInspector(application);
+ protected DexClass getClass(Path appPath, String className) {
+ try {
+ DexInspector inspector = new DexInspector(appPath);
+ ClassSubject clazz = inspector.clazz(className);
+ assertTrue(clazz.isPresent());
+ return clazz.getDexClass();
+ } catch (IOException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected DexEncodedMethod getMethod(
+ DexInspector inspector,
+ String className,
+ String returnType,
+ String methodName,
+ List<String> parameters) {
ClassSubject clazz = inspector.clazz(className);
assertTrue(clazz.isPresent());
MethodSubject method = clazz.method(returnType, methodName, parameters);
@@ -489,6 +540,30 @@
return method.getMethod();
}
+ protected DexEncodedMethod getMethod(
+ DexApplication application,
+ String className,
+ String returnType,
+ String methodName,
+ List<String> parameters) {
+ DexInspector inspector = new DexInspector(application);
+ return getMethod(inspector, className, returnType, methodName, parameters);
+ }
+
+ protected DexEncodedMethod getMethod(Path appPath, MethodSignature signature) {
+ try {
+ DexInspector inspector = new DexInspector(appPath);
+ return getMethod(
+ inspector,
+ signature.clazz,
+ signature.returnType,
+ signature.name,
+ signature.parameterTypes);
+ } catch (IOException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) {
return getMethod(application,
signature.clazz, signature.returnType, signature.name, signature.parameterTypes);
@@ -563,6 +638,14 @@
}
}
+ public static String runArt(Path path, String mainClass) {
+ try {
+ return ToolHelper.runArtNoVerificationErrors(path.toString(), mainClass);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
public void runDex2Oat(DexApplication application, InternalOptions options)
throws DexOverflowException {
try {