Update smali disassembly
* Add implemented interfaces
* Create a separate command and command line parsing
R=ager@google.com
Change-Id: I234d131f6225748a7a51de7b34160483161ab6c4
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 71daa5c..b183ecc 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -4,19 +4,145 @@
package com.android.tools.r8;
import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.ExecutionException;
-import java.util.stream.Collectors;
public class Disassemble {
+ public static class DisassembleCommand extends BaseCommand {
+
+ public static class Builder
+ extends BaseCommand.Builder<DisassembleCommand, DisassembleCommand.Builder> {
+ private boolean useSmali = false;
+
+ private Builder() {
+ super(CompilationMode.RELEASE);
+ }
+
+ @Override
+ DisassembleCommand.Builder self() {
+ return this;
+ }
+
+ public DisassembleCommand.Builder setProguardMapFile(Path path) {
+ getAppBuilder().setProguardMapFile(path);
+ return this;
+ }
+
+ public DisassembleCommand.Builder setUseSmali(boolean useSmali) {
+ this.useSmali = useSmali;
+ return this;
+ }
+
+ @Override
+ public DisassembleCommand build() throws CompilationException, IOException {
+ // If printing versions ignore everything else.
+ if (isPrintHelp() || isPrintVersion()) {
+ return new DisassembleCommand(isPrintHelp(), isPrintVersion());
+ }
+
+ return new DisassembleCommand(
+ getAppBuilder().build(),
+ getOutputPath(),
+ getOutputMode(),
+ getMode(),
+ getMinApiLevel(),
+ useSmali);
+ }
+ }
+
+ static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
+ "Usage: disasm [options] <input-files>",
+ " where <input-files> are dex files",
+ " and options are:",
+ " --smali # Disassemble using smali syntax.",
+ " --pg-map <file> # Proguard map <file> for mapping names.",
+ " --version # Print the version of r8.",
+ " --help # Print this message."));
+
+
+ private final boolean useSmali;
+
+ public static DisassembleCommand.Builder builder() {
+ return new DisassembleCommand.Builder();
+ }
+
+ public static DisassembleCommand.Builder parse(String[] args)
+ throws CompilationException, IOException {
+ DisassembleCommand.Builder builder = builder();
+ parse(args, builder);
+ return builder;
+ }
+
+ private static void parse(String[] args, DisassembleCommand.Builder builder)
+ throws CompilationException, IOException {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i].trim();
+ if (arg.length() == 0) {
+ continue;
+ } else if (arg.equals("--help")) {
+ builder.setPrintHelp(true);
+ } else if (arg.equals("--version")) {
+ builder.setPrintVersion(true);
+ } else if (arg.equals("--smali")) {
+ builder.setUseSmali(true);
+ } else if (arg.equals("--pg-map")) {
+ builder.setProguardMapFile(Paths.get(args[++i]));
+ } else {
+ if (arg.startsWith("--")) {
+ throw new CompilationException("Unknown option: " + arg);
+ }
+ builder.addProgramFiles(Paths.get(arg));
+ }
+ }
+ }
+
+ private DisassembleCommand(
+ AndroidApp inputApp,
+ Path outputPath,
+ OutputMode outputMode,
+ CompilationMode mode,
+ int minApiLevel,
+ boolean useSmali) {
+ super(inputApp, outputPath, outputMode, mode, minApiLevel);
+ //assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
+ this.useSmali = useSmali;
+ }
+
+ private DisassembleCommand(boolean printHelp, boolean printVersion) {
+ super(printHelp, printVersion);
+ this.useSmali = false;
+ }
+
+ public boolean useSmali() {
+ return useSmali;
+ }
+
+ @Override
+ InternalOptions getInternalOptions() {
+ InternalOptions internal = new InternalOptions();
+ internal.useSmaliSyntax = useSmali;
+ return internal;
+ }
+ }
+
public static void main(String[] args)
throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
- List<Path> files = Arrays.stream(args).map(s -> Paths.get(s)).collect(Collectors.toList());
- R8Command command = R8Command.builder().addProgramFiles(files).build();
+ DisassembleCommand.Builder builder = DisassembleCommand.parse(args);
+ DisassembleCommand command = builder.build();
+ if (command.isPrintHelp()) {
+ System.out.println(DisassembleCommand.USAGE_MESSAGE);
+ return;
+ }
+ if (command.isPrintVersion()) {
+ System.out.println("R8 v0.0.1");
+ return;
+ }
R8.disassemble(command);
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 306f37b..caf7f46 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -145,7 +145,8 @@
return result;
}
- public static void disassemble(R8Command command) throws IOException, ExecutionException {
+ public static void disassemble(Disassemble.DisassembleCommand command)
+ throws IOException, ExecutionException {
Path output = command.getOutputPath();
AndroidApp app = command.getInputApp();
InternalOptions options = command.getInternalOptions();
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index acbc205..0941b44 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -202,8 +202,12 @@
if (clazz.type != dexItemFactory.objectType) {
builder.append(".super ");
builder.append(clazz.superType.toSmaliString());
- // TODO(sgjesse): Add implemented interfaces
builder.append("\n");
+ for (DexType iface : clazz.interfaces.values) {
+ builder.append(".implements ");
+ builder.append(iface.toSmaliString());
+ builder.append("\n");
+ }
}
ps.append(builder.toString());
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java b/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
index 1f20559..d455841 100644
--- a/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
@@ -6,8 +6,8 @@
import static com.android.tools.r8.utils.AndroidApp.DEFAULT_PROGUARD_MAP_FILE;
import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.Disassemble;
import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.FileUtils;
import java.io.IOException;
@@ -27,11 +27,8 @@
static final String APP_DIR = "third_party/gmscore/v5/";
- public boolean deobfuscate;
-
- @Test
- public void disassemble() throws IOException, ExecutionException, ProguardRuleParserException,
- CompilationException {
+ public void testDisassemble(boolean deobfuscate, boolean smali)
+ throws IOException, ExecutionException, ProguardRuleParserException, CompilationException {
// This test only ensures that we do not break disassembling of dex code. It does not
// check the generated code. To make it fast, we get rid of the output.
PrintStream originalOut = System.out;
@@ -40,7 +37,8 @@
}));
try {
- R8Command.Builder builder = R8Command.builder();
+ Disassemble.DisassembleCommand.Builder builder = Disassemble.DisassembleCommand.builder();
+ builder.setUseSmali(smali);
if (deobfuscate) {
builder.setProguardMapFile(Paths.get(APP_DIR, DEFAULT_PROGUARD_MAP_FILE));
}
@@ -54,4 +52,24 @@
System.setOut(originalOut);
}
}
+
+ @Test
+ public void test1() throws Exception {
+ testDisassemble(false, false);
+ }
+
+ @Test
+ public void test2() throws Exception {
+ testDisassemble(false, true);
+ }
+
+ @Test
+ public void test3() throws Exception {
+ testDisassemble(true, false);
+ }
+
+ @Test
+ public void test4() throws Exception {
+ testDisassemble(true, true);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
index b1fd38f..0c1d13d 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -347,7 +347,6 @@
roundTripRawSmali(expected);
}
-
@Test
public void interfaceClass() {
SmaliBuilder builder = new SmaliBuilder();
@@ -368,4 +367,26 @@
roundTripRawSmali(expected);
}
+
+ @Test
+ public void implementsInterface() {
+ SmaliBuilder builder = new SmaliBuilder();
+ builder.addClass("Test", "java.lang.Object", ImmutableList.of("java.util.List"));
+ builder.addAbstractMethod("int", "test", ImmutableList.of());
+ DexApplication application = buildApplication(builder);
+ assertEquals(1, Iterables.size(application.classes()));
+
+ String expected =
+ ".class public LTest;\n" +
+ "\n" +
+ ".super Ljava/lang/Object;\n" +
+ ".implements Ljava/util/List;\n" +
+ "\n" +
+ ".method public abstract test()I\n" +
+ ".end method\n";
+
+ assertEquals(expected, application.smali(new InternalOptions()));
+
+ roundTripRawSmali(expected);
+ }
}
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 3665a86..6b377f4 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Smali;
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;
@@ -77,11 +78,27 @@
abstract class Builder {
String name;
String superName;
+ List<String> implementedInterfaces;
List<String> source = new ArrayList<>();
- Builder(String name, String superName) {
+ Builder(String name, String superName, List<String> implementedInterfaces) {
this.name = name;
this.superName = superName;
+ this.implementedInterfaces = implementedInterfaces;
+ }
+
+ protected void appendSuper(StringBuilder builder) {
+ builder.append(".super ");
+ builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
+ builder.append("\n");
+ }
+
+ protected void appendImplementedInterfaces(StringBuilder builder) {
+ for (String implementedInterface : implementedInterfaces) {
+ builder.append(".implements ");
+ builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface));
+ builder.append("\n");
+ }
}
protected void writeSource(StringBuilder builder) {
@@ -94,8 +111,8 @@
public class ClassBuilder extends Builder {
- ClassBuilder(String name, String superName) {
- super(name, superName);
+ ClassBuilder(String name, String superName, List<String> implementedInterfaces) {
+ super(name, superName, implementedInterfaces);
}
public String toString() {
@@ -103,9 +120,9 @@
builder.append(".class public ");
builder.append(DescriptorUtils.javaTypeToDescriptor(name));
builder.append("\n");
- builder.append(".super ");
- builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
- builder.append("\n\n");
+ appendSuper(builder);
+ appendImplementedInterfaces(builder);
+ builder.append("\n");
writeSource(builder);
return builder.toString();
}
@@ -114,7 +131,7 @@
public class InterfaceBuilder extends Builder {
InterfaceBuilder(String name, String superName) {
- super(name, superName);
+ super(name, superName, ImmutableList.of());
}
public String toString() {
@@ -122,9 +139,9 @@
builder.append(".class public interface abstract ");
builder.append(DescriptorUtils.javaTypeToDescriptor(name));
builder.append("\n");
- builder.append(".super ");
- builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
- builder.append("\n\n");
+ appendSuper(builder);
+ appendImplementedInterfaces(builder);
+ builder.append("\n");
writeSource(builder);
return builder.toString();
}
@@ -162,9 +179,13 @@
}
public void addClass(String name, String superName) {
+ addClass(name, superName, ImmutableList.of());
+ }
+
+ public void addClass(String name, String superName, List<String> implementedInterfaces) {
assert !classes.containsKey(name);
currentClassName = name;
- classes.put(name, new ClassBuilder(name, superName));
+ classes.put(name, new ClassBuilder(name, superName, implementedInterfaces));
}
public void addInterface(String name) {