Merge "Make CF frontend for testing lazy"
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index e51e4d4..4f64416 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -32,6 +32,7 @@
private Path outputPath = null;
private Path proguardMapFile = null;
private boolean useSmali = false;
+ private boolean allInfo = false;
@Override
Builder self() {
@@ -52,6 +53,11 @@
return this;
}
+ public Builder setAllInfo(boolean allInfo) {
+ this.allInfo = allInfo;
+ return this;
+ }
+
public Builder setUseSmali(boolean useSmali) {
this.useSmali = useSmali;
return this;
@@ -67,6 +73,7 @@
getAppBuilder().build(),
getOutputPath(),
proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
+ allInfo,
useSmali);
}
}
@@ -75,6 +82,7 @@
"Usage: disasm [options] <input-files>",
" where <input-files> are dex files",
" and options are:",
+ " --all # Include all information in disassembly.",
" --smali # Disassemble using smali syntax.",
" --pg-map <file> # Proguard map <file> for mapping names.",
" --output # Specify a file or directory to write to.",
@@ -82,6 +90,7 @@
" --help # Print this message."));
+ private final boolean allInfo;
private final boolean useSmali;
public static Builder builder() {
@@ -103,6 +112,8 @@
builder.setPrintHelp(true);
} else if (arg.equals("--version")) {
builder.setPrintVersion(true);
+ } else if (arg.equals("--all")) {
+ builder.setAllInfo(true);
} else if (arg.equals("--smali")) {
builder.setUseSmali(true);
} else if (arg.equals("--pg-map")) {
@@ -121,10 +132,12 @@
}
private DisassembleCommand(
- AndroidApp inputApp, Path outputPath, StringResource proguardMap, boolean useSmali) {
+ AndroidApp inputApp, Path outputPath, StringResource proguardMap,
+ boolean allInfo, boolean useSmali) {
super(inputApp);
this.outputPath = outputPath;
this.proguardMap = proguardMap;
+ this.allInfo = allInfo;
this.useSmali = useSmali;
}
@@ -132,6 +145,7 @@
super(printHelp, printVersion);
outputPath = null;
proguardMap = null;
+ allInfo = false;
useSmali = false;
}
@@ -177,7 +191,7 @@
new ApplicationReader(app, options, timing).read(command.proguardMap, executor);
DexByteCodeWriter writer = command.useSmali()
? new SmaliWriter(application, options)
- : new AssemblyWriter(application, options);
+ : new AssemblyWriter(application, options, command.allInfo);
if (command.getOutputPath() != null) {
writer.write(command.getOutputPath());
} else {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 50ff578..63a2958 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -4,13 +4,21 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.utils.InternalOptions;
import java.io.PrintStream;
public class AssemblyWriter extends DexByteCodeWriter {
- public AssemblyWriter(DexApplication application, InternalOptions options) {
+ private final boolean writeAllClassInfo;
+ private final boolean writeFields;
+ private final boolean writeAnnotations;
+
+ public AssemblyWriter(DexApplication application, InternalOptions options, boolean allInfo) {
super(application, options);
+ this.writeAllClassInfo = allInfo;
+ this.writeFields = allInfo;
+ this.writeAnnotations = allInfo;
}
@Override
@@ -28,12 +36,43 @@
}
ps.println("# Bytecode for");
ps.println("# Class: '" + clazzName + "'");
+ if (writeAllClassInfo) {
+ writeAnnotations(clazz.annotations, ps);
+ ps.println("# Flags: '" + clazz.accessFlags + "'");
+ if (clazz.superType != application.dexItemFactory.objectType) {
+ ps.println("# Extends: '" + clazz.superType.toSourceString() + "'");
+ }
+ for (DexType value : clazz.interfaces.values) {
+ ps.println("# Implements: '" + value.toSourceString() + "'");
+ }
+ }
ps.println();
}
@Override
+ void writeFieldsHeader(DexProgramClass clazz, PrintStream ps) {
+ if (writeFields) {
+ ps.println("#");
+ ps.println("# Fields:");
+ ps.println("#");
+ }
+ }
+
+ @Override
void writeField(DexEncodedField field, PrintStream ps) {
- // Not implemented, yet.
+ if (writeFields) {
+ ClassNameMapper naming = application.getProguardMap();
+ FieldSignature fieldSignature = naming != null
+ ? naming.originalSignatureOf(field.field)
+ : FieldSignature.fromDexField(field.field);
+ writeAnnotations(field.annotations, ps);
+ ps.println(fieldSignature);
+ }
+ }
+
+ @Override
+ void writeFieldsFooter(DexProgramClass clazz, PrintStream ps) {
+ ps.println();
}
@Override
@@ -44,6 +83,7 @@
: method.method.name.toString();
ps.println("#");
ps.println("# Method: '" + methodName + "':");
+ writeAnnotations(method.annotations, ps);
ps.println("#");
ps.println();
Code code = method.getCode();
@@ -52,6 +92,18 @@
}
}
+ private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
+ if (writeAnnotations) {
+ if (!annotations.isEmpty()) {
+ ps.println("# Annotations:");
+ for (DexAnnotation annotation : annotations.annotations) {
+ ps.print("# ");
+ ps.println(annotation);
+ }
+ }
+ }
+ }
+
@Override
void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index 025a502..21900a01 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -82,8 +82,12 @@
private void writeClass(DexProgramClass clazz, PrintStream ps) {
writeClassHeader(clazz, ps);
+ writeFieldsHeader(clazz, ps);
clazz.forEachField(field -> writeField(field, ps));
+ writeFieldsFooter(clazz, ps);
+ writeMethodsHeader(clazz, ps);
clazz.forEachMethod(method -> writeMethod(method, ps));
+ writeMethodsFooter(clazz, ps);
writeClassFooter(clazz, ps);
}
@@ -91,9 +95,25 @@
abstract void writeClassHeader(DexProgramClass clazz, PrintStream ps);
+ void writeFieldsHeader(DexProgramClass clazz, PrintStream ps) {
+ // Do nothing.
+ }
+
abstract void writeField(DexEncodedField field, PrintStream ps);
+ void writeFieldsFooter(DexProgramClass clazz, PrintStream ps) {
+ // Do nothing.
+ }
+
+ void writeMethodsHeader(DexProgramClass clazz, PrintStream ps) {
+ // Do nothing.
+ }
+
abstract void writeMethod(DexEncodedMethod method, PrintStream ps);
+ void writeMethodsFooter(DexProgramClass clazz, PrintStream ps) {
+ // Do nothing.
+ }
+
abstract void writeClassFooter(DexProgramClass clazz, PrintStream ps);
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index de85da5..6ec9e6e 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -1279,9 +1279,10 @@
return false;
}
- private boolean longOverlappingLong(int register1, int register2) {
- return register1 == register2 || register1 == (register2 + 1)
- || (register1 + 1) == register2 || (register1 + 1) == (register2 + 1);
+ // Check if the two longs are half-overlapping, that is first register of one is the second
+ // register of the other.
+ private boolean longHalfOverlappingLong(int register1, int register2) {
+ return register1 == (register2 + 1) || (register1 + 1) == register2;
}
private boolean isLongResultOverlappingLongOperands(
@@ -1297,7 +1298,8 @@
// The dalvik bug is actually only for overlap with the second operand, For now we
// make sure that there is no overlap with either register of either operand. Some vendor
// optimization have bees seen to need this more conservative check.
- return longOverlappingLong(register, leftReg) || longOverlappingLong(register, rightReg);
+ return longHalfOverlappingLong(register, leftReg)
+ || longHalfOverlappingLong(register, rightReg);
}
// Intervals overlap a move exception interval if one of the splits of the intervals does.
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 30a5418..5b908af 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -110,9 +110,9 @@
return (MethodSignature) canonicalizeSignature(signature);
}
- public Signature getRenamedFieldSignature(DexField field) {
+ public FieldSignature getRenamedFieldSignature(DexField field) {
String type = deobfuscateType(field.type.toDescriptorString());
- return canonicalizeSignature(new FieldSignature(field.name.toString(), type));
+ return (FieldSignature) canonicalizeSignature(new FieldSignature(field.name.toString(), type));
}
/**
@@ -231,6 +231,20 @@
return memberNaming.signature;
}
+ public FieldSignature originalSignatureOf(DexField field) {
+ String decoded = descriptorToJavaType(field.clazz.descriptor.toString());
+ FieldSignature memberSignature = getRenamedFieldSignature(field);
+ ClassNaming classNaming = getClassNaming(decoded);
+ if (classNaming == null) {
+ return memberSignature;
+ }
+ MemberNaming memberNaming = classNaming.lookup(memberSignature);
+ if (memberNaming == null) {
+ return memberSignature;
+ }
+ return (FieldSignature) memberNaming.signature;
+ }
+
public String originalNameOf(DexType clazz) {
return deobfuscateType(clazz.descriptor.toString());
}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
new file mode 100644
index 0000000..36cdad0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
@@ -0,0 +1,117 @@
+// 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.debug;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test to check that locals that are introduced in a block that is not hit, still start in the
+ * block where they first become visible.
+ *
+ * <p>See b/75251251 or b/78617758
+ */
+public class LocalsLiveAtBlockEntryDebugTest extends DebugTestBase {
+
+ final String className = "LocalsLiveAtEntry";
+ final String sourcefile = className + ".j";
+ final String methodName = "test";
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void testCF() throws Throwable {
+ JasminBuilder builder = getBuilderForTest(className, methodName);
+ Path outdir = temp.newFolder().toPath();
+ builder.writeClassFiles(outdir);
+ CfDebugTestConfig config = new CfDebugTestConfig();
+ config.addPaths(outdir);
+ runTest(config);
+ }
+
+ @Test
+ @Ignore("b/78617758")
+ public void testD8() throws Throwable {
+ JasminBuilder builder = getBuilderForTest(className, methodName);
+ List<Path> outputs = builder.writeClassFiles(temp.newFolder().toPath());
+ runTest(new D8DebugTestConfig().compileAndAdd(temp, outputs));
+ }
+
+ private void runTest(DebugTestConfig config) throws Throwable {
+ DexInspector inspector =
+ new DexInspector(
+ (config instanceof CfDebugTestConfig)
+ ? Collections.singletonList(config.getPaths().get(1).resolve(className + ".class"))
+ : config.getPaths());
+ ClassSubject clazz = inspector.clazz(className);
+ MethodSubject method = clazz.method("void", methodName, ImmutableList.of("java.lang.Object"));
+ assertTrue(method.isPresent());
+ runDebugTest(
+ config,
+ className,
+ breakpoint(className, methodName),
+ run(),
+ checkLine(sourcefile, 1),
+ checkNoLocal("obj"),
+ stepOver(),
+ checkLine(sourcefile, 3),
+ checkLocal("obj"),
+ stepOver(),
+ checkLine(sourcefile, 100),
+ run());
+ }
+
+ private JasminBuilder getBuilderForTest(String testClassName, String testMethodName) {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass(testClassName);
+
+ clazz.addStaticMethod(
+ testMethodName,
+ ImmutableList.of("Ljava/lang/Object;"),
+ "V",
+ ".limit stack 2",
+ ".limit locals 3",
+ ".var 0 is obj L" + testClassName + "; from L1 to L3",
+ "L0:", // Preamble code that does not have any live locals (eg, no formals too!).
+ ".line 1",
+ " ldc 42",
+ " lookupswitch",
+ " 0: L1",
+ " default: L2",
+ "L1:", // late introduction of formals.
+ ".line 2",
+ " aconst_null",
+ " pop",
+ "L2:", // target block with first visible location of locals.
+ ".line 3",
+ " aconst_null",
+ " pop",
+ " return",
+ "L3:");
+
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ ".line 100",
+ "aconst_null",
+ "invokestatic " + testClassName + "/" + testMethodName + "(Ljava/lang/Object;)V",
+ "return");
+
+ return builder;
+ }
+}