[Retrace] Correctly handle holes in compositions

Change-Id: I2dd2f8f82efdf538810cdd28b6331ec2743d2df8
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 da66dfd..ab3c150 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -674,7 +674,7 @@
     }
 
     @Override
-    public Signature getOriginalSignature() {
+    public MethodSignature getOriginalSignature() {
       return signature;
     }
 
@@ -787,9 +787,13 @@
       Range splitOriginalRange =
           new Range(
               getOriginalLineNumber(minifiedRange.from), getOriginalLineNumber(minifiedRange.to));
+      return copyWithNewRanges(minifiedRange, splitOriginalRange);
+    }
+
+    public MappedRange copyWithNewRanges(Range minifiedRange, Range originalRange) {
       MappedRange splitMappedRange =
-          new MappedRange(minifiedRange, signature, splitOriginalRange, renamedName);
-      if (minifiedRange.to >= this.minifiedRange.to) {
+          new MappedRange(minifiedRange, signature, originalRange, renamedName);
+      if (minifiedRange.from <= this.minifiedRange.from) {
         splitMappedRange.additionalMappingInformation =
             new ArrayList<>(this.additionalMappingInformation);
       }
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 502f120..716c7cd 100644
--- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
+++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
@@ -24,15 +24,14 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.SegmentTree;
 import com.android.tools.r8.utils.ThrowingBiFunction;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
@@ -537,6 +536,50 @@
       return composingClassBuilder.fieldMembers.get(signature);
     }
 
+    private List<MappedRange> getExistingMappedRanges(MappedRange newMappedRange)
+        throws MappingComposeException {
+      MethodSignature signature = newMappedRange.getOriginalSignature().toUnqualifiedIfQualified();
+      SegmentTree<List<MappedRange>> listSegmentTree = methodsWithPosition.get(signature);
+      // If there are no previously mapped positions for the range check methods without position.
+      if (listSegmentTree == null) {
+        MappedRange existingMappedRange = methodsWithoutPosition.get(signature);
+        return existingMappedRange == null ? null : Collections.singletonList(existingMappedRange);
+      }
+      // Any new transformation can be lossy - for example, D8 only promises to keep line
+      // positions if the method is not throwing (and is not debug). Additionally, R8/PG
+      // emits `1:1:void foo() -> a` instead of `1:1:void foo():1:1 -> a`, so R8 must
+      // capture the preamble position by explicitly inserting 0 as original range.
+      int firstPositionOfOriginalRange =
+          newMappedRange.getFirstPositionOfOriginalRange(NO_RANGE_FROM);
+      Entry<Integer, List<MappedRange>> firstPositionEntries =
+          listSegmentTree.findEntry(firstPositionOfOriginalRange);
+      if (firstPositionEntries != null) {
+        return firstPositionEntries.getValue();
+      }
+      // If the first position is 0 we can try at find the existing mapped ranges by last
+      // position.
+      if (firstPositionOfOriginalRange == 0 && !newMappedRange.isOriginalRangePreamble()) {
+        Entry<Integer, List<MappedRange>> lastPositionEntries =
+            listSegmentTree.findEntry(newMappedRange.getLastPositionOfOriginalRange());
+        if (lastPositionEntries != null) {
+          return lastPositionEntries.getValue();
+        }
+      }
+      // We assume that all new minified ranges for a method are rewritten in the new map
+      // such that no previous existing positions exists.
+      // The original can be discarded if it no longer exists or if the method is
+      // non-throwing.
+      if (!newMappedRange.isOriginalRangePreamble()
+          && !options.mappingComposeOptions().allowNonExistingOriginalRanges) {
+        throw new MappingComposeException(
+            "Could not find original starting position of '"
+                + newMappedRange.minifiedRange.from
+                + "' which should be "
+                + firstPositionOfOriginalRange);
+      }
+      return null;
+    }
+
     private void composeMethodNamings(
         ClassNamingForNameMapper mapper, ClassNameMapper classNameMapper)
         throws MappingComposeException {
@@ -547,7 +590,7 @@
             entry.getValue().partitionOnMethodSignature();
         for (MappedRangesOfName rangesOfName : mappedRangesOfNames) {
           MemberNaming memberNaming = rangesOfName.getMemberNaming(mapper);
-          List<MappedRange> newMappedRanges = rangesOfName.getMappedRanges();
+          List<MappedRange> newRangesToCompose = rangesOfName.getMappedRanges();
           RangeBuilder minified = new RangeBuilder();
           // The new minified ranges may have ranges that range over positions that has additional
           // information, such as inlinees:
@@ -572,9 +615,9 @@
           //  ...
           List<MappedRange> composedRanges = new ArrayList<>();
           ComputedOutlineInformation computedOutlineInformation = new ComputedOutlineInformation();
-          List<List<MappedRange>> composedInlineFrames = new ArrayList<>();
-          for (int i = 0; i < newMappedRanges.size(); i++) {
-            MappedRange mappedRange = newMappedRanges.get(i);
+          List<MappedRange> composedInlineFrames = new ArrayList<>();
+          for (int i = 0; i < newRangesToCompose.size(); i++) {
+            MappedRange mappedRange = newRangesToCompose.get(i);
             minified.addRange(mappedRange.minifiedRange);
             // Register mapping information that is dependent on the residual naming to allow
             // updating later on.
@@ -583,7 +626,6 @@
             MethodSignature originalSignature =
                 mappedRange.getOriginalSignature().asMethodSignature();
 
-            // Remove the existing entry if it exists.
             List<MappedRange> existingMappedRanges = null;
             ComposingClassBuilder existingClassBuilder = getExistingClassBuilder(originalSignature);
             if (existingClassBuilder != null) {
@@ -592,269 +634,32 @@
                           ? originalSignature.toUnqualifiedSignature()
                           : originalSignature)
                       .asMethodSignature();
+              // Remove the existing entry since we are now composing the signature.
               current.addSignatureToRemove(existingClassBuilder, signature);
-              SegmentTree<List<MappedRange>> listSegmentTree =
-                  existingClassBuilder.methodsWithPosition.get(signature);
-              if (listSegmentTree != null) {
-                // Any new transformation can be lossy - for example, D8 only promises to keep line
-                // positions if the method is not throwing (and is not debug). Additionally, R8/PG
-                // emits `1:1:void foo() -> a` instead of `1:1:void foo():1:1 -> a`, so R8 must
-                // capture the preamble position by explicitly inserting 0 as original range.
-                int firstPositionOfOriginalRange =
-                    mappedRange.getFirstPositionOfOriginalRange(NO_RANGE_FROM);
-                Entry<Integer, List<MappedRange>> existingEntry =
-                    listSegmentTree.findEntry(firstPositionOfOriginalRange);
-                if (existingEntry == null
-                    && firstPositionOfOriginalRange == 0
-                    && !mappedRange.isOriginalRangePreamble()) {
-                  existingEntry =
-                      listSegmentTree.findEntry(mappedRange.getLastPositionOfOriginalRange());
-                }
-                // We assume that all new minified ranges for a method are rewritten in the new map
-                // such that no previous existing positions exists.
-                if (existingEntry != null) {
-                  existingMappedRanges = existingEntry.getValue();
-                } else {
-                  // The original can be discarded if it no longer exists or if the method is
-                  // non-throwing.
-                  if (!mappedRange.isOriginalRangePreamble()
-                      && !options.mappingComposeOptions().allowNonExistingOriginalRanges) {
-                    throw new MappingComposeException(
-                        "Could not find original starting position of '"
-                            + mappedRange.minifiedRange.from
-                            + "' which should be "
-                            + firstPositionOfOriginalRange);
-                  }
-                }
-                assert minified.hasValue() || mappedRange.getOriginalRangeOrIdentity() == null;
-              } else {
-                MappedRange existingMappedRange =
-                    existingClassBuilder.methodsWithoutPosition.get(signature);
-                existingMappedRanges =
-                    existingMappedRange == null
-                        ? null
-                        : Collections.singletonList(existingMappedRange);
-              }
+              existingMappedRanges = existingClassBuilder.getExistingMappedRanges(mappedRange);
+              assert existingMappedRanges != null
+                  || minified.hasValue()
+                  || mappedRange.getOriginalRangeOrIdentity() == null;
             }
-            // 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(
-                      existingClassBuilder,
-                      existingMappedRanges,
-                      mappedRange,
-                      computedOutlineInformation),
-                  Collections.emptyList(),
-                  newComposedInlineFrames::add);
-            } else {
-              for (List<MappedRange> composedInlineFrame : composedInlineFrames) {
-                MappedRange splitMappedRange =
-                    mappedRange.partitionOnMinifiedRange(composedInlineFrame.get(0).minifiedRange);
-                splitOnNewMinifiedRange(
-                    composeMappedRangesForMethod(
-                        existingClassBuilder,
-                        existingMappedRanges,
-                        splitMappedRange,
-                        computedOutlineInformation),
-                    composedInlineFrame,
-                    newComposedInlineFrames::add);
-              }
-            }
-            composedInlineFrames = newComposedInlineFrames;
-            if (!isInlineMappedRange(newMappedRanges, i)) {
-              for (List<MappedRange> composedInlineFrame : composedInlineFrames) {
-                composedRanges.addAll(composedInlineFrame);
-              }
+            // Now that we have obtained a potential existing mapping, we can compose the frames.
+            List<MappedRange> newComposedRange =
+                composeMappedRangesForMethod(
+                    existingClassBuilder,
+                    existingMappedRanges,
+                    mappedRange,
+                    computedOutlineInformation);
+            composedInlineFrames = composeInlineFrames(newComposedRange, composedInlineFrames);
+            if (!isInlineMappedRange(newRangesToCompose, i)) {
+              composedRanges.addAll(composedInlineFrames);
               composedInlineFrames = Collections.emptyList();
             }
           }
-          // Check if we could have inlined an outline which is true if we see both an outline and
-          // call site to patch up.
-          if (!computedOutlineInformation.seenOutlineMappingInformation.isEmpty()
-              && !computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) {
-            Set<OutlineCallsiteMappingInformation> outlineCallSitesToRemove =
-                Sets.newIdentityHashSet();
-            Set<OutlineMappingInformation> outlinesToRemove = Sets.newIdentityHashSet();
-            // We patch up all ranges from top to bottom with the invariant that all at a given
-            // index, all above have been updated correctly. We will do expansion of frames
-            // when separating out single minified lines, but we keep the outline information
-            // present such that we can fix them when seeing them later.
-            int composedRangeIndex = 0;
-            while (composedRangeIndex < composedRanges.size() - 1) {
-              MappedRange outline = composedRanges.get(composedRangeIndex++);
-              if (outline.isOutlineFrame()
-                  && outline.minifiedRange.equals(
-                      composedRanges.get(composedRangeIndex).minifiedRange)) {
-                // We should replace the inlined outline frame positions with the synthesized
-                // positions from the outline call site.
-                MappedRange outlineCallSite = composedRanges.get(composedRangeIndex);
-                if (outlineCallSite.getOutlineCallsiteInformation().size() != 1) {
-                  // If we have an inlined outline it must be such that the outer frame is an
-                  // outline callsite.
-                  throw new MappingComposeException(
-                      "Expected exactly one outline call site for a mapped range with signature '"
-                          + outlineCallSite.getOriginalSignature()
-                          + "'.");
-                }
-                OutlineCallsiteMappingInformation outlineCallSiteInformation =
-                    outlineCallSite.getOutlineCallsiteInformation().get(0);
-                // The original positions in the outline callsite have been composed, so we have to
-                // find the existing mapped range and iterate the original positions for that range.
-                ComputedMappedRangeForOutline computedInformationForCallSite =
-                    computedOutlineInformation.getComputedRange(
-                        outlineCallSiteInformation, outlineCallSite);
-                if (computedInformationForCallSite == null) {
-                  continue;
-                }
-                Map<Integer, List<MappedRange>> mappedRangesForOutline =
-                    new HashMap<>(outlineCallSiteInformation.getPositions().size());
-                visitOutlineMappedPositions(
-                    outlineCallSiteInformation,
-                    computedInformationForCallSite
-                        .current
-                        .getOriginalSignature()
-                        .asMethodSignature(),
-                    mappedRangesForOutline::put);
-                List<MappedRange> newComposedRanges = new ArrayList<>();
-                // Copy all previous handled mapped ranges into a new list.
-                for (MappedRange previousMappedRanges : composedRanges) {
-                  if (previousMappedRanges == outline) {
-                    break;
-                  }
-                  newComposedRanges.add(previousMappedRanges);
-                }
-                // The original positions in the outline have been composed, so we have to find the
-                // existing mapped range and iterate the original positions for that range.
-                ComputedMappedRangeForOutline computedInformationForOutline =
-                    computedOutlineInformation.getComputedRange(
-                        outline.getOutlineMappingInformation(), outline);
-                if (computedInformationForOutline == null) {
-                  continue;
-                }
-                // The outline could have additional inlined positions in it, but we should be
-                // guaranteed to find call site information on all original line numbers. We
-                // therefore iterate one by one and amend the subsequent outer frames as well.
-                MappedRange current = computedInformationForOutline.current;
-                int minifiedLine = outline.minifiedRange.from;
-                for (int originalLine = current.getOriginalRangeOrIdentity().from;
-                    originalLine <= current.getOriginalRangeOrIdentity().to;
-                    originalLine++) {
-                  // If the outline is itself an inline frame it is bound to only have one original
-                  // position and we can simply insert all inline frames on that position with the
-                  // existing minified range.
-                  Range newMinifiedRange =
-                      outline.originalRange.isCardinal
-                          ? outline.minifiedRange
-                          : new Range(minifiedLine, minifiedLine);
-                  List<MappedRange> outlineMappedRanges = mappedRangesForOutline.get(originalLine);
-                  if (outlineMappedRanges != null) {
-                    outlineMappedRanges.forEach(
-                        range -> {
-                          if (range != ListUtils.last(outlineMappedRanges)) {
-                            newComposedRanges.add(
-                                new MappedRange(
-                                    newMinifiedRange,
-                                    range.getOriginalSignature().asMethodSignature(),
-                                    range.originalRange,
-                                    outlineCallSite.getRenamedName()));
-                          }
-                        });
-                    newComposedRanges.add(
-                        new MappedRange(
-                            newMinifiedRange,
-                            outlineCallSite.getOriginalSignature().asMethodSignature(),
-                            ListUtils.last(outlineMappedRanges).originalRange,
-                            outlineCallSite.getRenamedName()));
-                  }
-                  for (int tailInlineFrameIndex = composedRangeIndex + 1;
-                      tailInlineFrameIndex < composedRanges.size();
-                      tailInlineFrameIndex++) {
-                    MappedRange originalMappedRange = composedRanges.get(tailInlineFrameIndex);
-                    if (!originalMappedRange.minifiedRange.equals(outlineCallSite.minifiedRange)) {
-                      break;
-                    }
-                    MappedRange newMappedRange =
-                        originalMappedRange.withMinifiedRange(newMinifiedRange);
-                    newMappedRange.setAdditionalMappingInformationInternal(
-                        originalMappedRange.getAdditionalMappingInformation());
-                    newComposedRanges.add(newMappedRange);
-                  }
-                  minifiedLine++;
-                }
-                // We have patched up the the inlined outline and all subsequent inline frames
-                // (although some of the subsequent frames above could also be inlined outlines). We
-                // therefore need to copy the remaining frames.
-                boolean seenMinifiedRange = false;
-                for (MappedRange range : composedRanges) {
-                  if (range.minifiedRange.equals(outline.minifiedRange)) {
-                    seenMinifiedRange = true;
-                  } else if (seenMinifiedRange) {
-                    newComposedRanges.add(range);
-                  }
-                }
-                composedRanges = newComposedRanges;
-                outlineCallSitesToRemove.add(outlineCallSiteInformation);
-                outlinesToRemove.add(outline.getOutlineMappingInformation());
-              }
-            }
-            // If we removed any outlines or call site, remove the processing of them.
-            outlineCallSitesToRemove.forEach(
-                computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp::remove);
-            outlinesToRemove.forEach(
-                computedOutlineInformation.seenOutlineMappingInformation::remove);
-          }
-          if (!computedOutlineInformation.seenOutlineMappingInformation.isEmpty()) {
-            MappedRange lastComposedRange = ListUtils.last(composedRanges);
-            current
-                .getUpdateOutlineCallsiteInformation(
-                    committedPreviousClassBuilder.getRenamedName(),
-                    ListUtils.last(newMappedRanges).signature.getName(),
-                    lastComposedRange.getRenamedName())
-                .setNewMappedRanges(newMappedRanges);
-          }
-          if (!computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) {
-            MappedRange lastComposedRange = ListUtils.last(composedRanges);
-            List<MappedRange> composedRangesFinal = composedRanges;
-            // Outline positions are synthetic positions and they have no position in the residual
-            // program. We therefore have to find the original positions and copy all inline frames
-            // and amend the outermost frame with the residual signature and the next free position.
-            List<OutlineCallsiteMappingInformation> outlineCallSites =
-                new ArrayList<>(
-                    computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.keySet());
-            outlineCallSites.sort(Comparator.comparing(mapping -> mapping.getOutline().toString()));
-            IntBox firstAvailableRange = new IntBox(lastComposedRange.minifiedRange.to + 1);
-            for (OutlineCallsiteMappingInformation outlineCallSite : outlineCallSites) {
-              Int2IntSortedMap newPositionMap =
-                  new Int2IntLinkedOpenHashMap(outlineCallSite.getPositions().size());
-              visitOutlineMappedPositions(
-                  outlineCallSite,
+          composedRanges =
+              fixupOutlineInformation(
+                  computedOutlineInformation,
                   memberNaming.getOriginalSignature().asMethodSignature(),
-                  (originalPosition, mappedRangesForOutlinePosition) -> {
-                    int newIndex = firstAvailableRange.getAndIncrement();
-                    Range newMinifiedRange = new Range(newIndex, newIndex);
-                    MappedRange outerMostOutlineFrame =
-                        ListUtils.last(mappedRangesForOutlinePosition);
-                    for (MappedRange inlineMappedRangeInOutlinePosition :
-                        mappedRangesForOutlinePosition) {
-                      if (inlineMappedRangeInOutlinePosition != outerMostOutlineFrame) {
-                        composedRangesFinal.add(
-                            inlineMappedRangeInOutlinePosition.withMinifiedRange(newMinifiedRange));
-                      }
-                    }
-                    composedRangesFinal.add(
-                        new MappedRange(
-                            newMinifiedRange,
-                            lastComposedRange.signature,
-                            outerMostOutlineFrame.originalRange,
-                            lastComposedRange.getRenamedName()));
-                    newPositionMap.put((int) originalPosition, newIndex);
-                    outlineCallSite.setPositionsInternal(newPositionMap);
-                  });
-            }
-          }
+                  newRangesToCompose,
+                  composedRanges);
           MethodSignature residualSignature =
               memberNaming
                   .computeResidualSignature(type -> inverseClassMapping.getOrDefault(type, type))
@@ -873,6 +678,217 @@
       }
     }
 
+    private List<MappedRange> fixupOutlineInformation(
+        ComputedOutlineInformation computedOutlineInformation,
+        MethodSignature originalSignature,
+        List<MappedRange> newRangesToBeComposed,
+        List<MappedRange> composedRanges)
+        throws MappingComposeException {
+      composedRanges = fixupInlinedOutlines(computedOutlineInformation, composedRanges);
+      fixupOutlineInformation(computedOutlineInformation, newRangesToBeComposed, composedRanges);
+      fixupOutlineCallsiteInformation(
+          computedOutlineInformation, originalSignature, composedRanges);
+      return composedRanges;
+    }
+
+    private List<MappedRange> fixupInlinedOutlines(
+        ComputedOutlineInformation computedOutlineInformation, List<MappedRange> composedRanges)
+        throws MappingComposeException {
+      // Check if we could have inlined an outline which is true if we see both an outline and call
+      // site to patch up. When an outline has been inlined we have to retrace through the inlined
+      // positions, by positions only existing in the previous mapped ranges, to form new ranges.
+      if (computedOutlineInformation.seenOutlineMappingInformation.isEmpty()
+          || computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) {
+        return composedRanges;
+      }
+      Set<OutlineCallsiteMappingInformation> outlineCallSitesToRemove = Sets.newIdentityHashSet();
+      Set<OutlineMappingInformation> outlinesToRemove = Sets.newIdentityHashSet();
+      // We patch up all ranges from top to bottom with the invariant that all at a given
+      // index, all above have been updated correctly. We will do expansion of frames
+      // when separating out single minified lines, but we keep the outline information
+      // present such that we can fix them when seeing them later.
+      int composedRangeIndex = 0;
+      while (composedRangeIndex < composedRanges.size() - 1) {
+        MappedRange outline = composedRanges.get(composedRangeIndex++);
+        if (outline.isOutlineFrame()
+            && outline.minifiedRange.equals(composedRanges.get(composedRangeIndex).minifiedRange)) {
+          // We should replace the inlined outline frame positions with the synthesized
+          // positions from the outline call site.
+          MappedRange outlineCallSite = composedRanges.get(composedRangeIndex);
+          if (outlineCallSite.getOutlineCallsiteInformation().size() != 1) {
+            // If we have an inlined outline it must be such that the outer frame is an
+            // outline callsite.
+            throw new MappingComposeException(
+                "Expected exactly one outline call site for a mapped range with signature '"
+                    + outlineCallSite.getOriginalSignature()
+                    + "'.");
+          }
+          OutlineCallsiteMappingInformation outlineCallSiteInformation =
+              outlineCallSite.getOutlineCallsiteInformation().get(0);
+          // The original positions in the outline callsite have been composed, so we have to
+          // find the existing mapped range and iterate the original positions for that range.
+          ComputedMappedRangeForOutline computedInformationForCallSite =
+              computedOutlineInformation.getComputedRange(
+                  outlineCallSiteInformation, outlineCallSite);
+          if (computedInformationForCallSite == null) {
+            continue;
+          }
+          Map<Integer, List<MappedRange>> mappedRangesForOutline =
+              new HashMap<>(outlineCallSiteInformation.getPositions().size());
+          visitOutlineMappedPositions(
+              outlineCallSiteInformation,
+              computedInformationForCallSite.current.getOriginalSignature(),
+              mappedRangesForOutline::put);
+          List<MappedRange> newComposedRanges = new ArrayList<>();
+          // Copy all previous handled mapped ranges into a new list.
+          for (MappedRange previousMappedRanges : composedRanges) {
+            if (previousMappedRanges == outline) {
+              break;
+            }
+            newComposedRanges.add(previousMappedRanges);
+          }
+          // The original positions in the outline have been composed, so we have to find the
+          // existing mapped range and iterate the original positions for that range.
+          ComputedMappedRangeForOutline computedInformationForOutline =
+              computedOutlineInformation.getComputedRange(
+                  outline.getOutlineMappingInformation(), outline);
+          if (computedInformationForOutline == null) {
+            continue;
+          }
+          // The outline could have additional inlined positions in it, but we should be
+          // guaranteed to find call site information on all original line numbers. We
+          // therefore iterate one by one and amend the subsequent outer frames as well.
+          MappedRange current = computedInformationForOutline.current;
+          int minifiedLine = outline.minifiedRange.from;
+          for (int originalLine = current.getOriginalRangeOrIdentity().from;
+              originalLine <= current.getOriginalRangeOrIdentity().to;
+              originalLine++) {
+            // If the outline is itself an inline frame it is bound to only have one original
+            // position and we can simply insert all inline frames on that position with the
+            // existing minified range.
+            Range newMinifiedRange =
+                outline.originalRange.isCardinal
+                    ? outline.minifiedRange
+                    : new Range(minifiedLine, minifiedLine);
+            List<MappedRange> outlineMappedRanges = mappedRangesForOutline.get(originalLine);
+            if (outlineMappedRanges != null) {
+              outlineMappedRanges.forEach(
+                  range -> {
+                    if (range != ListUtils.last(outlineMappedRanges)) {
+                      newComposedRanges.add(
+                          new MappedRange(
+                              newMinifiedRange,
+                              range.getOriginalSignature().asMethodSignature(),
+                              range.originalRange,
+                              outlineCallSite.getRenamedName()));
+                    }
+                  });
+              newComposedRanges.add(
+                  new MappedRange(
+                      newMinifiedRange,
+                      outlineCallSite.getOriginalSignature().asMethodSignature(),
+                      ListUtils.last(outlineMappedRanges).originalRange,
+                      outlineCallSite.getRenamedName()));
+            }
+            for (int tailInlineFrameIndex = composedRangeIndex + 1;
+                tailInlineFrameIndex < composedRanges.size();
+                tailInlineFrameIndex++) {
+              MappedRange originalMappedRange = composedRanges.get(tailInlineFrameIndex);
+              if (!originalMappedRange.minifiedRange.equals(outlineCallSite.minifiedRange)) {
+                break;
+              }
+              MappedRange newMappedRange = originalMappedRange.withMinifiedRange(newMinifiedRange);
+              newMappedRange.setAdditionalMappingInformationInternal(
+                  originalMappedRange.getAdditionalMappingInformation());
+              newComposedRanges.add(newMappedRange);
+            }
+            minifiedLine++;
+          }
+          // We have patched up the the inlined outline and all subsequent inline frames
+          // (although some of the subsequent frames above could also be inlined outlines). We
+          // therefore need to copy the remaining frames.
+          boolean seenMinifiedRange = false;
+          for (MappedRange range : composedRanges) {
+            if (range.minifiedRange.equals(outline.minifiedRange)) {
+              seenMinifiedRange = true;
+            } else if (seenMinifiedRange) {
+              newComposedRanges.add(range);
+            }
+          }
+          composedRanges = newComposedRanges;
+          outlineCallSitesToRemove.add(outlineCallSiteInformation);
+          outlinesToRemove.add(outline.getOutlineMappingInformation());
+        }
+      }
+      // If we removed any outlines or call sites, remove the processing of them.
+      outlineCallSitesToRemove.forEach(
+          computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp::remove);
+      outlinesToRemove.forEach(computedOutlineInformation.seenOutlineMappingInformation::remove);
+      return composedRanges;
+    }
+
+    private void fixupOutlineInformation(
+        ComputedOutlineInformation computedOutlineInformation,
+        List<MappedRange> newRangesToBeComposed,
+        List<MappedRange> composedRanges) {
+      if (computedOutlineInformation.seenOutlineMappingInformation.isEmpty()) {
+        return;
+      }
+      MappedRange lastComposedRange = ListUtils.last(composedRanges);
+      current
+          .getUpdateOutlineCallsiteInformation(
+              committedPreviousClassBuilder.getRenamedName(),
+              ListUtils.last(newRangesToBeComposed).signature.getName(),
+              lastComposedRange.getRenamedName())
+          .setNewMappedRanges(newRangesToBeComposed);
+    }
+
+    private void fixupOutlineCallsiteInformation(
+        ComputedOutlineInformation computedOutlineInformation,
+        MethodSignature originalSignature,
+        List<MappedRange> composedRanges)
+        throws MappingComposeException {
+      if (computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) {
+        return;
+      }
+      MappedRange lastComposedRange = ListUtils.last(composedRanges);
+      // Outline positions are synthetic positions, and they have no position in the residual
+      // program. We therefore have to find the original positions and copy all inline frames
+      // and amend the outermost frame with the residual signature and the next free position.
+      List<OutlineCallsiteMappingInformation> outlineCallSites =
+          new ArrayList<>(
+              computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.keySet());
+      outlineCallSites.sort(Comparator.comparing(mapping -> mapping.getOutline().toString()));
+      IntBox firstAvailableRange = new IntBox(lastComposedRange.minifiedRange.to + 1);
+      for (OutlineCallsiteMappingInformation outlineCallSite : outlineCallSites) {
+        Int2IntSortedMap newPositionMap =
+            new Int2IntLinkedOpenHashMap(outlineCallSite.getPositions().size());
+        visitOutlineMappedPositions(
+            outlineCallSite,
+            originalSignature,
+            (originalPosition, mappedRangesForOutlinePosition) -> {
+              int newIndex = firstAvailableRange.getAndIncrement();
+              Range newMinifiedRange = new Range(newIndex, newIndex);
+              MappedRange outerMostOutlineFrame = ListUtils.last(mappedRangesForOutlinePosition);
+              for (MappedRange inlineMappedRangeInOutlinePosition :
+                  mappedRangesForOutlinePosition) {
+                if (inlineMappedRangeInOutlinePosition != outerMostOutlineFrame) {
+                  composedRanges.add(
+                      inlineMappedRangeInOutlinePosition.withMinifiedRange(newMinifiedRange));
+                }
+              }
+              composedRanges.add(
+                  new MappedRange(
+                      newMinifiedRange,
+                      lastComposedRange.signature,
+                      outerMostOutlineFrame.originalRange,
+                      lastComposedRange.getRenamedName()));
+              newPositionMap.put((int) originalPosition, newIndex);
+              outlineCallSite.setPositionsInternal(newPositionMap);
+            });
+      }
+    }
+
     private void visitOutlineMappedPositions(
         OutlineCallsiteMappingInformation outlineCallSite,
         MethodSignature originalSignature,
@@ -903,10 +919,12 @@
                   + originalSignature
                   + "'.");
         }
-        ExistingMapping existingMapping = computeExistingMapping(mappedRanges);
+        ExistingMappings existingMappings = ExistingMappings.create(mappedRanges);
         List<MappedRange> mappedRangesForOutlinePosition =
-            existingMapping.getMappedRangesForPosition(originalDestination);
-        if (mappedRangesForOutlinePosition == null) {
+            existingMappings.getNextRanges(originalDestination);
+        if (mappedRangesForOutlinePosition == null
+            || mappedRangesForOutlinePosition.isEmpty()
+            || !mappedRangesForOutlinePosition.get(0).minifiedRange.contains(originalDestination)) {
           throw new MappingComposeException(
               "Could not find ranges for outline position '"
                   + keyPosition
@@ -918,36 +936,56 @@
       }
     }
 
-    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;
+    /**
+     * Utility function to compose inline frames
+     *
+     * <p>Say that we have the following input:
+     *
+     * <pre>
+     *   previousInlineFrames:
+     *     10:10:void m1():10:10 -> w
+     *     10:10:void y():30 -> w
+     *     11:11:void m2(int):20:20 -> w
+     *     11:11:void y():31 -> w
+     *   newComposedRanges:
+     *     10:11:void new_synthetic_method():0 -> w
+     * </pre>
+     *
+     * The newComposedRanges are outer frames, but we cannot place them below since their range
+     * overlap. We therefore need to place a copy of newComposedRanges into the correct position at
+     * the minified range to produce:
+     *
+     * <pre>
+     *   result:
+     *     10:10:void m1():10 -> w
+     *     10:10:void y():30:30 -> w
+     *     10:10:void new_synthetic_method():0 -> w
+     *     11:11:void m2(int):20 -> w
+     *     11:11:void y():31:31 -> w
+     *     11:11:void new_synthetic_method():0 -> w
+     * </pre>
+     */
+    private List<MappedRange> composeInlineFrames(
+        List<MappedRange> newComposedRanges, List<MappedRange> previousInlineFrames) {
+      if (previousInlineFrames.isEmpty()) {
+        return newComposedRanges;
       }
-      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;
+      List<MappedRange> newComposedInlineFrames = new ArrayList<>();
+      Range lastMinifiedRange = previousInlineFrames.get(0).minifiedRange;
+      for (MappedRange previousFrame : previousInlineFrames) {
+        if (!lastMinifiedRange.equals(previousFrame.minifiedRange)) {
+          for (MappedRange newComposedRange : newComposedRanges) {
+            newComposedInlineFrames.add(
+                newComposedRange.partitionOnMinifiedRange(lastMinifiedRange));
+          }
+          lastMinifiedRange = previousFrame.minifiedRange;
         }
+        newComposedInlineFrames.add(previousFrame.partitionOnMinifiedRange(lastMinifiedRange));
       }
-      consumer.accept(
-          ListUtils.joinNewArrayList(
-              ListUtils.map(
-                  previouslyMapped, x -> x.partitionOnMinifiedRange(lastMinifiedRange.get())),
-              mappedRanges.subList(lastMappedIndex, mappedRanges.size())));
+      for (MappedRange newComposedRange : newComposedRanges) {
+        newComposedInlineFrames.add(newComposedRange.partitionOnMinifiedRange(lastMinifiedRange));
+      }
+      return newComposedInlineFrames;
     }
 
     private ComposingClassBuilder getExistingClassBuilder(MethodSignature originalSignature) {
@@ -991,229 +1029,196 @@
         ComputedOutlineInformation computedOutlineInformation)
         throws MappingComposeException {
       assert newRange != null;
+      // If there are no existing ranges, then just return the new range.
       if (existingRanges == null || existingRanges.isEmpty()) {
         return Collections.singletonList(newRange);
       }
       MappedRange lastExistingRange = ListUtils.last(existingRanges);
+      // Check if there are no range information in the original range. If not we do a simple
+      // composition of methods.
       if (newRange.getOriginalRangeOrIdentity() == null) {
-        MappedRange newComposedRange =
-            new MappedRange(
-                newRange.minifiedRange,
-                potentiallyQualifySignature(
-                    newRange.signature,
-                    lastExistingRange.signature,
-                    existingClassBuilder.getOriginalName()),
-                null,
-                newRange.renamedName);
-        composeMappingInformation(
-            newComposedRange.getAdditionalMappingInformation(),
-            lastExistingRange.getAdditionalMappingInformation(),
-            info -> newComposedRange.addMappingInformation(info, ConsumerUtils.emptyConsumer()));
-        return Collections.singletonList(newComposedRange);
+        return Collections.singletonList(
+            composeOnMethodSignature(existingClassBuilder, newRange, lastExistingRange));
       }
-      ExistingMapping mappedRangesForPosition = computeExistingMapping(existingRanges);
+      ExistingMappings mappedRangesForPosition = ExistingMappings.create(existingRanges);
       List<MappedRange> newComposedRanges = new ArrayList<>();
       assert newRange.minifiedRange != null;
-      // First check if the original range matches the existing minified range.
-      List<MappedRange> existingMappedRanges =
-          mappedRangesForPosition.getMappedRangesForPosition(
-              newRange.getFirstPositionOfOriginalRange(NO_RANGE_FROM));
-      MappedRange lastExistingMappedRange = ListUtils.lastOrNull(existingMappedRanges);
-      int startingPosition = newRange.minifiedRange.from;
-      if (existingMappedRanges == null) {
-        // If we cannot lookup the original position because it has been removed we compose with
-        // the existing method signature.
-        if (newRange.isOriginalRangePreamble()
-            || (existingRanges.size() == 1 && lastExistingRange.minifiedRange == null)) {
-          return Collections.singletonList(
-              new MappedRange(
-                  newRange.minifiedRange,
-                  potentiallyQualifySignature(
-                      newRange.signature,
-                      lastExistingRange.signature,
-                      existingClassBuilder.getOriginalName()),
-                  lastExistingRange.originalRange != null
-                          && lastExistingRange.originalRange.span() == 1
-                      ? lastExistingRange.originalRange
-                      : EMPTY_RANGE,
-                  newRange.renamedName));
-        } else if (newRange.getOriginalRangeOrIdentity().from == 0) {
-          // Similar to the trick below we create a synthetic range to map the preamble to.
-          Pair<Integer, MappedRange> emptyRange =
-              createEmptyRange(
-                  newRange, lastExistingRange, mappedRangesForPosition, startingPosition);
-          lastExistingMappedRange = emptyRange.getSecond();
-          existingMappedRanges = Collections.singletonList(emptyRange.getSecond());
-          startingPosition += emptyRange.getFirst() + 1;
-        } else {
-          throw new MappingComposeException(
-              "Unexpected missing original position for '" + newRange + "'.");
+      int minifiedStart = newRange.minifiedRange.from;
+      int minifiedEnd = newRange.minifiedRange.to;
+      int originalLineNumberEnd = newRange.getOriginalLineNumber(minifiedEnd);
+      while (minifiedStart <= minifiedEnd) {
+        int originalLineNumber = newRange.getOriginalLineNumber(minifiedStart);
+        // First query if there is an interval ranging over the minifiedStart:
+        // [--existing--]
+        //        [minifiedStart----minifiedEnd]
+        // Note that if existing mapped range has no line information, it will start at position -1.
+        List<MappedRange> existingRangesForPosition =
+            mappedRangesForPosition.getPreviousRanges(originalLineNumber);
+        // If we find no previous ranges or the previous range is outside the minified start, we
+        // query for an interval defined later:
+        //                      [--existing--]
+        // [minifiedStart----minifiedEnd]
+        if (isRangeNullOrBefore(existingRangesForPosition, originalLineNumber)) {
+          existingRangesForPosition = mappedRangesForPosition.getNextRanges(originalLineNumber);
         }
-      }
-      assert lastExistingMappedRange != null;
-      // If the existing mapped minified range is equal to the original range of the new range
-      // then we have a perfect mapping that we can translate directly.
-      if (lastExistingMappedRange.minifiedRange.equals(newRange.getOriginalRangeOrIdentity())) {
-        computeComposedMappedRange(
-            existingClassBuilder,
-            newComposedRanges,
-            newRange,
-            existingMappedRanges,
-            computedOutlineInformation,
-            newRange.minifiedRange.from,
-            newRange.minifiedRange.to);
-        return newComposedRanges;
-      }
-      // Otherwise, we have a situation where the minified range references over multiple
-      // existing ranges. We simply chop op when the range changes on the right hand side. To
-      // ensure we do not mess up the spans from the original range, we have to check if the
-      // current starting position is inside an original range, and then chop it off.
-      // Similarly, when writing the last block, we have to cut it off to match.
-      int lastStartingMinifiedFrom = newRange.minifiedRange.from;
-      for (int position = startingPosition; position <= newRange.minifiedRange.to; position++) {
-        List<MappedRange> existingMappedRangesForPosition =
-            mappedRangesForPosition.getMappedRangesForPosition(
-                newRange.getOriginalLineNumber(position));
-        MappedRange lastExistingMappedRangeForPosition =
-            ListUtils.lastOrNull(existingMappedRangesForPosition);
-        if (lastExistingMappedRangeForPosition == null
-            || !lastExistingMappedRange.minifiedRange.equals(
-                lastExistingMappedRangeForPosition.minifiedRange)) {
-          // We have seen an existing range we have to compute a splitting for.
-          computeComposedMappedRange(
-              existingClassBuilder,
-              newComposedRanges,
-              newRange,
-              existingMappedRanges,
-              computedOutlineInformation,
-              lastStartingMinifiedFrom,
-              position - 1);
-          // Advance the last starting position to this point and advance the existing mapped
-          // ranges for this position.
-          lastStartingMinifiedFrom = position;
-          if (existingMappedRangesForPosition == null) {
-            if (!options.mappingComposeOptions().allowNonExistingOriginalRanges) {
-              throw new MappingComposeException(
-                  "Unexpected missing original position for '" + newRange + "'.");
-            }
-            // We are at the first position of a hole. If we have existing ranges:
-            // 1:1:void foo():41:41 -> a
-            // 9:9:void foo():49:49 -> a
-            // We may have a new range that is:
-            // 21:29:void foo():1:9
-            // We end up here at position 2 and we have already committed
-            // 21:21:void foo():41:41.
-            // We then introduce a "fake" normal range to simulate the result of retracing one after
-            // the other to end up with:
-            // 21:21:void foo():41:41
-            // 22:28:void foo():2:8
-            // 29:29:void foo():49:49.
-            Pair<Integer, MappedRange> emptyRange =
-                createEmptyRange(
-                    newRange,
-                    lastExistingMappedRange,
-                    mappedRangesForPosition,
-                    newRange.getOriginalLineNumber(position));
-            lastExistingMappedRange = emptyRange.getSecond();
-            existingMappedRanges = Collections.singletonList(emptyRange.getSecond());
-            position += emptyRange.getFirst();
+        // Check if we have the case:
+        // [--existing--]                                [--existing--]
+        //                [minifiedStart----minifiedEnd]
+        // Where the previous existing interval is not the special one defined on -1.
+        if (isRangeNullOrAfter(existingRangesForPosition, originalLineNumberEnd)) {
+          Range minifiedRange = new Range(minifiedStart, minifiedEnd);
+          newComposedRanges.add(newRange.copyWithNewRanges(minifiedRange, minifiedRange));
+          minifiedStart = minifiedEnd + 1;
+        } else {
+          // Check if we have an existing mapped range without line information.
+          MappedRange lastMappedRange = ListUtils.last(existingRangesForPosition);
+          if (lastMappedRange.minifiedRange == null) {
+            return Collections.singletonList(
+                composeOnMethodSignature(existingClassBuilder, newRange, lastExistingRange));
+          }
+          // If we have the case:
+          //                    [--existing--]
+          // [minifiedStart----minifiedEnd]
+          // We have to insert a mapped range from minifiedStart to existingStart.
+          if (originalLineNumber < lastMappedRange.minifiedRange.from) {
+            int tempMinifiedTo =
+                minifiedStart + (lastMappedRange.minifiedRange.from - originalLineNumber - 1);
+            Range minifiedRange = new Range(minifiedStart, tempMinifiedTo);
+            Range originalRange =
+                new Range(originalLineNumber, lastMappedRange.minifiedRange.from - 1);
+            newComposedRanges.add(
+                newRange.copyWithNewRanges(
+                    minifiedRange, originalRange.isPreamble() ? originalRange : minifiedRange));
+            minifiedStart = tempMinifiedTo + 1;
           } else {
-            lastExistingMappedRange = lastExistingMappedRangeForPosition;
-            existingMappedRanges = existingMappedRangesForPosition;
+            // Otherwise we have the case where we can compose directly up until the first end. If
+            // the starting point matches directly, we've found the range when looking up
+            // previousRanges since interval search is inclusive, the intervals are disjoint and the
+            // interval is registered at minifiedStart.
+            // [--existing--]
+            //    [minifiedStart----minifiedEnd]
+            int span = computeSpan(minifiedStart, minifiedEnd, newRange, lastMappedRange);
+            composeMappedRange(
+                existingClassBuilder,
+                newComposedRanges,
+                newRange,
+                existingRangesForPosition,
+                computedOutlineInformation,
+                minifiedStart,
+                minifiedStart + span);
+            minifiedStart += span + 1;
           }
         }
       }
-      computeComposedMappedRange(
-          existingClassBuilder,
-          newComposedRanges,
-          newRange,
-          existingMappedRanges,
-          computedOutlineInformation,
-          lastStartingMinifiedFrom,
-          newRange.minifiedRange.to);
       return newComposedRanges;
     }
 
-    private Pair<Integer, MappedRange> createEmptyRange(
-        MappedRange newRange,
-        MappedRange lastExistingMappedRange,
-        ExistingMapping mappedRangesForPosition,
-        int position) {
-      int startOriginalPosition = newRange.getOriginalLineNumber(position);
-      Integer endOriginalPosition =
-          mappedRangesForPosition.getCeilingForPosition(startOriginalPosition);
-      if (endOriginalPosition == null) {
-        endOriginalPosition = newRange.getLastPositionOfOriginalRange();
-      } else if (endOriginalPosition > startOriginalPosition) {
-        endOriginalPosition = endOriginalPosition - 1;
-      }
-      Range newOriginalRange = new Range(startOriginalPosition, endOriginalPosition);
-      MappedRange nonExistingMappedRange =
-          new MappedRange(
-              newOriginalRange,
-              lastExistingMappedRange.getOriginalSignature().asMethodSignature(),
-              newOriginalRange,
-              lastExistingMappedRange.getRenamedName());
-      nonExistingMappedRange.setResidualSignatureInternal(
-          lastExistingMappedRange.getResidualSignature());
-      return Pair.create((endOriginalPosition - startOriginalPosition), nonExistingMappedRange);
+    private boolean isRangeNullOrBefore(
+        List<MappedRange> existingRangesForPosition, int originalLineNumber) {
+      return existingRangesForPosition == null
+          || existingRangesForPosition.isEmpty()
+          || (ListUtils.last(existingRangesForPosition).minifiedRange != null
+              && ListUtils.last(existingRangesForPosition).minifiedRange.to < originalLineNumber);
     }
 
-    public interface ExistingMapping {
+    private boolean isRangeNullOrAfter(
+        List<MappedRange> existingRangesForPosition, int originalLineNumberEnd) {
+      return existingRangesForPosition == null
+          || (ListUtils.last(existingRangesForPosition).minifiedRange != null
+              && originalLineNumberEnd
+                  < ListUtils.last(existingRangesForPosition).minifiedRange.from);
+    }
 
-      Integer getCeilingForPosition(int i);
+    private int computeSpan(
+        int minifiedStart, int minifiedEnd, MappedRange newRange, MappedRange lastMappedRange) {
+      int minifiedSpan = minifiedEnd - minifiedStart;
+      if (minifiedSpan >= 1 && newRange.getOriginalRangeOrIdentity().isSingleLine()) {
+        return minifiedSpan;
+      }
+      return Math.min(
+          minifiedSpan,
+          lastMappedRange.minifiedRange.to - newRange.getOriginalLineNumber(minifiedStart));
+    }
 
-      List<MappedRange> getMappedRangesForPosition(int i);
+    private MappedRange composeOnMethodSignature(
+        ComposingClassBuilder existingClassBuilder,
+        MappedRange newRange,
+        MappedRange lastExistingRange)
+        throws MappingComposeException {
+      Range originalRange;
+      if (newRange.getOriginalRangeOrIdentity() == null) {
+        originalRange = null;
+      } else {
+        originalRange =
+            lastExistingRange.originalRange != null
+                    && lastExistingRange.originalRange.isSingleLine()
+                ? lastExistingRange.originalRange
+                : EMPTY_RANGE;
+      }
+      MappedRange newComposedRange =
+          new MappedRange(
+              newRange.minifiedRange,
+              potentiallyQualifySignature(
+                  newRange.signature,
+                  lastExistingRange.signature,
+                  existingClassBuilder.getOriginalName()),
+              originalRange,
+              newRange.renamedName);
+      composeMappingInformation(
+          newComposedRange.getAdditionalMappingInformation(),
+          lastExistingRange.getAdditionalMappingInformation(),
+          info -> newComposedRange.addMappingInformation(info, ConsumerUtils.emptyConsumer()));
+      return newComposedRange;
     }
 
     /***
-     * Builds a position to mapped ranges for mappings by looking up all mapped ranges for a given
-     * position.
+     * Builds a range to mapped ranges for mappings by building a lookup table sorted on the
+     * starting coordinate.
      */
-    private ExistingMapping computeExistingMapping(List<MappedRange> existingRanges) {
-      TreeMap<Integer, List<MappedRange>> mappedRangesForPosition = new TreeMap<>();
-      List<MappedRange> currentRangesForPosition = new ArrayList<>();
-      int startExisting = NO_RANGE_FROM;
-      int endExisting = NO_RANGE_FROM;
-      boolean isCatchAll = false;
-      for (int i = 0; i < existingRanges.size(); i++) {
-        MappedRange mappedRange = existingRanges.get(i);
-        currentRangesForPosition.add(mappedRange);
-        if (!isInlineMappedRange(existingRanges, i)) {
-          if (startExisting == NO_RANGE_FROM) {
-            startExisting = mappedRange.getFirstPositionOfOriginalRange(NO_RANGE_FROM);
-          }
-          endExisting = Math.max(mappedRange.getLastPositionOfOriginalRange(), endExisting);
-          if (mappedRange.minifiedRange == null) {
-            mappedRangesForPosition.put(NO_RANGE_FROM, currentRangesForPosition);
-          } else if (mappedRange.minifiedRange.isCatchAll()) {
-            isCatchAll = true;
-          } else {
-            for (int position = mappedRange.minifiedRange.from;
-                position <= mappedRange.minifiedRange.to;
-                position++) {
-              mappedRangesForPosition.put(position, currentRangesForPosition);
-            }
-          }
-          currentRangesForPosition = new ArrayList<>();
-        }
-      }
-      boolean finalIsCatchAll = isCatchAll;
-      List<MappedRange> finalCurrentRangesForPosition = currentRangesForPosition;
-      return new ExistingMapping() {
-        @Override
-        public Integer getCeilingForPosition(int i) {
-          return finalIsCatchAll ? i : mappedRangesForPosition.ceilingKey(i);
-        }
+    private static class ExistingMappings {
 
-        @Override
-        public List<MappedRange> getMappedRangesForPosition(int i) {
-          return finalIsCatchAll ? finalCurrentRangesForPosition : mappedRangesForPosition.get(i);
+      private final TreeMap<Integer, List<MappedRange>> mappedRangesForPosition;
+
+      private ExistingMappings(TreeMap<Integer, List<MappedRange>> mappedRangesForPosition) {
+        this.mappedRangesForPosition = mappedRangesForPosition;
+      }
+
+      private static ExistingMappings create(List<MappedRange> existingRanges) {
+        TreeMap<Integer, List<MappedRange>> mappedRangesForPosition = new TreeMap<>();
+        if (!existingRanges.isEmpty()) {
+          ImmutableList.Builder<MappedRange> builder = ImmutableList.builder();
+          Range lastRange = existingRanges.get(0).minifiedRange;
+          if (lastRange == null) {
+            assert existingRanges.size() == 1;
+            mappedRangesForPosition.put(
+                NO_RANGE_FROM, Collections.singletonList(existingRanges.get(0)));
+          } else {
+            for (MappedRange mappedRange : existingRanges) {
+              if (!lastRange.equals(mappedRange.minifiedRange)) {
+                mappedRangesForPosition.put(lastRange.from, builder.build());
+                builder = new ImmutableList.Builder<>();
+                lastRange = mappedRange.minifiedRange;
+              }
+              builder.add(mappedRange);
+            }
+            mappedRangesForPosition.put(lastRange.from, builder.build());
+          }
         }
-      };
+        return new ExistingMappings(mappedRangesForPosition);
+      }
+
+      private List<MappedRange> getNextRanges(int startPosition) {
+        Integer ceilingKey = mappedRangesForPosition.ceilingKey(startPosition);
+        return ceilingKey == null ? null : mappedRangesForPosition.get(ceilingKey);
+      }
+
+      private List<MappedRange> getPreviousRanges(int startPosition) {
+        Integer floorKey = mappedRangesForPosition.floorKey(startPosition);
+        return floorKey == null ? null : mappedRangesForPosition.get(floorKey);
+        }
     }
 
-    private void computeComposedMappedRange(
+    private void composeMappedRange(
         ComposingClassBuilder existingClassBuilder,
         List<MappedRange> newComposedRanges,
         MappedRange newMappedRange,
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 5ddf11e..7387282 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -415,6 +415,10 @@
       return new MethodSignature(toUnqualifiedName(), type, parameters);
     }
 
+    public MethodSignature toUnqualifiedIfQualified() {
+      return isQualified() ? new MethodSignature(toUnqualifiedName(), type, parameters) : this;
+    }
+
     public DexMethod toDexMethod(DexItemFactory factory, DexType clazz) {
       DexType[] paramTypes = new DexType[parameters.length];
       for (int i = 0; i < parameters.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/naming/Range.java b/src/main/java/com/android/tools/r8/naming/Range.java
index 5c0714b..3156ea7 100644
--- a/src/main/java/com/android/tools/r8/naming/Range.java
+++ b/src/main/java/com/android/tools/r8/naming/Range.java
@@ -58,6 +58,10 @@
     return (to - from) + 1;
   }
 
+  public boolean isSingleLine() {
+    return isCardinal || to == from;
+  }
+
   @Override
   public int hashCode() {
     return Objects.hash(from, to, isCardinal);
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleSyntheticTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleSyntheticTest.java
index 71c344d..aab1805 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleSyntheticTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleSyntheticTest.java
@@ -40,7 +40,7 @@
           "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
           "a -> b:",
           "    1:2:void lambda$0(boolean):0:1 ->" + " lambda$0$com-r8-Base",
-          "    1:2:void" + " lambda$0$com-r8-Base(boolean):0" + " -> lambda$0$com-r8-Base",
+          "    1:2:void lambda$0$com-r8-Base(boolean):0 -> lambda$0$com-r8-Base",
           "    # {'id':'com.android.tools.r8.synthesized'}");
   private static final String mappingResult =
       StringUtils.unixLines(
@@ -48,9 +48,9 @@
           "com.foo -> b:",
           "    1:1:void lambda$0(boolean):0:0 -> lambda$0$com-r8-Base",
           "    1:1:void lambda$0$com-r8-Base(boolean):0:0 -> lambda$0$com-r8-Base",
+          "    # {'id':'com.android.tools.r8.synthesized'}",
           "    2:2:void lambda$0(boolean):355:355 -> lambda$0$com-r8-Base",
-          "    2:2:void lambda$0$com-r8-Base(boolean):0:0 -> lambda$0$com-r8-Base",
-          "    # {'id':'com.android.tools.r8.synthesized'}");
+          "    2:2:void lambda$0$com-r8-Base(boolean):0:0 -> lambda$0$com-r8-Base");
 
   @Test
   public void testCompose() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleTest.java
index 57b70a0..f9a16e8 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposePreambleTest.java
@@ -46,7 +46,7 @@
       StringUtils.unixLines(
           "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
           "com.foo -> b:",
-          "    1:1:int f1():0:0 -> h1",
+          "    1:1:int g1():0:0 -> h1",
           "    2:3:int f1():41:42 -> h1",
           "    0:65535:void f2():112:112 -> h2");
 
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeWithHolesTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeWithHolesTest.java
new file mode 100644
index 0000000..2445d9c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeWithHolesTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2023, 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.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ComposeWithHolesTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private static final String mappingFoo =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "com.foo -> A:",
+          "    2:11:java.lang.String internalToDescriptor(java.lang.String,boolean):45:54 -> a",
+          "    72:72:java.lang.String internalToDescriptor(java.lang.String,boolean):56:56 -> a");
+  private static final String mappingBar =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "A -> B:",
+          "    1:71:java.lang.String a(java.lang.String,boolean):2:72 -> e");
+  private static final String mappingResult =
+      StringUtils.unixLines(
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "com.foo -> B:",
+          "    1:10:java.lang.String internalToDescriptor(java.lang.String,boolean):45:54 -> e",
+          "    11:70:java.lang.String a(java.lang.String,boolean) -> e",
+          "    71:71:java.lang.String internalToDescriptor(java.lang.String,boolean):56:56 -> e");
+
+  @Test
+  public void testCompose() throws Exception {
+    ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+    ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
+    String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
+  }
+}