[MappingCompose] Extend mapping composer to account for methods
Bug: b/241763080
Change-Id: If11f95c9edf56ce5832be46626e4ec3ce4cfd028
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 f9266b0..050b1ff 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -504,7 +504,7 @@
private List<MappingInformation> additionalMappingInfo = EMPTY_MAPPING_INFORMATION;
- private MappedRange(
+ MappedRange(
Range minifiedRange, MethodSignature signature, Range originalRange, String renamedName) {
this.minifiedRange = minifiedRange;
this.signature = signature;
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 badc6fe..2fcbd3d 100644
--- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
+++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
@@ -6,12 +6,21 @@
import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.ChainableStringConsumer;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SegmentTree;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
public class ComposingBuilder {
@@ -56,10 +65,16 @@
public static class ComposingClassBuilder {
private static final String INDENTATION = " ";
+ private static final int NO_RANGE_FROM = -1;
private final String originalName;
private String renamedName;
private final Map<String, List<MemberNaming>> fieldMembers = new HashMap<>();
+ // Ideally we would have liked to use the signature as a key since this uniquely specifies a
+ // method. However, because a mapping file has no right hand side, we therefore assume that a
+ // starting position uniquely identifies a method. If no position is given there can be only
+ // one method since any shrinker should put in line numbers for overloads.
+ private final Map<String, SegmentTree<List<MappedRange>>> methodMembers = new HashMap<>();
private ComposingClassBuilder(String originalName, String renamedName) {
this.originalName = originalName;
@@ -79,6 +94,12 @@
}
public void compose(ClassNamingForNameMapper mapper) throws MappingComposeException {
+ composeFieldNamings(mapper);
+ composeMethodNamings(mapper);
+ }
+
+ private void composeFieldNamings(ClassNamingForNameMapper mapper)
+ throws MappingComposeException {
mapper.forAllFieldNaming(
fieldNaming -> {
List<MemberNaming> memberNamings = fieldMembers.get(fieldNaming.getOriginalName());
@@ -89,8 +110,8 @@
return;
}
// There is no right-hand side of field mappings thus if we have seen an existing
- // mapping we cannot compose the type. For fields we check that the original type is the
- // same or we throw an error since we cannot guarantee a proper composition.
+ // mapping we cannot compose the type. For fields we check that the original type is
+ // the same or we throw an error since we cannot guarantee a proper composition.
for (int i = 0; i < memberNamings.size(); i++) {
MemberNaming memberNaming = memberNamings.get(i);
assert memberNaming.getRenamedName().equals(fieldNaming.getOriginalName());
@@ -109,14 +130,274 @@
});
}
+ private void composeMethodNamings(ClassNamingForNameMapper mapper)
+ throws MappingComposeException {
+ for (Entry<String, MappedRangesOfName> entry : mapper.mappedRangesByRenamedName.entrySet()) {
+ MappedRangesOfName value = entry.getValue();
+ List<MappedRange> mappedRanges = value.getMappedRanges();
+ MappedRangeResult mappedRangeResult;
+ int index = 0;
+ while ((mappedRangeResult = getMappedRangesForMethod(mappedRanges, index)) != null) {
+ index = mappedRangeResult.endIndex;
+ MappedRange newMappedRange = mappedRangeResult.lastRange;
+ String originalName = newMappedRange.signature.getName();
+ SegmentTree<List<MappedRange>> listSegmentTree = methodMembers.get(originalName);
+ List<MappedRange> existingMappedRanges = null;
+ if (listSegmentTree != null) {
+ Entry<Integer, List<MappedRange>> existingEntry =
+ listSegmentTree.findEntry(mappedRangeResult.startOriginalPosition);
+ // 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) {
+ listSegmentTree.removeSegment(existingEntry.getKey());
+ existingMappedRanges = existingEntry.getValue();
+ }
+ }
+ Range minifiedRange = mappedRangeResult.lastRange.minifiedRange;
+ int endMinifiedPosition = minifiedRange == null ? NO_RANGE_FROM : minifiedRange.to;
+ methodMembers
+ .computeIfAbsent(newMappedRange.renamedName, ignored -> new SegmentTree<>(false))
+ .add(
+ mappedRangeResult.startMinifiedPosition,
+ endMinifiedPosition,
+ composeMappedRangesForMethod(existingMappedRanges, mappedRangeResult.allRanges));
+ }
+ }
+ }
+
+ /***
+ * Iterates over mapped ranges in order, starting from index, and adds to an internal result as
+ * long as the current mapped range is the same method and return a mapped range result
+ * containing all ranges for a method along with some additional information.
+ */
+ private MappedRangeResult getMappedRangesForMethod(List<MappedRange> mappedRanges, int index) {
+ if (index >= mappedRanges.size()) {
+ return null;
+ }
+ List<MappedRange> seenMappedRanges = new ArrayList<>();
+ MappedRange lastSeen = mappedRanges.get(index);
+ seenMappedRanges.add(lastSeen);
+ if (lastSeen.minifiedRange == null) {
+ return new MappedRangeResult(
+ NO_RANGE_FROM, NO_RANGE_FROM, index + 1, lastSeen, seenMappedRanges);
+ }
+ int startMinifiedPosition = lastSeen.minifiedRange.from;
+ int startOriginalPosition =
+ lastSeen.originalRange == null ? NO_RANGE_FROM : lastSeen.originalRange.from;
+ for (int i = index + 1; i < mappedRanges.size(); i++) {
+ MappedRange thisMappedRange = mappedRanges.get(i);
+ // We assume that if we see a mapping where the minified range is 0 then we will not find
+ // another mapping where it is not null.
+ if (thisMappedRange.minifiedRange == null) {
+ assert !lastSeen.signature.equals(thisMappedRange.signature);
+ break;
+ }
+ // Otherwise break if we see a signature that is not equal to the current one and it
+ // has another minified range meaning it is not an inlinee.
+ if (!thisMappedRange.signature.equals(lastSeen.signature)
+ && !thisMappedRange.minifiedRange.equals(lastSeen.minifiedRange)) {
+ break;
+ }
+ seenMappedRanges.add(thisMappedRange);
+ lastSeen = thisMappedRange;
+ }
+ return new MappedRangeResult(
+ startMinifiedPosition,
+ startOriginalPosition,
+ index + seenMappedRanges.size(),
+ lastSeen,
+ seenMappedRanges);
+ }
+
+ private List<MappedRange> composeMappedRangesForMethod(
+ List<MappedRange> existingRanges, List<MappedRange> newRanges)
+ throws MappingComposeException {
+ assert !newRanges.isEmpty();
+ if (existingRanges == null || existingRanges.isEmpty()) {
+ return newRanges;
+ }
+ // The new minified ranges may have ranges that range over positions that has additional
+ // information, such as inlinees:
+ // Existing:
+ // 1:2:void caller():14:15 -> x
+ // 3:3:void inlinee():42:42 -> x
+ // 3:3:void caller():16:16 -> x
+ // 4:10:void caller():17:23 -> x
+ // ...
+ // New mapping:
+ // 1:5:void x():1:5 -> y
+ // 6:10:void x():6:6 -> y
+ // ...
+ // It is important that we therefore split up the new ranges to map everything back to the
+ // existing original mappings:
+ // Resulting mapping:
+ // 1:2:void caller():14:15 -> y
+ // 3:3:void inlinee():42:42 -> y
+ // 3:3:void caller():16:16 -> y
+ // 4:5:void caller():17:18 -> y
+ // 6:10:void caller():19:19 -> y
+ // ...
+ Int2ReferenceMap<List<MappedRange>> mappedRangesForPosition =
+ getExistingMapping(existingRanges);
+ List<MappedRange> newComposedRanges = new ArrayList<>();
+ for (int i = 0; i < newRanges.size(); i++) {
+ if (isInlineMappedRange(newRanges, i)) {
+ throw new MappingComposeException(
+ "Unexpected inline frame '" + existingRanges.get(i) + "' in composing map.");
+ }
+ MappedRange newRange = newRanges.get(i);
+ if (newRange.originalRange == null) {
+ List<MappedRange> existingMappedRanges = mappedRangesForPosition.get(NO_RANGE_FROM);
+ assert existingMappedRanges.size() <= 1;
+ if (existingMappedRanges.isEmpty()) {
+ newComposedRanges.add(newRange);
+ } else {
+ MappedRange existingRange = existingMappedRanges.get(0);
+ newComposedRanges.add(
+ new MappedRange(
+ newRange.minifiedRange,
+ existingRange.signature,
+ existingRange.originalRange,
+ newRange.renamedName));
+ }
+ } else {
+ // First check if the original range matches the existing minified range.
+ List<MappedRange> existingMappedRanges =
+ mappedRangesForPosition.get(newRange.originalRange.from);
+ if (existingMappedRanges == null) {
+ throw new MappingComposeException(
+ "Unexpected missing original position for '" + newRange + "'.");
+ }
+ if (ListUtils.last(existingMappedRanges).minifiedRange.equals(newRange.originalRange)) {
+ computeComposedMappedRange(
+ newComposedRanges,
+ newRange,
+ existingMappedRanges,
+ newRange.minifiedRange.from,
+ newRange.minifiedRange.to);
+ } else {
+ // 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 = newRange.minifiedRange.from;
+ position <= newRange.minifiedRange.to;
+ position++) {
+ List<MappedRange> existingMappedRangesForPosition =
+ mappedRangesForPosition.get(newRange.getOriginalLineNumber(position));
+ if (existingMappedRangesForPosition == null) {
+ throw new MappingComposeException(
+ "Unexpected missing original position for '" + newRange + "'.");
+ }
+ if (!ListUtils.last(existingMappedRanges)
+ .minifiedRange
+ .equals(ListUtils.last(existingMappedRangesForPosition).minifiedRange)) {
+ computeComposedMappedRange(
+ newComposedRanges,
+ newRange,
+ existingMappedRanges,
+ lastStartingMinifiedFrom,
+ position - 1);
+ lastStartingMinifiedFrom = position;
+ existingMappedRanges = existingMappedRangesForPosition;
+ }
+ }
+ computeComposedMappedRange(
+ newComposedRanges,
+ newRange,
+ existingMappedRanges,
+ lastStartingMinifiedFrom,
+ newRange.minifiedRange.to);
+ }
+ }
+ }
+ return newComposedRanges;
+ }
+
+ /***
+ * Builds a position to mapped ranges for mappings for looking up all mapped ranges for a given
+ * position.
+ */
+ private Int2ReferenceMap<List<MappedRange>> getExistingMapping(
+ List<MappedRange> existingRanges) {
+ Int2ReferenceMap<List<MappedRange>> mappedRangesForPosition =
+ new Int2ReferenceOpenHashMap<>();
+ List<MappedRange> currentRangesForPosition = new ArrayList<>();
+ for (int i = 0; i < existingRanges.size(); i++) {
+ MappedRange mappedRange = existingRanges.get(i);
+ currentRangesForPosition.add(mappedRange);
+ if (!isInlineMappedRange(existingRanges, i)) {
+ if (mappedRange.minifiedRange == null) {
+ mappedRangesForPosition.put(NO_RANGE_FROM, currentRangesForPosition);
+ } else {
+ for (int position = mappedRange.minifiedRange.from;
+ position <= mappedRange.minifiedRange.to;
+ position++) {
+ mappedRangesForPosition.put(position, currentRangesForPosition);
+ }
+ }
+ currentRangesForPosition = new ArrayList<>();
+ }
+ }
+ return mappedRangesForPosition;
+ }
+
+ private void computeComposedMappedRange(
+ List<MappedRange> newComposedRanges,
+ MappedRange newMappedRange,
+ List<MappedRange> existingMappedRanges,
+ int lastStartingMinifiedFrom,
+ int position) {
+ Range existingRange = existingMappedRanges.get(0).minifiedRange;
+ assert existingMappedRanges.stream().allMatch(x -> x.minifiedRange.equals(existingRange));
+ Range newMinifiedRange = new Range(lastStartingMinifiedFrom, position);
+ boolean copyOriginalRange = existingRange.equals(newMappedRange.originalRange);
+ for (MappedRange existingMappedRange : existingMappedRanges) {
+ Range existingOriginalRange = existingMappedRange.originalRange;
+ Range newOriginalRange;
+ if (copyOriginalRange || existingOriginalRange.span() == 1) {
+ newOriginalRange = existingOriginalRange;
+ } else {
+ // Find the window that the new range points to into the original range.
+ int existingMinifiedPos = newMappedRange.getOriginalLineNumber(lastStartingMinifiedFrom);
+ int newOriginalStart = existingMappedRange.getOriginalLineNumber(existingMinifiedPos);
+ if (newMappedRange.originalRange.span() == 1) {
+ newOriginalRange = new Range(newOriginalStart, newOriginalStart);
+ } else {
+ assert newMinifiedRange.span() <= existingOriginalRange.span();
+ newOriginalRange =
+ new Range(newOriginalStart, newOriginalStart + newMinifiedRange.span() - 1);
+ }
+ }
+ newComposedRanges.add(
+ new MappedRange(
+ newMinifiedRange,
+ existingMappedRange.signature,
+ newOriginalRange,
+ newMappedRange.renamedName));
+ }
+ }
+
+ private boolean isInlineMappedRange(List<MappedRange> mappedRanges, int index) {
+ if (index + 1 >= mappedRanges.size()) {
+ return false;
+ }
+ return mappedRanges
+ .get(index)
+ .minifiedRange
+ .equals(mappedRanges.get(index + 1).minifiedRange);
+ }
+
public void write(ChainableStringConsumer consumer) {
consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n");
// TODO(b/241763080): Support mapping information.
- writeFieldNames(consumer);
- // TODO(b/241763080): Support function mappings.
+ writeFields(consumer);
+ writeMethods(consumer);
}
- private void writeFieldNames(ChainableStringConsumer consumer) {
+ private void writeFields(ChainableStringConsumer consumer) {
ArrayList<MemberNaming> fieldNamings = new ArrayList<>();
for (List<MemberNaming> namingsForKey : fieldMembers.values()) {
fieldNamings.addAll(namingsForKey);
@@ -125,5 +406,53 @@
fieldNamings.forEach(
naming -> consumer.accept(INDENTATION).accept(naming.toString()).accept("\n"));
}
+
+ private void writeMethods(ChainableStringConsumer consumer) {
+ Map<String, List<MappedRange>> signatureToMappedRanges = new HashMap<>();
+ methodMembers
+ .values()
+ .forEach(
+ segmentTree -> {
+ segmentTree.visitSegments(
+ mappedRanges -> {
+ MethodSignature originalSignature = ListUtils.last(mappedRanges).signature;
+ List<MappedRange> put =
+ signatureToMappedRanges.put(
+ originalSignature.getName() + "_" + originalSignature, mappedRanges);
+ assert put == null;
+ });
+ });
+ ArrayList<String> strings = new ArrayList<>(signatureToMappedRanges.keySet());
+ Collections.sort(strings);
+ for (String key : strings) {
+ signatureToMappedRanges
+ .get(key)
+ .forEach(
+ mappedRange ->
+ consumer.accept(INDENTATION).accept(mappedRange.toString()).accept("\n"));
+ }
+ }
+
+ private static class MappedRangeResult {
+
+ private final int startMinifiedPosition;
+ private final int startOriginalPosition;
+ private final int endIndex;
+ private final MappedRange lastRange;
+ private final List<MappedRange> allRanges;
+
+ public MappedRangeResult(
+ int startMinifiedPosition,
+ int startOriginalPosition,
+ int endIndex,
+ MappedRange lastRange,
+ List<MappedRange> allRanges) {
+ this.startMinifiedPosition = startMinifiedPosition;
+ this.startOriginalPosition = startOriginalPosition;
+ this.endIndex = endIndex;
+ this.lastRange = lastRange;
+ this.allRanges = allRanges;
+ }
+ }
}
}
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 bf0a8dd..0c833bd 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -162,6 +162,10 @@
}
}
+ public String getName() {
+ return name;
+ }
+
enum SignatureKind {
METHOD,
FIELD
diff --git a/src/main/java/com/android/tools/r8/utils/SegmentTree.java b/src/main/java/com/android/tools/r8/utils/SegmentTree.java
index 1fc95b0..9962b1c 100644
--- a/src/main/java/com/android/tools/r8/utils/SegmentTree.java
+++ b/src/main/java/com/android/tools/r8/utils/SegmentTree.java
@@ -6,9 +6,10 @@
import java.util.Map;
import java.util.TreeMap;
+import java.util.function.Consumer;
/**
- * Implementation of a discrete segment tree where intervals are specified by their start end and
+ * Implementation of a discrete segment tree where intervals are specified by their start and end
* point. Both points are considered part of the interval.
*/
public class SegmentTree<V> {
@@ -79,4 +80,21 @@
public int size() {
return size;
}
+
+ public void removeSegment(int start) {
+ if (internalTree.remove(start) != null) {
+ size = size - 1;
+ }
+ }
+
+ public void visitSegments(Consumer<V> consumer) {
+ internalTree
+ .values()
+ .forEach(
+ segment -> {
+ if (segment != null) {
+ consumer.accept(segment);
+ }
+ });
+ }
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeChangeLineNumberTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeChangeLineNumberTest.java
new file mode 100644
index 0000000..bffa4b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeChangeLineNumberTest.java
@@ -0,0 +1,45 @@
+// 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 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 ComposeChangeLineNumberTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private static final String mappingFoo =
+ StringUtils.lines("com.foo -> a:", " 1:1:void m():42:42 -> m");
+ private static final String mappingBar =
+ StringUtils.lines("a -> b:", " 2:2:void m():1:1 -> m");
+ private static final String mappingResult =
+ StringUtils.lines("com.foo -> b:", " 2:2:void m():42:42 -> m");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingResult, composed);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeChangeMethodAndLineNumberTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeChangeMethodAndLineNumberTest.java
new file mode 100644
index 0000000..3e4270c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeChangeMethodAndLineNumberTest.java
@@ -0,0 +1,45 @@
+// 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 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 ComposeChangeMethodAndLineNumberTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private static final String mappingFoo =
+ StringUtils.lines("com.foo -> a:", " 1:1:void m1():42:42 -> m2");
+ private static final String mappingBar =
+ StringUtils.lines("a -> b:", " 2:2:void m2():1:1 -> m3");
+ private static final String mappingResult =
+ StringUtils.lines("com.foo -> b:", " 2:2:void m1():42:42 -> m3");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingResult, composed);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeChangeMethodTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeChangeMethodTest.java
new file mode 100644
index 0000000..03b2baa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeChangeMethodTest.java
@@ -0,0 +1,44 @@
+// 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 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 ComposeChangeMethodTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private static final String mappingFoo =
+ StringUtils.lines("com.foo -> a:", " void f1() -> f2");
+ private static final String mappingBar = StringUtils.lines("a -> b:", " void f2() -> f3");
+ private static final String mappingResult =
+ StringUtils.lines("com.foo -> b:", " void f1() -> f3");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingResult, composed);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodTest.java
new file mode 100644
index 0000000..b0ceccc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodTest.java
@@ -0,0 +1,48 @@
+// 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 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 ComposeDifferentMethodTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ // Here we are testing that a shrinker could have changed the method signature. Since we have no
+ // right hand side mapping we have no other choice than to map the naming through. This would not
+ // be ok if we had overloads but any shrinker should put in line numbering for those.
+ private static final String mappingFoo =
+ StringUtils.lines("com.foo -> a:", " void f1(int) -> f2");
+ private static final String mappingBar =
+ StringUtils.lines("a -> b:", " int f2(boolean) -> f3");
+ private static final String mappingResult =
+ StringUtils.lines("com.foo -> b:", " void f1(int) -> f3");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingResult, composed);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java
new file mode 100644
index 0000000..a58342f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java
@@ -0,0 +1,47 @@
+// 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 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 ComposeDifferentMethodWithLineNumberTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private static final String mappingFoo =
+ StringUtils.lines(
+ "com.foo -> a:", " 1:1:int f1(boolean) -> f2", " 2:2:void f1(int) -> f2");
+ private static final String mappingBar =
+ StringUtils.lines("a -> b:", " 8:8:void f2(boolean):2:2 -> f3");
+ private static final String mappingResult =
+ StringUtils.lines(
+ "com.foo -> b:", " 1:1:int f1(boolean) -> f2", " 8:8:void f1(int) -> f3");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingResult, composed);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByLineNumberChangeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByLineNumberChangeTest.java
new file mode 100644
index 0000000..8c6116e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeInlineFollowedByLineNumberChangeTest.java
@@ -0,0 +1,53 @@
+// 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 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 ComposeInlineFollowedByLineNumberChangeTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private static final String mappingFoo =
+ StringUtils.lines(
+ "com.foo -> a:",
+ " 1:1:void inlinee2():42:42 -> x",
+ " 1:1:void foo.bar.baz.inlinee1():41 -> x",
+ " 1:1:void caller():40 -> x");
+ private static final String mappingBar =
+ StringUtils.lines("a -> b:", " 2:2:void x():1:1 -> y");
+ private static final String mappingResult =
+ StringUtils.lines(
+ "com.foo -> b:",
+ " 2:2:void inlinee2():42:42 -> y",
+ " 2:2:void foo.bar.baz.inlinee1():41 -> y",
+ " 2:2:void caller():40 -> y");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingResult, composed);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSplitRangeStartTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSplitRangeStartTest.java
new file mode 100644
index 0000000..84bceed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSplitRangeStartTest.java
@@ -0,0 +1,57 @@
+// 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 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 ComposeSplitRangeStartTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private static final String mappingFoo =
+ StringUtils.lines(
+ "com.foo -> a:", " 1:5:void m():41:45 -> x", " 6:10:void m():56:60 -> x");
+ private static final String mappingBar =
+ StringUtils.lines(
+ "a -> b:",
+ " 11:12:void x():1:2 -> y",
+ " 13:17:void x():3:7 -> y",
+ " 18:24:void x():8:8 -> y",
+ " 25:26:void x():9:10 -> y");
+ private static final String mappingResult =
+ StringUtils.lines(
+ "com.foo -> b:",
+ " 11:12:void m():41:42 -> y",
+ " 13:15:void m():43:45 -> y",
+ " 16:17:void m():56:57 -> y",
+ " 18:24:void m():58:58 -> y",
+ " 25:26:void m():59:60 -> y");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingResult, composed);
+ }
+}