Ensure pc2pc encoding of debug info has non-zero line info.

Bug: b/235319568
Change-Id: I151b52dcfdbe269a43f0dec1d3d1f0fa198c63cf
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index 516abb9..dcc31f7 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -464,8 +464,12 @@
   }
 
   public static Instruction getLastExecutableInstruction(DexCode code) {
+    return getLastExecutableInstruction(code.instructions);
+  }
+
+  public static Instruction getLastExecutableInstruction(Instruction[] instructions) {
     Instruction lastInstruction = null;
-    for (Instruction instruction : code.instructions) {
+    for (Instruction instruction : instructions) {
       if (!instruction.isPayload()) {
         lastInstruction = instruction;
       }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 7a3ccf5..d7266cd 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InstructionFactory;
+import com.android.tools.r8.debuginfo.DebugRepresentation;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.ApplicationReaderMap;
 import com.android.tools.r8.graph.ClassAccessFlags;
@@ -129,6 +130,13 @@
   // Mapping from offset to dex item;
   private Int2ReferenceMap<Object> offsetMap = new Int2ReferenceOpenHashMap<>();
 
+  // Mapping from offset to cached debug info that is not pc2pc based.
+  // This is a secondary map that is used for each debug info item that structurally looks like
+  // a pc2pc encoding but which is referenced from methods that don't fit within the encoding.
+  // This can happen because the two overlap in representation.
+  private Int2ReferenceMap<EventBasedDebugInfo> nonPcBasedDebugInfo =
+      new Int2ReferenceOpenHashMap<>();
+
   // Factory to canonicalize certain dexitems.
   private final DexItemFactory dexItemFactory;
 
@@ -519,13 +527,36 @@
         parameters);
   }
 
-  private DexDebugInfo debugInfoAt(int offset) {
-    return (DexDebugInfo) cacheAt(offset, this::parseDebugInfo);
+  private DexDebugInfo debugInfoAt(int offset, Instruction[] instructions) {
+    DexDebugInfo debugInfo = (DexDebugInfo) cacheAt(offset, this::parseDebugInfoAllowPc2PcEncoding);
+    // If the debug information matches a pc2pc encoding check that the instructions are within
+    // the max-pc bound of this method. If not, the info is not an actual pc encoding. Re-read the
+    // info as a normal event based encoding (and cache it to preserve sharing).
+    if (debugInfo != null && debugInfo.isPcBasedInfo()) {
+      PcBasedDebugInfo pcBasedInfo = debugInfo.asPcBasedInfo();
+      int maxPc = pcBasedInfo.getMaxPc();
+      Instruction last = DebugRepresentation.getLastExecutableInstruction(instructions);
+      if (last.getOffset() > maxPc) {
+        return nonPcBasedDebugInfo.computeIfAbsent(
+            offset, this::parseDebugInfoDisallowPc2PcEncoding);
+      }
+    }
+    return debugInfo;
   }
 
-  private DexDebugInfo parseDebugInfo() {
+  private DexDebugInfo parseDebugInfoAllowPc2PcEncoding() {
+    return parseDebugInfo(true);
+  }
+
+  private EventBasedDebugInfo parseDebugInfoDisallowPc2PcEncoding(int offset) {
+    dexReader.position(offset);
+    EventBasedDebugInfo debugInfo = parseDebugInfo(false).asEventBasedInfo();
+    return debugInfo;
+  }
+
+  private DexDebugInfo parseDebugInfo(boolean allowPc2PcEncoding) {
     int start = dexReader.getUleb128();
-    boolean isPcBasedDebugInfo = start == 0;
+    boolean isPcBasedDebugInfo = allowPc2PcEncoding && start == PcBasedDebugInfo.START_LINE;
     int parametersSize = dexReader.getUleb128();
     DexString[] parameters = new DexString[parametersSize];
     for (int i = 0; i < parametersSize; i++) {
@@ -983,13 +1014,15 @@
         }
       }
     }
-    // Store and restore offset information around reading debug info.
-    int saved = dexReader.position();
-    DexDebugInfo debugInfo = debugInfoAt(debugInfoOff);
-    dexReader.position(saved);
     InstructionFactory factory = new InstructionFactory();
     Instruction[] instructions =
         factory.readSequenceFrom(ShortBuffer.wrap(code), 0, code.length, indexedItems);
+
+    // Store and restore offset information around reading debug info.
+    int saved = dexReader.position();
+    DexDebugInfo debugInfo = debugInfoAt(debugInfoOff, instructions);
+    dexReader.position(saved);
+
     return new DexCode(registerSize, insSize, outsSize, instructions, tries, handlers, debugInfo);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index 0b944c3..a7c9b0d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -85,7 +85,7 @@
   }
 
   public static class PcBasedDebugInfo extends DexDebugInfo implements DexDebugInfoForWriting {
-    private static final int START_LINE = 0;
+    public static final int START_LINE = 1;
     private final int parameterCount;
     private final int maxPc;
 
@@ -302,9 +302,15 @@
     assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
         code, pcBasedDebugInfo.maxPc);
     // Generate a line event at each throwing instruction.
-    List<DexDebugEvent> events = new ArrayList<>(code.instructions.length);
+    Instruction[] instructions = code.instructions;
+    return forceConvertToEventBasedDebugInfo(pcBasedDebugInfo, instructions, factory);
+  }
+
+  public static EventBasedDebugInfo forceConvertToEventBasedDebugInfo(
+      PcBasedDebugInfo pcBasedDebugInfo, Instruction[] instructions, DexItemFactory factory) {
+    List<DexDebugEvent> events = new ArrayList<>(instructions.length);
     int delta = 0;
-    for (Instruction instruction : code.instructions) {
+    for (Instruction instruction : instructions) {
       if (instruction.canThrow()) {
         DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary(delta, delta, events, factory);
         delta = 0;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index f75f98e..3cff666 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -371,6 +371,8 @@
      * items to be installed.
      */
     void updateDebugInfoInCodeObjects();
+
+    int getPcEncoding(int pc);
   }
 
   private static class Pc2PcMappingSupport implements PcBasedDebugInfoRecorder {
@@ -411,8 +413,10 @@
       singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null;
     }
 
-    private int getLastInstructionOffset(DexCode code) {
-      return DebugRepresentation.getLastExecutableInstruction(code).getOffset();
+    @Override
+    public int getPcEncoding(int pc) {
+      assert pc >= 0;
+      return pc + 1;
     }
 
     private boolean cantAddToClearSet(ProgramMethod method) {
@@ -464,6 +468,12 @@
 
   private static class NativePcSupport implements PcBasedDebugInfoRecorder {
 
+    @Override
+    public int getPcEncoding(int pc) {
+      assert pc >= 0;
+      return pc;
+    }
+
     private void clearDebugInfo(ProgramMethod method) {
       // Always strip the info in full as the runtime will emit the PC directly.
       method.getDefinition().getCode().asDexCode().setDebugInfo(null);
@@ -1175,6 +1185,7 @@
                 singleOriginalLine.set(false);
               }
               remapAndAddForPc(
+                  debugInfoProvider,
                   lastPosition.getFirst(),
                   getCurrentPc(),
                   lastPosition.getSecond(),
@@ -1194,6 +1205,7 @@
     int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(dexCode).getOffset();
     if (lastPosition.getSecond() != null) {
       remapAndAddForPc(
+          debugInfoProvider,
           lastPosition.getFirst(),
           lastInstructionPc + 1,
           lastPosition.getSecond(),
@@ -1274,6 +1286,7 @@
   }
 
   private static void remapAndAddForPc(
+      PcBasedDebugInfoRecorder debugInfoProvider,
       int startPc,
       int endPc,
       Position position,
@@ -1288,7 +1301,7 @@
               oldPosition.getMethod(),
               oldPosition.getLine(),
               oldPosition.getCallerPosition(),
-              currentPc,
+              debugInfoProvider.getPcEncoding(currentPc),
               // Outline info is placed exactly on the positions that relate to it so we should
               // only emit it for the first entry.
               firstEntry && oldPosition.isOutline(),