[Compose] Compose mapping information

Bug: b/241763080
Bug: b/242682464
Bug: b/242673239
Change-Id: I3580cedc32a33a3990149dbe5cee3d7ee0ffa0cc
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 403aa85..6e1bc00 100644
--- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
+++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
@@ -9,7 +9,14 @@
 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.naming.mappinginformation.MapVersionMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.ChainableStringConsumer;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SegmentTree;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -26,14 +33,26 @@
 
   /**
    * To ensure we can do alpha renaming of classes and members without polluting the existing
-   * mapping, we use a committed map that we update for each class name mapping. That allows us to
+   * mappping, we use a committed map that we update for each class name mapping. That allows us to
    * rename to existing renamed names as long as these are also renamed later in the map.
    */
   private final Map<String, ComposingClassBuilder> committed = new HashMap<>();
 
   private Map<String, ComposingClassBuilder> current = new HashMap<>();
 
+  private MapVersionMappingInformation currentMapVersion = null;
+
+  private final ComposingSharedData sharedData = new ComposingSharedData();
+
   public void compose(ClassNameMapper classNameMapper) throws MappingComposeException {
+    MapVersionMappingInformation thisMapVersion = classNameMapper.getFirstMapVersionInformation();
+    if (thisMapVersion != null) {
+      if (currentMapVersion == null
+          || currentMapVersion.getMapVersion().isLessThan(thisMapVersion.getMapVersion())) {
+        currentMapVersion = thisMapVersion;
+      }
+    }
+    sharedData.patchupMappingInformation(classNameMapper);
     for (ClassNamingForNameMapper classMapping : classNameMapper.getClassNameMappings().values()) {
       compose(classMapping);
     }
@@ -45,7 +64,7 @@
     ComposingClassBuilder composingClassBuilder = committed.get(originalName);
     String renamedName = classMapping.renamedName;
     if (composingClassBuilder == null) {
-      composingClassBuilder = new ComposingClassBuilder(originalName, renamedName);
+      composingClassBuilder = new ComposingClassBuilder(originalName, renamedName, sharedData);
     } else {
       composingClassBuilder.setRenamedName(renamedName);
       committed.remove(originalName);
@@ -88,6 +107,10 @@
     List<ComposingClassBuilder> classBuilders = new ArrayList<>(committed.values());
     classBuilders.sort(Comparator.comparing(ComposingClassBuilder::getOriginalName));
     StringBuilder sb = new StringBuilder();
+    // TODO(b/241763080): Keep preamble of mapping files"
+    if (currentMapVersion != null) {
+      sb.append("# ").append(currentMapVersion.serialize()).append("\n");
+    }
     ChainableStringConsumer wrap = ChainableStringConsumer.wrap(sb::append);
     for (ComposingClassBuilder classBuilder : classBuilders) {
       classBuilder.write(wrap);
@@ -95,6 +118,37 @@
     return sb.toString();
   }
 
+  public static class ComposingSharedData {
+
+    /**
+     * RewriteFrameInformation contains condition clauses that are bound to the residual program. As
+     * a result of that, we have to patch up the conditions when we compose new class mappings.
+     */
+    private final List<RewriteFrameMappingInformation> mappingInformationToPatchUp =
+        new ArrayList<>();
+
+    private void patchupMappingInformation(ClassNameMapper classNameMapper) {
+      BiMapContainer<String, String> obfuscatedToOriginalMapping =
+          classNameMapper.getObfuscatedToOriginalMapping();
+      for (RewriteFrameMappingInformation rewriteMappingInfo : mappingInformationToPatchUp) {
+        rewriteMappingInfo
+            .getConditions()
+            .forEach(
+                rewriteCondition -> {
+                  ThrowsCondition throwsCondition = rewriteCondition.asThrowsCondition();
+                  if (throwsCondition != null) {
+                    String originalName = throwsCondition.getClassReference().getTypeName();
+                    String obfuscatedName = obfuscatedToOriginalMapping.inverse.get(originalName);
+                    if (obfuscatedName != null) {
+                      throwsCondition.setClassReferenceInternal(
+                          Reference.classFromTypeName(obfuscatedName));
+                    }
+                  }
+                });
+      }
+    }
+  }
+
   public static class ComposingClassBuilder {
 
     private static final String INDENTATION = "    ";
@@ -108,10 +162,14 @@
     // 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 List<MappingInformation> additionalMappingInfo = null;
+    private final ComposingSharedData sharedData;
 
-    private ComposingClassBuilder(String originalName, String renamedName) {
+    private ComposingClassBuilder(
+        String originalName, String renamedName, ComposingSharedData sharedData) {
       this.originalName = originalName;
       this.renamedName = renamedName;
+      this.sharedData = sharedData;
     }
 
     public void setRenamedName(String renamedName) {
@@ -127,6 +185,13 @@
     }
 
     public void compose(ClassNamingForNameMapper mapper) throws MappingComposeException {
+      List<MappingInformation> newMappingInfo = mapper.getAdditionalMappingInfo();
+      if (newMappingInfo != null && !newMappingInfo.isEmpty()) {
+        if (additionalMappingInfo == null) {
+          additionalMappingInfo = new ArrayList<>();
+        }
+        additionalMappingInfo.addAll(newMappingInfo);
+      }
       composeFieldNamings(mapper);
       composeMethodNamings(mapper);
     }
@@ -232,6 +297,12 @@
             && !isInlineMappedRange(mappedRanges, i)) {
           break;
         }
+        for (MappingInformation mappingInformation : thisMappedRange.getAdditionalMappingInfo()) {
+          if (mappingInformation.isRewriteFrameMappingInformation()) {
+            sharedData.mappingInformationToPatchUp.add(
+                mappingInformation.asRewriteFrameMappingInformation());
+          }
+        }
         seenMappedRanges.add(thisMappedRange);
         lastSeen = thisMappedRange;
       }
@@ -405,12 +476,17 @@
                 new Range(newOriginalStart, newOriginalStart + newMinifiedRange.span() - 1);
           }
         }
-        newComposedRanges.add(
+        MappedRange computedRange =
             new MappedRange(
                 newMinifiedRange,
                 existingMappedRange.signature,
                 newOriginalRange,
-                newMappedRange.renamedName));
+                newMappedRange.renamedName);
+        existingMappedRange
+            .getAdditionalMappingInfo()
+            .forEach(
+                info -> computedRange.addMappingInformation(info, ConsumerUtils.emptyConsumer()));
+        newComposedRanges.add(computedRange);
       }
     }
 
@@ -426,7 +502,10 @@
 
     public void write(ChainableStringConsumer consumer) {
       consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n");
-      // TODO(b/241763080): Support mapping information.
+      if (additionalMappingInfo != null) {
+        additionalMappingInfo.forEach(
+            info -> consumer.accept("# " + info.serialize()).accept("\n"));
+      }
       writeFields(consumer);
       writeMethods(consumer);
     }
@@ -438,7 +517,9 @@
       }
       fieldNamings.sort(Comparator.comparing(MemberNaming::getOriginalName));
       fieldNamings.forEach(
-          naming -> consumer.accept(INDENTATION).accept(naming.toString()).accept("\n"));
+          naming -> {
+            consumer.accept(INDENTATION).accept(naming.toString()).accept("\n");
+          });
     }
 
     private void writeMethods(ChainableStringConsumer consumer) {
@@ -462,8 +543,12 @@
         signatureToMappedRanges
             .get(key)
             .forEach(
-                mappedRange ->
-                    consumer.accept(INDENTATION).accept(mappedRange.toString()).accept("\n"));
+                mappedRange -> {
+                  consumer.accept(INDENTATION).accept(mappedRange.toString()).accept("\n");
+                  for (MappingInformation info : mappedRange.getAdditionalMappingInfo()) {
+                    consumer.accept(INDENTATION).accept("# ").accept(info.serialize()).accept("\n");
+                  }
+                });
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
index 82b2a0b..00eb800 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
@@ -162,7 +162,7 @@
 
     static final String FUNCTION_NAME = "throws";
 
-    private final ClassReference classReference;
+    private ClassReference classReference;
 
     private ThrowsCondition(ClassReference classReference) {
       this.classReference = classReference;
@@ -183,6 +183,14 @@
       return this;
     }
 
+    public void setClassReferenceInternal(ClassReference reference) {
+      this.classReference = reference;
+    }
+
+    public ClassReference getClassReference() {
+      return classReference;
+    }
+
     @Override
     public boolean evaluate(RetraceStackTraceContextImpl context) {
       return classReference.equals(context.getThrownException());
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeAlphaRenamingTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeAlphaRenamingTest.java
index 38a9899..a3f010d 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeAlphaRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeAlphaRenamingTest.java
@@ -31,19 +31,19 @@
   private static final String mapping =
       StringUtils.unixLines(
           "a -> b:",
-          "    int x -> y",
-          "    void x() -> y",
+          "    int a -> b",
+          "    void a() -> b",
           "b -> a:",
-          "    int y -> x",
-          "    void y() -> x");
+          "    int b -> a",
+          "    void b() -> a");
   private static final String mappingResult =
       StringUtils.unixLines(
           "a -> a:",
-          "    int x -> x",
-          "    void x() -> x",
+          "    int a -> a",
+          "    void a() -> a",
           "b -> b:",
-          "    int y -> y",
-          "    void y() -> y");
+          "    int b -> b",
+          "    void b() -> b");
 
   @Test
   public void testCompose() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeHelpers.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeHelpers.java
new file mode 100644
index 0000000..61656a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeHelpers.java
@@ -0,0 +1,12 @@
+// 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;
+
+public class ComposeHelpers {
+
+  public static String doubleToSingleQuote(String str) {
+    return str.replace("\"", "'");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMapVersionTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMapVersionTest.java
index 1774246..bbc6c01 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMapVersionTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMapVersionTest.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.mappingcompose;
 
-import static org.junit.Assert.assertNotEquals;
+import static com.android.tools.r8.mappingcompose.ComposeHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -35,14 +36,13 @@
       StringUtils.unixLines("# { id: 'com.android.tools.r8.mapping', version: '2.0' }", "a -> b:");
   private static final String mappingResult =
       StringUtils.unixLines(
-          "# { id: 'com.android.tools.r8.mapping', version: '2.0' }", "com.bar -> b:");
+          "# {'id':'com.android.tools.r8.mapping','version':'2.0'}", "com.foo -> b:");
 
   @Test
   public void testCompose() throws Exception {
     ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
     ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
     String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
-    // TODO(b/241763080): Support mapping information.
-    assertNotEquals(mappingResult, composed);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
index 2f89df6..98f1cd5 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.mappingcompose;
 
+import static com.android.tools.r8.mappingcompose.ComposeHelpers.doubleToSingleQuote;
 import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.TestBase;
@@ -50,24 +51,24 @@
           "    42:42:int s(int):27:27 -> o");
   private static final String mappingResult =
       StringUtils.unixLines(
-          "# { id: 'com.android.tools.r8.mapping', version: '2.0' }",
-          "outline.Class -> b:",
-          "    4:5:int some.inlinee():75:76 -> a",
-          "    4:5:int outline():0 -> a",
-          "    # { 'id':'com.android.tools.r8.outline' }",
+          "# {'id':'com.android.tools.r8.mapping','version':'2.0'}",
           "outline.Callsite -> c:",
           "    8:8:int outlineCaller(int):23 -> o",
           "    9:9:int foo.bar.baz.outlineCaller(int):98:98 -> o",
           "    9:9:int outlineCaller(int):24 -> o",
           "    42:42:int outlineCaller(int):0:0 -> o",
-          "    # { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '4': 8, '5': 9 } }");
+          "    # { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '4': 8, '5': 9 } }",
+          "outline.Class -> b:",
+          "    4:5:int some.inlinee():75:76 -> m",
+          "    4:5:int outline():0 -> m",
+          "    # {'id':'com.android.tools.r8.outline'}");
 
   @Test
   public void testCompose() throws Exception {
     ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
     ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
     String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
-    // TODO(b/241763080): Support mapping information.
-    assertNotEquals(mappingResult, composed);
+    // TODO(b/242682464): Update this test when the link has been added to the mapping information.
+    assertNotEquals(mappingResult, doubleToSingleQuote(composed));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java
index b6a93a6..cae1f89 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.mappingcompose;
 
-import static org.junit.Assert.assertNotEquals;
+import static com.android.tools.r8.mappingcompose.ComposeHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -42,23 +43,22 @@
           "# { id: 'com.android.tools.r8.mapping', version: '2.0' }",
           "a -> b:",
           "x -> c:",
-          "    void a(Other.Class) -> m");
+          "    8:8:void a(Other.Class):4:4 -> m");
   private static final String mappingResult =
       StringUtils.unixLines(
-          "# { id: 'com.android.tools.r8.mapping', version: '2.0' }",
-          "my.CustomException -> b:",
+          "# {'id':'com.android.tools.r8.mapping','version':'2.0'}",
           "foo.Bar -> c:",
-          "    4:4:void other.Class.inlinee():23:23 -> m",
-          "    4:4:void caller(other.Class):7 -> m",
-          "    # { id: 'com.android.tools.r8.rewriteFrame', "
-              + "conditions: ['throws(Lb;)'], actions: ['removeInnerFrames(1)'] }");
+          "    8:8:void other.Class.inlinee():23:23 -> m",
+          "    8:8:void caller(other.Class):7 -> m",
+          "    # {'id':'com.android.tools.r8.rewriteFrame','conditions':['throws(Lb;)'],"
+              + "'actions':['removeInnerFrames(1)']}",
+          "my.CustomException -> b:");
 
   @Test
   public void testCompose() throws Exception {
     ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
     ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
     String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
-    // TODO(b/241763080): Support mapping information.
-    assertNotEquals(mappingResult, composed);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java
index 9cd77f2..9ec3ec6 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.mappingcompose;
 
-import static org.junit.Assert.assertNotEquals;
+import static com.android.tools.r8.mappingcompose.ComposeHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -36,16 +37,15 @@
   private static final String mappingResult =
       StringUtils.unixLines(
           "com.bar -> c:",
-          "    # {'id':'sourceFile','fileName':'Bar.kt'}",
+          "# {'id':'sourceFile','fileName':'Bar.kt'}",
           "com.foo -> b:",
-          "    # {'id':'sourceFile','fileName':'Foo.kt'}");
+          "# {'id':'sourceFile','fileName':'Foo.kt'}");
 
   @Test
   public void testCompose() throws Exception {
     ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
     ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
     String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
-    // TODO(b/241763080): Support mapping information.
-    assertNotEquals(mappingResult, composed);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java
index ad939a6..20585e9 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.mappingcompose;
 
-import static org.junit.Assert.assertNotEquals;
+import static com.android.tools.r8.mappingcompose.ComposeHelpers.doubleToSingleQuote;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -30,6 +31,7 @@
 
   private static final String mappingFoo =
       StringUtils.unixLines(
+          "# { id: 'com.android.tools.r8.mapping', version: '1.0' }",
           "com.foo -> a:",
           "# { id: 'com.android.tools.r8.synthesized' }",
           "    int f -> a",
@@ -38,6 +40,7 @@
           "    # { id: 'com.android.tools.r8.synthesized' }");
   private static final String mappingBar =
       StringUtils.unixLines(
+          "# { id: 'com.android.tools.r8.mapping', version: '1.0' }",
           "a -> b:",
           "    int a -> b",
           "com.bar -> c:",
@@ -46,23 +49,23 @@
           "    # { id: 'com.android.tools.r8.synthesized' }");
   private static final String mappingResult =
       StringUtils.unixLines(
-          "com.foo -> b:",
-          "# { id: 'com.android.tools.r8.synthesized' }",
-          "    int f -> b",
-          "    # { id: 'com.android.tools.r8.synthesized' }",
-          "    void m() -> b",
-          "    # { id: 'com.android.tools.r8.synthesized' }",
+          "# {'id':'com.android.tools.r8.mapping','version':'1.0'}",
           "com.bar -> c:",
-          "# { id: 'com.android.tools.r8.synthesized' }",
+          "# {'id':'com.android.tools.r8.synthesized'}",
           "    void bar() -> a",
-          "    # { id: 'com.android.tools.r8.synthesized' }");
+          "    # {'id':'com.android.tools.r8.synthesized'}",
+          "com.foo -> b:",
+          "# {'id':'com.android.tools.r8.synthesized'}",
+          "    int f -> b",
+          // TODO(b/242673239): When fixed, we should emit synthetized info here as well.
+          "    void m() -> b",
+          "    # {'id':'com.android.tools.r8.synthesized'}");
 
   @Test
   public void testCompose() throws Exception {
     ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromString(mappingFoo);
     ClassNameMapper mappingForBar = ClassNameMapper.mapperFromString(mappingBar);
     String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
-    // TODO(b/241763080): Support mapping information.
-    assertNotEquals(mappingResult, composed);
+    assertEquals(mappingResult, doubleToSingleQuote(composed));
   }
 }