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;
+  }
+}