[Compose] Account for invalid minified range when composing inlinees

Bug: b/241763080
Change-Id: I3e1dba290e0b0ab0607b586302ccaad845477abc
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 2f5c0f9..bb4b37a 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -728,5 +728,20 @@
     public List<MappingInformation> getAdditionalMappingInformation() {
       return Collections.unmodifiableList(additionalMappingInformation);
     }
+
+    public MappedRange partitionOnMinifiedRange(Range minifiedRange) {
+      if (minifiedRange.equals(this.minifiedRange)) {
+        return this;
+      }
+      Range splitOriginalRange =
+          new Range(
+              getOriginalLineNumber(minifiedRange.from), getOriginalLineNumber(minifiedRange.to));
+      MappedRange splitMappedRange =
+          new MappedRange(minifiedRange, signature, splitOriginalRange, renamedName);
+      if (minifiedRange.to >= this.minifiedRange.to) {
+        splitMappedRange.additionalMappingInformation = this.getAdditionalMappingInformation();
+      }
+      return splitMappedRange;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
index 926fb77..d5ac7cb 100644
--- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
+++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
@@ -547,8 +547,9 @@
       Map<String, String> inverseClassMapping =
           classNameMapper.getObfuscatedToOriginalMapping().inverse;
       for (Entry<String, MappedRangesOfName> entry : mapper.mappedRangesByRenamedName.entrySet()) {
-        MappedRangesOfName mappedRangesOfName = entry.getValue();
-        for (MappedRangesOfName rangesOfName : mappedRangesOfName.partitionOnMethodSignature()) {
+        List<MappedRangesOfName> mappedRangesOfNames =
+            entry.getValue().partitionOnMethodSignature();
+        for (MappedRangesOfName rangesOfName : mappedRangesOfNames) {
           MemberNaming memberNaming = rangesOfName.getMemberNaming(mapper);
           List<MappedRange> newMappedRanges = rangesOfName.getMappedRanges();
           RangeBuilder minified = new RangeBuilder();
@@ -575,7 +576,9 @@
           //  ...
           List<MappedRange> composedRanges = new ArrayList<>();
           ComputedOutlineInformation computedOutlineInformation = new ComputedOutlineInformation();
-          for (MappedRange mappedRange : newMappedRanges) {
+          List<List<MappedRange>> composedInlineFrames = new ArrayList<>();
+          for (int i = 0; i < newMappedRanges.size(); i++) {
+            MappedRange mappedRange = newMappedRanges.get(i);
             minified.addRange(mappedRange.minifiedRange);
             // Register mapping information that is dependent on the residual naming to allow
             // updating later on.
@@ -631,9 +634,34 @@
                         : Collections.singletonList(existingMappedRange);
               }
             }
-            composedRanges.addAll(
-                composeMappedRangesForMethod(
-                    existingMappedRanges, mappedRange, computedOutlineInformation));
+            // Mapping the original ranges all the way back may cause the minified map range and the
+            // original mapped range to have different spans. We therefore maintain a collection of
+            // inline frames to add when we see the last mapped range.
+            List<List<MappedRange>> newComposedInlineFrames = new ArrayList<>();
+            if (composedInlineFrames.isEmpty()) {
+              splitOnNewMinifiedRange(
+                  composeMappedRangesForMethod(
+                      existingMappedRanges, mappedRange, computedOutlineInformation),
+                  Collections.emptyList(),
+                  newComposedInlineFrames::add);
+            } else {
+              for (List<MappedRange> composedInlineFrame : composedInlineFrames) {
+                MappedRange splitMappedRange =
+                    mappedRange.partitionOnMinifiedRange(composedInlineFrame.get(0).minifiedRange);
+                splitOnNewMinifiedRange(
+                    composeMappedRangesForMethod(
+                        existingMappedRanges, splitMappedRange, computedOutlineInformation),
+                    composedInlineFrame,
+                    newComposedInlineFrames::add);
+              }
+            }
+            composedInlineFrames = newComposedInlineFrames;
+            if (!isInlineMappedRange(newMappedRanges, i)) {
+              for (List<MappedRange> composedInlineFrame : composedInlineFrames) {
+                composedRanges.addAll(composedInlineFrame);
+              }
+              composedInlineFrames = Collections.emptyList();
+            }
           }
           MappedRange lastComposedRange = ListUtils.last(composedRanges);
           if (computedOutlineInformation.seenOutlineMappingInformation != null) {
@@ -684,6 +712,38 @@
       }
     }
 
+    private void splitOnNewMinifiedRange(
+        List<MappedRange> mappedRanges,
+        List<MappedRange> previouslyMapped,
+        Consumer<List<MappedRange>> consumer) {
+      assert !mappedRanges.isEmpty();
+      Range minifiedRange = mappedRanges.get(0).minifiedRange;
+      if (minifiedRange == null) {
+        consumer.accept(ListUtils.joinNewArrayList(previouslyMapped, mappedRanges));
+        return;
+      }
+      Box<Range> lastMinifiedRange = new Box<>(minifiedRange);
+      int lastMappedIndex = 0;
+      for (int i = 0; i < mappedRanges.size(); i++) {
+        MappedRange mappedRange = mappedRanges.get(i);
+        Range lastMinifiedRangeFinal = lastMinifiedRange.get();
+        if (!mappedRange.minifiedRange.equals(lastMinifiedRangeFinal)) {
+          consumer.accept(
+              ListUtils.joinNewArrayList(
+                  ListUtils.map(
+                      previouslyMapped, x -> x.partitionOnMinifiedRange(lastMinifiedRangeFinal)),
+                  mappedRanges.subList(lastMappedIndex, i)));
+          lastMinifiedRange.set(mappedRange.minifiedRange);
+          lastMappedIndex = i;
+        }
+      }
+      consumer.accept(
+          ListUtils.joinNewArrayList(
+              ListUtils.map(
+                  previouslyMapped, x -> x.partitionOnMinifiedRange(lastMinifiedRange.get())),
+              mappedRanges.subList(lastMappedIndex, mappedRanges.size())));
+    }
+
     private ComposingClassBuilder getExistingClassBuilder(MethodSignature originalSignature) {
       return originalSignature.isQualified()
           ? committedPreviousClassBuilders.get(originalSignature.toHolderFromQualified())
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 406d482..68e530f 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -328,4 +328,11 @@
     }
     return true;
   }
+
+  public static <T> List<T> joinNewArrayList(List<T> one, List<T> other) {
+    ArrayList<T> ts = new ArrayList<>(one.size() + other.size());
+    ts.addAll(one);
+    ts.addAll(other);
+    return ts;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsThatViolateNewRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsThatViolateNewRangeTest.java
index a60b723..a732f72 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsThatViolateNewRangeTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineOfPositionsThatViolateNewRangeTest.java
@@ -52,15 +52,15 @@
           "    10:11:void z():3:4 -> w",
           "    10:11:void new_synthetic_method():0 -> w");
   private static final String mappingResult =
-      // TODO(b/241763080): This is now only a bit wrong.
       StringUtils.unixLines(
           "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
           "com.foo -> c:",
           "    10:10:void m1():10 -> w",
           "    10:10:void y():30:30 -> w",
+          "    10:10:void new_synthetic_method():0:0 -> w",
           "    11:11:void m2(int):20 -> w",
           "    11:11:void y():31:31 -> w",
-          "    10:11:void new_synthetic_method():0 -> w"); // The range here is invalid.
+          "    11:11:void new_synthetic_method():0:0 -> w");
 
   @Test
   public void testCompose() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOriginalViolatesNewRangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOriginalViolatesNewRangeTest.java
new file mode 100644
index 0000000..fe94d29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOriginalViolatesNewRangeTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2022, 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ComposeOriginalViolatesNewRangeTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ComposeOriginalViolatesNewRangeTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  private static final String mappingFoo =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "com.foo -> a:",
+          "    1:1:void m1():10:10 -> x",
+          "    2:2:void m1():20:20 -> x");
+  private static final String mappingBar =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "a -> b:",
+          "    3:4:void x():1:2 -> z");
+  private static final String mappingResult =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "com.foo -> b:",
+          "    3:3:void m1():10:10 -> z",
+          "    4:4:void m1():20:20 -> z");
+
+  @Test
+  public void testCompose() throws Exception {
+    ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+    ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
+  }
+}