[Retrace] Add metadata to partitioning and joining

Bug: b/201666766
Bug: b/211603371
Change-Id: Iefc8217346f4efd85d045117c30a2215a2b34ca9
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 9591d26..668f6de 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -418,7 +418,7 @@
     return mapVersions;
   }
 
-  public MapVersionMappingInformation getFirstMappingInformation() {
+  public MapVersionMappingInformation getFirstMapVersionInformation() {
     return mapVersions.isEmpty() ? null : mapVersions.iterator().next();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingPartitionKeyInfo.java b/src/main/java/com/android/tools/r8/retrace/MappingPartitionKeyInfo.java
deleted file mode 100644
index 56f20b1..0000000
--- a/src/main/java/com/android/tools/r8/retrace/MappingPartitionKeyInfo.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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.retrace;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.FieldReference;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.TypeReference;
-import java.util.function.Consumer;
-
-@Keep
-public interface MappingPartitionKeyInfo {
-
-  void getKeysForClass(ClassReference reference, Consumer<String> keyConsumer);
-
-  void getKeysForClassAndMethodName(
-      ClassReference reference, String methodName, Consumer<String> keyConsumer);
-
-  void getKeysForMethod(MethodReference reference, Consumer<String> keyConsumer);
-
-  void getKeysForField(FieldReference fieldReference, Consumer<String> keyConsumer);
-
-  void getKeysForType(TypeReference typeReference, Consumer<String> keyConsumer);
-
-  static MappingPartitionKeyInfo getDefault(byte[] metadata) {
-    return null;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
index d5a8f6a..707023d 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
@@ -18,7 +18,8 @@
    *
    * @param classReference The minified class reference allowed to be lookup up.
    */
-  public abstract T registerClassUse(ClassReference classReference);
+  public abstract T registerClassUse(
+      DiagnosticsHandler diagnosticsHandler, ClassReference classReference);
 
   public abstract void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java
index c57d88a..2a8a646 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBuilder.java
@@ -8,7 +8,7 @@
 
 @Keep
 public abstract class MappingSupplierBuilder<
-    P extends MappingSupplier, T extends MappingSupplierBuilder<P, T>> {
+    P extends MappingSupplier<P>, T extends MappingSupplierBuilder<P, T>> {
 
   public abstract T self();
 
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java
new file mode 100644
index 0000000..cec8467
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java
@@ -0,0 +1,55 @@
+// 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.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.PartitionMappingSupplierBuilderImpl;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+@Keep
+public abstract class PartitionMappingSupplier extends MappingSupplier<PartitionMappingSupplier> {
+
+  public static Builder builder() {
+    return new PartitionMappingSupplierBuilderImpl();
+  }
+
+  @Keep
+  public abstract static class Builder
+      extends MappingSupplierBuilder<PartitionMappingSupplier, Builder> {
+
+    /***
+     * Sets the serialized metadata that was obtained when partitioning.
+     *
+     * @param metadata the serialized metadata
+     */
+    public abstract Builder setMetadata(byte[] metadata);
+
+    /***
+     * Callback to be notified of a partition that is later going to be needed. When all needed
+     * partitions has been found, the callback specified to {@code setPrepareNewPartitions} will
+     * be called.
+     *
+     * @param partitionToFetchConsumer the consumer to get keys for partitions.
+     */
+    public abstract Builder setPartitionToFetchConsumer(Consumer<String> partitionToFetchConsumer);
+
+    /***
+     * A callback notifying that all partitions should be prepared.
+     *
+     * @param run the callback to listen for when partitions should be prepared.
+     */
+    public abstract Builder setPrepareNewPartitions(Runnable run);
+
+    /***
+     * Set the partition supplier that is needed for retracing. All partitions needed has been
+     * declared earlier and this should block until the bytes associated with the partition is
+     * ready.
+     *
+     * @param supplier the function to return a partition to retrace
+     */
+    public abstract Builder setPartitionSupplier(Function<String, byte[]> supplier);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index bb1bd0e..8cc063e 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -361,14 +361,18 @@
         parsedStackTrace.forEach(
             proxy -> {
               if (proxy.hasClassName()) {
-                mappingSupplier.registerClassUse(proxy.getClassReference());
+                mappingSupplier.registerClassUse(diagnosticsHandler, proxy.getClassReference());
               }
               if (proxy.hasMethodArguments()) {
                 Arrays.stream(proxy.getMethodArguments().split(","))
-                    .forEach(typeName -> registerUseFromTypeReference(mappingSupplier, typeName));
+                    .forEach(
+                        typeName ->
+                            registerUseFromTypeReference(
+                                mappingSupplier, typeName, diagnosticsHandler));
               }
               if (proxy.hasFieldOrReturnType() && !proxy.getFieldOrReturnType().equals("void")) {
-                registerUseFromTypeReference(mappingSupplier, proxy.getFieldOrReturnType());
+                registerUseFromTypeReference(
+                    mappingSupplier, proxy.getFieldOrReturnType(), diagnosticsHandler);
               }
             });
         timing.begin("Retracing");
@@ -393,13 +397,13 @@
   }
 
   private static void registerUseFromTypeReference(
-      MappingSupplier<?> mappingSupplier, String typeName) {
+      MappingSupplier<?> mappingSupplier, String typeName, DiagnosticsHandler diagnosticsHandler) {
     TypeReference typeReference = Reference.typeFromTypeName(typeName);
     if (typeReference.isArray()) {
       typeReference = typeReference.asArray().getBaseType();
     }
     if (typeReference.isClass()) {
-      mappingSupplier.registerClassUse(typeReference.asClass());
+      mappingSupplier.registerClassUse(diagnosticsHandler, typeReference.asClass());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java
new file mode 100644
index 0000000..ad53968
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.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.retrace.internal;
+
+public enum MappingPartitionKeyStrategy {
+  OBFUSCATED_TYPE_NAME_AS_KEY(0);
+
+  final int serializedKey;
+
+  MappingPartitionKeyStrategy(int serializedKey) {
+    this.serializedKey = serializedKey;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
new file mode 100644
index 0000000..6c2e3ca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
@@ -0,0 +1,79 @@
+// 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.retrace.internal;
+
+import static com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.google.common.primitives.Ints;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public interface MappingPartitionMetadataInternal extends MappingPartitionMetadata {
+
+  String getKey(ClassReference classReference);
+
+  MapVersion getMapVersion();
+
+  byte ZERO_BYTE = (byte) 0;
+
+  static MappingPartitionMetadataInternal createFromBytes(
+      byte[] bytes, MapVersion fallBackMapVersion, DiagnosticsHandler diagnosticsHandler) {
+    if (bytes == null) {
+      return obfuscatedTypeNameAsKey(fallBackMapVersion);
+    } else if (bytes.length > 2) {
+      int serializedStrategyId = Ints.fromBytes(ZERO_BYTE, ZERO_BYTE, bytes[0], bytes[1]);
+      MapVersion mapVersion = MapVersion.fromName(new String(bytes, 2, bytes.length - 2));
+      if (serializedStrategyId == OBFUSCATED_TYPE_NAME_AS_KEY.serializedKey) {
+        return obfuscatedTypeNameAsKey(mapVersion);
+      }
+    }
+    RuntimeException exception = new RuntimeException("Unable to build key strategy from metadata");
+    diagnosticsHandler.error(new ExceptionDiagnostic(exception));
+    throw exception;
+  }
+
+  static ObfuscatedTypeNameAsKeyMetadata obfuscatedTypeNameAsKey(MapVersion mapVersion) {
+    return new ObfuscatedTypeNameAsKeyMetadata(mapVersion);
+  }
+
+  class ObfuscatedTypeNameAsKeyMetadata implements MappingPartitionMetadataInternal {
+
+    private final MapVersion mapVersion;
+
+    private ObfuscatedTypeNameAsKeyMetadata(MapVersion mapVersion) {
+      this.mapVersion = mapVersion;
+    }
+
+    @Override
+    public String getKey(ClassReference classReference) {
+      return classReference.getTypeName();
+    }
+
+    @Override
+    public MapVersion getMapVersion() {
+      return mapVersion;
+    }
+
+    @Override
+    public byte[] getBytes() {
+      try {
+        ByteArrayOutputStream temp = new ByteArrayOutputStream();
+        DataOutputStream dataOutputStream = new DataOutputStream(temp);
+        dataOutputStream.writeShort(OBFUSCATED_TYPE_NAME_AS_KEY.serializedKey);
+        dataOutputStream.writeBytes(mapVersion.getName());
+        dataOutputStream.close();
+        return temp.toByteArray();
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBuilderImpl.java
new file mode 100644
index 0000000..f100f97
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBuilderImpl.java
@@ -0,0 +1,62 @@
+// 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.retrace.internal;
+
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.utils.ConsumerUtils;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class PartitionMappingSupplierBuilderImpl extends PartitionMappingSupplier.Builder {
+
+  private Function<String, byte[]> partitionSupplier;
+  private Consumer<String> partitionToFetchConsumer = ConsumerUtils.emptyConsumer();
+  private Runnable prepare = () -> {};
+  private byte[] metadata;
+  private boolean allowExperimental = false;
+
+  @Override
+  public PartitionMappingSupplier.Builder self() {
+    return this;
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setAllowExperimental(boolean allowExperimental) {
+    this.allowExperimental = allowExperimental;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setMetadata(byte[] metadata) {
+    this.metadata = metadata;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setPartitionSupplier(
+      Function<String, byte[]> partitionSupplier) {
+    this.partitionSupplier = partitionSupplier;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setPartitionToFetchConsumer(
+      Consumer<String> partitionToFetchConsumer) {
+    this.partitionToFetchConsumer = partitionToFetchConsumer;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier.Builder setPrepareNewPartitions(Runnable prepare) {
+    this.prepare = prepare;
+    return self();
+  }
+
+  @Override
+  public PartitionMappingSupplier build() {
+    return new PartitionMappingSupplierImpl(
+        metadata, partitionToFetchConsumer, prepare, partitionSupplier, allowExperimental);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
new file mode 100644
index 0000000..7d7708d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
@@ -0,0 +1,131 @@
+// 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.retrace.internal;
+
+import static com.google.common.base.Predicates.alwaysTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.LineReader;
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.InvalidMappingFileException;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * IntelliJ highlights the class as being invalid because it cannot see getClassNameMapper is
+ * defined on the class for some reason.
+ */
+public class PartitionMappingSupplierImpl extends PartitionMappingSupplier {
+
+  private final byte[] metadata;
+  private final Consumer<String> partitionToFetchConsumer;
+  private final Runnable prepare;
+  private final Function<String, byte[]> partitionSupplier;
+  private final boolean allowExperimental;
+
+  private ClassNameMapper classNameMapper;
+  private final Set<String> pendingKeys = new LinkedHashSet<>();
+  private final Set<String> builtKeys = new HashSet<>();
+
+  private MappingPartitionMetadataInternal mappingPartitionMetadataCache;
+
+  PartitionMappingSupplierImpl(
+      byte[] metadata,
+      Consumer<String> partitionToFetchConsumer,
+      Runnable prepare,
+      Function<String, byte[]> partitionSupplier,
+      boolean allowExperimental) {
+    this.metadata = metadata;
+    this.partitionToFetchConsumer = partitionToFetchConsumer;
+    this.prepare = prepare;
+    this.partitionSupplier = partitionSupplier;
+    this.allowExperimental = allowExperimental;
+  }
+
+  private MappingPartitionMetadataInternal getMetadata(DiagnosticsHandler diagnosticsHandler) {
+    return mappingPartitionMetadataCache =
+        MappingPartitionMetadataInternal.createFromBytes(
+            metadata, MapVersion.MAP_VERSION_NONE, diagnosticsHandler);
+  }
+
+  @Override
+  Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler) {
+    return Collections.singleton(
+        new MapVersionMappingInformation(getMetadata(diagnosticsHandler).getMapVersion(), ""));
+  }
+
+  @Override
+  ClassNamingForNameMapper getClassNaming(DiagnosticsHandler diagnosticsHandler, String typeName) {
+    registerClassUse(diagnosticsHandler, Reference.classFromTypeName(typeName));
+    return getClassNameMapper(diagnosticsHandler).getClassNaming(typeName);
+  }
+
+  @Override
+  String getSourceFileForClass(DiagnosticsHandler diagnosticsHandler, String typeName) {
+    // Getting source file should not trigger new fetches of partitions so we are not calling
+    // register here.
+    return getClassNameMapper(diagnosticsHandler).getSourceFile(typeName);
+  }
+
+  private ClassNameMapper getClassNameMapper(DiagnosticsHandler diagnosticsHandler) {
+    MappingPartitionMetadataInternal metadata = getMetadata(diagnosticsHandler);
+    if (!pendingKeys.isEmpty()) {
+      prepare.run();
+    }
+    for (String pendingKey : pendingKeys) {
+      try {
+        LineReader reader =
+            new ProguardMapReaderWithFilteringInputBuffer(
+                new ByteArrayInputStream(partitionSupplier.apply(pendingKey)), alwaysTrue(), true);
+        classNameMapper =
+            ClassNameMapper.mapperFromLineReaderWithFiltering(
+                    reader, metadata.getMapVersion(), diagnosticsHandler, true, allowExperimental)
+                .combine(this.classNameMapper);
+      } catch (IOException e) {
+        throw new InvalidMappingFileException(e);
+      }
+    }
+    builtKeys.addAll(pendingKeys);
+    pendingKeys.clear();
+    if (classNameMapper == null) {
+      classNameMapper = ClassNameMapper.builder().build();
+    }
+    return classNameMapper;
+  }
+
+  @Override
+  public PartitionMappingSupplier registerClassUse(
+      DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
+    registerKeyUse(getMetadata(diagnosticsHandler).getKey(classReference));
+    return this;
+  }
+
+  private void registerKeyUse(String key) {
+    if (!builtKeys.contains(key) && pendingKeys.add(key)) {
+      partitionToFetchConsumer.accept(key);
+    }
+  }
+
+  @Override
+  public void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler) {
+    String errorMessage = "Cannot verify map file hash for partitions";
+    diagnosticsHandler.error(new StringDiagnostic(errorMessage));
+    throw new RuntimeException(errorMessage);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
index 6253f1a..dc0f565 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.LineReader;
 import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.retrace.MappingPartition;
 import com.android.tools.r8.retrace.MappingPartitionMetadata;
 import com.android.tools.r8.retrace.ProguardMapPartitioner;
@@ -55,8 +56,9 @@
                     proguardMapProducer.get(), alwaysTrue(), true));
     // Produce a global mapper to read all from the reader but also to capture all source file
     // mappings.
-    ClassNameMapper.mapperFromLineReaderWithFiltering(
-        reader, MapVersion.MAP_VERSION_UNKNOWN, diagnosticsHandler, true, true);
+    ClassNameMapper classMapper =
+        ClassNameMapper.mapperFromLineReaderWithFiltering(
+            reader, MapVersion.MAP_VERSION_UNKNOWN, diagnosticsHandler, true, true);
     // We can then iterate over all sections.
     reader.forEachClassMapping(
         (classMapping, entries) -> {
@@ -77,7 +79,12 @@
             diagnosticsHandler.error(new ExceptionDiagnostic(e));
           }
         });
-    return null;
+    MapVersion mapVersion = MapVersion.MAP_VERSION_UNKNOWN;
+    MapVersionMappingInformation mapVersionInfo = classMapper.getFirstMapVersionInformation();
+    if (mapVersionInfo != null) {
+      mapVersion = mapVersionInfo.getMapVersion();
+    }
+    return MappingPartitionMetadataInternal.obfuscatedTypeNameAsKey(mapVersion);
   }
 
   public static class PartitionLineReader implements LineReader {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
index a6ae27b..632ca66 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
@@ -112,13 +112,14 @@
     if (classNameMapper == null) {
       return MapVersion.MAP_VERSION_NONE;
     } else {
-      MapVersionMappingInformation mapVersion = classNameMapper.getFirstMappingInformation();
+      MapVersionMappingInformation mapVersion = classNameMapper.getFirstMapVersionInformation();
       return mapVersion == null ? MapVersion.MAP_VERSION_UNKNOWN : mapVersion.getMapVersion();
     }
   }
 
   @Override
-  public ProguardMappingSupplier registerClassUse(ClassReference classReference) {
+  public ProguardMappingSupplier registerClassUse(
+      DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
     if (!hasClassMappingFor(classReference.getTypeName())) {
       pendingClassMappings.add(classReference.getTypeName());
     }
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
index 84f5522..731fc0b 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
@@ -38,7 +38,9 @@
           RetraceApiOutlineInlineTest.ApiTest.class,
           RetraceApiOutlineInOutlineStackTrace.ApiTest.class,
           RetraceApiInlineInOutlineTest.ApiTest.class,
-          RetraceApiSingleFrameTest.ApiTest.class);
+          RetraceApiSingleFrameTest.ApiTest.class,
+          RetracePartitionStringTest.ApiTest.class,
+          RetracePartitionRoundTripTest.ApiTest.class);
 
   public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
       ImmutableList.of();
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java
new file mode 100644
index 0000000..6a91dab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java
@@ -0,0 +1,155 @@
+// 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.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.ProguardMapPartitioner;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
+import com.android.tools.r8.retrace.Retracer;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetracePartitionRoundTripTest extends RetraceApiTestBase {
+
+  public RetracePartitionRoundTripTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference outlineRenamed = Reference.classFromTypeName("a");
+    private final ClassReference callsiteOriginal = Reference.classFromTypeName("some.Class");
+    private final ClassReference callsiteRenamed = Reference.classFromTypeName("b");
+
+    private final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: '2.0' }\n"
+            + "outline.Class -> "
+            + outlineRenamed.getTypeName()
+            + ":\n"
+            + "  1:2:int outline():0:0 -> a\n"
+            + "# { 'id':'com.android.tools.r8.outline' }\n"
+            + callsiteOriginal.getTypeName()
+            + " -> "
+            + callsiteRenamed.getTypeName()
+            + ":\n"
+            + "  1:1:void foo.bar.Baz.qux():42:42 -> s\n"
+            + "  4:4:int foo.bar.baz.outlineCaller(int):98:98 -> s\n"
+            + "  4:4:int outlineCaller(int):24 -> s\n"
+            + "  27:27:int outlineCaller(int):0:0 -> s\n" // This is the actual call to the outline
+            + "# { 'id':'com.android.tools.r8.outlineCallsite', "
+            + "    'positions': { '1': 4, '2': 5 } }\n";
+
+    private int prepareCounter = 0;
+
+    @Test
+    public void test() throws IOException {
+      ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping);
+      Map<String, byte[]> partitions = new HashMap<>();
+      MappingPartitionMetadata metadataData =
+          ProguardMapPartitioner.builder(new DiagnosticsHandler() {})
+              .setProguardMapProducer(proguardMapProducer)
+              .setPartitionConsumer(
+                  partition -> partitions.put(partition.getKey(), partition.getPayload()))
+              .build()
+              .run();
+      assertNotNull(metadataData);
+      assertEquals(2, partitions.size());
+
+      Set<String> preFetchedKeys = new LinkedHashSet<>();
+      PartitionMappingSupplier mappingSupplier =
+          PartitionMappingSupplier.builder()
+              .setMetadata(metadataData.getBytes())
+              .setPartitionToFetchConsumer(preFetchedKeys::add)
+              .setPrepareNewPartitions(() -> prepareCounter++)
+              .setPartitionSupplier(
+                  key -> {
+                    assertTrue(preFetchedKeys.contains(key));
+                    return partitions.get(key);
+                  })
+              .build();
+      assertEquals(0, prepareCounter);
+      Retracer retracer = Retracer.builder().setMappingSupplier(mappingSupplier).build();
+      List<RetraceFrameElement> outlineRetraced =
+          retracer
+              .retraceFrame(
+                  RetraceStackTraceContext.empty(),
+                  OptionalInt.of(1),
+                  Reference.methodFromDescriptor(outlineRenamed, "a", "()I"))
+              .stream()
+              .collect(Collectors.toList());
+      // The retrace result should not be ambiguous or empty.
+      assertEquals(1, outlineRetraced.size());
+      assertEquals(1, preFetchedKeys.size());
+      assertEquals(1, prepareCounter);
+      RetraceFrameElement retraceFrameElement = outlineRetraced.get(0);
+
+      // Check that visiting all frames report the outline.
+      List<RetracedMethodReference> allMethodReferences =
+          retraceFrameElement.stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(1, allMethodReferences.size());
+      assertEquals(0, allMethodReferences.get(0).getOriginalPositionOrDefault(2));
+
+      // Check that visiting rewritten frames will not report anything.
+      List<RetracedMethodReference> rewrittenReferences =
+          retraceFrameElement
+              .streamRewritten(RetraceStackTraceContext.empty())
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(0, rewrittenReferences.size());
+
+      // Retrace the outline position
+      RetraceStackTraceContext context = retraceFrameElement.getRetraceStackTraceContext();
+      List<RetraceFrameElement> retraceOutlineCallee =
+          retracer
+              .retraceFrame(
+                  context,
+                  OptionalInt.of(27),
+                  Reference.methodFromDescriptor(callsiteRenamed, "s", "(I)V"))
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(1, retraceOutlineCallee.size());
+      assertEquals(partitions.keySet(), preFetchedKeys);
+      assertEquals(2, prepareCounter);
+
+      List<RetracedMethodReference> outlineCallSiteFrames =
+          retraceOutlineCallee.get(0).stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(2, outlineCallSiteFrames.size());
+      assertEquals(98, outlineCallSiteFrames.get(0).getOriginalPositionOrDefault(27));
+      assertEquals(24, outlineCallSiteFrames.get(1).getOriginalPositionOrDefault(27));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceSplitterStringTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStringTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/retrace/api/RetraceSplitterStringTest.java
rename to src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStringTest.java
index 1194cd6..5183bf6 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceSplitterStringTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStringTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.retrace.api;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
@@ -22,9 +22,9 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class RetraceSplitterStringTest extends RetraceApiTestBase {
+public class RetracePartitionStringTest extends RetraceApiTestBase {
 
-  public RetraceSplitterStringTest(TestParameters parameters) {
+  public RetracePartitionStringTest(TestParameters parameters) {
     super(parameters);
   }
 
@@ -68,8 +68,7 @@
                           new String(partition.getPayload(), StandardCharsets.UTF_8)))
               .build()
               .run();
-      // TODO(b/211603371): Figure out what metadata should be.
-      assertNull(metadataData);
+      assertNotNull(metadataData);
       assertEquals(2, partitions.size());
       assertEquals(aMapping, partitions.get("a"));
       assertEquals(bMapping, partitions.get("b"));
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index e9e1510..0325801 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-26b0387e484bb583397718f7ddab8d6f13ef8e5d
\ No newline at end of file
+e9a51bfcb895db2b47c64004dd94acb2356a84a4
\ No newline at end of file