[MappingCompose] Move composition into its own builder and add fields
Bug: b/241763080
Change-Id: I0bfeb4aa1088a1a74c2ad8fda02e6673f2eb3c2b
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 2cf9407..f9266b0 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -3,8 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
-import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
-
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
@@ -108,12 +106,6 @@
return range;
}
- public void addMappedRange(MappedRange mappedRange) {
- mappedRangesByName
- .computeIfAbsent(mappedRange.renamedName, ignoreArgument(ArrayList::new))
- .add(mappedRange);
- }
-
@Override
public void addMappingInformation(
MappingInformation info, Consumer<MappingInformation> onProhibitedAddition) {
@@ -473,15 +465,6 @@
return result;
}
- public void compose(Builder classNameMapperBuilder) {
- // TODO(b/241763080): Account for previous mappings.
- classNameMapperBuilder.additionalMappingInfo.addAll(additionalMappingInfo);
- forAllFieldNaming(classNameMapperBuilder::addMemberEntry);
- for (MappedRangesOfName ranges : mappedRangesByRenamedName.values()) {
- ranges.mappedRanges.forEach(classNameMapperBuilder::addMappedRange);
- }
- }
-
/**
* MappedRange describes an (original line numbers, signature) <-> (minified line numbers)
* mapping. It can describe 3 different things:
diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
new file mode 100644
index 0000000..badc6fe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
@@ -0,0 +1,129 @@
+// 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.naming;
+
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+
+import com.android.tools.r8.utils.ChainableStringConsumer;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ComposingBuilder {
+
+ private final Map<String, ComposingClassBuilder> mapping = new HashMap<>();
+
+ public void compose(ClassNamingForNameMapper classMapping) throws MappingComposeException {
+ String originalName = classMapping.originalName;
+ ComposingClassBuilder composingClassBuilder = mapping.get(originalName);
+ String renamedName = classMapping.renamedName;
+ if (composingClassBuilder == null) {
+ composingClassBuilder = new ComposingClassBuilder(originalName, renamedName);
+ } else {
+ composingClassBuilder.setRenamedName(renamedName);
+ mapping.remove(originalName);
+ }
+ ComposingClassBuilder previousMapping = mapping.put(renamedName, composingClassBuilder);
+ if (previousMapping != null) {
+ throw new MappingComposeException(
+ "Duplicate class mapping. Both '"
+ + previousMapping.getOriginalName()
+ + "' and '"
+ + originalName
+ + "' maps to '"
+ + renamedName
+ + "'.");
+ }
+ composingClassBuilder.compose(classMapping);
+ }
+
+ @Override
+ public String toString() {
+ List<ComposingClassBuilder> classBuilders = new ArrayList<>(mapping.values());
+ classBuilders.sort(Comparator.comparing(ComposingClassBuilder::getOriginalName));
+ StringBuilder sb = new StringBuilder();
+ ChainableStringConsumer wrap = ChainableStringConsumer.wrap(sb::append);
+ for (ComposingClassBuilder classBuilder : classBuilders) {
+ classBuilder.write(wrap);
+ }
+ return sb.toString();
+ }
+
+ public static class ComposingClassBuilder {
+
+ private static final String INDENTATION = " ";
+
+ private final String originalName;
+ private String renamedName;
+ private final Map<String, List<MemberNaming>> fieldMembers = new HashMap<>();
+
+ private ComposingClassBuilder(String originalName, String renamedName) {
+ this.originalName = originalName;
+ this.renamedName = renamedName;
+ }
+
+ public void setRenamedName(String renamedName) {
+ this.renamedName = renamedName;
+ }
+
+ public String getOriginalName() {
+ return originalName;
+ }
+
+ public String getRenamedName() {
+ return renamedName;
+ }
+
+ public void compose(ClassNamingForNameMapper mapper) throws MappingComposeException {
+ mapper.forAllFieldNaming(
+ fieldNaming -> {
+ List<MemberNaming> memberNamings = fieldMembers.get(fieldNaming.getOriginalName());
+ if (memberNamings == null) {
+ fieldMembers
+ .computeIfAbsent(fieldNaming.getRenamedName(), ignoreArgument(ArrayList::new))
+ .add(fieldNaming);
+ 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.
+ for (int i = 0; i < memberNamings.size(); i++) {
+ MemberNaming memberNaming = memberNamings.get(i);
+ assert memberNaming.getRenamedName().equals(fieldNaming.getOriginalName());
+ if (memberNaming.renamedSignature.equals(fieldNaming.getOriginalSignature())) {
+ memberNamings.set(
+ i,
+ new MemberNaming(
+ memberNaming.getOriginalSignature(), fieldNaming.getRenamedName()));
+ return;
+ }
+ }
+ throw new MappingComposeException(
+ "Unable to compose field naming '"
+ + fieldNaming
+ + "' since the original type has changed.");
+ });
+ }
+
+ 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.
+ }
+
+ private void writeFieldNames(ChainableStringConsumer consumer) {
+ ArrayList<MemberNaming> fieldNamings = new ArrayList<>();
+ for (List<MemberNaming> namingsForKey : fieldMembers.values()) {
+ fieldNamings.addAll(namingsForKey);
+ }
+ fieldNamings.sort(Comparator.comparing(MemberNaming::getOriginalName));
+ fieldNamings.forEach(
+ naming -> consumer.accept(INDENTATION).accept(naming.toString()).accept("\n"));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/MappingComposeException.java b/src/main/java/com/android/tools/r8/naming/MappingComposeException.java
new file mode 100644
index 0000000..f443d0e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/MappingComposeException.java
@@ -0,0 +1,15 @@
+// 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.naming;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class MappingComposeException extends Exception {
+
+ public MappingComposeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/MappingComposer.java b/src/main/java/com/android/tools/r8/naming/MappingComposer.java
index 242aff0..55ef9ee 100644
--- a/src/main/java/com/android/tools/r8/naming/MappingComposer.java
+++ b/src/main/java/com/android/tools/r8/naming/MappingComposer.java
@@ -4,35 +4,24 @@
package com.android.tools.r8.naming;
-import com.android.tools.r8.position.Position;
-
/**
* MappingComposer is a utility to do composition of mapping files to map line numbers correctly
* when having shrunken input that will end up using DEX PC mappings.
*/
public class MappingComposer {
- public static ClassNameMapper compose(ClassNameMapper... classNameMappers) {
+ public static String compose(ClassNameMapper... classNameMappers) throws MappingComposeException {
assert classNameMappers.length > 0;
if (classNameMappers.length == 1) {
- return classNameMappers[0];
+ return classNameMappers[0].toString();
}
- ClassNameMapper.Builder builder = ClassNameMapper.builder();
+ ComposingBuilder builder = new ComposingBuilder();
for (ClassNameMapper classNameMapper : classNameMappers) {
- compose(builder, classNameMapper);
+ for (ClassNamingForNameMapper classMapping :
+ classNameMapper.getClassNameMappings().values()) {
+ builder.compose(classMapping);
+ }
}
- return builder.build();
- }
-
- private static void compose(ClassNameMapper.Builder builder, ClassNameMapper toAdd) {
- toAdd
- .getClassNameMappings()
- .forEach(
- (key, classNamingForNameMapper) -> {
- ClassNamingForNameMapper.Builder classNameMapperBuilder =
- builder.classNamingBuilder(
- key, classNamingForNameMapper.originalName, Position.UNKNOWN);
- classNamingForNameMapper.compose(classNameMapperBuilder);
- });
+ return builder.toString();
}
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDistinctClassesTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDistinctClassesTest.java
index 3bdbedc..706f7ef 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDistinctClassesTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDistinctClassesTest.java
@@ -28,16 +28,14 @@
return getTestParameters().withNoneRuntime().build();
}
- private static final String mappingFoo =
- StringUtils.lines("com.foo -> a:", " 1:2:void m1() -> b");
- private static final String mappingBar =
- StringUtils.lines("com.bar -> b:", " 3:4:void m2() -> b");
+ private static final String mappingFoo = StringUtils.lines("com.foo -> a:");
+ private static final String mappingBar = StringUtils.lines("com.bar -> b:");
@Test
public void testCompose() throws Exception {
ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
- ClassNameMapper composed = MappingComposer.compose(mappingForFoo, mappingForBar);
- assertEquals(mappingBar + mappingFoo, composed.sorted().toString());
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingBar + mappingFoo, composed);
}
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDuplicateClassMappingTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDuplicateClassMappingTest.java
new file mode 100644
index 0000000..154d09d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDuplicateClassMappingTest.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 static org.junit.Assert.assertThrows;
+
+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.MappingComposeException;
+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 ComposeDuplicateClassMappingTest 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 -> b:");
+ private static final String mappingBar = StringUtils.lines("a -> b:");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+ MappingComposeException mappingComposeException =
+ assertThrows(
+ MappingComposeException.class,
+ () -> MappingComposer.compose(mappingForFoo, mappingForBar));
+ assertEquals(
+ "Duplicate class mapping. Both 'com.foo' and 'a' maps to 'b'.",
+ mappingComposeException.getMessage());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeFieldNameTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeFieldNameTest.java
new file mode 100644
index 0000000..e568a56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeFieldNameTest.java
@@ -0,0 +1,43 @@
+// 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 ComposeFieldNameTest 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:", " int f1 -> f2");
+ private static final String mappingBar = StringUtils.lines("a -> b:", " int f2 -> f3");
+ private static final String mappingResult =
+ StringUtils.lines("com.foo -> b:", " int 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/ComposeFieldWithAmbiguousTypeTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeFieldWithAmbiguousTypeTest.java
new file mode 100644
index 0000000..b6883aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeFieldWithAmbiguousTypeTest.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 static org.junit.Assert.assertThrows;
+
+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.MappingComposeException;
+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 ComposeFieldWithAmbiguousTypeTest 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:", " int f1 -> f2");
+ private static final String mappingBar = StringUtils.lines("a -> b:", " bool f2 -> f3");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
+ MappingComposeException mappingComposeException =
+ assertThrows(
+ MappingComposeException.class,
+ () -> MappingComposer.compose(mappingForFoo, mappingForBar));
+ assertEquals(
+ "Unable to compose field naming 'bool f2 -> f3' since the original type has changed.",
+ mappingComposeException.getMessage());
+ }
+}