Merge "Fix parsing of -dontwarn flag."
diff --git a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
index ae022a5..aec690d 100644
--- a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
@@ -7,8 +7,9 @@
 import static com.android.tools.r8.utils.FileUtils.isArchive;
 import static com.android.tools.r8.utils.FileUtils.isClassFile;
 
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.io.ByteStreams;
@@ -56,7 +57,7 @@
 
   private ArchiveClassFileProvider(FilteredClassPath archive) throws IOException {
     assert isArchive(archive.getPath());
-    origin = new Resource.PathOrigin(archive.getPath(), Origin.root());
+    origin = new PathOrigin(archive.getPath(), Origin.root());
     zipFile = new ZipFile(archive.getPath().toFile());
     final Enumeration<? extends ZipEntry> entries = zipFile.entries();
     while (entries.hasMoreElements()) {
diff --git a/src/main/java/com/android/tools/r8/Diagnostic.java b/src/main/java/com/android/tools/r8/Diagnostic.java
index 1c1961d..b0868cc 100644
--- a/src/main/java/com/android/tools/r8/Diagnostic.java
+++ b/src/main/java/com/android/tools/r8/Diagnostic.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 
 /**
  * Interface for all diagnostic message produced by D8 and R8.
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
index 8e69699..10f24f5 100644
--- a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
+++ b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 
 /**
  * A DiagnosticsHandler can be provided to customize handling of diagnostics information.
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 76a6ed1..c1b1096 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -45,6 +45,8 @@
 import com.android.tools.r8.utils.AndroidAppOutputSink;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.LineNumberOptimizer;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -314,6 +316,15 @@
               : new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
       timing.end();
 
+      if (options.lineNumberOptimization != LineNumberOptimization.OFF) {
+        timing.begin("Line number remapping");
+        LineNumberOptimizer.run(
+            application,
+            namingLens,
+            options.lineNumberOptimization == LineNumberOptimization.IDENTITY_MAPPING);
+        timing.end();
+      }
+
       // If a method filter is present don't produce output since the application is likely partial.
       if (options.hasMethodsFilter()) {
         System.out.println("Finished compilation with method filter: ");
diff --git a/src/main/java/com/android/tools/r8/Resource.java b/src/main/java/com/android/tools/r8/Resource.java
index b2bc53d..bbd5954 100644
--- a/src/main/java/com/android/tools/r8/Resource.java
+++ b/src/main/java/com/android/tools/r8/Resource.java
@@ -4,161 +4,18 @@
 
 package com.android.tools.r8;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
 import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Set;
 
 /** Represents application resources. */
 public abstract class Resource {
 
-  /**
-   * Origin description of a resource.
-   *
-   * <p>An origin is a list of parts that describe where a resource originates from. The first part
-   * is the most recent part and is associated with the present resource, each successive part is
-   * then associated with the context of the previous part.
-   *
-   * <p>For example, for a class file, say {@code my/class/Foo.class}, that is contained within a
-   * jar archive, say {@code myjar.jar}, the Origin of of this resource will be {@code
-   * myjar.jar:my/class/Foo.class} where each part is separated by a colon.
-   *
-   * <p>There are two top-most origins which have no parent: {@code Origin.root()} and {@code
-   * Origin.unknown()}. The former is the parent of any file path, while the latter is an unknown
-   * origin (e.g., for generated resources of raw bytes).
-   */
-  public abstract static class Origin implements Comparable<Origin> {
-
-    private static final Origin ROOT =
-        new Origin() {
-          @Override
-          public String part() {
-            return "";
-          }
-
-          @Override
-          List<String> buildParts(int size) {
-            return new ArrayList<>(size);
-          }
-        };
-
-    private static final Origin UNKNOWN =
-        new Origin() {
-          @Override
-          public String part() {
-            return "<unknown>";
-          }
-
-          @Override
-          List<String> buildParts(int size) {
-            List<String> parts = new ArrayList<>(size + 1);
-            parts.add(part());
-            return parts;
-          }
-        };
-
-    public static Origin root() {
-      return ROOT;
-    }
-
-    public static Origin unknown() {
-      return UNKNOWN;
-    }
-
-    private final Origin parent;
-
-    private Origin() {
-      this.parent = null;
-    }
-
-    protected Origin(Origin parent) {
-      assert parent != null;
-      this.parent = parent;
-    }
-
-    public abstract String part();
-
-    public Origin parent() {
-      return parent;
-    }
-
-    public List<String> parts() {
-      return buildParts(0);
-    }
-
-    List<String> buildParts(int size) {
-      List<String> parts = parent().buildParts(size + 1);
-      parts.add(part());
-      return parts;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (obj == this) {
-        return true;
-      }
-      if (!(obj instanceof Origin)) {
-        return false;
-      }
-      Origin self = this;
-      Origin other = (Origin) obj;
-      while (self != null && other != null && self.part().equals(other.part())) {
-        self = self.parent();
-        other = other.parent();
-      }
-      return self == other;
-    }
-
-    @Override
-    public int compareTo(Origin other) {
-      // Lexicographic ordering from root to leaf.
-      List<String> thisParts = parts();
-      List<String> otherParts = other.parts();
-      int len = Math.min(thisParts.size(), otherParts.size());
-      for (int i = 0; i < len; i++) {
-        int compare = thisParts.get(i).compareTo(otherParts.get(i));
-        if (compare != 0) {
-          return compare;
-        }
-      }
-      return Integer.compare(thisParts.size(), otherParts.size());
-    }
-
-    @Override
-    public int hashCode() {
-      int hash = 1;
-      for (String part : parts()) {
-        hash = 31 * hash + part.hashCode();
-      }
-      return hash;
-    }
-
-    @Override
-    public String toString() {
-      return String.join(":", parts());
-    }
-  }
-
-  /** Path component in an origin description. */
-  public static class PathOrigin extends Origin {
-    private final Path path;
-
-    public PathOrigin(Path path, Origin parent) {
-      super(parent);
-      assert path != null;
-      this.path = path;
-    }
-
-    @Override
-    public String part() {
-      return path.toString();
-    }
-  }
-
   /** Origin of the resource. */
   public final Origin origin;
 
diff --git a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
index e904e53..948178a 100644
--- a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
+++ b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.D8Output;
 import com.android.tools.r8.Resource;
-import com.android.tools.r8.Resource.Origin;
-import com.android.tools.r8.Resource.PathOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.OutputMode;
diff --git a/src/main/java/com/android/tools/r8/dex/DexFileReader.java b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
index 4b0d6e2..17d634b 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFileReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
@@ -8,8 +8,8 @@
 import static com.android.tools.r8.utils.EncodedValueUtils.parseSigned;
 import static com.android.tools.r8.utils.EncodedValueUtils.parseUnsigned;
 
-import com.android.tools.r8.Resource.Origin;
-import com.android.tools.r8.Resource.PathOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InstructionFactory;
 import com.android.tools.r8.graph.ClassAccessFlags;
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index f8c4cbd..a526c3f 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -1,6 +1,6 @@
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ProgramResource;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
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 d5fc0be..82cdbe1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 292b368..4094453 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 083c865..f998705 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.StringUtils;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import java.util.Arrays;
@@ -162,7 +163,9 @@
   @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
-    DexSourceCode source = new DexSourceCode(this, encodedMethod, null);
+    DexSourceCode source =
+        new DexSourceCode(
+            this, encodedMethod, null, options.lineNumberOptimization == LineNumberOptimization.ON);
     IRBuilder builder = new IRBuilder(encodedMethod, source, options);
     return builder.build();
   }
@@ -173,7 +176,12 @@
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition)
       throws ApiLevelException {
-    DexSourceCode source = new DexSourceCode(this, encodedMethod, callerPosition);
+    DexSourceCode source =
+        new DexSourceCode(
+            this,
+            encodedMethod,
+            callerPosition,
+            options.lineNumberOptimization == LineNumberOptimization.ON);
     IRBuilder builder = new IRBuilder(encodedMethod, source, options, valueNumberGenerator);
     return builder.build();
   }
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 9d42c82..58d8473 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -446,7 +446,7 @@
 
     @Override
     public String toString() {
-      return String.format("DEFAULT %d (dpc %d, %dline %d)", value, getPCDelta(), getLineDelta());
+      return String.format("DEFAULT %d (dpc %d, dline %d)", value, getPCDelta(), getLineDelta());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 1c71b9b..83e01a2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -29,7 +29,7 @@
  */
 public class DexDebugEventBuilder {
 
-  private static final int NO_PC_INFO = -1;
+  public static final int NO_PC_INFO = -1;
   private static final int NO_LINE_INFO = -1;
 
   private final DexEncodedMethod method;
@@ -234,7 +234,7 @@
     }
   }
 
-  private static void emitAdvancementEvents(
+  public static void emitAdvancementEvents(
       int previousPc,
       Position previousPosition,
       int nextPc,
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
index 280b27e..a02e546 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
@@ -17,8 +17,6 @@
  * the current state using the getters after a Default event.
  */
 public class DexDebugPositionState implements DexDebugEventVisitor {
-  private final DexMethod method;
-
   private int currentPc = 0;
   private int currentLine;
   private DexString currentFile = null;
@@ -26,7 +24,6 @@
   private Position currentCallerPosition = null;
 
   public DexDebugPositionState(int startLine, DexMethod method) {
-    this.method = method;
     currentLine = startLine;
     currentMethod = method;
   }
@@ -44,9 +41,6 @@
 
   @Override
   public void visit(SetInlineFrame setInlineFrame) {
-    assert (setInlineFrame.caller == null && setInlineFrame.callee == method)
-        || (setInlineFrame.caller != null
-            && setInlineFrame.caller.getOutermostCaller().method == method);
     currentMethod = setInlineFrame.callee;
     currentCallerPosition = setInlineFrame.caller;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java b/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
index 7777147..14fc568 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemBasedString.java
@@ -42,7 +42,8 @@
   @Override
   public void collectIndexedItems(IndexedItemCollection indexedItems) {
     // This instance should not exist when collecting indexed items.
-    // {@link IdentifierMinifier} will replace this with an appropriate {@link DexString}.
+    // {@link com.android.tools.r8.naming.IdentifierMinifier} will replace this with an appropriate
+    // {@link DexString}.
     throw new Unreachable("Remaining DexItemBasedString: " + this.toString());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index f2e262b..a38c01d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 4d16548..c96ee92 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.utils.ProgramResource;
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 2c573f4..1985869 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -7,7 +7,7 @@
 import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
 import static org.objectweb.asm.Opcodes.ASM6;
 
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
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 ce2de41..06eeeea 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ApiLevelException;
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index d02863f..a93253a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -194,6 +194,9 @@
 
   @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
+    if (other == this) {
+      return true;
+    }
     if (preciseTypeUnknown()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index eb055f2..ee00724 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -76,11 +76,14 @@
   private List<DexDebugEntry> debugEntries = null;
   private Position callerPosition; // In case of inlining the position of the invoke in the caller.
   private final DexMethod method;
+  private final boolean preserveCaller;
 
-  public DexSourceCode(DexCode code, DexEncodedMethod method, Position callerPosition) {
+  public DexSourceCode(
+      DexCode code, DexEncodedMethod method, Position callerPosition, boolean preserveCaller) {
     this.code = code;
     this.proto = method.method.proto;
     this.accessFlags = method.accessFlags;
+    this.preserveCaller = preserveCaller;
     argumentTypes = computeArgumentTypes();
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
@@ -242,6 +245,9 @@
   }
 
   private Position canonicalizeCallerPosition(Position caller) {
+    // We are not supposed to get here from getCanonicalPositionAppendCaller if !preserveCaller.
+    assert preserveCaller;
+
     if (caller == null) {
       return callerPosition;
     }
@@ -257,16 +263,15 @@
 
   private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
     // If this instruction has already been inlined then this.method must be the outermost caller.
-    assert (entry.callerPosition == null && entry.method == method)
-        || (entry.callerPosition != null
-            && entry.callerPosition.getOutermostCaller().method == method);
+    assert entry.callerPosition == null
+        || entry.callerPosition.getOutermostCaller().method == method;
 
     return getCanonicalPosition(
         new Position(
             entry.line,
             entry.sourceFile,
             entry.method,
-            canonicalizeCallerPosition(entry.callerPosition)));
+            preserveCaller ? canonicalizeCallerPosition(entry.callerPosition) : null));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index b466315..8547206 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -113,7 +113,7 @@
       this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping());
       if (appInfo.hasLiveness()) {
         this.protoLiteRewriter = new ProtoLitePruner(appInfo.withLiveness());
-        if (!appInfo.withLiveness().identifierNameStrings.isEmpty()) {
+        if (!appInfo.withLiveness().identifierNameStrings.isEmpty() && !options.skipMinification) {
           this.identifierNameStringMarker = new IdentifierNameStringMarker(appInfo.withLiveness());
         } else {
           this.identifierNameStringMarker = null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 5386163..b94a22b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1339,11 +1339,13 @@
     ConstInstruction[] values = new ConstInstruction[size];
     int remaining = size;
     Set<Instruction> users = newArray.outValue().uniqueUsers();
+    Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
     // We allow the array instantiations to cross block boundaries as long as it hasn't encountered
     // an instruction instance that can throw an exception.
     InstructionListIterator it = block.listIterator();
     it.nextUntil(i -> i == newArray);
     do {
+      visitedBlocks.add(block);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         // If we encounter an instruction that can throw an exception we need to bail out of the
@@ -1377,7 +1379,10 @@
           return values;
         }
       }
-      block = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
+      block =
+          block.exit().isGoto() && !visitedBlocks.contains(block.exit().asGoto().getTarget())
+              ? block.exit().asGoto().getTarget()
+              : null;
       it = block != null ? block.listIterator() : null;
     } while (it != null);
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index cd51b25..98b08d3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -370,7 +370,8 @@
               assert invokePosition.isNone();
               invokePosition = Position.noneWithMethod(method.method, null);
             }
-            assert invokePosition.getOutermostCaller().method == method.method;
+            assert invokePosition.callerPosition == null
+                || invokePosition.getOutermostCaller().method == method.method;
 
             IRCode inlinee =
                 result.buildIR(
diff --git a/src/main/java/com/android/tools/r8/origin/Origin.java b/src/main/java/com/android/tools/r8/origin/Origin.java
new file mode 100644
index 0000000..9e3d33e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/origin/Origin.java
@@ -0,0 +1,135 @@
+// 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.origin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Origin description of a resource.
+ *
+ * <p>An origin is a list of parts that describe where a resource originates from. The first part
+ * is the most recent part and is associated with the present resource, each successive part is
+ * then associated with the context of the previous part.
+ *
+ * <p>For example, for a class file, say {@code my/class/Foo.class}, that is contained within a
+ * jar archive, say {@code myjar.jar}, the Origin of of this resource will be {@code
+ * myjar.jar:my/class/Foo.class} where each part is separated by a colon.
+ *
+ * <p>There are two top-most origins which have no parent: {@code Origin.root()} and {@code
+ * Origin.unknown()}. The former is the parent of any file path, while the latter is an unknown
+ * origin (e.g., for generated resources of raw bytes).
+ */
+public abstract class Origin implements Comparable<Origin> {
+
+  private static final Origin ROOT =
+      new Origin() {
+        @Override
+        public String part() {
+          return "";
+        }
+
+        @Override
+        List<String> buildParts(int size) {
+          return new ArrayList<>(size);
+        }
+      };
+
+  private static final Origin UNKNOWN =
+      new Origin() {
+        @Override
+        public String part() {
+          return "<unknown>";
+        }
+
+        @Override
+        List<String> buildParts(int size) {
+          List<String> parts = new ArrayList<>(size + 1);
+          parts.add(part());
+          return parts;
+        }
+      };
+
+  public static Origin root() {
+    return ROOT;
+  }
+
+  public static Origin unknown() {
+    return UNKNOWN;
+  }
+
+  private final Origin parent;
+
+  private Origin() {
+    this.parent = null;
+  }
+
+  protected Origin(Origin parent) {
+    assert parent != null;
+    this.parent = parent;
+  }
+
+  public abstract String part();
+
+  public Origin parent() {
+    return parent;
+  }
+
+  public List<String> parts() {
+    return buildParts(0);
+  }
+
+  List<String> buildParts(int size) {
+    List<String> parts = parent().buildParts(size + 1);
+    parts.add(part());
+    return parts;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    }
+    if (!(obj instanceof Origin)) {
+      return false;
+    }
+    Origin self = this;
+    Origin other = (Origin) obj;
+    while (self != null && other != null && self.part().equals(other.part())) {
+      self = self.parent();
+      other = other.parent();
+    }
+    return self == other;
+  }
+
+  @Override
+  public int compareTo(Origin other) {
+    // Lexicographic ordering from root to leaf.
+    List<String> thisParts = parts();
+    List<String> otherParts = other.parts();
+    int len = Math.min(thisParts.size(), otherParts.size());
+    for (int i = 0; i < len; i++) {
+      int compare = thisParts.get(i).compareTo(otherParts.get(i));
+      if (compare != 0) {
+        return compare;
+      }
+    }
+    return Integer.compare(thisParts.size(), otherParts.size());
+  }
+
+  @Override
+  public int hashCode() {
+    int hash = 1;
+    for (String part : parts()) {
+      hash = 31 * hash + part.hashCode();
+    }
+    return hash;
+  }
+
+  @Override
+  public String toString() {
+    return String.join(":", parts());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/origin/PathOrigin.java b/src/main/java/com/android/tools/r8/origin/PathOrigin.java
new file mode 100644
index 0000000..919c4f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/origin/PathOrigin.java
@@ -0,0 +1,23 @@
+// 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.origin;
+
+import java.nio.file.Path;
+
+/** Path component in an origin description. */
+public class PathOrigin extends Origin {
+  private final Path path;
+
+  public PathOrigin(Path path, Origin parent) {
+    super(parent);
+    assert path != null;
+    this.path = path;
+  }
+
+  @Override
+  public String part() {
+    return path.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index ddac51f..f9f3f01 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.Resource;
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.dex.VDexFile;
 import com.android.tools.r8.dex.VDexFileReader;
 import com.android.tools.r8.errors.CompilationError;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
index 0f85cac..62a5e76 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.OutputSink;
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
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 8bb1514..24df0e4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,13 +4,13 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.Resource.Origin;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.google.common.collect.ImmutableList;
@@ -25,6 +25,12 @@
 
 public class InternalOptions {
 
+  public enum LineNumberOptimization {
+    OFF,
+    ON,
+    IDENTITY_MAPPING
+  }
+
   public final DexItemFactory itemFactory;
   public final ProguardConfiguration proguardConfiguration;
 
@@ -125,6 +131,8 @@
   public ImmutableList<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
   public boolean minimalMainDex;
 
+  public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.OFF;
+
   public static class InvalidParameterAnnotationInfo {
 
     final DexMethod method;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
new file mode 100644
index 0000000..37e1265
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -0,0 +1,316 @@
+// 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.utils;
+
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEventBuilder;
+import com.android.tools.r8.graph.DexDebugEventVisitor;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugPositionState;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+
+public class LineNumberOptimizer {
+
+  // EventFilter is a visitor for DebugEvents, splits events into two sinks:
+  // - Forwards non-positional events unchanged into a BypassedEventReceiver
+  // - Forwards positional events, accumulated into DexDebugPositionStates, into
+  //   positionEventReceiver.
+  private static class EventFilter implements DexDebugEventVisitor {
+    private final BypassedEventReceiver bypassedEventReceiver;
+    private final PositionEventReceiver positionEventReceiver;
+
+    private interface BypassedEventReceiver {
+      void receiveBypassedEvent(DexDebugEvent event);
+    }
+
+    private interface PositionEventReceiver {
+      void receivePositionEvent(DexDebugPositionState positionState);
+    }
+
+    private DexDebugPositionState positionState;
+
+    private EventFilter(
+        int startLine,
+        DexMethod method,
+        BypassedEventReceiver bypassedEventReceiver,
+        PositionEventReceiver positionEventReceiver) {
+      positionState = new DexDebugPositionState(startLine, method);
+      this.bypassedEventReceiver = bypassedEventReceiver;
+      this.positionEventReceiver = positionEventReceiver;
+    }
+
+    @Override
+    public void visit(DexDebugEvent.SetPrologueEnd event) {
+      bypassedEventReceiver.receiveBypassedEvent(event);
+    }
+
+    @Override
+    public void visit(DexDebugEvent.SetEpilogueBegin event) {
+      bypassedEventReceiver.receiveBypassedEvent(event);
+    }
+
+    @Override
+    public void visit(DexDebugEvent.StartLocal event) {
+      bypassedEventReceiver.receiveBypassedEvent(event);
+    }
+
+    @Override
+    public void visit(DexDebugEvent.EndLocal event) {
+      bypassedEventReceiver.receiveBypassedEvent(event);
+    }
+
+    @Override
+    public void visit(DexDebugEvent.RestartLocal event) {
+      bypassedEventReceiver.receiveBypassedEvent(event);
+    }
+
+    @Override
+    public void visit(DexDebugEvent.AdvancePC advancePC) {
+      positionState.visit(advancePC);
+    }
+
+    @Override
+    public void visit(DexDebugEvent.AdvanceLine advanceLine) {
+      positionState.visit(advanceLine);
+    }
+
+    @Override
+    public void visit(DexDebugEvent.SetInlineFrame setInlineFrame) {
+      positionState.visit(setInlineFrame);
+    }
+
+    @Override
+    public void visit(DexDebugEvent.Default defaultEvent) {
+      positionState.visit(defaultEvent);
+      positionEventReceiver.receivePositionEvent(positionState);
+    }
+
+    @Override
+    public void visit(DexDebugEvent.SetFile setFile) {
+      positionState.visit(setFile);
+    }
+  }
+
+  // PositionRemapper is a stateful function which takes a position (represented by a
+  // DexDebugPositionState) and returns a remapped Position.
+  private interface PositionRemapper {
+    Position createRemappedPosition(DexDebugPositionState positionState);
+  }
+
+  private static class IdentityPositionRemapper implements PositionRemapper {
+    @Override
+    public Position createRemappedPosition(DexDebugPositionState positionState) {
+      return new Position(
+          positionState.getCurrentLine(),
+          positionState.getCurrentFile(),
+          positionState.getCurrentMethod(),
+          positionState.getCurrentCallerPosition());
+    }
+  }
+
+  private static class OptimizingPositionRemapper implements PositionRemapper {
+    private int nextLineNumber = 1;
+
+    @Override
+    public Position createRemappedPosition(DexDebugPositionState positionState) {
+      Position newPosition =
+          new Position(
+              nextLineNumber,
+              positionState.getCurrentFile(),
+              positionState.getCurrentMethod(),
+              null);
+      ++nextLineNumber;
+      return newPosition;
+    }
+  }
+
+  // PositionEventEmitter is a stateful function which converts a Position into series of
+  // position-related DexDebugEvents and puts them into a processedEvents list.
+  private static class PositionEventEmitter {
+    private final DexItemFactory dexItemFactory;
+    private int startLine = -1;
+    private DexMethod method;
+    private int previousPc = DexDebugEventBuilder.NO_PC_INFO;
+    private Position previousPosition = null;
+    private List<DexDebugEvent> processedEvents;
+
+    private PositionEventEmitter(
+        DexItemFactory dexItemFactory, DexMethod method, List<DexDebugEvent> processedEvents) {
+      this.dexItemFactory = dexItemFactory;
+      this.method = method;
+      this.processedEvents = processedEvents;
+    }
+
+    private void emitPositionEvents(int currentPc, Position currentPosition) {
+      if (previousPosition == null) {
+        startLine = currentPosition.line;
+        previousPosition = new Position(startLine, null, method, null);
+      }
+      DexDebugEventBuilder.emitAdvancementEvents(
+          previousPc,
+          previousPosition,
+          currentPc,
+          currentPosition,
+          processedEvents,
+          dexItemFactory);
+      previousPc = currentPc;
+      previousPosition = currentPosition;
+    }
+
+    private int getStartLine() {
+      assert (startLine >= 0);
+      return startLine;
+    }
+  }
+
+  public static void run(
+      DexApplication application, NamingLens namingLens, boolean identityMapping) {
+    IdentityHashMap<DexString, List<DexProgramClass>> classesOfFiles = new IdentityHashMap<>();
+    // Collect which files contain which classes that need to have their line numbers optimized.
+    for (DexProgramClass clazz : application.classes()) {
+
+      // TODO(tamaskenez) fix b/69356670 and remove the conditional skipping.
+      if (!clazz.getSynthesizedFrom().isEmpty()) {
+        continue;
+      }
+
+      // Group methods by name
+      IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
+          new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
+      clazz.forEachMethod(
+          method -> {
+            if (doesContainPositions(method)) {
+              methodsByName.compute(
+                  method.method.name,
+                  (name, methods) -> {
+                    if (methods == null) {
+                      methods = new ArrayList<>();
+                    }
+                    methods.add(method);
+                    return methods;
+                  });
+            }
+          });
+      for (List<DexEncodedMethod> methods : methodsByName.values()) {
+        if (methods.size() > 1) {
+          // If there are multiple methods with the same name (overloaded) then sort them for
+          // deterministic behaviour: the algorithm will assign new line numbers in this order.
+          // Methods with different names can share the same line numbers, that's why they don't
+          // need to be sorted.
+          methods.sort(
+              (lhs, rhs) -> {
+                int startLineDiff =
+                    lhs.getCode().asDexCode().getDebugInfo().startLine
+                        - rhs.getCode().asDexCode().getDebugInfo().startLine;
+                if (startLineDiff != 0) return startLineDiff;
+                return DexEncodedMethod.slowCompare(lhs, rhs);
+              });
+        }
+
+        PositionRemapper positionRemapper =
+            identityMapping ? new IdentityPositionRemapper() : new OptimizingPositionRemapper();
+
+        for (DexEncodedMethod method : methods) {
+          // Do the actual processing for each method.
+          DexCode dexCode = method.getCode().asDexCode();
+          DexDebugInfo debugInfo = dexCode.getDebugInfo();
+          List<DexDebugEvent> processedEvents = new ArrayList<>();
+
+          // Our pipeline will be:
+          // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
+          // [processedEvents]
+          PositionEventEmitter positionEventEmitter =
+              new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
+
+          EventFilter eventFilter =
+              new EventFilter(
+                  debugInfo.startLine,
+                  method.method,
+                  processedEvents::add,
+                  positionState -> {
+                    Position position = positionRemapper.createRemappedPosition(positionState);
+                    positionEventEmitter.emitPositionEvents(positionState.getCurrentPc(), position);
+                  });
+          for (DexDebugEvent event : debugInfo.events) {
+            event.accept(eventFilter);
+          }
+
+          DexDebugInfo optimizedDebugInfo =
+              new DexDebugInfo(
+                  positionEventEmitter.getStartLine(),
+                  debugInfo.parameters,
+                  processedEvents.toArray(new DexDebugEvent[processedEvents.size()]));
+
+          // TODO(tamaskenez) Remove this as soon as we have external tests testing not only the
+          // remapping but whether the non-positional debug events remain intact.
+          if (identityMapping) {
+            assert (optimizedDebugInfo.startLine == debugInfo.startLine);
+            assert (optimizedDebugInfo.events.length == debugInfo.events.length);
+            for (int i = 0; i < debugInfo.events.length; ++i) {
+              assert optimizedDebugInfo.events[i].equals(debugInfo.events[i]);
+            }
+          }
+          dexCode.setDebugInfo(optimizedDebugInfo);
+        }
+      }
+    }
+  }
+
+  // Return true if any of the methods' debug infos describe a Position which lists the containing
+  // method as the outermost caller.
+  private static boolean checkMethodsForSelfReferenceInPositions(DexEncodedMethod[] methods) {
+    for (DexEncodedMethod method : methods) {
+      if (!doesContainPositions(method)) {
+        continue;
+      }
+      DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo();
+
+      DexDebugPositionState positionState =
+          new DexDebugPositionState(debugInfo.startLine, method.method);
+      for (DexDebugEvent event : debugInfo.events) {
+        event.accept(positionState);
+        if (event instanceof DexDebugEvent.Default) {
+          Position caller = positionState.getCurrentCallerPosition();
+          DexMethod outermostMethod =
+              caller == null
+                  ? positionState.getCurrentMethod()
+                  : caller.getOutermostCaller().method;
+          if (outermostMethod == method.method) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  private static boolean doesContainPositions(DexEncodedMethod method) {
+    Code code = method.getCode();
+    if (code == null || !code.isDexCode()) {
+      return false;
+    }
+    DexDebugInfo debugInfo = code.asDexCode().getDebugInfo();
+    if (debugInfo == null) {
+      return false;
+    }
+    for (DexDebugEvent event : debugInfo.events) {
+      if (event instanceof DexDebugEvent.Default) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java b/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
index cc9c1e3..e64ebfb 100644
--- a/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
+++ b/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.Resource;
+import com.android.tools.r8.origin.Origin;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
index 01ea7ba..ec5fccb 100644
--- a/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.Resource;
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.HashMap;
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
index 2cce6da..f2290b3 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
@@ -8,8 +8,8 @@
 import static com.android.tools.r8.utils.FileUtils.isDexFile;
 
 import com.android.tools.r8.Resource;
-import com.android.tools.r8.Resource.Origin;
-import com.android.tools.r8.Resource.PathOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.google.common.io.ByteStreams;
diff --git a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
index d2c6560..cd5bbcd 100644
--- a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.Resource.Origin;
+import com.android.tools.r8.origin.Origin;
 
 public class StringDiagnostic implements Diagnostic {
 
diff --git a/src/main/java/com/android/tools/r8/utils/VersionProperties.java b/src/main/java/com/android/tools/r8/utils/VersionProperties.java
index 28ac2ef..d384134 100644
--- a/src/main/java/com/android/tools/r8/utils/VersionProperties.java
+++ b/src/main/java/com/android/tools/r8/utils/VersionProperties.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.utils;
 
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Properties;
@@ -26,8 +25,11 @@
   private String releaser;
 
   private static VersionProperties get() {
-    try {
-      return new VersionProperties(VersionProperties.class.getClassLoader());
+    ClassLoader loader = VersionProperties.class.getClassLoader();
+    try (InputStream resourceStream = loader.getResourceAsStream(RESOURCE_NAME)) {
+      return resourceStream == null
+          ? new VersionProperties()
+          : new VersionProperties(resourceStream);
     } catch (IOException e) {
       return new VersionProperties();
     }
@@ -36,19 +38,9 @@
   private VersionProperties() {
   }
 
-  private VersionProperties(ClassLoader loader)
-      throws IOException {
-    try (InputStream resourceStream = loader.getResourceAsStream(RESOURCE_NAME)) {
-      if (resourceStream == null) {
-        throw new FileNotFoundException(RESOURCE_NAME);
-      }
-      initWithInputStream(resourceStream);
-    }
-  }
-
-  private void initWithInputStream(InputStream is) throws IOException {
+  private VersionProperties(InputStream resourceStream) throws IOException {
     Properties prop = new Properties();
-    prop.load(is);
+    prop.load(resourceStream);
 
     long versionFileVersion = Long.parseLong(prop.getProperty(VERSION_CODE_KEY));
     assert versionFileVersion >= 1;
diff --git a/src/test/debugTestResources/Inlining2.java b/src/test/debugTestResources/Inlining2.java
index 38f854c..a570ba3 100644
--- a/src/test/debugTestResources/Inlining2.java
+++ b/src/test/debugTestResources/Inlining2.java
@@ -2,6 +2,31 @@
 // 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.
 
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+
 public class Inlining2 {
   public static void inlineThisFromAnotherFile() {
     System.out.println("inlineThisFromAnotherFile");
diff --git a/src/test/debugTestResources/LineNumberOptimization1.java b/src/test/debugTestResources/LineNumberOptimization1.java
new file mode 100644
index 0000000..a97fc6d
--- /dev/null
+++ b/src/test/debugTestResources/LineNumberOptimization1.java
@@ -0,0 +1,24 @@
+// 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.
+
+public class LineNumberOptimization1 {
+  private static void callThisFromSameFile() {
+    System.out.println("callThisFromSameFile");
+    LineNumberOptimization2.callThisFromAnotherFile();
+  }
+
+  private static void callThisFromSameFile(int a) {
+    System.out.println("callThisFromSameFile second overload");
+  }
+
+  private static void callThisFromSameFile(int a, int b) {
+    System.out.println("callThisFromSameFile third overload");
+  }
+
+  public static void main(String[] args) {
+    callThisFromSameFile();
+    callThisFromSameFile(1);
+    callThisFromSameFile(1, 2);
+  }
+}
diff --git a/src/test/debugTestResources/LineNumberOptimization2.java b/src/test/debugTestResources/LineNumberOptimization2.java
new file mode 100644
index 0000000..0f07ae9
--- /dev/null
+++ b/src/test/debugTestResources/LineNumberOptimization2.java
@@ -0,0 +1,30 @@
+// 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.
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+public class LineNumberOptimization2 {
+  public static void callThisFromAnotherFile() {
+    System.out.println("callThisFromAnotherFile");
+  }
+}
diff --git a/src/test/examples/enclosingmethod/OuterClass.java b/src/test/examples/enclosingmethod/OuterClass.java
index d9ab4ba..387f233 100644
--- a/src/test/examples/enclosingmethod/OuterClass.java
+++ b/src/test/examples/enclosingmethod/OuterClass.java
@@ -4,11 +4,34 @@
 package enclosingmethod;
 
 public class OuterClass {
+  // Named member class.
   public class AClass {
 
   }
 
+  static {
+    // Named local class. Will have an enclosing-method annotation with a zero method by being
+    // defined in the static initializer.
+    class LocalClass extends AbstractClass {
+
+      @Override
+      public int anInt() {
+        return 7;
+      }
+    }
+
+    // Anonymous inner class. Will have the same zero-method enclosing-method annotation.
+    print(new AbstractClass() {
+      @Override
+      public int anInt() {
+        return 42;
+      }
+    });
+    print(new LocalClass());
+  }
+
   public void aMethod() {
+    // Local class with a non-zero-method enclosing-method annotation.
     class AnotherClass extends AbstractClass {
 
       @Override
@@ -17,6 +40,7 @@
       }
     }
 
+    // Anonymous inner class with a non-zero-method enclosing-method annotation.
     print(new AbstractClass() {
       @Override
       public int anInt() {
diff --git a/src/test/examples/enclosingmethod_proguarded/AbstractClass.java b/src/test/examples/enclosingmethod_proguarded/AbstractClass.java
new file mode 100644
index 0000000..e9fce35
--- /dev/null
+++ b/src/test/examples/enclosingmethod_proguarded/AbstractClass.java
@@ -0,0 +1,8 @@
+// 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 enclosingmethod_proguarded;
+
+public abstract class AbstractClass {
+  public abstract int anInt();
+}
diff --git a/src/test/examples/enclosingmethod_proguarded/Main.java b/src/test/examples/enclosingmethod_proguarded/Main.java
new file mode 100644
index 0000000..93b4c9f
--- /dev/null
+++ b/src/test/examples/enclosingmethod_proguarded/Main.java
@@ -0,0 +1,15 @@
+// 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 enclosingmethod_proguarded;
+
+public class Main {
+  public static void main(String... args) {
+    OuterClass anInstance = new OuterClass();
+    anInstance.aMethod();
+    final Class[] classes = OuterClass.class.getDeclaredClasses();
+    for (Class clazz : classes) {
+      System.out.println("InnerClass " + clazz.getName());
+    }
+  }
+}
diff --git a/src/test/examples/enclosingmethod_proguarded/OuterClass.java b/src/test/examples/enclosingmethod_proguarded/OuterClass.java
new file mode 100644
index 0000000..84deaad
--- /dev/null
+++ b/src/test/examples/enclosingmethod_proguarded/OuterClass.java
@@ -0,0 +1,37 @@
+// 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 enclosingmethod_proguarded;
+
+public class OuterClass {
+  public class AClass {
+
+  }
+
+  public void aMethod() {
+    class AnotherClass extends AbstractClass {
+
+      @Override
+      public int anInt() {
+        return 48;
+      }
+    }
+
+    print(new AbstractClass() {
+      @Override
+      public int anInt() {
+        return 42;
+      }
+    });
+    print(new AnotherClass());
+  }
+
+  private static void print(AbstractClass anInstance) {
+    System.out.println(anInstance.anInt());
+    System.out.println(anInstance.getClass().getEnclosingClass());
+    System.out.println(anInstance.getClass().getEnclosingMethod());
+    System.out.println(anInstance.getClass().isAnonymousClass());
+    System.out.println(anInstance.getClass().isLocalClass());
+    System.out.println(anInstance.getClass().isMemberClass());
+  }
+}
diff --git a/src/test/examples/enclosingmethod/proguard.cfg b/src/test/examples/enclosingmethod_proguarded/proguard.cfg
similarity index 87%
rename from src/test/examples/enclosingmethod/proguard.cfg
rename to src/test/examples/enclosingmethod_proguarded/proguard.cfg
index bc5bd8d..12d9b6b 100644
--- a/src/test/examples/enclosingmethod/proguard.cfg
+++ b/src/test/examples/enclosingmethod_proguarded/proguard.cfg
@@ -2,7 +2,7 @@
 # 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 class enclosingmethod.* {
+-keep class enclosingmethod_proguarded.* {
   *;
 }
 
diff --git a/src/test/examples/loop/UdpServer.java b/src/test/examples/loop/UdpServer.java
new file mode 100644
index 0000000..463bfde
--- /dev/null
+++ b/src/test/examples/loop/UdpServer.java
@@ -0,0 +1,39 @@
+// 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 loop;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class UdpServer {
+  private static final String PREFIX = "RANDOM_DATA_PREFIX_";
+  public static void main(String[] args) throws Exception {
+    ExecutorService service = Executors.newWorkStealingPool(2);
+    Callable c = new Callable() {
+      @Override
+      public Object call() throws Exception {
+        int counter = 0;
+        byte[] receiveData = new byte[1024];
+        while (true) {
+          // Mimic receiving data via socket. (A use of actual socket is IO blocking.)
+          receiveData = (PREFIX + counter++).getBytes();
+        }
+      }
+    };
+    Future<?> f = service.submit(c);
+    try {
+      f.get(1, TimeUnit.NANOSECONDS);
+    } catch (ExecutionException | InterruptedException | TimeoutException e) {
+      System.out.println(e);
+    } finally {
+      f.cancel(true);
+      service.shutdownNow();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 3485c65..e0aeaeb 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -64,6 +64,7 @@
           .put("floating_point_annotations.FloatingPointValuedAnnotationTest", match(R8_COMPILER))
           .put("regress_62300145.Regress", match(R8_COMPILER)) // npe
           .put("enclosingmethod.Main", match(R8_COMPILER)) // output differs
+          .put("enclosingmethod_proguarded.Main", match(R8_COMPILER)) // output differs
           .build();
 
   private static final Set<String> failingCompileCf =
@@ -76,6 +77,10 @@
       new ImmutableMap.Builder<String, TestCondition>()
           // Traverses stack frames that contain Art specific frames.
           .put("throwing.Throwing", TestCondition.any())
+          // DEX inner-classes annotations can't distinguish member classes from local classes.
+          // This results in Class.isLocalClass always being false and Class.isMemberClass always
+          // being true even when the converse is the case when running on the JVM.
+          .put("enclosingmethod.Main", TestCondition.any())
           // Early art versions incorrectly print Float.MIN_VALUE.
           .put(
               "filledarray.FilledArray",
@@ -103,6 +108,7 @@
         "invoke.Invoke",
         "jumbostring.JumboString",
         "loadconst.LoadConst",
+        "loop.UdpServer",
         "newarray.NewArray",
         "regalloc.RegAlloc",
         "returns.Returns",
@@ -129,6 +135,7 @@
         "memberrebinding3.Memberrebinding",
         "minification.Minification",
         "enclosingmethod.Main",
+        "enclosingmethod_proguarded.Main",
         "interfaceinlining.Main",
         "switchmaps.Switches",
     };
diff --git a/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
index 3222734..883a61f 100644
--- a/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
+++ b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
@@ -38,8 +38,8 @@
 public class CompatDxTests {
   private static final int MAX_METHOD_COUNT = Constants.U16BIT_MAX;
 
-  private static final String EXAMPLE_JAR_FILE1 = "build/test/examples/arithmetic.jar";
-  private static final String EXAMPLE_JAR_FILE2 = "build/test/examples/barray.jar";
+  private static final String EXAMPLE_JAR_FILE1 = ToolHelper.EXAMPLES_BUILD_DIR + "arithmetic.jar";
+  private static final String EXAMPLE_JAR_FILE2 = ToolHelper.EXAMPLES_BUILD_DIR + "barray.jar";
 
   private static final String NO_LOCALS = "--no-locals";
   private static final String NO_POSITIONS = "--positions=none";
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
index 168b794..f6e1ef0 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -5,36 +5,33 @@
 
 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.CompilationException;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.InvokeStatic;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
+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.io.IOException;
 import java.nio.file.Path;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
-import org.antlr.runtime.RecognitionException;
 import org.junit.Test;
 
 public class ForNameTest extends SmaliTestBase {
 
+  private final String CLASS_NAME = "Example";
   private final static String BOO = "Boo";
 
   @Test
   public void forName_renamed() throws Exception {
-    SmaliBuilder builder = new SmaliBuilder("Example");
-    MethodSignature main = builder.addMainMethod(
+    SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
+    builder.addMainMethod(
         1,
         "const-string v0, \"" + BOO + "\"",
         "invoke-static {v0}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;",
@@ -44,16 +41,19 @@
     builder.addClass(BOO);
 
     List<String> pgConfigs = ImmutableList.of(
-        "-keep class Example { *; }",
+        "-keep class " + CLASS_NAME + " { *; }",
         "-keep,allowobfuscation class " + BOO,
         "-dontshrink",
         "-dontoptimize");
-    Path processedApp = runCompatProguard(builder, pgConfigs);
 
-    DexEncodedMethod method = getMethod(processedApp, main);
-    assertNotNull(method);
+    DexInspector inspector = runCompatProguard(builder, pgConfigs);
 
-    DexCode code = method.getCode().asDexCode();
+    ClassSubject clazz = inspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    MethodSubject method = clazz.method(DexInspector.MAIN);
+    assertTrue(method.isPresent());
+
+    DexCode code = method.getMethod().getCode().asDexCode();
     // TODO(b/36799092): DeadCodeRemover should be able to remove this instruction.
     assertTrue(code.instructions[0] instanceof ConstString);
     ConstString constString = (ConstString) code.instructions[0];
@@ -65,20 +65,49 @@
     assertTrue(code.instructions[3] instanceof ReturnVoid);
   }
 
-  private Path runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations) {
-    try {
-      Path dexOutputDir = temp.newFolder().toPath();
-      R8Command command =
-          new CompatProguardCommandBuilder(true, true)
-              .addDexProgramData(builder.compile())
-              .setOutputPath(dexOutputDir)
-              .addProguardConfiguration(proguardConfigurations)
-              .build();
-      ToolHelper.runR8(command);
-      return dexOutputDir.resolve("classes.dex");
-    } catch (CompilationException | IOException | RecognitionException | ExecutionException e) {
-      throw new RuntimeException(e);
-    }
+  @Test
+  public void forName_noMinification() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
+    builder.addMainMethod(
+        1,
+        "const-string v0, \"" + BOO + "\"",
+        "invoke-static {v0}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;",
+        "move-result-object v0",
+        "return-void");
+
+    builder.addClass(BOO);
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-keep class " + CLASS_NAME +" { *; }",
+        "-keep class " + BOO,
+        "-dontshrink",
+        "-dontoptimize",
+        "-dontobfuscate");
+
+    DexInspector inspector = runCompatProguard(builder, pgConfigs);
+
+    ClassSubject clazz = inspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    MethodSubject method = clazz.method(DexInspector.MAIN);
+    assertTrue(method.isPresent());
+
+    DexCode code = method.getMethod().getCode().asDexCode();
+    assertTrue(code.instructions[0] instanceof ConstString);
+    ConstString constString = (ConstString) code.instructions[0];
+    assertEquals(BOO, constString.getString().toString());
+    assertTrue(code.instructions[1] instanceof InvokeStatic);
+    assertTrue(code.instructions[2] instanceof ReturnVoid);
   }
 
+  private DexInspector runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations)
+      throws Exception{
+    Path dexOutputDir = temp.newFolder().toPath();
+    R8Command command =
+        new CompatProguardCommandBuilder(true, true)
+            .addDexProgramData(builder.compile())
+            .setOutputPath(dexOutputDir)
+            .addProguardConfiguration(proguardConfigurations)
+            .build();
+    return new DexInspector(ToolHelper.runR8(command));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index d8649c7..2d109e3 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -4,7 +4,11 @@
 package com.android.tools.r8.debug;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -12,33 +16,64 @@
 public class DebugInfoWhenInliningTest extends DebugTestBase {
 
   public static final String SOURCE_FILE = "Inlining1.java";
-  private static DebuggeePath debuggeePath;
+  private static DebuggeePath debuggeePathNotOptimized;
+  private static DebuggeePath debuggeePathOptimized;
+
+  private static DebuggeePath makeDex(LineNumberOptimization lineNumberOptimization)
+      throws Exception {
+    return DebuggeePath.makeDex(
+        compileToDexViaR8(
+            oc -> {
+              oc.lineNumberOptimization = lineNumberOptimization;
+            },
+            null,
+            DEBUGGEE_JAR,
+            Collections.<String>emptyList(),
+            true,
+            CompilationMode.RELEASE));
+  }
 
   @BeforeClass
   public static void initDebuggeePath() throws Exception {
-    debuggeePath =
-        DebuggeePath.makeDex(
-            compileToDexViaR8(
-                null,
-                null,
-                DEBUGGEE_JAR,
-                Collections.<String>emptyList(),
-                true,
-                CompilationMode.RELEASE));
+    debuggeePathNotOptimized = makeDex(LineNumberOptimization.OFF);
+    debuggeePathOptimized = makeDex(LineNumberOptimization.ON);
   }
 
   @Test
-  public void testEachLine() throws Throwable {
+  public void testEachLineNotOptimized() throws Throwable {
+    // The reason why the not-optimized test contains half as many line numbers as the optimized
+    // one:
+    //
+    // In the Java source (Inlining1) each call is duplicated. Since they end up on the same line
+    // (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
+    // are emitted duplicated in the dex code, the debugger stops only when there's a change.
+    int[] lineNumbers = {7, 32, 11, 7};
+    testEachLine(debuggeePathNotOptimized, lineNumbers);
+  }
+
+  @Test
+  public void testEachLineOptimized() throws Throwable {
+    int[] lineNumbers = {1, 2, 3, 4, 5, 6, 7, 8};
+    testEachLine(debuggeePathOptimized, lineNumbers);
+  }
+
+  private void testEachLine(DebuggeePath debuggeePath, int[] lineNumbers) throws Throwable {
     final String className = "Inlining1";
-    final String methodName = "main";
-    final String signature = "([Ljava/lang/String;)V";
-    runDebugTest(
-        debuggeePath,
-        className,
-        breakpoint(className, methodName, signature),
-        run(),
-        checkMethod(className, methodName, signature),
-        // TODO(tamaskenez) to be continued as the feature is implemented in class Inliner
-        run());
+    final String mainSignature = "([Ljava/lang/String;)V";
+    List<Command> commands = new ArrayList<Command>();
+    commands.add(breakpoint(className, "main", mainSignature));
+    commands.add(run());
+    boolean first = true;
+    for (int i : lineNumbers) {
+      if (first) {
+        first = false;
+      } else {
+        commands.add(stepOver());
+      }
+      commands.add(checkMethod(className, "main", mainSignature));
+      commands.add(checkLine(SOURCE_FILE, i));
+    }
+    commands.add(run());
+    runDebugTest(debuggeePath, className, commands);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
new file mode 100644
index 0000000..74a46ab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -0,0 +1,103 @@
+// 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.debug;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import java.util.Collections;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Tests source file and line numbers on inlined methods. */
+public class LineNumberOptimizationTest extends DebugTestBase {
+
+  public static final String SOURCE_FILE = "LineNumberOptimization1.java";
+  private static DebuggeePath debuggeePathOptimized;
+  private static DebuggeePath debuggeePathNotOptimized;
+  private static DebuggeePath debuggeePathIdentityTest;
+
+  private static DebuggeePath makeDex(LineNumberOptimization lineNumberOptimization)
+      throws Exception {
+    return DebuggeePath.makeDex(
+        compileToDexViaR8(
+            oc -> {
+              oc.lineNumberOptimization = lineNumberOptimization;
+              oc.inlineAccessors = false;
+            },
+            null,
+            DEBUGGEE_JAR,
+            Collections.<String>emptyList(),
+            true,
+            CompilationMode.RELEASE));
+  }
+
+  @BeforeClass
+  public static void initDebuggeePath() throws Exception {
+    debuggeePathNotOptimized = makeDex(LineNumberOptimization.OFF);
+    debuggeePathOptimized = makeDex(LineNumberOptimization.ON);
+    debuggeePathIdentityTest = makeDex(LineNumberOptimization.IDENTITY_MAPPING);
+  }
+
+  @Test
+  public void testNotOptimized() throws Throwable {
+    int[] lineNumbers = {20, 7, 8, 28, 8, 20, 21, 12, 21, 22, 16, 22};
+    test(debuggeePathNotOptimized, lineNumbers);
+  }
+
+  @Test
+  public void testOptimized() throws Throwable {
+    int[] lineNumbers = {1, 1, 2, 1, 2, 1, 2, 3, 2, 3, 4, 3};
+    test(debuggeePathOptimized, lineNumbers);
+  }
+
+  private void test(DebuggeePath debuggeePath, int[] lineNumbers) throws Throwable {
+    final String class1 = "LineNumberOptimization1";
+    final String class2 = "LineNumberOptimization2";
+    final String file1 = class1 + ".java";
+    final String file2 = class2 + ".java";
+    final String mainSignature = "([Ljava/lang/String;)V";
+
+    runDebugTest(
+        debuggeePath,
+        class1,
+        breakpoint(class1, "main", mainSignature),
+        run(),
+        checkMethod(class1, "main", mainSignature),
+        checkLine(file1, lineNumbers[0]),
+        stepInto(),
+        checkMethod(class1, "callThisFromSameFile", "()V"),
+        checkLine(file1, lineNumbers[1]),
+        stepOver(),
+        checkMethod(class1, "callThisFromSameFile", "()V"),
+        checkLine(file1, lineNumbers[2]),
+        stepInto(INTELLIJ_FILTER),
+        checkMethod(class2, "callThisFromAnotherFile", "()V"),
+        checkLine(file2, lineNumbers[3]),
+        stepOver(),
+        checkMethod(class1, "callThisFromSameFile", "()V"),
+        checkLine(file1, lineNumbers[4]),
+        stepOver(),
+        checkMethod(class1, "main", mainSignature),
+        checkLine(file1, lineNumbers[5]),
+        stepOver(),
+        checkMethod(class1, "main", mainSignature),
+        checkLine(file1, lineNumbers[6]),
+        stepInto(),
+        checkMethod(class1, "callThisFromSameFile", "(I)V"),
+        checkLine(file1, lineNumbers[7]),
+        stepOver(),
+        checkMethod(class1, "main", mainSignature),
+        checkLine(file1, lineNumbers[8]),
+        stepOver(),
+        checkMethod(class1, "main", mainSignature),
+        checkLine(file1, lineNumbers[9]),
+        stepInto(),
+        checkMethod(class1, "callThisFromSameFile", "(II)V"),
+        checkLine(file1, lineNumbers[10]),
+        stepOver(),
+        checkMethod(class1, "main", mainSignature),
+        checkLine(file1, lineNumbers[11]),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 94f53f4..23fc147 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -5,8 +5,8 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.getPathFromDescriptor;
 
-import com.android.tools.r8.Resource.Origin;
-import com.android.tools.r8.Resource.PathOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -84,6 +84,7 @@
     private boolean makeInit = false;
     private boolean hasInit = false;
     private boolean isInterface = false;
+    private String access = "public";
 
     private ClassBuilder(String name) {
       this(name, "java/lang/Object");
@@ -135,6 +136,14 @@
       return addMethod("public static", name, argumentTypes, returnType, lines);
     }
 
+    public MethodSignature addPackagePrivateStaticMethod(
+        String name,
+        List<String> argumentTypes,
+        String returnType,
+        String... lines) {
+      return addMethod("static", name, argumentTypes, returnType, lines);
+    }
+
     public MethodSignature addMainMethod(String... lines) {
       return addStaticMethod("main", ImmutableList.of("[Ljava/lang/String;"), "V", lines);
     }
@@ -187,7 +196,7 @@
       if (isInterface) {
         builder.append(" interface abstract");
       }
-      builder.append(" public ").append(name).append('\n');
+      builder.append(" ").append(access).append(" ").append(name).append('\n');
       builder.append(".super ").append(superName).append('\n');
       for (String iface : interfaces) {
         builder.append(".implements ").append(iface).append('\n');
@@ -215,6 +224,10 @@
       isInterface = true;
     }
 
+    void setAccess(String access) {
+      this.access = access;
+    }
+
     public MethodSignature addDefaultConstructor() {
       assert !hasInit;
       hasInit = true;
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 3fa67af..7576912 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -19,6 +19,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -39,6 +40,17 @@
     return ToolHelper.runJavaNoVerify(ImmutableList.of(out.toString()), main);
   }
 
+  protected ProcessResult runOnJavaNoVerifyRaw(JasminBuilder program, JasminBuilder library,
+      String main)
+      throws Exception {
+    Path out = temp.newFolder().toPath();
+    program.writeClassFiles(out);
+    Path libraryOut = temp.newFolder().toPath();
+    library.writeClassFiles(libraryOut);
+    return ToolHelper.runJavaNoVerify(ImmutableList.of(out.toString(), libraryOut.toString()),
+        main);
+  }
+
   private String assertNormalExitAndGetStdout(ProcessResult result) {
     if (result.exitCode != 0) {
       System.out.println("Std out:");
@@ -71,6 +83,11 @@
     return runOnArtRaw(compileWithD8(builder), main);
   }
 
+  protected ProcessResult runOnArtD8Raw(JasminBuilder program, JasminBuilder library, String main)
+      throws Exception {
+    return runOnArtRaw(compileWithD8(program), compileWithD8(library), main);
+  }
+
   protected AndroidApp compileWithR8(JasminBuilder builder) throws Exception {
     return compileWithR8(builder, null);
   }
@@ -86,11 +103,34 @@
       throws Exception {
     R8Command command =
         ToolHelper.prepareR8CommandBuilder(builder.build())
+            .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
             .addProguardConfiguration(ImmutableList.of(proguardConfig))
             .build();
     return ToolHelper.runR8(command, optionsConsumer);
   }
 
+  protected AndroidApp compileWithR8(JasminBuilder program, Path library,
+      Consumer<InternalOptions> optionsConsumer)
+      throws Exception {
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(program.build())
+            .addLibraryFiles(library)
+            .build();
+    return ToolHelper.runR8(command, optionsConsumer);
+  }
+
+  protected AndroidApp compileWithR8(JasminBuilder program, Path library,
+      String proguardConfig, Consumer<InternalOptions> optionsConsumer)
+      throws Exception {
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(program.build())
+            .addProguardConfiguration(ImmutableList.of(proguardConfig))
+            .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
+            .addLibraryFiles(library)
+            .build();
+    return ToolHelper.runR8(command, optionsConsumer);
+  }
+
   protected String runOnArtR8(JasminBuilder builder, String main) throws Exception {
     return runOnArtR8(builder, main, null);
   }
@@ -116,13 +156,25 @@
     return runOnArtRaw(result, main);
   }
 
-  protected ProcessResult runOnArtR8Raw(JasminBuilder builder, String main, String proguardConfig,
-      Consumer<InternalOptions> optionsConsumer)
+  protected ProcessResult runOnArtR8Raw(JasminBuilder builder, String main,
+      String proguardConfig, Consumer<InternalOptions> optionsConsumer)
       throws Exception {
     AndroidApp result = compileWithR8(builder, proguardConfig, optionsConsumer);
     return runOnArtRaw(result, main);
   }
 
+  protected ProcessResult runOnArtR8Raw(JasminBuilder program, JasminBuilder library, String main,
+      String proguardConfig, Consumer<InternalOptions> optionsConsumer)
+      throws Exception {
+    Path libraryClasses = temp.newFolder().toPath();
+    library.writeClassFiles(libraryClasses);
+    AndroidApp result = proguardConfig == null
+        ? compileWithR8(program, libraryClasses, optionsConsumer)
+        : compileWithR8(program, libraryClasses, proguardConfig, optionsConsumer);
+    AndroidApp libraryApp = compileWithR8(library);
+    return runOnArtRaw(result, libraryApp, main);
+  }
+
   private ProcessResult runDx(JasminBuilder builder, File classes, Path dex) throws Exception {
     builder.writeClassFiles(classes.toPath());
     List<String> args = new ArrayList<>();
@@ -151,12 +203,33 @@
     return ToolHelper.runArtRaw(dex.toString(), main);
   }
 
+  protected ProcessResult runOnArtDxRaw(JasminBuilder program, JasminBuilder library, String main)
+      throws Exception {
+    Path dex = temp.getRoot().toPath().resolve("classes.dex");
+    ProcessResult result = runDx(program, temp.newFolder("classes_for_dx"), dex);
+    assertNormalExitAndGetStdout(result);
+    Path libraryDex = temp.getRoot().toPath().resolve("library.dex");
+    result = runDx(library, temp.newFolder("classes_for_library_dx"), libraryDex);
+    assertNormalExitAndGetStdout(result);
+    return ToolHelper.runArtRaw(ImmutableList.of(dex.toString(), libraryDex.toString()), main, null);
+  }
+
   protected ProcessResult runOnArtRaw(AndroidApp app, String main) throws IOException {
     Path out = temp.getRoot().toPath().resolve("out.zip");
     app.writeToZip(out, OutputMode.Indexed);
     return ToolHelper.runArtRaw(out.toString(), main);
   }
 
+  protected ProcessResult runOnArtRaw(AndroidApp program, AndroidApp library, String main)
+      throws IOException {
+    Path out = temp.getRoot().toPath().resolve("out.zip");
+    program.writeToZip(out, OutputMode.Indexed);
+    Path libraryOut = temp.getRoot().toPath().resolve("libraryOut.zip");
+    library.writeToZip(libraryOut, OutputMode.Indexed);
+    return ToolHelper.runArtRaw(ImmutableList.of(out.toString(), libraryOut.toString()), main,
+        null);
+  }
+
   protected String runOnArt(AndroidApp app, String main) throws IOException {
     Path out = temp.getRoot().toPath().resolve("out.zip");
     app.writeToZip(out, OutputMode.Indexed);
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index fb1672f..e122f9b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -201,7 +201,7 @@
         "  invokespecial SubClass/<init>()V",
         "  invokevirtual SubClass/aMethod()V",
         "  return");
-    ensureIAEExceptJava(builder);
+    ensureIAE(builder);
   }
 
   @Test
@@ -238,7 +238,7 @@
         "  invokespecial SubClass/<init>()V",
         "  invokespecial SubClass/aMethod()V",
         "  return");
-    ensureIAEExceptJava(builder);
+    ensureIAE(builder);
   }
 
   @Test
@@ -275,7 +275,7 @@
         "  invokevirtual SubClass/callAMethod()V",
         "  return");
 
-    ensureIAEExceptJava(builder);
+    ensureIAE(builder);
   }
 
   @Test
@@ -352,6 +352,111 @@
     ensureICCE(builder);
   }
 
+  @Test
+  public void testRebindVirtualCallToStatic() throws Exception {
+    // Library classes.
+    JasminBuilder libraryBuilder = new JasminBuilder();
+    ClassBuilder classWithStatic = libraryBuilder.addClass("ClassWithStatic");
+    classWithStatic.addDefaultConstructor();
+    classWithStatic.addStaticMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 42",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+
+    // Program classes.
+    JasminBuilder programBuilder = new JasminBuilder();
+    programBuilder.addClass("SubClass", "ClassWithStatic")
+        .addDefaultConstructor();
+    programBuilder.addClass("SubSubClass", "SubClass")
+        .addDefaultConstructor();
+    ClassBuilder mainClass = programBuilder.addClass("Main");
+    mainClass.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  new SubSubClass",
+        "  dup",
+        "  invokespecial SubSubClass/<init>()V",
+        "  invokevirtual SubSubClass/aMethod()V",
+        "  return");
+
+    ensureICCE(programBuilder, libraryBuilder);
+  }
+
+  @Test
+  public void testRebindVirtualCallToPackagePrivateStatic() throws Exception {
+    // Library classes.
+    JasminBuilder libraryBuilder = new JasminBuilder();
+    ClassBuilder classWithStatic = libraryBuilder.addClass("ClassWithStatic");
+    classWithStatic.addDefaultConstructor();
+    classWithStatic.addPackagePrivateStaticMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 42",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+    libraryBuilder.addClass("p/LibSub", "ClassWithStatic")
+        .addDefaultConstructor();
+
+    // Program classes.
+    JasminBuilder programBuilder = new JasminBuilder();
+    programBuilder.addClass("p/SubClass", "p/LibSub")
+        .addDefaultConstructor();
+    programBuilder.addClass("SubSubClass", "p/SubClass")
+        .addDefaultConstructor();
+    ClassBuilder mainClass = programBuilder.addClass("Main");
+    mainClass.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  new SubSubClass",
+        "  dup",
+        "  invokespecial SubSubClass/<init>()V",
+        "  invokevirtual SubSubClass/aMethod()V",
+        "  return");
+
+    ensureICCE(programBuilder, libraryBuilder);
+  }
+
+  @Test
+  @Ignore("b/69356146")
+  public void testRebindVirtualCallToStaticInPackagePrivateClass() throws Exception {
+    // Library classes.
+    JasminBuilder libraryBuilder = new JasminBuilder();
+    ClassBuilder classWithStatic = libraryBuilder.addClass("ClassWithStatic");
+    classWithStatic.setAccess("");
+    classWithStatic.addDefaultConstructor();
+    classWithStatic.addStaticMethod("aMethod", emptyList(), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  bipush 42",
+        "  invokevirtual java/io/PrintStream/println(I)V",
+        "  return");
+    libraryBuilder.addClass("LibSub", "ClassWithStatic");
+    libraryBuilder.addClass("p/LibSubSub", "LibSub");
+
+    // Program classes.
+    JasminBuilder programBuilder = new JasminBuilder();
+    programBuilder.addClass("p/SubClass", "p/LibSubSub")
+        .addDefaultConstructor();
+    programBuilder.addClass("SubSubClass", "p/SubClass")
+        .addDefaultConstructor();
+    ClassBuilder mainClass = programBuilder.addClass("Main");
+    mainClass.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  new SubSubClass",
+        "  dup",
+        "  invokespecial SubSubClass/<init>()V",
+        "  invokevirtual SubSubClass/aMethod()V",
+        "  return");
+
+    ensureICCE(programBuilder, libraryBuilder);
+  }
+
   private void ensureSameOutput(JasminBuilder app) throws Exception {
     String javaOutput = runOnJava(app, MAIN_CLASS);
     String dxOutput = runOnArtDx(app, MAIN_CLASS);
@@ -369,11 +474,18 @@
     ensureRuntimeException(app, IncompatibleClassChangeError.class);
   }
 
-  private void ensureIAEExceptJava(JasminBuilder app)
-      throws Exception {
+  private void ensureICCE(JasminBuilder app, JasminBuilder library) throws Exception {
+    ensureRuntimeException(app, library, IncompatibleClassChangeError.class);
+  }
+
+  private void ensureIAE(JasminBuilder app) throws Exception {
     ensureRuntimeException(app, IllegalAccessError.class);
   }
 
+  private void ensureIAE(JasminBuilder app, JasminBuilder library) throws Exception {
+    ensureRuntimeException(app, library, IllegalAccessError.class);
+  }
+
   private void ensureRuntimeException(JasminBuilder app, Class exception) throws Exception {
     String name = exception.getSimpleName();
     ProcessResult dxOutput = runOnArtDxRaw(app, MAIN_CLASS);
@@ -388,4 +500,20 @@
     ProcessResult javaOutput = runOnJavaNoVerifyRaw(app, MAIN_CLASS);
     Assert.assertTrue(javaOutput.stderr.contains(name));
   }
+
+  private void ensureRuntimeException(JasminBuilder app, JasminBuilder library, Class exception)
+      throws Exception {
+    String name = exception.getSimpleName();
+    ProcessResult dxOutput = runOnArtDxRaw(app, library, MAIN_CLASS);
+    Assert.assertTrue(dxOutput.stderr.contains(name));
+    ProcessResult d8Output = runOnArtD8Raw(app, library, MAIN_CLASS);
+    Assert.assertTrue(d8Output.stderr.contains(name));
+    ProcessResult r8Output = runOnArtR8Raw(app, library, MAIN_CLASS, null, null);
+    Assert.assertTrue(r8Output.stderr.contains(name));
+    ProcessResult r8ShakenOutput = runOnArtR8Raw(app, library, MAIN_CLASS,
+        keepMainProguardConfiguration(MAIN_CLASS), null);
+    Assert.assertTrue(r8ShakenOutput.stderr.contains(name));
+    ProcessResult javaOutput = runOnJavaNoVerifyRaw(app, library, MAIN_CLASS);
+    Assert.assertTrue(javaOutput.stderr.contains(name));
+  }
 }