Version 2.1.68

Align release version of R8 retrace with 4.2 version.

Bug: 163015628
Change-Id: Id593bc4eb3a38b7479e63f3ee48c98cf79f2c847
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 2c3a634..944cc82 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.1.67";
+  public static final String LABEL = "2.1.68";
 
   private Version() {
   }
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 e98d9b9..43a0511 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.naming.ClassNameMapper.MissingFileAction.MISSING_FILE_IS_ERROR;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.ChainableStringConsumer;
+import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
@@ -68,7 +70,7 @@
 
   public static ClassNameMapper mapperFromInputStream(InputStream in) throws IOException {
     return mapperFromBufferedReader(
-        new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)));
+        new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)), null);
   }
 
   public static ClassNameMapper mapperFromFile(Path path) throws IOException {
@@ -87,12 +89,20 @@
   }
 
   public static ClassNameMapper mapperFromString(String contents) throws IOException {
-    return mapperFromBufferedReader(CharSource.wrap(contents).openBufferedStream());
+    return mapperFromBufferedReader(CharSource.wrap(contents).openBufferedStream(), null);
   }
 
-  private static ClassNameMapper mapperFromBufferedReader(BufferedReader reader)
-      throws IOException {
-    try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) {
+  public static ClassNameMapper mapperFromString(
+      String contents, DiagnosticsHandler diagnosticsHandler) throws IOException {
+    return mapperFromBufferedReader(
+        CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler);
+  }
+
+  private static ClassNameMapper mapperFromBufferedReader(
+      BufferedReader reader, DiagnosticsHandler diagnosticsHandler) throws IOException {
+    try (ProguardMapReader proguardReader =
+        new ProguardMapReader(
+            reader, diagnosticsHandler != null ? diagnosticsHandler : new Reporter())) {
       ClassNameMapper.Builder builder = ClassNameMapper.builder();
       proguardReader.parse(builder);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
index 1404387..5a282d0 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.utils.ThrowingConsumer;
 
 /**
@@ -14,8 +16,16 @@
 public interface ClassNaming {
 
   abstract class Builder {
+
     public abstract Builder addMemberEntry(MemberNaming entry);
 
+    public abstract Builder addMappingInformation(MappingInformation mappingInformation);
+
+    public abstract Builder addMappingInformation(
+        MappingInformation mappingInformation,
+        DiagnosticsHandler diagnosticsHandler,
+        int lineNumber);
+
     public abstract ClassNaming build();
 
     /** This is an optional method, may be implemented as no-op */
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index 65bc602..acbb577 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -10,6 +11,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -35,6 +37,7 @@
 public class ClassNamingForMapApplier implements ClassNaming {
 
   public static class Builder extends ClassNaming.Builder {
+
     private final String originalName;
     private final String renamedName;
     private final Position position;
@@ -75,6 +78,21 @@
     }
 
     @Override
+    public ClassNaming.Builder addMappingInformation(MappingInformation mappingInformation) {
+      // Intentionally kept empty until we support additional information with -applymapping.
+      return this;
+    }
+
+    @Override
+    public ClassNaming.Builder addMappingInformation(
+        MappingInformation mappingInformation,
+        DiagnosticsHandler diagnosticsHandler,
+        int lineNumber) {
+      // Intentionally kept empty until we support additional information with -applymapping.
+      return this;
+    }
+
+    @Override
     public ClassNamingForMapApplier build() {
       return new ClassNamingForMapApplier(
           renamedName, originalName, position, qualifiedMethodMembers, methodMembers, fieldMembers);
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 5a00fcb..98293ab 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -3,10 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.naming.MemberNaming.NoSignature.NO_SIGNATURE;
+
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.ImmutableMap;
@@ -19,6 +24,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * Stores name information for a class.
@@ -28,12 +34,14 @@
 public class ClassNamingForNameMapper implements ClassNaming {
 
   public static class Builder extends ClassNaming.Builder {
+
     private final String originalName;
     private final String renamedName;
     private final Map<MethodSignature, MemberNaming> methodMembers = Maps.newHashMap();
     private final Map<FieldSignature, MemberNaming> fieldMembers = Maps.newHashMap();
     private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap();
     private final Map<String, List<MemberNaming>> mappedNamingsByName = Maps.newHashMap();
+    private final Map<Signature, List<MappingInformation>> additionalMappings = Maps.newHashMap();
 
     private Builder(String renamedName, String originalName) {
       this.originalName = originalName;
@@ -43,9 +51,9 @@
     @Override
     public ClassNaming.Builder addMemberEntry(MemberNaming entry) {
       if (entry.isMethodNaming()) {
-        methodMembers.put((MethodSignature) entry.getRenamedSignature(), entry);
+        methodMembers.put(entry.getRenamedSignature().asMethodSignature(), entry);
       } else {
-        fieldMembers.put((FieldSignature) entry.getRenamedSignature(), entry);
+        fieldMembers.put(entry.getRenamedSignature().asFieldSignature(), entry);
       }
       mappedNamingsByName
           .computeIfAbsent(entry.getRenamedName(), m -> new ArrayList<>())
@@ -54,6 +62,45 @@
     }
 
     @Override
+    public ClassNaming.Builder addMappingInformation(MappingInformation mappingInformation) {
+      return addMappingInformation(
+          mappingInformation,
+          other -> {
+            assert false;
+          });
+    }
+
+    @Override
+    public ClassNaming.Builder addMappingInformation(
+        MappingInformation mappingInformation,
+        DiagnosticsHandler diagnosticsHandler,
+        int lineNumber) {
+      return addMappingInformation(
+          mappingInformation,
+          other ->
+              diagnosticsHandler.warning(
+                  MappingInformationDiagnostics.notAllowedCombination(
+                      originalName, renamedName, mappingInformation, other, lineNumber)));
+    }
+
+    private ClassNaming.Builder addMappingInformation(
+        MappingInformation mappingInformation, Consumer<MappingInformation> notAllowedCombination) {
+      Signature signature =
+          mappingInformation.isSignatureMappingInformation()
+              ? mappingInformation.asSignatureMappingInformation().getSignature()
+              : NO_SIGNATURE;
+      List<MappingInformation> additionalMappingForSignature =
+          additionalMappings.computeIfAbsent(signature, ignored -> new ArrayList<>());
+      for (MappingInformation information : additionalMappingForSignature) {
+        if (!information.allowOther(mappingInformation)) {
+          notAllowedCombination.accept(information);
+        }
+      }
+      additionalMappingForSignature.add(mappingInformation);
+      return this;
+    }
+
+    @Override
     public ClassNamingForNameMapper build() {
       Map<String, MappedRangesOfName> map;
 
@@ -67,7 +114,13 @@
       }
 
       return new ClassNamingForNameMapper(
-          renamedName, originalName, methodMembers, fieldMembers, map, mappedNamingsByName);
+          renamedName,
+          originalName,
+          methodMembers,
+          fieldMembers,
+          map,
+          mappedNamingsByName,
+          additionalMappings);
     }
 
     /** The parameters are forwarded to MappedRange constructor, see explanation there. */
@@ -198,19 +251,23 @@
 
   public final Map<String, List<MemberNaming>> mappedNamingsByName;
 
+  private final Map<Signature, List<MappingInformation>> additionalMappings;
+
   private ClassNamingForNameMapper(
       String renamedName,
       String originalName,
       Map<MethodSignature, MemberNaming> methodMembers,
       Map<FieldSignature, MemberNaming> fieldMembers,
       Map<String, MappedRangesOfName> mappedRangesByRenamedName,
-      Map<String, List<MemberNaming>> mappedNamingsByName) {
+      Map<String, List<MemberNaming>> mappedNamingsByName,
+      Map<Signature, List<MappingInformation>> additionalMappings) {
     this.renamedName = renamedName;
     this.originalName = originalName;
     this.methodMembers = ImmutableMap.copyOf(methodMembers);
     this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
     this.mappedRangesByRenamedName = mappedRangesByRenamedName;
     this.mappedNamingsByName = mappedNamingsByName;
+    this.additionalMappings = additionalMappings;
   }
 
   public MappedRangesOfName getMappedRangesForRenamedName(String renamedName) {
@@ -298,7 +355,16 @@
   void write(ChainableStringConsumer consumer) {
     consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n");
 
-    // First print field member namings.
+    // Print all additional mapping information.
+    additionalMappings.forEach(
+        (signature, mappingInformations) -> {
+          assert !mappingInformations.isEmpty();
+          for (MappingInformation mappingInformation : mappingInformations) {
+            consumer.accept("# " + mappingInformation.serialize()).accept("\n");
+          }
+        });
+
+    // Print field member namings.
     forAllFieldNaming(m -> consumer.accept("    ").accept(m.toString()).accept("\n"));
 
     // Sort MappedRanges by sequence number to restore construction order (original Proguard-map
@@ -313,6 +379,10 @@
     }
   }
 
+  public Map<Signature, List<MappingInformation>> getAdditionalMappings() {
+    return additionalMappings;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
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 84f726d..51c3272 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -58,12 +58,12 @@
   final Position position;
 
   public MemberNaming(Signature signature, String renamedName) {
-    this(signature, renamedName, Position.UNKNOWN);
+    this(signature, signature.asRenamed(renamedName), Position.UNKNOWN);
   }
 
-  public MemberNaming(Signature signature, String renamedName, Position position) {
+  public MemberNaming(Signature signature, Signature renamedSignature, Position position) {
     this.signature = signature;
-    this.renamedSignature = signature.asRenamed(renamedName);
+    this.renamedSignature = renamedSignature;
     this.position = position;
   }
 
@@ -168,6 +168,40 @@
     }
   }
 
+  public static class NoSignature extends Signature {
+
+    public static final NoSignature NO_SIGNATURE = new NoSignature();
+
+    public NoSignature() {
+      super("NO SIGNATURE");
+    }
+
+    @Override
+    Signature asRenamed(String renamedName) {
+      throw new Unreachable("Should not be called on NoSignature");
+    }
+
+    @Override
+    public SignatureKind kind() {
+      throw new Unreachable("Should not be called on NoSignature");
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return o == this;
+    }
+
+    @Override
+    public int hashCode() {
+      return 7;
+    }
+
+    @Override
+    void write(Writer builder) throws IOException {
+      throw new Unreachable("Should not be called on NoSignature");
+    }
+  }
+
   public static class FieldSignature extends Signature {
 
     public final String type;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 747423f..23a9a06 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -3,17 +3,24 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.SignatureMappingInformation;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.utils.IdentifierUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Maps;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -54,16 +61,19 @@
 public class ProguardMapReader implements AutoCloseable {
 
   private final BufferedReader reader;
+  private final JsonParser jsonParser = new JsonParser();
+  private final DiagnosticsHandler diagnosticsHandler;
 
   @Override
   public void close() throws IOException {
-    if (reader != null) {
-      reader.close();
-    }
+    reader.close();
   }
 
-  ProguardMapReader(BufferedReader reader) {
+  ProguardMapReader(BufferedReader reader, DiagnosticsHandler diagnosticsHandler) {
     this.reader = reader;
+    this.diagnosticsHandler = diagnosticsHandler;
+    assert reader != null;
+    assert diagnosticsHandler != null;
   }
 
   // Internal parser state
@@ -118,7 +128,7 @@
     for (int i = 0; i < line.length(); ++i) {
       char c = line.charAt(i);
       if (c == '#') {
-        return true;
+        return !hasFirstCharJsonBrace(line, i);
       } else if (!StringUtils.isWhitespace(c)) {
         return false;
       }
@@ -126,6 +136,33 @@
     return true;
   }
 
+  private boolean isCommentLineWithJsonBrace() {
+    if (line == null) {
+      return false;
+    }
+    for (int i = 0; i < line.length(); ++i) {
+      char c = line.charAt(i);
+      if (c == '#') {
+        return hasFirstCharJsonBrace(line, i);
+      } else if (!Character.isWhitespace(c)) {
+        return false;
+      }
+    }
+    return false;
+  }
+
+  private static boolean hasFirstCharJsonBrace(String line, int commentCharIndex) {
+    for (int i = commentCharIndex + 1; i < line.length(); i++) {
+      char c = line.charAt(i);
+      if (c == '{') {
+        return true;
+      } else if (!Character.isWhitespace(c)) {
+        return false;
+      }
+    }
+    return false;
+  }
+
   private boolean skipLine() throws IOException {
     lineOffset = 0;
     do {
@@ -213,10 +250,26 @@
     MemberNaming lastAddedNaming = null;
     MemberNaming activeMemberNaming = null;
     Range previousMappedRange = null;
+    Map<Signature, SignatureMappingInformation> mappingInformation = Maps.newHashMap();
     do {
       Object originalRange = null;
       Range mappedRange = null;
-
+      // Try to parse any information added in comments above member namings
+      if (isCommentLineWithJsonBrace()) {
+        MappingInformation mappingInfo =
+            MappingInformation.fromJsonObject(parseJsonInComment(), diagnosticsHandler, lineNo);
+        if (mappingInfo != null) {
+          if (mappingInfo.isSignatureMappingInformation()) {
+            SignatureMappingInformation sigMapInfo = mappingInfo.asSignatureMappingInformation();
+            mappingInformation.put(sigMapInfo.getSignature(), sigMapInfo);
+          } else {
+            classNamingBuilder.addMappingInformation(mappingInfo, diagnosticsHandler, lineNo);
+          }
+        }
+        // Skip reading the rest of the line.
+        lineOffset = line.length();
+        continue;
+      }
       // Parse the member line '  x:y:name:z:q -> renamedName'.
       if (!StringUtils.isWhitespace(peekCodePoint())) {
         break;
@@ -274,7 +327,16 @@
           }
         }
       }
-      activeMemberNaming = new MemberNaming(signature, renamedName, getPosition());
+      if (mappingInformation.containsKey(signature)) {
+        activeMemberNaming =
+            new MemberNaming(
+                signature,
+                mappingInformation.get(signature).apply(signature, renamedName, diagnosticsHandler),
+                getPosition());
+      } else {
+        activeMemberNaming =
+            new MemberNaming(signature, signature.asRenamed(renamedName), getPosition());
+      }
       previousMappedRange = mappedRange;
     } while (nextLine());
 
@@ -451,6 +513,20 @@
     return result;
   }
 
+  private JsonObject parseJsonInComment() {
+    assert isCommentLineWithJsonBrace();
+    try {
+      int firstIndex = 0;
+      while (line.charAt(firstIndex) != '{') {
+        firstIndex++;
+      }
+      return jsonParser.parse(line.substring(firstIndex)).getAsJsonObject();
+    } catch (com.google.gson.JsonSyntaxException ex) {
+      // An info message is reported in MappingInformation.
+      return null;
+    }
+  }
+
   private class ParseException extends RuntimeException {
 
     private final int lineNo;
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index be69416..5c50c06 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -74,7 +74,7 @@
   private static SeedMapper seedMapperFromInputStream(Reporter reporter, InputStream in)
       throws IOException {
     BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
-    try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) {
+    try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter)) {
       SeedMapper.Builder builder = SeedMapper.builder(reporter);
       proguardReader.parse(builder);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
new file mode 100644
index 0000000..87b3e7b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, 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.mappinginformation;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+public class FileNameInformation extends MappingInformation {
+
+  private final String fileName;
+
+  public static final String ID = "sourceFile";
+  static final String FILE_NAME_KEY = "fileName";
+
+  private FileNameInformation(String fileName) {
+    super(NO_LINE_NUMBER);
+    this.fileName = fileName;
+  }
+
+  public String getFileName() {
+    return fileName;
+  }
+
+  @Override
+  public String serialize() {
+    JsonObject result = new JsonObject();
+    result.add(MAPPING_ID_KEY, new JsonPrimitive(ID));
+    result.add(FILE_NAME_KEY, new JsonPrimitive(fileName));
+    return result.toString();
+  }
+
+  @Override
+  public boolean isFileNameInformation() {
+    return true;
+  }
+
+  @Override
+  public FileNameInformation asFileNameInformation() {
+    return this;
+  }
+
+  @Override
+  public boolean allowOther(MappingInformation information) {
+    return !information.isFileNameInformation();
+  }
+
+  public static FileNameInformation build(String fileName) {
+    return new FileNameInformation(fileName);
+  }
+
+  public static FileNameInformation build(
+      JsonObject object, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+    try {
+      JsonElement fileName =
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, FILE_NAME_KEY, ID);
+      if (fileName == null) {
+        return null;
+      }
+      return new FileNameInformation(fileName.getAsString());
+    } catch (UnsupportedOperationException | IllegalStateException ignored) {
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.invalidValueForObjectWithId(lineNumber, FILE_NAME_KEY, ID));
+      return null;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
new file mode 100644
index 0000000..c1c5105
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2019, 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.mappinginformation;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+public abstract class MappingInformation {
+
+  static final int NO_LINE_NUMBER = -1;
+
+  public static final String MAPPING_ID_KEY = "id";
+
+  private final int lineNumber;
+
+  MappingInformation(int lineNumber) {
+    this.lineNumber = lineNumber;
+  }
+
+  public int getLineNumber() {
+    return lineNumber;
+  }
+
+  public abstract String serialize();
+
+  public boolean isSignatureMappingInformation() {
+    return false;
+  }
+
+  public SignatureMappingInformation asSignatureMappingInformation() {
+    return null;
+  }
+
+  public boolean isFileNameInformation() {
+    return false;
+  }
+
+  public FileNameInformation asFileNameInformation() {
+    return null;
+  }
+
+  public boolean isMethodSignatureChangedInformation() {
+    return false;
+  }
+
+  public MethodSignatureChangedInformation asMethodSignatureChangedInformation() {
+    return null;
+  }
+
+  public abstract boolean allowOther(MappingInformation information);
+
+  public static MappingInformation fromJsonObject(
+      JsonObject object, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+    if (object == null) {
+      diagnosticsHandler.info(MappingInformationDiagnostics.notValidJson(lineNumber));
+      return null;
+    }
+    JsonElement id = object.get(MAPPING_ID_KEY);
+    if (id == null) {
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.noKeyInJson(lineNumber, MAPPING_ID_KEY));
+      return null;
+    }
+    String idString = id.getAsString();
+    if (idString == null) {
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.notValidString(lineNumber, MAPPING_ID_KEY));
+      return null;
+    }
+    switch (idString) {
+      case MethodSignatureChangedInformation.ID:
+        return MethodSignatureChangedInformation.build(object, diagnosticsHandler, lineNumber);
+      case FileNameInformation.ID:
+        return FileNameInformation.build(object, diagnosticsHandler, lineNumber);
+      default:
+        diagnosticsHandler.info(MappingInformationDiagnostics.noHandlerFor(lineNumber, idString));
+        return null;
+    }
+  }
+
+  static JsonElement getJsonElementFromObject(
+      JsonObject object,
+      DiagnosticsHandler diagnosticsHandler,
+      int lineNumber,
+      String key,
+      String id) {
+    JsonElement element = object.get(key);
+    if (element == null) {
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.noKeyForObjectWithId(lineNumber, key, MAPPING_ID_KEY, id));
+    }
+    return element;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
new file mode 100644
index 0000000..6cc0b28
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2019, 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.mappinginformation;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+
+@Keep
+public class MappingInformationDiagnostics implements Diagnostic {
+
+  private final String message;
+  private final Position position;
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return message;
+  }
+
+  private MappingInformationDiagnostics(String message, Position position) {
+    this.message = message;
+    this.position = position;
+  }
+
+  static MappingInformationDiagnostics noHandlerFor(int lineNumber, String value) {
+    return new MappingInformationDiagnostics(
+        String.format("Could not find a handler for %s", value),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics noKeyInJson(int lineNumber, String key) {
+    return new MappingInformationDiagnostics(
+        String.format("Could not locate '%s' in the JSON object", key),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics notValidJson(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "Not valid JSON", new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics notValidString(int lineNumber, String key) {
+    return new MappingInformationDiagnostics(
+        String.format("The value of '%s' is not a valid string in the JSON object", key),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics tooManyInformationalParameters(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "More informational parameters than actual parameters for method signature",
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics noKeyForObjectWithId(
+      int lineNumber, String key, String mappingKey, String mappingValue) {
+    return new MappingInformationDiagnostics(
+        String.format("Could not find '%s' for object with %s '%s'", key, mappingKey, mappingValue),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics invalidValueForObjectWithId(
+      int lineNumber, String mappingKey, String mappingValue) {
+    return new MappingInformationDiagnostics(
+        String.format(
+            "Could not decode the information for the object with %s '%s'",
+            mappingKey, mappingValue),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics tooManyEntriesForParameterInformation(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "Parameter information do not have 1 or 2 entries",
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics invalidParameterInformationObject(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "Parameter information is not an index and a string representation of a type",
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  public static MappingInformationDiagnostics notAllowedCombination(
+      String className,
+      String renamedClassName,
+      MappingInformation one,
+      MappingInformation other,
+      int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "The mapping '"
+            + one.serialize()
+            + "' is not allowed in combination with '"
+            + other.serialize()
+            + "' in the mapping for "
+            + className
+            + " -> "
+            + renamedClassName,
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java
new file mode 100644
index 0000000..8bb023d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java
@@ -0,0 +1,258 @@
+// Copyright (c) 2019, 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.mappinginformation;
+
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.invalidParameterInformationObject;
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.invalidValueForObjectWithId;
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.tooManyEntriesForParameterInformation;
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.tooManyInformationalParameters;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+/**
+ * The MethodSignatureChangedInformation structure adds extra information regarding the mapped
+ * method signature that is otherwise not available in the existing proguard mapping format. The
+ * JSON-structure is as follows:
+ *
+ * <pre>
+ *   {
+ *     "id": "argumentsChanged",
+ *     "signature": { methodSignature },
+ *     "returnType": "java.lang.String",
+ *     "receiver": false,
+ *     "params": [
+ *       [1], // <-- parameter with original index 1 (starting index based on receiver) is removed.
+ *       [2, Foo] // <-- parameter with index 2 has type Foo
+ *     ]
+ *   }
+ * </pre>
+ */
+public class MethodSignatureChangedInformation extends SignatureMappingInformation {
+
+  private ParameterInformation[] argumentInfos;
+  private final boolean receiver;
+  private final String returnType;
+  private final MethodSignature signature;
+
+  public static final String ID = "methodSignatureChanged";
+  private static final String RETURN_TYPE_KEY = "returnType";
+  private static final String PARAMS_KEY = "params";
+  private static final String RECEIVER_KEY = "receiver";
+
+  @Override
+  public String serialize() {
+    JsonObject result = new JsonObject();
+    serializeMethodSignature(result, signature);
+    result.add(MAPPING_ID_KEY, new JsonPrimitive(ID));
+    result.add(RECEIVER_KEY, new JsonPrimitive(receiver));
+    result.add(RETURN_TYPE_KEY, new JsonPrimitive(returnType));
+    JsonArray arguments = new JsonArray();
+    for (ParameterInformation argInfo : argumentInfos) {
+      arguments.add(argInfo.serialize());
+    }
+    result.add(PARAMS_KEY, arguments);
+    return result.toString();
+  }
+
+  @Override
+  public boolean allowOther(MappingInformation information) {
+    return !information.isMethodSignatureChangedInformation();
+  }
+
+  @Override
+  public Signature getSignature() {
+    return signature;
+  }
+
+  @Override
+  public Signature apply(
+      Signature originalSignature, String renamedName, DiagnosticsHandler diagnosticsHandler) {
+    if (originalSignature == null || !originalSignature.isMethodSignature()) {
+      assert false : "Should only call apply for method signature";
+      return originalSignature;
+    }
+    MethodSignature signature = originalSignature.asMethodSignature();
+    String type = signature.type;
+    String[] parameters = signature.parameters;
+    int numberOfArgumentsRemoved = getNumberOfArgumentsRemoved();
+    if (numberOfArgumentsRemoved > parameters.length) {
+      // The mapping information is not up to date with the current signature.
+      diagnosticsHandler.warning(tooManyInformationalParameters(getLineNumber()));
+      return new MethodSignature(renamedName, type, parameters);
+    }
+    String[] newParameters = new String[parameters.length - numberOfArgumentsRemoved];
+    int insertIndex = 0;
+    for (int i = 0; i < parameters.length; i++) {
+      ParameterInformation argInfo = getParameterInformation(i);
+      if (argInfo != null && argInfo.getType() == null) {
+        // Argument has been removed.
+      } else {
+        if (insertIndex >= newParameters.length) {
+          // The mapping information is not up to date with the current signature.
+          diagnosticsHandler.warning(tooManyInformationalParameters(getLineNumber()));
+          return new MethodSignature(renamedName, type, parameters);
+        } else if (argInfo == null) {
+          // Unchanged, take current parameter.
+          newParameters[insertIndex++] = parameters[i];
+        } else {
+          newParameters[insertIndex++] = argInfo.getType();
+        }
+      }
+    }
+    assert insertIndex == newParameters.length;
+    return new MethodSignature(renamedName, getReturnType(), newParameters);
+  }
+
+  @Override
+  public boolean isMethodSignatureChangedInformation() {
+    return true;
+  }
+
+  public int getNumberOfArgumentsRemoved() {
+    int removedCount = 0;
+    for (ParameterInformation argInfo : argumentInfos) {
+      if (argInfo.type == null) {
+        removedCount++;
+      }
+    }
+    return removedCount;
+  }
+
+  public boolean hasReceiver() {
+    return receiver;
+  }
+
+  public String getReturnType() {
+    return returnType;
+  }
+
+  public ParameterInformation getParameterInformation(int index) {
+    int subtractIndex = receiver ? 1 : 0;
+    for (int i = 0; i < argumentInfos.length; i++) {
+      if (argumentInfos[i].index - subtractIndex == index) {
+        return argumentInfos[i];
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public MethodSignatureChangedInformation asMethodSignatureChangedInformation() {
+    return this;
+  }
+
+  private MethodSignatureChangedInformation(
+      MethodSignature signature,
+      String returnType,
+      boolean hasReceiver,
+      ParameterInformation[] argumentInfos,
+      int lineNumber) {
+    super(lineNumber);
+    this.signature = signature;
+    this.argumentInfos = argumentInfos;
+    this.returnType = returnType;
+    this.receiver = hasReceiver;
+  }
+
+  public static MappingInformation build(
+      JsonObject object, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+    try {
+      JsonElement returnTypeElement =
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, RETURN_TYPE_KEY, ID);
+      JsonElement receiverElement =
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, RECEIVER_KEY, ID);
+      JsonElement argsElement =
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, PARAMS_KEY, ID);
+      MethodSignature signature = getMethodSignature(object, ID, diagnosticsHandler, lineNumber);
+      if (signature == null
+          || returnTypeElement == null
+          || receiverElement == null
+          || argsElement == null) {
+        return null;
+      }
+      JsonArray argumentsArray = argsElement.getAsJsonArray();
+      if (argumentsArray == null) {
+        return null;
+      }
+      ParameterInformation[] args = new ParameterInformation[argumentsArray.size()];
+      for (int i = 0; i < argumentsArray.size(); i++) {
+        args[i] =
+            ParameterInformation.fromJsonArray(
+                argumentsArray.get(i).getAsJsonArray(), diagnosticsHandler, lineNumber);
+      }
+      return new MethodSignatureChangedInformation(
+          signature,
+          returnTypeElement.getAsString(),
+          receiverElement.getAsBoolean(),
+          args,
+          lineNumber);
+    } catch (UnsupportedOperationException | IllegalStateException ignored) {
+      diagnosticsHandler.info(invalidValueForObjectWithId(lineNumber, MAPPING_ID_KEY, ID));
+      return null;
+    }
+  }
+
+  public static class ParameterInformation {
+    private final int index;
+    private final String type;
+
+    public int getIndex() {
+      return index;
+    }
+
+    public String getType() {
+      return type;
+    }
+
+    private ParameterInformation(int index, String type) {
+      this.index = index;
+      this.type = type;
+    }
+
+    static ParameterInformation fromJsonArray(
+        JsonArray argumentInfo, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+      assert argumentInfo != null;
+      try {
+        if (argumentInfo.size() > 2) {
+          diagnosticsHandler.info(tooManyEntriesForParameterInformation(lineNumber));
+          return null;
+        }
+        int index = argumentInfo.get(0).getAsInt();
+        if (argumentInfo.size() == 1) {
+          // This is a removed argument - no type information
+          return new ParameterInformation(index, null);
+        } else {
+          return new ParameterInformation(index, argumentInfo.get(1).getAsString());
+        }
+      } catch (UnsupportedOperationException | IllegalStateException ignored) {
+        diagnosticsHandler.info(invalidParameterInformationObject(lineNumber));
+        return null;
+      }
+    }
+
+    public static ParameterInformation buildRemovedParameterInformation(int index) {
+      return new ParameterInformation(index, null);
+    }
+
+    public static ParameterInformation buildChangedParameterInformation(int index, String type) {
+      return new ParameterInformation(index, type);
+    }
+
+    JsonArray serialize() {
+      JsonArray serializedArray = new JsonArray();
+      serializedArray.add(index);
+      if (type != null) {
+        serializedArray.add(type);
+      }
+      return serializedArray;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java
new file mode 100644
index 0000000..aba33bb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, 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.mappinginformation;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+public abstract class SignatureMappingInformation extends MappingInformation {
+
+  private static final String SIGNATURE_KEY = "signature";
+
+  SignatureMappingInformation(int lineNumber) {
+    super(lineNumber);
+  }
+
+  @Override
+  public boolean isSignatureMappingInformation() {
+    return true;
+  }
+
+  @Override
+  public SignatureMappingInformation asSignatureMappingInformation() {
+    return this;
+  }
+
+  public abstract Signature getSignature();
+
+  public abstract Signature apply(
+      Signature originalSignature, String renamedName, DiagnosticsHandler diagnosticsHandler);
+
+  JsonObject serializeMethodSignature(JsonObject object, MethodSignature signature) {
+    JsonArray signatureArr = new JsonArray();
+    signatureArr.add(signature.type);
+    signatureArr.add(signature.name);
+    for (String parameter : signature.parameters) {
+      signatureArr.add(parameter);
+    }
+    object.add(SIGNATURE_KEY, signatureArr);
+    return object;
+  }
+
+  static MethodSignature getMethodSignature(
+      JsonObject object, String id, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+    JsonElement signatureElement =
+        getJsonElementFromObject(object, diagnosticsHandler, lineNumber, SIGNATURE_KEY, id);
+    if (signatureElement == null || !signatureElement.isJsonArray()) {
+      return null;
+    }
+    // Signature will be [returnType, name, param1, param2, ...].
+    JsonArray signature = signatureElement.getAsJsonArray();
+    String[] parameters = new String[signature.size() - 2];
+    for (int i = 2; i < signature.size(); i++) {
+      parameters[i - 2] = signature.get(i).getAsString();
+    }
+    return new MethodSignature(
+        signature.get(1).getAsString(), signature.get(0).getAsString(), parameters);
+  }
+}
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 0edcea8..f34a5aa 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.Version;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
 import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
@@ -17,7 +18,11 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Charsets;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -35,9 +40,16 @@
 @Keep
 public class Retrace {
 
+  // This is a slight modification of the default regular expression shown for proguard retrace
+  // that allow for retracing classes in the form <class>: lorem ipsum...
+  // Seems like Proguard retrace is expecting the form "Caused by: <class>".
+  public static final String DEFAULT_REGULAR_EXPRESSION =
+      "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)"
+          + "|(?:(?:(?:%c|.*)?[:\"]\\s+)?%c(?::.*)?)";
+
   public static final String USAGE_MESSAGE =
       StringUtils.lines(
-          "Usage: retrace <proguard-map> <stacktrace-file> [--regex <regexp>, --verbose, --info]",
+          "Usage: retrace <proguard-map> [stack-trace-file] [--regex <regexp>, --verbose, --info]",
           "  where <proguard-map> is an r8 generated mapping file.");
 
   private static Builder parseArguments(String[] args, DiagnosticsHandler diagnosticsHandler) {
@@ -45,11 +57,16 @@
     Builder builder = RetraceCommand.builder(diagnosticsHandler);
     boolean hasSetProguardMap = false;
     boolean hasSetStackTrace = false;
+    boolean hasSetRegularExpression = false;
     while (context.head() != null) {
       Boolean help = OptionsParsing.tryParseBoolean(context, "--help");
       if (help != null) {
         return null;
       }
+      Boolean version = OptionsParsing.tryParseBoolean(context, "--version");
+      if (version != null) {
+        return null;
+      }
       Boolean info = OptionsParsing.tryParseBoolean(context, "--info");
       if (info != null) {
         // This is already set in the diagnostics handler.
@@ -63,6 +80,7 @@
       String regex = OptionsParsing.tryParseSingle(context, "--regex", "r");
       if (regex != null && !regex.isEmpty()) {
         builder.setRegularExpression(regex);
+        hasSetRegularExpression = true;
         continue;
       }
       if (!hasSetProguardMap) {
@@ -88,6 +106,9 @@
     if (!hasSetStackTrace) {
       builder.setStackTrace(getStackTraceFromStandardInput());
     }
+    if (!hasSetRegularExpression) {
+      builder.setRegularExpression(DEFAULT_REGULAR_EXPRESSION);
+    }
     return builder;
   }
 
@@ -105,7 +126,7 @@
   private static List<String> getStackTraceFromFile(
       String stackTracePath, DiagnosticsHandler diagnostics) {
     try {
-      return Files.readAllLines(Paths.get(stackTracePath));
+      return Files.readAllLines(Paths.get(stackTracePath), Charsets.UTF_8);
     } catch (IOException e) {
       diagnostics.error(new StringDiagnostic("Could not find stack trace file: " + stackTracePath));
       throw new RetraceAbortException();
@@ -122,23 +143,25 @@
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       timing.begin("Read proguard map");
       ClassNameMapper classNameMapper =
-          ClassNameMapper.mapperFromString(command.proguardMapProducer.get());
+          ClassNameMapper.mapperFromString(
+              command.proguardMapProducer.get(), command.diagnosticsHandler);
       timing.end();
-      RetraceBase retraceBase = RetraceBaseImpl.create(classNameMapper);
+      RetraceApi retracer = Retracer.create(classNameMapper);
       RetraceCommandLineResult result;
       timing.begin("Parse and Retrace");
       if (command.regularExpression != null) {
         result =
             new RetraceRegularExpression(
-                    retraceBase,
+                    retracer,
                     command.stackTrace,
                     command.diagnosticsHandler,
-                    command.regularExpression)
+                    command.regularExpression,
+                    command.isVerbose)
                 .retrace();
       } else {
         result =
             new RetraceStackTrace(
-                    retraceBase, command.stackTrace, command.diagnosticsHandler, command.isVerbose)
+                    retracer, command.stackTrace, command.diagnosticsHandler, command.isVerbose)
                 .retrace();
       }
       timing.end();
@@ -156,18 +179,47 @@
   }
 
   public static void run(String[] args) {
+    // To be compatible with standard retrace and remapper, we translate -arg into --arg.
+    String[] mappedArgs = new String[args.length];
+    boolean printInfo = false;
+    for (int i = 0; i < args.length; i++) {
+      String arg = args[i];
+      if (arg == null || arg.length() < 2) {
+        mappedArgs[i] = arg;
+        continue;
+      }
+      if (arg.charAt(0) == '-' && arg.charAt(1) != '-') {
+        mappedArgs[i] = "-" + arg;
+      } else {
+        mappedArgs[i] = arg;
+      }
+      if (mappedArgs[i].equals("--info")) {
+        printInfo = true;
+      }
+    }
     RetraceDiagnosticsHandler retraceDiagnosticsHandler =
-        new RetraceDiagnosticsHandler(
-            new DiagnosticsHandler() {}, Arrays.asList(args).contains("--info"));
-    Builder builder = parseArguments(args, retraceDiagnosticsHandler);
+        new RetraceDiagnosticsHandler(new DiagnosticsHandler() {}, printInfo);
+    Builder builder = parseArguments(mappedArgs, retraceDiagnosticsHandler);
     if (builder == null) {
-      // --help was an argument to list
-      assert Arrays.asList(args).contains("--help");
+      // --help or --version was an argument to list
+      if (Arrays.asList(mappedArgs).contains("--version")) {
+        System.out.println("Retrace " + Version.getVersionString());
+        return;
+      }
+      assert Arrays.asList(mappedArgs).contains("--help");
       System.out.print(USAGE_MESSAGE);
       return;
     }
     builder.setRetracedStackTraceConsumer(
-        retraced -> System.out.print(StringUtils.lines(retraced)));
+        retraced -> {
+          try (PrintStream printStream = new PrintStream(System.out, true, Charsets.UTF_8.name())) {
+            for (String line : retraced) {
+              printStream.println(line);
+            }
+          } catch (UnsupportedEncodingException e) {
+            retraceDiagnosticsHandler.error(new StringDiagnostic(e.getMessage()));
+          }
+        });
     run(builder.build());
   }
 
@@ -181,7 +233,7 @@
   }
 
   private static List<String> getStackTraceFromStandardInput() {
-    Scanner sc = new Scanner(System.in);
+    Scanner sc = new Scanner(new InputStreamReader(System.in, Charsets.UTF_8));
     List<String> readLines = new ArrayList<>();
     while (sc.hasNext()) {
       readLines.add(sc.nextLine());
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBase.java b/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/retrace/RetraceBase.java
rename to src/main/java/com/android/tools/r8/retrace/RetraceApi.java
index 2874fc8..7bb9232 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBase.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
@@ -4,12 +4,15 @@
 
 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;
 
-public interface RetraceBase {
+/** This is the main api interface for retrace. */
+@Keep
+public interface RetraceApi {
 
   RetraceMethodResult retrace(MethodReference methodReference);
 
@@ -18,12 +21,4 @@
   RetraceClassResult retrace(ClassReference classReference);
 
   RetraceTypeResult retrace(TypeReference typeReference);
-
-  String retraceSourceFile(ClassReference classReference, String sourceFile);
-
-  String retraceSourceFile(
-      ClassReference classReference,
-      String sourceFile,
-      ClassReference retracedClassReference,
-      boolean hasRetraceResult);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
deleted file mode 100644
index 9a68706..0000000
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (c) 2019, 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.naming.ClassNameMapper;
-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 com.android.tools.r8.utils.Box;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-import java.util.Set;
-
-public class RetraceBaseImpl implements RetraceBase {
-
-  private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
-      Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source");
-
-  private final ClassNameMapper classNameMapper;
-
-  private RetraceBaseImpl(ClassNameMapper classNameMapper) {
-    this.classNameMapper = classNameMapper;
-  }
-
-  public static RetraceBase create(ClassNameMapper classNameMapper) {
-    return new RetraceBaseImpl(classNameMapper);
-  }
-
-  @Override
-  public RetraceMethodResult retrace(MethodReference methodReference) {
-    return retrace(methodReference.getHolderClass()).lookupMethod(methodReference.getMethodName());
-  }
-
-  @Override
-  public RetraceFieldResult retrace(FieldReference fieldReference) {
-    return retrace(fieldReference.getHolderClass()).lookupField(fieldReference.getFieldName());
-  }
-
-  @Override
-  public RetraceClassResult retrace(ClassReference classReference) {
-    return RetraceClassResult.create(
-        classReference, classNameMapper.getClassNaming(classReference.getTypeName()));
-  }
-
-  @Override
-  public String retraceSourceFile(ClassReference classReference, String sourceFile) {
-    Box<String> retracedSourceFile = new Box<>();
-    retrace(classReference)
-        .forEach(element -> retracedSourceFile.set(element.retraceSourceFile(sourceFile, this)));
-    return retracedSourceFile.get();
-  }
-
-  @Override
-  public RetraceTypeResult retrace(TypeReference typeReference) {
-    return new RetraceTypeResult(typeReference, this);
-  }
-
-  @Override
-  public String retraceSourceFile(
-      ClassReference obfuscatedClass,
-      String sourceFile,
-      ClassReference retracedClassReference,
-      boolean hasRetraceResult) {
-    boolean fileNameProbablyChanged =
-        hasRetraceResult
-            && !retracedClassReference.getTypeName().startsWith(obfuscatedClass.getTypeName());
-    if (!UNKNOWN_SOURCEFILE_NAMES.contains(sourceFile) && !fileNameProbablyChanged) {
-      // We have no new information, only rewrite filename if it is unknown.
-      // PG-retrace will always rewrite the filename, but that seems a bit to harsh to do.
-      return sourceFile;
-    }
-    if (!hasRetraceResult) {
-      // We have no mapping but but the file name is unknown, so the best we can do is take the
-      // name of the obfuscated clazz.
-      assert obfuscatedClass.getTypeName().equals(retracedClassReference.getTypeName());
-      return getClassSimpleName(obfuscatedClass.getTypeName()) + ".java";
-    }
-    String newFileName = getClassSimpleName(retracedClassReference.getTypeName());
-    String extension = Files.getFileExtension(sourceFile);
-    if (extension.isEmpty()) {
-      extension = "java";
-    }
-    return newFileName + "." + extension;
-  }
-
-  private static String getClassSimpleName(String clazz) {
-    int lastIndexOfPeriod = clazz.lastIndexOf('.');
-    // Check if we can find a subclass separator.
-    int endIndex = clazz.lastIndexOf('$');
-    if (lastIndexOfPeriod > endIndex || endIndex < 0) {
-      endIndex = clazz.length();
-    }
-    return clazz.substring(lastIndexOfPeriod + 1, endIndex);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index 25505fe..e13c832 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.naming.MemberNaming.NoSignature.NO_SIGNATURE;
+import static com.android.tools.r8.retrace.RetraceUtils.synthesizeFileName;
+
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.RetraceClassResult.Element;
@@ -22,15 +26,18 @@
 
   private final ClassReference obfuscatedReference;
   private final ClassNamingForNameMapper mapper;
+  private final RetraceApi retracer;
 
-  private RetraceClassResult(ClassReference obfuscatedReference, ClassNamingForNameMapper mapper) {
+  private RetraceClassResult(
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetraceApi retracer) {
     this.obfuscatedReference = obfuscatedReference;
     this.mapper = mapper;
+    this.retracer = retracer;
   }
 
   static RetraceClassResult create(
-      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper) {
-    return new RetraceClassResult(obfuscatedReference, mapper);
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetraceApi retracer) {
+    return new RetraceClassResult(obfuscatedReference, mapper, retracer);
   }
 
   public RetraceFieldResult lookupField(String fieldName) {
@@ -71,12 +78,12 @@
           if (element.mapper != null) {
             mappedRangesForT = lookupFunction.apply(element.mapper, name);
           }
-          elementBox.set(constructor.create(element, mappedRangesForT, name));
+          elementBox.set(constructor.create(element, mappedRangesForT, name, retracer));
         });
     return elementBox.get();
   }
 
-  private boolean hasRetraceResult() {
+  boolean hasRetraceResult() {
     return mapper != null;
   }
 
@@ -86,7 +93,8 @@
         new Element(
             this,
             mapper == null ? obfuscatedReference : Reference.classFromTypeName(mapper.originalName),
-            mapper));
+            mapper,
+            false));
   }
 
   @Override
@@ -96,7 +104,12 @@
   }
 
   private interface ResultConstructor<T, R> {
-    R create(Element element, T mappings, String obfuscatedName);
+    R create(Element element, T mappings, String obfuscatedName, RetraceApi retraceApi);
+  }
+
+  public boolean isAmbiguous() {
+    // Currently we have no way of producing ambiguous class results.
+    return false;
   }
 
   public static class Element {
@@ -108,7 +121,8 @@
     public Element(
         RetraceClassResult classResult,
         ClassReference classReference,
-        ClassNamingForNameMapper mapper) {
+        ClassNamingForNameMapper mapper,
+        boolean isAmbiguous) {
       this.classResult = classResult;
       this.classReference = classReference;
       this.mapper = mapper;
@@ -122,9 +136,26 @@
       return classResult;
     }
 
-    public String retraceSourceFile(String fileName, RetraceBase retraceBase) {
-      return retraceBase.retraceSourceFile(
-          classResult.obfuscatedReference, fileName, classReference, mapper != null);
+    public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
+      if (mapper != null && mapper.getAdditionalMappings().size() > 0) {
+        List<MappingInformation> mappingInformations =
+            mapper.getAdditionalMappings().get(NO_SIGNATURE);
+        if (mappingInformations != null) {
+          for (MappingInformation mappingInformation : mappingInformations) {
+            if (mappingInformation.isFileNameInformation()) {
+              return new RetraceSourceFileResult(
+                  mappingInformation.asFileNameInformation().getFileName(), false);
+            }
+          }
+        }
+      }
+      return new RetraceSourceFileResult(
+          synthesizeFileName(
+              classReference.getTypeName(),
+              classResult.obfuscatedReference.getTypeName(),
+              sourceFile,
+              mapper != null),
+          true);
     }
 
     public RetraceFieldResult lookupField(String fieldName) {
@@ -158,7 +189,10 @@
         BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
         ResultConstructor<T, R> constructor) {
       return constructor.create(
-          this, mapper != null ? lookupFunction.apply(mapper, name) : null, name);
+          this,
+          mapper != null ? lookupFunction.apply(mapper, name) : null,
+          name,
+          classResult.retracer);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
index d02913e..5477eaa 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -23,14 +23,17 @@
   private final RetraceClassResult.Element classElement;
   private final List<MemberNaming> memberNamings;
   private final String obfuscatedName;
+  private final RetraceApi retracer;
 
   RetraceFieldResult(
       RetraceClassResult.Element classElement,
       List<MemberNaming> memberNamings,
-      String obfuscatedName) {
+      String obfuscatedName,
+      RetraceApi retracer) {
     this.classElement = classElement;
     this.memberNamings = memberNamings;
     this.obfuscatedName = obfuscatedName;
+    this.retracer = retracer;
     assert classElement != null;
     assert memberNamings == null
         || (!memberNamings.isEmpty() && memberNamings.stream().allMatch(Objects::nonNull));
@@ -40,7 +43,7 @@
     return memberNamings != null;
   }
 
-  private boolean isAmbiguous() {
+  public boolean isAmbiguous() {
     if (!hasRetraceResult()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index f0719ea..7977858 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -26,15 +26,18 @@
   private final String obfuscatedName;
   private final RetraceClassResult.Element classElement;
   private final MappedRangesOfName mappedRanges;
+  private final RetraceApi retracer;
   private Boolean isAmbiguousCached = null;
 
   RetraceMethodResult(
       RetraceClassResult.Element classElement,
       MappedRangesOfName mappedRanges,
-      String obfuscatedName) {
+      String obfuscatedName,
+      RetraceApi retracer) {
     this.classElement = classElement;
     this.mappedRanges = mappedRanges;
     this.obfuscatedName = obfuscatedName;
+    this.retracer = retracer;
     assert classElement != null;
   }
 
@@ -87,7 +90,7 @@
       }
     }
     return new RetraceMethodResult(
-        classElement, new MappedRangesOfName(narrowedRanges), obfuscatedName);
+        classElement, new MappedRangesOfName(narrowedRanges), obfuscatedName, retracer);
   }
 
   @Override
@@ -180,5 +183,10 @@
       }
       return mappedRange.getFirstLineNumberOfOriginalRange();
     }
+
+    public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
+      return RetraceUtils.getSourceFile(
+          classElement, methodReference.getHolderClass(), sourceFile, retraceMethodResult.retracer);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
index b561305..40b3f41 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -5,100 +5,120 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceClassResult.Element;
-import com.android.tools.r8.retrace.RetraceRegularExpression.RetraceString.RetraceStringBuilder;
+import com.android.tools.r8.retrace.RetraceRegularExpression.ClassNameGroup.ClassNameGroupHandler;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Set;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 public class RetraceRegularExpression {
 
-  private final RetraceBase retraceBase;
+  private final RetraceApi retracer;
   private final List<String> stackTrace;
   private final DiagnosticsHandler diagnosticsHandler;
   private final String regularExpression;
 
   private static final int NO_MATCH = -1;
 
-  private final RegularExpressionGroup[] groups =
-      new RegularExpressionGroup[] {
-        new TypeNameGroup(),
-        new BinaryNameGroup(),
-        new MethodNameGroup(),
-        new FieldNameGroup(),
-        new SourceFileGroup(),
-        new LineNumberGroup(),
-        new FieldOrReturnTypeGroup(),
-        new MethodArgumentsGroup()
-      };
+  private final SourceFileLineNumberGroup sourceFileLineNumberGroup =
+      new SourceFileLineNumberGroup();
+  private final TypeNameGroup typeNameGroup = new TypeNameGroup();
+  private final BinaryNameGroup binaryNameGroup = new BinaryNameGroup();
+  private final SourceFileGroup sourceFileGroup = new SourceFileGroup();
+  private final LineNumberGroup lineNumberGroup = new LineNumberGroup();
+  private final FieldOrReturnTypeGroup fieldOrReturnTypeGroup = new FieldOrReturnTypeGroup();
+  private final MethodArgumentsGroup methodArgumentsGroup = new MethodArgumentsGroup();
+
+  private final MethodNameGroup methodNameGroup;
+  private final FieldNameGroup fieldNameGroup;
 
   private static final String CAPTURE_GROUP_PREFIX = "captureGroup";
+  private static final int FIRST_CAPTURE_GROUP_INDEX = 0;
 
   RetraceRegularExpression(
-      RetraceBase retraceBase,
+      RetraceApi retracer,
       List<String> stackTrace,
       DiagnosticsHandler diagnosticsHandler,
-      String regularExpression) {
-    this.retraceBase = retraceBase;
+      String regularExpression,
+      boolean isVerbose) {
+    this.retracer = retracer;
     this.stackTrace = stackTrace;
     this.diagnosticsHandler = diagnosticsHandler;
     this.regularExpression = regularExpression;
+    methodNameGroup = new MethodNameGroup(isVerbose);
+    fieldNameGroup = new FieldNameGroup(isVerbose);
   }
 
   public RetraceCommandLineResult retrace() {
     List<RegularExpressionGroupHandler> handlers = new ArrayList<>();
-    String regularExpression = registerGroups(this.regularExpression, handlers);
-    Pattern compiledPattern = Pattern.compile(regularExpression);
+    StringBuilder refinedRegularExpressionBuilder = new StringBuilder();
+    registerGroups(
+        this.regularExpression,
+        refinedRegularExpressionBuilder,
+        handlers,
+        FIRST_CAPTURE_GROUP_INDEX);
+    String refinedRegularExpression = refinedRegularExpressionBuilder.toString();
+    Pattern compiledPattern = Pattern.compile(refinedRegularExpression);
     List<String> result = new ArrayList<>();
     for (String string : stackTrace) {
       Matcher matcher = compiledPattern.matcher(string);
-      List<RetraceString> retracedStrings =
-          Lists.newArrayList(RetraceStringBuilder.create(string).build());
-      if (matcher.matches()) {
-        for (RegularExpressionGroupHandler handler : handlers) {
-          retracedStrings = handler.handleMatch(retracedStrings, matcher, retraceBase);
-        }
+      if (!matcher.matches()) {
+        result.add(string);
+        continue;
+      }
+      // Iterate through handlers to set contexts. That will allow us to process all handlers from
+      // left to right.
+      RetraceStringContext initialContext = RetraceStringContext.empty();
+      for (RegularExpressionGroupHandler handler : handlers) {
+        initialContext = handler.buildInitial(initialContext, matcher, retracer);
+      }
+      final RetraceString initialRetraceString = RetraceString.start(initialContext);
+      List<RetraceString> retracedStrings = Lists.newArrayList(initialRetraceString);
+      for (RegularExpressionGroupHandler handler : handlers) {
+        retracedStrings = handler.handleMatch(string, retracedStrings, matcher, retracer);
       }
       if (retracedStrings.isEmpty()) {
         // We could not find a match. Output the identity.
         result.add(string);
-      } else {
-        boolean isAmbiguous = retracedStrings.size() > 1 && retracedStrings.get(0).isAmbiguous;
-        if (isAmbiguous) {
-          retracedStrings.sort(new RetraceLineComparator());
-        }
-        ClassReference previousContext = null;
-        for (RetraceString retracedString : retracedStrings) {
-          String finalString = retracedString.getRetracedString();
-          if (!isAmbiguous) {
-            result.add(finalString);
-            continue;
-          }
-          assert retracedString.getClassContext() != null;
-          ClassReference currentContext = retracedString.getClassContext().getClassReference();
-          if (currentContext.equals(previousContext)) {
-            int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(finalString);
-            finalString =
-                finalString.substring(0, firstNonWhitespaceCharacter)
-                    + "<OR> "
-                    + finalString.substring(firstNonWhitespaceCharacter);
-          }
-          previousContext = currentContext;
+      }
+      boolean isAmbiguous = retracedStrings.size() > 1 && retracedStrings.get(0).isAmbiguous();
+      if (isAmbiguous) {
+        retracedStrings.sort(new RetraceLineComparator());
+      }
+      ClassReference previousContext = null;
+      for (RetraceString retracedString : retracedStrings) {
+        String finalString = retracedString.builder.build(string);
+        if (!isAmbiguous) {
           result.add(finalString);
+          continue;
         }
+        assert retracedString.getClassContext() != null;
+        ClassReference currentContext = retracedString.getClassContext().getClassReference();
+        if (currentContext.equals(previousContext)) {
+          int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(finalString);
+          finalString =
+              finalString.substring(0, firstNonWhitespaceCharacter)
+                  + "<OR> "
+                  + finalString.substring(firstNonWhitespaceCharacter);
+        }
+        previousContext = currentContext;
+        result.add(finalString);
       }
     }
     return new RetraceCommandLineResult(result);
@@ -126,273 +146,344 @@
     }
   }
 
-  private String registerGroups(
-      String regularExpression, List<RegularExpressionGroupHandler> handlers) {
-    int currentIndex = 0;
-    int captureGroupIndex = 0;
-    while (currentIndex < regularExpression.length()) {
-      RegularExpressionGroup firstGroup = null;
-      int firstIndexFromCurrent = regularExpression.length();
-      for (RegularExpressionGroup group : groups) {
-        int nextIndexOf = regularExpression.indexOf(group.shortName(), currentIndex);
-        if (nextIndexOf > NO_MATCH && nextIndexOf < firstIndexFromCurrent) {
-          // Check if previous character in the regular expression is not \\ to ensure not
-          // overriding a matching on shortName.
-          if (nextIndexOf > 0 && regularExpression.charAt(nextIndexOf - 1) == '\\') {
-            continue;
+  private int registerGroups(
+      String regularExpression,
+      StringBuilder refinedRegularExpression,
+      List<RegularExpressionGroupHandler> handlers,
+      int captureGroupIndex) {
+    int lastCommittedIndex = 0;
+    RegularExpressionGroupHandler lastHandler = null;
+    boolean seenPercentage = false;
+    boolean escaped = false;
+    for (int i = 0; i < regularExpression.length(); i++) {
+      if (seenPercentage) {
+        assert !escaped;
+        final RegularExpressionGroup group = getGroupFromVariable(regularExpression.charAt(i));
+        refinedRegularExpression.append(regularExpression, lastCommittedIndex, i - 1);
+        if (group.isSynthetic()) {
+          captureGroupIndex =
+              registerGroups(
+                  group.subExpression(), refinedRegularExpression, handlers, captureGroupIndex);
+        } else {
+          String captureGroupName = CAPTURE_GROUP_PREFIX + (captureGroupIndex++);
+          refinedRegularExpression
+              .append("(?<")
+              .append(captureGroupName)
+              .append(">")
+              .append(group.subExpression())
+              .append(")");
+          final RegularExpressionGroupHandler handler = group.createHandler(captureGroupName);
+          // If we see a pattern as %c.%m or %C/%m, then register the groups to allow delaying
+          // writing of the class string until we have the fully qualified member.
+          if (lastHandler != null
+              && handler.isQualifiedHandler()
+              && lastHandler.isClassNameGroupHandler()
+              && lastCommittedIndex == i - 3
+              && isTypeOrBinarySeparator(regularExpression, lastCommittedIndex, i - 2)) {
+            final ClassNameGroupHandler classNameGroupHandler =
+                lastHandler.asClassNameGroupHandler();
+            final QualifiedRegularExpressionGroupHandler qualifiedHandler =
+                handler.asQualifiedHandler();
+            classNameGroupHandler.setQualifiedHandler(qualifiedHandler);
+            qualifiedHandler.setClassNameGroupHandler(classNameGroupHandler);
           }
-          firstGroup = group;
-          firstIndexFromCurrent = nextIndexOf;
+          lastHandler = handler;
+          handlers.add(handler);
         }
+        lastCommittedIndex = i + 1;
+        seenPercentage = false;
+      } else {
+        seenPercentage = !escaped && regularExpression.charAt(i) == '%';
+        escaped = !escaped && regularExpression.charAt(i) == '\\';
       }
-      if (firstGroup != null) {
-        String captureGroupName = CAPTURE_GROUP_PREFIX + (captureGroupIndex++);
-        String patternToInsert = "(?<" + captureGroupName + ">" + firstGroup.subExpression() + ")";
-        regularExpression =
-            regularExpression.substring(0, firstIndexFromCurrent)
-                + patternToInsert
-                + regularExpression.substring(
-                    firstIndexFromCurrent + firstGroup.shortName().length());
-        handlers.add(firstGroup.createHandler(captureGroupName));
-        firstIndexFromCurrent += patternToInsert.length();
-      }
-      currentIndex = firstIndexFromCurrent;
     }
-    return regularExpression;
+    refinedRegularExpression.append(
+        regularExpression, lastCommittedIndex, regularExpression.length());
+    return captureGroupIndex;
   }
 
-  static class RetraceString {
+  private boolean isTypeOrBinarySeparator(String regularExpression, int startIndex, int endIndex) {
+    assert endIndex < regularExpression.length();
+    if (startIndex + 1 != endIndex) {
+      return false;
+    }
+    if (regularExpression.charAt(startIndex) != '\\') {
+      return false;
+    }
+    return regularExpression.charAt(startIndex + 1) == '.'
+        || regularExpression.charAt(startIndex + 1) == '/';
+  }
 
+  private RegularExpressionGroup getGroupFromVariable(char variable) {
+    switch (variable) {
+      case 'c':
+        return typeNameGroup;
+      case 'C':
+        return binaryNameGroup;
+      case 'm':
+        return methodNameGroup;
+      case 'f':
+        return fieldNameGroup;
+      case 's':
+        return sourceFileGroup;
+      case 'l':
+        return lineNumberGroup;
+      case 'S':
+        return sourceFileLineNumberGroup;
+      case 't':
+        return fieldOrReturnTypeGroup;
+      case 'a':
+        return methodArgumentsGroup;
+      default:
+        throw new Unreachable("Unexpected variable: " + variable);
+    }
+  }
+
+  static class RetraceStringContext {
     private final Element classContext;
-    private final ClassNameGroup classNameGroup;
     private final ClassReference qualifiedContext;
+    private final String methodName;
     private final RetraceMethodResult.Element methodContext;
-    private final TypeReference typeOrReturnTypeContext;
-    private final boolean hasTypeOrReturnTypeContext;
-    private final String retracedString;
-    private final int adjustedIndex;
-    private final boolean isAmbiguous;
-    private final int lineNumber;
+    private final int minifiedLineNumber;
+    private final int originalLineNumber;
     private final String source;
+    private final boolean isAmbiguous;
 
-    private RetraceString(
+    private RetraceStringContext(
         Element classContext,
-        ClassNameGroup classNameGroup,
         ClassReference qualifiedContext,
+        String methodName,
         RetraceMethodResult.Element methodContext,
-        TypeReference typeOrReturnTypeContext,
-        boolean hasTypeOrReturnTypeContext,
-        String retracedString,
-        int adjustedIndex,
-        boolean isAmbiguous,
-        int lineNumber,
-        String source) {
+        int minifiedLineNumber,
+        int originalLineNumber,
+        String source,
+        boolean isAmbiguous) {
       this.classContext = classContext;
-      this.classNameGroup = classNameGroup;
       this.qualifiedContext = qualifiedContext;
+      this.methodName = methodName;
       this.methodContext = methodContext;
-      this.typeOrReturnTypeContext = typeOrReturnTypeContext;
-      this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
-      this.retracedString = retracedString;
-      this.adjustedIndex = adjustedIndex;
-      this.isAmbiguous = isAmbiguous;
-      this.lineNumber = lineNumber;
+      this.minifiedLineNumber = minifiedLineNumber;
+      this.originalLineNumber = originalLineNumber;
       this.source = source;
+      this.isAmbiguous = isAmbiguous;
     }
 
-    String getRetracedString() {
-      return retracedString;
+    private static RetraceStringContext empty() {
+      return new RetraceStringContext(null, null, null, null, NO_MATCH, NO_MATCH, null, false);
     }
 
-    boolean hasTypeOrReturnTypeContext() {
-      return hasTypeOrReturnTypeContext;
+    private RetraceStringContext withClassContext(
+        Element classContext, ClassReference qualifiedContext) {
+      return new RetraceStringContext(
+          classContext,
+          qualifiedContext,
+          methodName,
+          methodContext,
+          minifiedLineNumber,
+          originalLineNumber,
+          source,
+          isAmbiguous);
     }
 
-    Element getClassContext() {
-      return classContext;
+    private RetraceStringContext withMethodName(String methodName) {
+      return new RetraceStringContext(
+          classContext,
+          qualifiedContext,
+          methodName,
+          methodContext,
+          minifiedLineNumber,
+          originalLineNumber,
+          source,
+          isAmbiguous);
     }
 
-    RetraceMethodResult.Element getMethodContext() {
-      return methodContext;
+    private RetraceStringContext withMethodContext(
+        RetraceMethodResult.Element methodContext,
+        ClassReference qualifiedContext,
+        boolean isAmbiguous) {
+      return new RetraceStringContext(
+          classContext,
+          qualifiedContext,
+          methodName,
+          methodContext,
+          minifiedLineNumber,
+          originalLineNumber,
+          source,
+          isAmbiguous);
     }
 
-    TypeReference getTypeOrReturnTypeContext() {
-      return typeOrReturnTypeContext;
+    private RetraceStringContext withQualifiedContext(ClassReference qualifiedContext) {
+      return new RetraceStringContext(
+          classContext,
+          qualifiedContext,
+          methodName,
+          methodContext,
+          minifiedLineNumber,
+          originalLineNumber,
+          source,
+          isAmbiguous);
     }
 
-    public ClassReference getQualifiedContext() {
-      return qualifiedContext;
+    public RetraceStringContext withSource(String source) {
+      return new RetraceStringContext(
+          classContext,
+          qualifiedContext,
+          methodName,
+          methodContext,
+          minifiedLineNumber,
+          originalLineNumber,
+          source,
+          isAmbiguous);
     }
 
-    RetraceStringBuilder transform() {
-      return RetraceStringBuilder.create(this);
+    public RetraceStringContext withLineNumbers(int minifiedLineNumber, int originalLineNumber) {
+      return new RetraceStringContext(
+          classContext,
+          qualifiedContext,
+          methodName,
+          methodContext,
+          minifiedLineNumber,
+          originalLineNumber,
+          source,
+          isAmbiguous);
+    }
+  }
+
+  static class RetraceStringBuilder {
+
+    private final StringBuilder retracedString;
+    private int lastCommittedIndex;
+
+    private RetraceStringBuilder(String retracedString, int lastCommittedIndex) {
+      this.retracedString = new StringBuilder(retracedString);
+      this.lastCommittedIndex = lastCommittedIndex;
     }
 
-    public int getLineNumber() {
-      return lineNumber;
+    private void appendRetracedString(
+        String source, String stringToAppend, int originalFromIndex, int originalToIndex) {
+      retracedString.append(source, lastCommittedIndex, originalFromIndex);
+      retracedString.append(stringToAppend);
+      lastCommittedIndex = originalToIndex;
     }
 
-    public String getSource() {
-      return source;
+    private String build(String source) {
+      return retracedString.append(source, lastCommittedIndex, source.length()).toString();
+    }
+  }
+
+  private static class RetraceString {
+
+    private final RetraceStringBuilder builder;
+    private final RetraceStringContext context;
+
+    private RetraceString(RetraceStringBuilder builder, RetraceStringContext context) {
+      this.builder = builder;
+      this.context = context;
     }
 
-    static class RetraceStringBuilder {
+    private Element getClassContext() {
+      return context.classContext;
+    }
 
-      private Element classContext;
-      private ClassNameGroup classNameGroup;
-      private ClassReference qualifiedContext;
-      private RetraceMethodResult.Element methodContext;
-      private TypeReference typeOrReturnTypeContext;
-      private boolean hasTypeOrReturnTypeContext;
-      private String retracedString;
-      private int adjustedIndex;
-      private boolean isAmbiguous;
-      private int lineNumber;
-      private String source;
+    private RetraceMethodResult.Element getMethodContext() {
+      return context.methodContext;
+    }
 
-      private int maxReplaceStringIndex = NO_MATCH;
+    private String getSource() {
+      return context.source;
+    }
 
-      private RetraceStringBuilder(
-          Element classContext,
-          ClassNameGroup classNameGroup,
-          ClassReference qualifiedContext,
-          RetraceMethodResult.Element methodContext,
-          TypeReference typeOrReturnTypeContext,
-          boolean hasTypeOrReturnTypeContext,
-          String retracedString,
-          int adjustedIndex,
-          boolean isAmbiguous,
-          int lineNumber,
-          String source) {
-        this.classContext = classContext;
-        this.classNameGroup = classNameGroup;
-        this.qualifiedContext = qualifiedContext;
-        this.methodContext = methodContext;
-        this.typeOrReturnTypeContext = typeOrReturnTypeContext;
-        this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext;
-        this.retracedString = retracedString;
-        this.adjustedIndex = adjustedIndex;
-        this.isAmbiguous = isAmbiguous;
-        this.lineNumber = lineNumber;
-        this.source = source;
-      }
+    private int getLineNumber() {
+      return context.originalLineNumber;
+    }
 
-      static RetraceStringBuilder create(String string) {
-        return new RetraceStringBuilder(
-            null, null, null, null, null, false, string, 0, false, 0, "");
-      }
+    private boolean isAmbiguous() {
+      return context.isAmbiguous;
+    }
 
-      static RetraceStringBuilder create(RetraceString string) {
-        return new RetraceStringBuilder(
-            string.classContext,
-            string.classNameGroup,
-            string.qualifiedContext,
-            string.methodContext,
-            string.typeOrReturnTypeContext,
-            string.hasTypeOrReturnTypeContext,
-            string.retracedString,
-            string.adjustedIndex,
-            string.isAmbiguous,
-            string.lineNumber,
-            string.source);
-      }
+    private static RetraceString start(RetraceStringContext initialContext) {
+      return new RetraceString(new RetraceStringBuilder("", 0), initialContext);
+    }
 
-      RetraceStringBuilder setClassContext(Element classContext, ClassNameGroup classNameGroup) {
-        this.classContext = classContext;
-        this.classNameGroup = classNameGroup;
-        return this;
-      }
+    private RetraceString updateContext(
+        Function<RetraceStringContext, RetraceStringContext> update) {
+      return new RetraceString(builder, update.apply(context));
+    }
 
-      RetraceStringBuilder setMethodContext(RetraceMethodResult.Element methodContext) {
-        this.methodContext = methodContext;
-        return this;
-      }
+    private RetraceString duplicate(RetraceStringContext newContext) {
+      return new RetraceString(
+          new RetraceStringBuilder(builder.retracedString.toString(), builder.lastCommittedIndex),
+          newContext);
+    }
 
-      RetraceStringBuilder setTypeOrReturnTypeContext(TypeReference typeOrReturnTypeContext) {
-        hasTypeOrReturnTypeContext = true;
-        this.typeOrReturnTypeContext = typeOrReturnTypeContext;
-        return this;
-      }
-
-      RetraceStringBuilder setQualifiedContext(ClassReference qualifiedContext) {
-        this.qualifiedContext = qualifiedContext;
-        return this;
-      }
-
-      RetraceStringBuilder setAmbiguous(boolean isAmbiguous) {
-        this.isAmbiguous = isAmbiguous;
-        return this;
-      }
-
-      RetraceStringBuilder setLineNumber(int lineNumber) {
-        this.lineNumber = lineNumber;
-        return this;
-      }
-
-      RetraceStringBuilder setSource(String source) {
-        this.source = source;
-        return this;
-      }
-
-      RetraceStringBuilder replaceInString(String oldString, String newString) {
-        int oldStringStartIndex = retracedString.indexOf(oldString);
-        assert oldStringStartIndex > NO_MATCH;
-        int oldStringEndIndex = oldStringStartIndex + oldString.length();
-        return replaceInStringRaw(newString, oldStringStartIndex, oldStringEndIndex);
-      }
-
-      RetraceStringBuilder replaceInString(String newString, int originalFrom, int originalTo) {
-        return replaceInStringRaw(
-            newString, originalFrom + adjustedIndex, originalTo + adjustedIndex);
-      }
-
-      RetraceStringBuilder replaceInStringRaw(String newString, int from, int to) {
-        assert from <= to;
-        assert from > maxReplaceStringIndex;
-        String prefix = retracedString.substring(0, from);
-        String postFix = retracedString.substring(to);
-        this.retracedString = prefix + newString + postFix;
-        this.adjustedIndex = adjustedIndex + newString.length() - (to - from);
-        maxReplaceStringIndex = prefix.length() + newString.length();
-        return this;
-      }
-
-      RetraceString build() {
-        return new RetraceString(
-            classContext,
-            classNameGroup,
-            qualifiedContext,
-            methodContext,
-            typeOrReturnTypeContext,
-            hasTypeOrReturnTypeContext,
-            retracedString,
-            adjustedIndex,
-            isAmbiguous,
-            lineNumber,
-            source);
-      }
+    private RetraceString appendRetracedString(
+        String source, String stringToAppend, int originalFromIndex, int originalToIndex) {
+      builder.appendRetracedString(source, stringToAppend, originalFromIndex, originalToIndex);
+      return this;
     }
   }
 
   private interface RegularExpressionGroupHandler {
 
     List<RetraceString> handleMatch(
-        List<RetraceString> strings, Matcher matcher, RetraceBase retraceBase);
+        String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer);
+
+    default RetraceStringContext buildInitial(
+        RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+      return context;
+    }
+
+    default boolean isClassNameGroupHandler() {
+      return false;
+    }
+
+    default ClassNameGroupHandler asClassNameGroupHandler() {
+      return null;
+    }
+
+    default boolean isQualifiedHandler() {
+      return false;
+    }
+
+    default QualifiedRegularExpressionGroupHandler asQualifiedHandler() {
+      return null;
+    }
+  }
+
+  private interface QualifiedRegularExpressionGroupHandler extends RegularExpressionGroupHandler {
+
+    @Override
+    default boolean isQualifiedHandler() {
+      return true;
+    }
+
+    @Override
+    default QualifiedRegularExpressionGroupHandler asQualifiedHandler() {
+      return this;
+    }
+
+    void setClassNameGroupHandler(ClassNameGroupHandler handler);
   }
 
   private abstract static class RegularExpressionGroup {
 
-    abstract String shortName();
-
     abstract String subExpression();
 
     abstract RegularExpressionGroupHandler createHandler(String captureGroup);
+
+    boolean isSynthetic() {
+      return false;
+    }
   }
 
   // TODO(b/145731185): Extend support for identifiers with strings inside back ticks.
-  private static final String javaIdentifierSegment = "[\\p{L}\\p{N}_\\p{Sc}]+";
+  private static final String javaIdentifierSegment =
+      "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
 
-  private abstract static class ClassNameGroup extends RegularExpressionGroup {
+  private static final String METHOD_NAME_REGULAR_EXPRESSION =
+      "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))";
+
+  abstract static class ClassNameGroup extends RegularExpressionGroup {
 
     abstract String getClassName(ClassReference classReference);
 
@@ -400,41 +491,107 @@
 
     @Override
     RegularExpressionGroupHandler createHandler(String captureGroup) {
-      return (strings, matcher, retraceBase) -> {
-        if (matcher.start(captureGroup) == NO_MATCH) {
+      return new ClassNameGroupHandler(this, captureGroup);
+    }
+
+    static class ClassNameGroupHandler implements RegularExpressionGroupHandler {
+
+      private RetraceClassResult retraceClassResult = null;
+      private final ClassNameGroup classNameGroup;
+      private final String captureGroup;
+      private RegularExpressionGroupHandler qualifiedHandler;
+
+      public ClassNameGroupHandler(ClassNameGroup classNameGroup, String captureGroup) {
+        this.classNameGroup = classNameGroup;
+        this.captureGroup = captureGroup;
+      }
+
+      @Override
+      public List<RetraceString> handleMatch(
+          String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+        final int startOfGroup = matcher.start(captureGroup);
+        if (startOfGroup == NO_MATCH) {
           return strings;
         }
         String typeName = matcher.group(captureGroup);
-        RetraceClassResult retraceResult = retraceBase.retrace(classFromMatch(typeName));
-        List<RetraceString> retracedStrings = new ArrayList<>();
+        RetraceClassResult retraceResult =
+            retraceClassResult == null
+                ? retracer.retrace(classNameGroup.classFromMatch(typeName))
+                : retraceClassResult;
+        assert !retraceResult.isAmbiguous();
+        List<RetraceString> retraceStrings = new ArrayList<>(strings.size());
         for (RetraceString retraceString : strings) {
           retraceResult.forEach(
               element -> {
-                retracedStrings.add(
-                    retraceString
-                        .transform()
-                        .setClassContext(element, this)
-                        .setMethodContext(null)
-                        .replaceInString(
-                            getClassName(element.getClassReference()),
-                            matcher.start(captureGroup),
-                            matcher.end(captureGroup))
-                        .build());
+                final RetraceString newRetraceString =
+                    retraceString.updateContext(
+                        context -> context.withClassContext(element, element.getClassReference()));
+                retraceStrings.add(newRetraceString);
+                if (qualifiedHandler == null) {
+                  // If there is no qualified handler, commit right away.
+                  newRetraceString.builder.appendRetracedString(
+                      original,
+                      classNameGroup.getClassName(element.getClassReference()),
+                      startOfGroup,
+                      matcher.end(captureGroup));
+                }
               });
         }
-        return retracedStrings;
-      };
+        return retraceStrings;
+      }
+
+      void commitClassName(
+          String original,
+          RetraceString retraceString,
+          ClassReference qualifiedContext,
+          Matcher matcher) {
+        if (matcher.start(captureGroup) == NO_MATCH) {
+          return;
+        }
+        retraceString.builder.appendRetracedString(
+            original,
+            classNameGroup.getClassName(qualifiedContext),
+            matcher.start(captureGroup),
+            matcher.end(captureGroup));
+      }
+
+      @Override
+      public RetraceStringContext buildInitial(
+          RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+        // Reset the local class context since this the same handler is used for multiple lines.
+        retraceClassResult = null;
+        if (matcher.start(captureGroup) == NO_MATCH || context.classContext != null) {
+          return context;
+        }
+        String typeName = matcher.group(captureGroup);
+        retraceClassResult = retracer.retrace(classNameGroup.classFromMatch(typeName));
+        assert !retraceClassResult.isAmbiguous();
+        Box<RetraceStringContext> box = new Box<>();
+        retraceClassResult.forEach(
+            element -> box.set(context.withClassContext(element, element.getClassReference())));
+        return box.get();
+      }
+
+      public void setQualifiedHandler(RegularExpressionGroupHandler handler) {
+        assert handler.isQualifiedHandler();
+        this.qualifiedHandler = handler;
+      }
+
+      @Override
+      public boolean isClassNameGroupHandler() {
+        return true;
+      }
+
+      @Override
+      public ClassNameGroupHandler asClassNameGroupHandler() {
+        return this;
+      }
     }
   }
 
   private static class TypeNameGroup extends ClassNameGroup {
 
     @Override
-    String shortName() {
-      return "%c";
-    }
-
-    @Override
     String subExpression() {
       return "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment;
     }
@@ -453,11 +610,6 @@
   private static class BinaryNameGroup extends ClassNameGroup {
 
     @Override
-    String shortName() {
-      return "%C";
-    }
-
-    @Override
     String subExpression() {
       return "(?:" + javaIdentifierSegment + "\\/)*" + javaIdentifierSegment;
     }
@@ -475,78 +627,112 @@
 
   private static class MethodNameGroup extends RegularExpressionGroup {
 
-    @Override
-    String shortName() {
-      return "%m";
+    private final boolean printVerbose;
+
+    public MethodNameGroup(boolean printVerbose) {
+      this.printVerbose = printVerbose;
     }
 
     @Override
     String subExpression() {
-      return "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))";
+      return METHOD_NAME_REGULAR_EXPRESSION;
     }
 
     @Override
     RegularExpressionGroupHandler createHandler(String captureGroup) {
-      return (strings, matcher, retraceBase) -> {
-        if (matcher.start(captureGroup) == NO_MATCH) {
-          return strings;
+      return new QualifiedRegularExpressionGroupHandler() {
+
+        private ClassNameGroupHandler classNameGroupHandler;
+
+        @Override
+        public void setClassNameGroupHandler(ClassNameGroupHandler handler) {
+          classNameGroupHandler = handler;
         }
-        String methodName = matcher.group(captureGroup);
-        List<RetraceString> retracedStrings = new ArrayList<>();
-        for (RetraceString retraceString : strings) {
-          if (retraceString.classContext == null) {
-            retracedStrings.add(retraceString);
-            continue;
+
+        @Override
+        public List<RetraceString> handleMatch(
+            String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+          final int startOfGroup = matcher.start(captureGroup);
+          if (startOfGroup == NO_MATCH) {
+            if (classNameGroupHandler != null) {
+              for (RetraceString string : strings) {
+                classNameGroupHandler.commitClassName(
+                    original, string, string.context.qualifiedContext, matcher);
+              }
+            }
+            return strings;
           }
-          retraceString
-              .getClassContext()
-              .lookupMethod(methodName)
-              .forEach(
-                  element -> {
-                    MethodReference methodReference = element.getMethodReference();
-                    if (retraceString.hasTypeOrReturnTypeContext()) {
-                      if (methodReference.getReturnType() == null
-                          && retraceString.getTypeOrReturnTypeContext() != null) {
-                        return;
-                      } else if (methodReference.getReturnType() != null
-                          && !methodReference
-                              .getReturnType()
-                              .equals(retraceString.getTypeOrReturnTypeContext())) {
-                        return;
-                      }
-                    }
-                    RetraceStringBuilder newRetraceString = retraceString.transform();
-                    ClassReference existingClass =
-                        retraceString.getClassContext().getClassReference();
-                    ClassReference holder = methodReference.getHolderClass();
-                    if (holder != existingClass) {
-                      // The element is defined on another holder.
-                      newRetraceString
-                          .replaceInString(
-                              newRetraceString.classNameGroup.getClassName(existingClass),
-                              newRetraceString.classNameGroup.getClassName(holder))
-                          .setQualifiedContext(holder);
-                    }
-                    newRetraceString
-                        .setMethodContext(element)
-                        .setAmbiguous(element.getRetraceMethodResult().isAmbiguous())
-                        .replaceInString(
-                            methodReference.getMethodName(),
-                            matcher.start(captureGroup),
-                            matcher.end(captureGroup));
-                    retracedStrings.add(newRetraceString.build());
-                  });
+          String methodName = matcher.group(captureGroup);
+          List<RetraceString> retracedStrings = new ArrayList<>();
+          for (RetraceString retraceString : strings) {
+            retraceMethodForString(
+                retraceString,
+                methodName,
+                (element, newContext) -> {
+                  final RetraceString newRetraceString = retraceString.duplicate(newContext);
+                  if (classNameGroupHandler != null) {
+                    classNameGroupHandler.commitClassName(
+                        original,
+                        newRetraceString,
+                        element.getMethodReference().getHolderClass(),
+                        matcher);
+                  }
+                  retracedStrings.add(
+                      newRetraceString.appendRetracedString(
+                          original,
+                          printVerbose
+                              ? RetraceUtils.methodDescriptionFromMethodReference(
+                                  element.getMethodReference(), false, true)
+                              : element.getMethodReference().getMethodName(),
+                          startOfGroup,
+                          matcher.end(captureGroup)));
+                });
+          }
+          return retracedStrings;
         }
-        return retracedStrings;
+
+        @Override
+        public RetraceStringContext buildInitial(
+            RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+          final int startOfGroup = matcher.start(captureGroup);
+          if (startOfGroup == NO_MATCH || context.methodName != null) {
+            return context;
+          }
+          return context.withMethodName(matcher.group(captureGroup));
+        }
       };
     }
+
+    private static void retraceMethodForString(
+        RetraceString retraceString,
+        String methodName,
+        BiConsumer<RetraceMethodResult.Element, RetraceStringContext> process) {
+      if (retraceString.context.classContext == null) {
+        return;
+      }
+      RetraceMethodResult retraceMethodResult =
+          retraceString.getClassContext().lookupMethod(methodName);
+      if (retraceString.context.minifiedLineNumber > NO_MATCH) {
+        retraceMethodResult =
+            retraceMethodResult.narrowByLine(retraceString.context.minifiedLineNumber);
+      }
+      retraceMethodResult.forEach(
+          element ->
+              process.accept(
+                  element,
+                  retraceString.context.withMethodContext(
+                      element,
+                      element.getMethodReference().getHolderClass(),
+                      element.getRetraceMethodResult().isAmbiguous())));
+    }
   }
 
   private static class FieldNameGroup extends RegularExpressionGroup {
 
-    @Override
-    String shortName() {
-      return "%f";
+    private final boolean printVerbose;
+
+    public FieldNameGroup(boolean printVerbose) {
+      this.printVerbose = printVerbose;
     }
 
     @Override
@@ -556,86 +742,112 @@
 
     @Override
     RegularExpressionGroupHandler createHandler(String captureGroup) {
-      return (strings, matcher, retraceBase) -> {
-        if (matcher.start(captureGroup) == NO_MATCH) {
-          return strings;
+      return new QualifiedRegularExpressionGroupHandler() {
+
+        private ClassNameGroupHandler classNameGroupHandler;
+
+        @Override
+        public void setClassNameGroupHandler(ClassNameGroupHandler handler) {
+          classNameGroupHandler = handler;
         }
-        String methodName = matcher.group(captureGroup);
-        List<RetraceString> retracedStrings = new ArrayList<>();
-        for (RetraceString retraceString : strings) {
-          if (retraceString.getClassContext() == null) {
-            retracedStrings.add(retraceString);
-            continue;
+
+        @Override
+        public List<RetraceString> handleMatch(
+            String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+          final int startOfGroup = matcher.start(captureGroup);
+          if (startOfGroup == NO_MATCH) {
+            if (classNameGroupHandler != null) {
+              for (RetraceString string : strings) {
+                classNameGroupHandler.commitClassName(
+                    original, string, string.context.qualifiedContext, matcher);
+              }
+            }
+            return strings;
           }
-          retraceString
-              .getClassContext()
-              .lookupField(methodName)
-              .forEach(
-                  element -> {
-                    RetraceStringBuilder newRetraceString = retraceString.transform();
-                    ClassReference existingClass =
-                        retraceString.getClassContext().getClassReference();
-                    ClassReference holder = element.getFieldReference().getHolderClass();
-                    if (holder != existingClass) {
-                      // The element is defined on another holder.
-                      newRetraceString
-                          .replaceInString(
-                              newRetraceString.classNameGroup.getClassName(existingClass),
-                              newRetraceString.classNameGroup.getClassName(holder))
-                          .setQualifiedContext(holder);
-                    }
-                    newRetraceString.replaceInString(
-                        element.getFieldReference().getFieldName(),
-                        matcher.start(captureGroup),
-                        matcher.end(captureGroup));
-                    retracedStrings.add(newRetraceString.build());
-                  });
+          String methodName = matcher.group(captureGroup);
+          List<RetraceString> retracedStrings = new ArrayList<>();
+          for (RetraceString retraceString : strings) {
+            if (retraceString.getClassContext() == null) {
+              assert classNameGroupHandler == null;
+              return strings;
+            }
+            final RetraceFieldResult retraceFieldResult =
+                retraceString.getClassContext().lookupField(methodName);
+            assert !retraceFieldResult.isAmbiguous();
+            retraceFieldResult.forEach(
+                element -> {
+                  if (classNameGroupHandler != null) {
+                    classNameGroupHandler.commitClassName(
+                        original,
+                        retraceString,
+                        element.getFieldReference().getHolderClass(),
+                        matcher);
+                  }
+                  retracedStrings.add(
+                      retraceString
+                          .updateContext(
+                              context ->
+                                  context.withQualifiedContext(
+                                      element.getFieldReference().getHolderClass()))
+                          .appendRetracedString(
+                              original,
+                              getFieldString(element.getFieldReference()),
+                              startOfGroup,
+                              matcher.end(captureGroup)));
+                });
+          }
+          return retracedStrings;
         }
-        return retracedStrings;
       };
     }
+
+    private String getFieldString(FieldReference fieldReference) {
+      if (!printVerbose) {
+        return fieldReference.getFieldName();
+      }
+      return fieldReference.getFieldType().getTypeName() + " " + fieldReference.getFieldName();
+    }
   }
 
   private static class SourceFileGroup extends RegularExpressionGroup {
 
     @Override
-    String shortName() {
-      return "%s";
-    }
-
-    @Override
     String subExpression() {
       return "(?:(\\w*[\\. ])?(\\w*)?)";
     }
 
     @Override
     RegularExpressionGroupHandler createHandler(String captureGroup) {
-      return (strings, matcher, retraceBase) -> {
-        if (matcher.start(captureGroup) == NO_MATCH) {
+      return (original, strings, matcher, retracer) -> {
+        final int startOfGroup = matcher.start(captureGroup);
+        if (startOfGroup == NO_MATCH) {
           return strings;
         }
         String fileName = matcher.group(captureGroup);
-        List<RetraceString> retracedStrings = new ArrayList<>();
+        List<RetraceString> retracedStrings = null;
         for (RetraceString retraceString : strings) {
-          if (retraceString.classContext == null) {
-            retracedStrings.add(retraceString);
-            continue;
+          if (retraceString.context.classContext == null) {
+            return strings;
           }
-          String newSourceFile =
-              retraceString.getQualifiedContext() != null
-                  ? retraceBase.retraceSourceFile(
-                      retraceString.classContext.getClassReference(),
+          if (retracedStrings == null) {
+            retracedStrings = new ArrayList<>();
+          }
+          RetraceSourceFileResult sourceFileResult =
+              retraceString.getMethodContext() != null
+                  ? retraceString.getMethodContext().retraceSourceFile(fileName)
+                  : RetraceUtils.getSourceFile(
+                      retraceString.getClassContext(),
+                      retraceString.context.qualifiedContext,
                       fileName,
-                      retraceString.getQualifiedContext(),
-                      true)
-                  : retraceString.classContext.retraceSourceFile(fileName, retraceBase);
+                      retracer);
           retracedStrings.add(
               retraceString
-                  .transform()
-                  .setSource(fileName)
-                  .replaceInString(
-                      newSourceFile, matcher.start(captureGroup), matcher.end(captureGroup))
-                  .build());
+                  .updateContext(context -> context.withSource(sourceFileResult.getFilename()))
+                  .appendRetracedString(
+                      original,
+                      sourceFileResult.getFilename(),
+                      startOfGroup,
+                      matcher.end(captureGroup)));
         }
         return retracedStrings;
       };
@@ -645,97 +857,133 @@
   private class LineNumberGroup extends RegularExpressionGroup {
 
     @Override
-    String shortName() {
-      return "%l";
-    }
-
-    @Override
     String subExpression() {
       return "\\d*";
     }
 
     @Override
     RegularExpressionGroupHandler createHandler(String captureGroup) {
-      return (strings, matcher, retraceBase) -> {
-        if (matcher.start(captureGroup) == NO_MATCH) {
-          return strings;
+      return new RegularExpressionGroupHandler() {
+        @Override
+        public List<RetraceString> handleMatch(
+            String original, List<RetraceString> strings, Matcher matcher, RetraceApi retracer) {
+          final int startOfGroup = matcher.start(captureGroup);
+          if (startOfGroup == NO_MATCH) {
+            return strings;
+          }
+          String lineNumberAsString = matcher.group(captureGroup);
+          if (lineNumberAsString.isEmpty()) {
+            return strings;
+          }
+          int lineNumber = Integer.parseInt(lineNumberAsString);
+          List<RetraceString> retracedStrings = new ArrayList<>();
+          for (RetraceString retraceString : strings) {
+            RetraceMethodResult.Element methodContext = retraceString.context.methodContext;
+            if (methodContext == null) {
+              if (retraceString.context.classContext == null
+                  || retraceString.context.methodName == null) {
+                // We have no way of retracing the line number.
+                retracedStrings.add(retraceString);
+                continue;
+              }
+              // This situation arises when we have a matched pattern as %l..%c.%m where the
+              // line number handler is defined before the methodname handler.
+              MethodNameGroup.retraceMethodForString(
+                  retraceString,
+                  retraceString.context.methodName,
+                  (element, newContext) -> {
+                    // The same method can be represented multiple times if it has multiple
+                    // mappings.
+                    if (element.hasNoLineNumberRange()
+                        || !element.containsMinifiedLineNumber(lineNumber)) {
+                      diagnosticsHandler.info(
+                          new StringDiagnostic(
+                              "Pruning "
+                                  + retraceString.builder.retracedString.toString()
+                                  + " from result because method is not in range on line number "
+                                  + lineNumber));
+                    }
+                    final int originalLineNumber = element.getOriginalLineNumber(lineNumber);
+                    retracedStrings.add(
+                        retraceString
+                            .updateContext(
+                                context -> context.withLineNumbers(lineNumber, originalLineNumber))
+                            .appendRetracedString(
+                                original,
+                                originalLineNumber + "",
+                                startOfGroup,
+                                matcher.end(captureGroup)));
+                  });
+              continue;
+            }
+            // If the method context is unknown, do nothing.
+            if (methodContext.getMethodReference().isUnknown()
+                || methodContext.hasNoLineNumberRange()) {
+              retracedStrings.add(retraceString);
+              continue;
+            }
+            int originalLineNumber = methodContext.getOriginalLineNumber(lineNumber);
+            retracedStrings.add(
+                retraceString
+                    .updateContext(
+                        context -> context.withLineNumbers(lineNumber, originalLineNumber))
+                    .appendRetracedString(
+                        original,
+                        originalLineNumber + "",
+                        startOfGroup,
+                        matcher.end(captureGroup)));
+          }
+          return retracedStrings;
         }
-        String lineNumberAsString = matcher.group(captureGroup);
-        int lineNumber =
-            lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString);
-        List<RetraceString> retracedStrings = new ArrayList<>();
-        boolean seenRange = false;
-        for (RetraceString retraceString : strings) {
-          RetraceMethodResult.Element methodContext = retraceString.methodContext;
-          if (methodContext == null || methodContext.getMethodReference().isUnknown()) {
-            retracedStrings.add(retraceString);
-            continue;
+
+        @Override
+        public RetraceStringContext buildInitial(
+            RetraceStringContext context, Matcher matcher, RetraceApi retracer) {
+          if (matcher.start(captureGroup) == NO_MATCH || context.minifiedLineNumber > NO_MATCH) {
+            return context;
           }
-          if (methodContext.hasNoLineNumberRange()) {
-            continue;
-          }
-          seenRange = true;
-          Set<MethodReference> narrowedSet =
-              methodContext.getRetraceMethodResult().narrowByLine(lineNumber).stream()
-                  .map(RetraceMethodResult.Element::getMethodReference)
-                  .collect(Collectors.toSet());
-          if (!narrowedSet.contains(methodContext.getMethodReference())) {
-            // Prune the retraceString since we now have line number information and this is not
-            // a part of the result.
-            diagnosticsHandler.info(
-                new StringDiagnostic(
-                    "Pruning "
-                        + retraceString.getRetracedString()
-                        + " from result because method is not defined on line number "
-                        + lineNumber));
-            continue;
-          }
-          // The same method can be represented multiple times if it has multiple mappings.
-          if (!methodContext.containsMinifiedLineNumber(lineNumber)) {
-            diagnosticsHandler.info(
-                new StringDiagnostic(
-                    "Pruning "
-                        + retraceString.getRetracedString()
-                        + " from result because method is not in range on line number "
-                        + lineNumber));
-            continue;
-          }
-          int originalLineNumber = methodContext.getOriginalLineNumber(lineNumber);
-          retracedStrings.add(
-              retraceString
-                  .transform()
-                  .setAmbiguous(false)
-                  .setLineNumber(originalLineNumber)
-                  .replaceInString(
-                      originalLineNumber + "",
-                      matcher.start(captureGroup),
-                      matcher.end(captureGroup))
-                  .build());
+          String lineNumberAsString = matcher.group(captureGroup);
+          return context.withLineNumbers(
+              lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString),
+              NO_MATCH);
         }
-        return seenRange ? retracedStrings : strings;
       };
     }
   }
 
+  private static class SourceFileLineNumberGroup extends RegularExpressionGroup {
+
+    @Override
+    String subExpression() {
+      return "%s(?::%l)?";
+    }
+
+    @Override
+    RegularExpressionGroupHandler createHandler(String captureGroup) {
+      throw new Unreachable("Should never be called");
+    }
+
+    @Override
+    boolean isSynthetic() {
+      return true;
+    }
+  }
+
   private static final String JAVA_TYPE_REGULAR_EXPRESSION =
       "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment + "[\\[\\]]*";
 
   private static class FieldOrReturnTypeGroup extends RegularExpressionGroup {
 
     @Override
-    String shortName() {
-      return "%t";
-    }
-
-    @Override
     String subExpression() {
       return JAVA_TYPE_REGULAR_EXPRESSION;
     }
 
     @Override
     RegularExpressionGroupHandler createHandler(String captureGroup) {
-      return (strings, matcher, retraceBase) -> {
-        if (matcher.start(captureGroup) == NO_MATCH) {
+      return (original, strings, matcher, retracer) -> {
+        final int startOfGroup = matcher.start(captureGroup);
+        if (startOfGroup == NO_MATCH) {
           return strings;
         }
         String typeName = matcher.group(captureGroup);
@@ -744,34 +992,25 @@
           return strings;
         }
         TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor);
-        List<RetraceString> retracedStrings = new ArrayList<>();
-        RetraceTypeResult retracedType = retraceBase.retrace(typeReference);
+        RetraceTypeResult retracedType = retracer.retrace(typeReference);
+        assert !retracedType.isAmbiguous();
         for (RetraceString retraceString : strings) {
           retracedType.forEach(
               element -> {
                 TypeReference retracedReference = element.getTypeReference();
-                retracedStrings.add(
-                    retraceString
-                        .transform()
-                        .setTypeOrReturnTypeContext(retracedReference)
-                        .replaceInString(
-                            retracedReference == null ? "void" : retracedReference.getTypeName(),
-                            matcher.start(captureGroup),
-                            matcher.end(captureGroup))
-                        .build());
+                retraceString.appendRetracedString(
+                    original,
+                    retracedReference == null ? "void" : retracedReference.getTypeName(),
+                    startOfGroup,
+                    matcher.end(captureGroup));
               });
         }
-        return retracedStrings;
+        return strings;
       };
     }
   }
 
-  private class MethodArgumentsGroup extends RegularExpressionGroup {
-
-    @Override
-    String shortName() {
-      return "%a";
-    }
+  private static class MethodArgumentsGroup extends RegularExpressionGroup {
 
     @Override
     String subExpression() {
@@ -780,74 +1019,36 @@
 
     @Override
     RegularExpressionGroupHandler createHandler(String captureGroup) {
-      return (strings, matcher, retraceBase) -> {
-        if (matcher.start(captureGroup) == NO_MATCH) {
+      return (original, strings, matcher, retracer) -> {
+        final int startOfGroup = matcher.start(captureGroup);
+        if (startOfGroup == NO_MATCH) {
           return strings;
         }
-        Set<List<TypeReference>> initialValue = new LinkedHashSet<>();
-        initialValue.add(new ArrayList<>());
-        Set<List<TypeReference>> allRetracedReferences =
+        final String formals =
             Arrays.stream(matcher.group(captureGroup).split(","))
-                .map(String::trim)
-                .reduce(
-                    initialValue,
-                    (acc, typeName) -> {
+                .map(
+                    typeName -> {
+                      typeName = typeName.trim();
+                      if (typeName.isEmpty()) {
+                        return null;
+                      }
                       String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName);
                       if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) {
-                        return acc;
+                        return typeName;
                       }
-                      TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor);
-                      Set<List<TypeReference>> retracedTypes = new LinkedHashSet<>();
-                      retraceBase
-                          .retrace(typeReference)
-                          .forEach(
-                              element -> {
-                                for (List<TypeReference> currentReferences : acc) {
-                                  ArrayList<TypeReference> newList =
-                                      new ArrayList<>(currentReferences);
-                                  newList.add(element.getTypeReference());
-                                  retracedTypes.add(newList);
-                                }
-                              });
-                      return retracedTypes;
-                    },
-                    (l1, l2) -> {
-                      l1.addAll(l2);
-                      return l1;
-                    });
-        List<RetraceString> retracedStrings = new ArrayList<>();
-        for (RetraceString retraceString : strings) {
-          if (retraceString.getMethodContext() != null
-              && !allRetracedReferences.contains(
-                  retraceString.getMethodContext().getMethodReference().getFormalTypes())) {
-            // Prune the string since we now know the formals.
-            String formals =
-                retraceString.getMethodContext().getMethodReference().getFormalTypes().stream()
-                    .map(TypeReference::getTypeName)
-                    .collect(Collectors.joining(","));
-            diagnosticsHandler.info(
-                new StringDiagnostic(
-                    "Pruning "
-                        + retraceString.getRetracedString()
-                        + " from result because formals ("
-                        + formals
-                        + ") do not match result set."));
-            continue;
-          }
-          for (List<TypeReference> retracedReferences : allRetracedReferences) {
-            retracedStrings.add(
-                retraceString
-                    .transform()
-                    .replaceInString(
-                        retracedReferences.stream()
-                            .map(TypeReference::getTypeName)
-                            .collect(Collectors.joining(",")),
-                        matcher.start(captureGroup),
-                        matcher.end(captureGroup))
-                    .build());
-          }
+                      final RetraceTypeResult retraceResult =
+                          retracer.retrace(Reference.returnTypeFromDescriptor(descriptor));
+                      assert !retraceResult.isAmbiguous();
+                      final Box<TypeReference> elementBox = new Box<>();
+                      retraceResult.forEach(element -> elementBox.set(element.getTypeReference()));
+                      return elementBox.get().getTypeName();
+                    })
+                .filter(Objects::nonNull)
+                .collect(Collectors.joining(","));
+        for (RetraceString string : strings) {
+          string.appendRetracedString(original, formals, startOfGroup, matcher.end(captureGroup));
         }
-        return retracedStrings;
+        return strings;
       };
     }
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
new file mode 100644
index 0000000..4b21be1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, 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;
+
+@Keep
+public class RetraceSourceFileResult {
+
+  private final String filename;
+  private final boolean synthesized;
+
+  RetraceSourceFileResult(String filename, boolean synthesized) {
+    this.filename = filename;
+    this.synthesized = synthesized;
+  }
+
+  public String getFilename() {
+    return filename;
+  }
+
+  public boolean isSynthesized() {
+    return synthesized;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index c63a707..fd28127 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -86,17 +86,17 @@
     }
   }
 
-  private final RetraceBase retraceBase;
+  private final RetraceApi retracer;
   private final List<String> stackTrace;
   private final DiagnosticsHandler diagnosticsHandler;
   private final boolean verbose;
 
   RetraceStackTrace(
-      RetraceBase retraceBase,
+      RetraceApi retracer,
       List<String> stackTrace,
       DiagnosticsHandler diagnosticsHandler,
       boolean verbose) {
-    this.retraceBase = retraceBase;
+    this.retracer = retracer;
     this.stackTrace = stackTrace;
     this.diagnosticsHandler = diagnosticsHandler;
     this.verbose = verbose;
@@ -117,7 +117,7 @@
       return;
     }
     StackTraceLine stackTraceLine = parseLine(index + 1, stackTrace.get(index));
-    List<StackTraceLine> retraced = stackTraceLine.retrace(retraceBase, verbose);
+    List<StackTraceLine> retraced = stackTraceLine.retrace(retracer, verbose);
     StackTraceNode node = new StackTraceNode(retraced);
     result.add(node);
     retraceLine(stackTrace, index + 1, result);
@@ -125,7 +125,7 @@
 
   abstract static class StackTraceLine {
 
-    abstract List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose);
+    abstract List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose);
 
     static int firstNonWhiteSpaceCharacterFromIndex(String line, int index) {
       return firstFromIndex(line, index, not(Character::isWhitespace));
@@ -225,9 +225,9 @@
     }
 
     @Override
-    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
+    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
       List<StackTraceLine> exceptionLines = new ArrayList<>();
-      retraceBase
+      retracer
           .retrace(Reference.classFromTypeName(exceptionClass))
           .forEach(
               element ->
@@ -397,31 +397,29 @@
     }
 
     @Override
-    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
+    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
       List<StackTraceLine> lines = new ArrayList<>();
       String retraceClassLoaderName = classLoaderName;
       if (retraceClassLoaderName != null) {
         ClassReference classLoaderReference = Reference.classFromTypeName(retraceClassLoaderName);
-        retraceBase
+        retracer
             .retrace(classLoaderReference)
             .forEach(
                 classElement -> {
                   retraceClassAndMethods(
-                      retraceBase, verbose, lines, classElement.getClassReference().getTypeName());
+                      retracer, verbose, lines, classElement.getClassReference().getTypeName());
                 });
       } else {
-        retraceClassAndMethods(retraceBase, verbose, lines, retraceClassLoaderName);
+        retraceClassAndMethods(retracer, verbose, lines, retraceClassLoaderName);
       }
       return lines;
     }
 
     private void retraceClassAndMethods(
-        RetraceBase retraceBase,
-        boolean verbose,
-        List<StackTraceLine> lines,
-        String classLoaderName) {
+        RetraceApi retracer, boolean verbose, List<StackTraceLine> lines, String classLoaderName) {
       ClassReference classReference = Reference.classFromTypeName(clazz);
-      RetraceMethodResult retraceResult = retraceBase.retrace(classReference).lookupMethod(method);
+      RetraceClassResult classResult = retracer.retrace(classReference);
+      RetraceMethodResult retraceResult = classResult.lookupMethod(method);
       if (linePosition != NO_POSITION && linePosition != INVALID_POSITION) {
         retraceResult = retraceResult.narrowByLine(linePosition);
       }
@@ -436,9 +434,8 @@
                     moduleName,
                     methodReference.getHolderClass().getTypeName(),
                     methodReference.getMethodName(),
-                    methodDescriptionFromMethodReference(methodReference, verbose),
-                    retraceBase.retraceSourceFile(
-                        classReference, fileName, methodReference.getHolderClass(), true),
+                    methodDescriptionFromMethodReference(methodReference, true, verbose),
+                    methodElement.retraceSourceFile(fileName).getFilename(),
                     hasLinePosition()
                         ? methodElement.getOriginalLineNumber(linePosition)
                         : linePosition,
@@ -509,7 +506,7 @@
     }
 
     @Override
-    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
+    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
       return ImmutableList.of(new MoreLine(line));
     }
 
@@ -562,9 +559,9 @@
     }
 
     @Override
-    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
+    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
       List<StackTraceLine> exceptionLines = new ArrayList<>();
-      retraceBase
+      retracer
           .retrace(Reference.classFromTypeName(exceptionClass))
           .forEach(
               element ->
@@ -590,7 +587,7 @@
     }
 
     @Override
-    List<StackTraceLine> retrace(RetraceBase retraceBase, boolean verbose) {
+    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
       return ImmutableList.of(new UnknownLine(line));
     }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
index 2ab72a7..b70602d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
@@ -13,11 +13,11 @@
 public class RetraceTypeResult extends Result<Element, RetraceTypeResult> {
 
   private final TypeReference obfuscatedType;
-  private final RetraceBase retraceBase;
+  private final RetraceApi retracer;
 
-  RetraceTypeResult(TypeReference obfuscatedType, RetraceBase retraceBase) {
+  RetraceTypeResult(TypeReference obfuscatedType, RetraceApi retracer) {
     this.obfuscatedType = obfuscatedType;
-    this.retraceBase = retraceBase;
+    this.retracer = retracer;
   }
 
   @Override
@@ -28,13 +28,17 @@
     }
     if (obfuscatedType.isArray()) {
       int dimensions = obfuscatedType.asArray().getDimensions();
-      return retraceBase.retrace(obfuscatedType.asArray().getBaseType()).stream()
+      return retracer.retrace(obfuscatedType.asArray().getBaseType()).stream()
           .map(base -> new Element(Reference.array(base.getTypeReference(), dimensions)));
     }
-    return retraceBase.retrace(obfuscatedType.asClass()).stream()
+    return retracer.retrace(obfuscatedType.asClass()).stream()
         .map(clazz -> new Element(clazz.getClassReference()));
   }
 
+  public boolean isAmbiguous() {
+    return false;
+  }
+
   @Override
   public RetraceTypeResult forEach(Consumer<Element> resultConsumer) {
     stream().forEach(resultConsumer);
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
index 81c2272..77aff7d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -4,24 +4,34 @@
 
 package com.android.tools.r8.retrace;
 
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.Box;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+import java.util.Set;
 
 public class RetraceUtils {
 
+  private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
+      Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source", "PG");
+
   public static String methodDescriptionFromMethodReference(
-      MethodReference methodReference, boolean verbose) {
+      MethodReference methodReference, boolean appendHolder, boolean verbose) {
     if (!verbose || methodReference.isUnknown()) {
       return methodReference.getHolderClass().getTypeName() + "." + methodReference.getMethodName();
     }
     StringBuilder sb = new StringBuilder();
+    if (appendHolder) {
+      sb.append(methodReference.getHolderClass().getTypeName());
+      sb.append(".");
+    }
     sb.append(
         methodReference.getReturnType() == null
             ? "void"
             : methodReference.getReturnType().getTypeName());
     sb.append(" ");
-    sb.append(methodReference.getHolderClass().getTypeName());
-    sb.append(".");
     sb.append(methodReference.getMethodName());
     sb.append("(");
     boolean seenFirstIndex = false;
@@ -35,4 +45,76 @@
     sb.append(")");
     return sb.toString();
   }
+
+  public static boolean hasPredictableSourceFileName(String originalClassName, String sourceFile) {
+    String synthesizedSourceFileName = getClassSimpleName(originalClassName) + ".java";
+    return synthesizedSourceFileName.equals(sourceFile);
+  }
+
+  private static String getClassSimpleName(String clazz) {
+    int lastIndexOfPeriod = clazz.lastIndexOf('.');
+    // Check if we can find a subclass separator.
+    int endIndex = clazz.lastIndexOf('$');
+    if (lastIndexOfPeriod > endIndex || endIndex < 0) {
+      endIndex = clazz.length();
+    }
+    return clazz.substring(lastIndexOfPeriod + 1, endIndex);
+  }
+
+  static RetraceSourceFileResult getSourceFile(
+      RetraceClassResult.Element classElement,
+      ClassReference context,
+      String sourceFile,
+      RetraceApi retracer) {
+    // If no context is specified always retrace using the found class element.
+    if (context == null) {
+      return classElement.retraceSourceFile(sourceFile);
+    }
+    if (context.equals(classElement.getClassReference())) {
+      return classElement.retraceSourceFile(sourceFile);
+    } else {
+      RetraceClassResult contextClassResult = retracer.retrace(context);
+      assert !contextClassResult.isAmbiguous();
+      if (contextClassResult.hasRetraceResult()) {
+        Box<RetraceSourceFileResult> retraceSourceFile = new Box<>();
+        contextClassResult.forEach(
+            element -> retraceSourceFile.set(element.retraceSourceFile(sourceFile)));
+        return retraceSourceFile.get();
+      } else {
+        return new RetraceSourceFileResult(
+            synthesizeFileName(
+                context.getTypeName(),
+                classElement.getClassReference().getTypeName(),
+                sourceFile,
+                true),
+            true);
+      }
+    }
+  }
+
+  public static String synthesizeFileName(
+      String retracedClassName,
+      String minifiedClassName,
+      String sourceFile,
+      boolean hasRetraceResult) {
+    boolean fileNameProbablyChanged =
+        hasRetraceResult && !retracedClassName.startsWith(minifiedClassName);
+    if (!UNKNOWN_SOURCEFILE_NAMES.contains(sourceFile) && !fileNameProbablyChanged) {
+      // We have no new information, only rewrite filename if it is unknown.
+      // PG-retrace will always rewrite the filename, but that seems a bit to harsh to do.
+      return sourceFile;
+    }
+    String extension = Files.getFileExtension(sourceFile);
+    if (extension.isEmpty()) {
+      extension = "java";
+    }
+    if (!hasRetraceResult) {
+      // We have no mapping but but file name is unknown, so the best we can do is take the
+      // name of the obfuscated clazz.
+      assert minifiedClassName.equals(retracedClassName);
+      return getClassSimpleName(minifiedClassName) + "." + extension;
+    }
+    String newFileName = getClassSimpleName(retracedClassName);
+    return newFileName + "." + extension;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
new file mode 100644
index 0000000..0f7d547
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2019, 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.naming.ClassNameMapper;
+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;
+
+/** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
+@Keep
+public class Retracer implements RetraceApi {
+
+  private final ClassNameMapper classNameMapper;
+
+  private Retracer(ClassNameMapper classNameMapper) {
+    this.classNameMapper = classNameMapper;
+  }
+
+  public static RetraceApi create(ClassNameMapper classNameMapper) {
+    return new Retracer(classNameMapper);
+  }
+
+  @Override
+  public RetraceMethodResult retrace(MethodReference methodReference) {
+    return retrace(methodReference.getHolderClass()).lookupMethod(methodReference.getMethodName());
+  }
+
+  @Override
+  public RetraceFieldResult retrace(FieldReference fieldReference) {
+    return retrace(fieldReference.getHolderClass()).lookupField(fieldReference.getFieldName());
+  }
+
+  @Override
+  public RetraceClassResult retrace(ClassReference classReference) {
+    return RetraceClassResult.create(
+        classReference, classNameMapper.getClassNaming(classReference.getTypeName()), this);
+  }
+
+  @Override
+  public RetraceTypeResult retrace(TypeReference typeReference) {
+    return new RetraceTypeResult(typeReference, this);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
new file mode 100644
index 0000000..bbe2d5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, 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 static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.PositionMatcher;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DuplicateMappingsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DuplicateMappingsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testSourceFileName() {
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    Retrace.run(
+        RetraceCommand.builder(diagnosticsHandler)
+            .setProguardMapProducer(
+                () ->
+                    StringUtils.lines(
+                        "com.android.tools.r8.retrace.SourceFileTest$ClassWithCustomFileName ->"
+                            + " com.android.tools.r8.retrace.a:",
+                        "# {'id':'sourceFile','fileName':'foobarbaz.java'}",
+                        "# {'id':'sourceFile','fileName':'foobarbaz2.java'}"))
+            .setStackTrace(ImmutableList.of())
+            .setRetracedStackTraceConsumer(
+                strings -> {
+                  // No need to do anything, we are just checking for diagnostics.
+                })
+            .build());
+    diagnosticsHandler
+        .assertWarningsCount(1)
+        .assertWarningsMatch(
+            allOf(
+                DiagnosticsMatcher.diagnosticMessage(containsString("The mapping")),
+                DiagnosticsMatcher.diagnosticMessage(
+                    containsString("is not allowed in combination with")),
+                DiagnosticsMatcher.diagnosticPosition(PositionMatcher.positionLine(3))));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 102f7f0..8a0f5e5 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -4,23 +4,27 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.RetraceTests.DEFAULT_REGULAR_EXPRESSION;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.Version;
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTraceWithInfo;
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
+import com.android.tools.r8.retrace.stacktraces.PGStackTrace;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.base.Charsets;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -38,6 +42,8 @@
 
   @Rule public TemporaryFolder folder = new TemporaryFolder();
 
+  private static String SMILEY_EMOJI = "\uD83D\uDE00";
+
   @Test
   public void testPrintIdentityStackTraceFile() throws IOException {
     runTest("", nonMappableStackTrace, false, nonMappableStackTrace);
@@ -70,14 +76,34 @@
   }
 
   @Test
+  public void testVerboseSingleHyphen() throws IOException {
+    FoundMethodVerboseStackTrace stackTrace = new FoundMethodVerboseStackTrace();
+    runTest(
+        stackTrace.mapping(),
+        StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR,
+        "-verbose");
+  }
+
+  @Test
   public void testRegularExpression() throws IOException {
     ActualRetraceBotStackTrace stackTrace = new ActualRetraceBotStackTrace();
     runTest(
         stackTrace.mapping(),
         StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
         false,
-        StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR,
-        "--regex=" + DEFAULT_REGULAR_EXPRESSION);
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR);
+  }
+
+  @Test
+  public void testRegularExpressionSingleHyphen() throws IOException {
+    ActualRetraceBotStackTrace stackTrace = new ActualRetraceBotStackTrace();
+    runTest(
+        stackTrace.mapping(),
+        StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR);
   }
 
   @Test
@@ -88,11 +114,20 @@
         StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
         false,
         StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR,
-        "--regex=" + DEFAULT_REGULAR_EXPRESSION,
         "--info");
   }
 
   @Test
+  public void testPGStackTrace() throws Exception {
+    PGStackTrace pgStackTrace = new PGStackTrace();
+    runTest(
+        pgStackTrace.mapping(),
+        StringUtils.joinLines(pgStackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(pgStackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR);
+  }
+
+  @Test
   public void testEmpty() throws IOException {
     runTest("", "", false, "");
   }
@@ -104,6 +139,23 @@
     assertEquals(Retrace.USAGE_MESSAGE, processResult.stdout);
   }
 
+  @Test
+  public void testVersion() throws Exception {
+    ProcessResult processResult = runRetraceCommandLine(null, Arrays.asList("--version"));
+    assertEquals(0, processResult.exitCode);
+    assertEquals(StringUtils.lines("Retrace " + Version.getVersionString()), processResult.stdout);
+  }
+
+  @Test
+  public void testNonAscii() throws IOException {
+    runTest("", SMILEY_EMOJI, false, SMILEY_EMOJI + StringUtils.LINE_SEPARATOR);
+  }
+
+  @Test
+  public void testNonAsciiStdIn() throws IOException {
+    runTest("", SMILEY_EMOJI, true, SMILEY_EMOJI + StringUtils.LINE_SEPARATOR);
+  }
+
   private final String nonMappableStackTrace =
       StringUtils.lines(
           "com.android.r8.R8Exception: Problem when compiling program",
@@ -123,6 +175,14 @@
     assertEquals(expected, result.stdout);
   }
 
+  private void runTestNotEquals(
+      String mapping, String stackTrace, boolean stacktraceStdIn, String expected, String... args)
+      throws IOException {
+    ProcessResult result = runRetrace(mapping, stackTrace, stacktraceStdIn, args);
+    assertEquals(0, result.exitCode);
+    assertNotEquals(expected, result.stdout);
+  }
+
   private void runAbortTest(Matcher<String> errorMatch, String... args) throws IOException {
     ProcessResult result = runRetraceCommandLine(null, Arrays.asList(args));
     assertEquals(1, result.exitCode);
@@ -135,7 +195,7 @@
     Path mappingFile = folder.newFile("mapping.txt").toPath();
     Files.write(mappingFile, mapping.getBytes());
     File stackTraceFile = folder.newFile("stacktrace.txt");
-    Files.write(stackTraceFile.toPath(), stackTrace.getBytes());
+    Files.write(stackTraceFile.toPath(), stackTrace.getBytes(StandardCharsets.UTF_8));
 
     Collection<String> args = new ArrayList<>();
     args.add(mappingFile.toString());
@@ -187,8 +247,8 @@
       System.setErr(originalErr);
       return new ProcessResult(
           exitCode,
-          outputByteStream.toString(),
-          errorByteStream.toString(),
+          outputByteStream.toString(Charsets.UTF_8.name()),
+          errorByteStream.toString(Charsets.UTF_8.name()),
           StringUtils.joinLines(args));
     }
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
index bb69fc3..4fb8389 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.retrace.Retrace.DEFAULT_REGULAR_EXPRESSION;
 import static junit.framework.TestCase.assertEquals;
 
 import com.android.tools.r8.TestBase;
@@ -11,11 +12,13 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
+import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import java.util.List;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -24,9 +27,6 @@
 @RunWith(Parameterized.class)
 public class RetraceRegularExpressionTests extends TestBase {
 
-  private static final String DEFAULT_REGULAR_EXPRESSION =
-      "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)|(?:(?:.*?[:\"]\\s+)?%c(?::.*)?)";
-
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withNoneRuntime().build();
@@ -395,7 +395,7 @@
   }
 
   @Test
-  public void testNotFoundLineNumberInMethodContext() {
+  public void testNoLineNumberInMethodContext() {
     runRetraceTest(
         "%c\\.%m\\(%l\\)",
         new StackTraceForTest() {
@@ -412,7 +412,35 @@
 
           @Override
           public List<String> retracedStackTrace() {
-            return ImmutableList.of("a.b.c.a()");
+            return ImmutableList.of("com.android.tools.r8.R8.foo()");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
+  @Test
+  public void testNotFoundLineNumberInMethodContext() {
+    runRetraceTest(
+        "%c\\.%m\\(%l\\)",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("a.b.c.a(42)");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "com.android.tools.r8.R8 -> a.b.c:", "  3:3:boolean foo():7 -> a");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("com.android.tools.r8.R8.a(42)");
           }
 
           @Override
@@ -540,6 +568,7 @@
   }
 
   @Test
+  @Ignore("b/165782924")
   public void useReturnTypeToNarrowMethodMatches() {
     runRetraceTest(
         "%t %c.%m",
@@ -663,6 +692,7 @@
   }
 
   @Test
+  @Ignore("b/165782924")
   public void testPruningOfMethodsByFormals() {
     runRetraceTest(
         "%c.%m\\(%a\\)",
@@ -731,6 +761,41 @@
         });
   }
 
+  @Test
+  public void testSourceFileLineNumber() {
+    runRetraceTest(
+        DEFAULT_REGULAR_EXPRESSION.replace("%s(?::%l)?", "%S"),
+        new RetraceAssertionErrorStackTrace());
+  }
+
+  @Test
+  public void testEscaping() {
+    runRetraceTest(
+        "\\%c\\\\%c\\\\\\%c.%m\\(\\\\%S\\)\\\\\\%S",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of("%c\\com.android.tools.r8.Foo\\%c.a(\\SourceFile:1)\\%S");
+          }
+
+          @Override
+          public String mapping() {
+            return "com.android.tools.r8.Bar -> com.android.tools.r8.Foo:\n"
+                + "  1:1:void m():13:13 -> a";
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of("%c\\com.android.tools.r8.Bar\\%c.m(\\Bar.java:13)\\%S");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
   private TestDiagnosticMessagesImpl runRetraceTest(
       String regularExpression, StackTraceForTest stackTraceForTest) {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 47dc5c3..8bf3b29 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.retrace.Retrace.DEFAULT_REGULAR_EXPRESSION;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineSourceFileContextStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
@@ -45,12 +47,6 @@
 @RunWith(Parameterized.class)
 public class RetraceTests extends TestBase {
 
-  // This is a slight modification of the default regular expression shown for proguard retrace
-  // that allow for retracing classes in the form <class>: lorem ipsum...
-  // Seems like Proguard retrace is expecting the form "Caused by: <class>".
-  public static final String DEFAULT_REGULAR_EXPRESSION =
-      "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)|(?:(?:(?:%c|.*)?[:\"]\\s+)?%c(?::.*)?)";
-
   @Parameters(name = "{0}, use regular expression: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
@@ -178,6 +174,11 @@
     runRetraceTest(new UnknownSourceStackTrace());
   }
 
+  @Test
+  public void testInlineSourceFileContext() {
+    runRetraceTest(new InlineSourceFileContextStackTrace());
+  }
+
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
index 53aa7c6..d57b024 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTraceWithInfo.java
@@ -45,59 +45,6 @@
   @Override
   public List<String> retracedStackTrace() {
     return Arrays.asList(
-        "Pruning \tat com.android.tools.r8.utils.Reporter.error(Reporter.java:21) from result"
-            + " because method is not defined on line number 21",
-        "Pruning \tat com.android.tools.r8.utils.Reporter.error(Reporter.java:21) from result"
-            + " because method is not defined on line number 21",
-        "Pruning \tat com.android.tools.r8.utils.Reporter.fatalError(Reporter.java:21) from result"
-            + " because method is not defined on line number 21",
-        "Pruning \tat"
-            + " com.android.tools.r8.utils.Reporter.addSuppressedExceptions(Reporter.java:21) from"
-            + " result because method is not defined on line number 21",
-        "Pruning \tat"
-            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
-            + " from result because method is not defined on line number 19",
-        "Pruning \tat"
-            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
-            + " from result because method is not defined on line number 19",
-        "Pruning \tat"
-            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
-            + " from result because method is not in range on line number 19",
-        "Pruning \tat"
-            + " com.android.tools.r8.shaking.ProguardConfigurationParser.parse(ProguardConfigurationParser.java:19)"
-            + " from result because method is not in range on line number 19",
-        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:11) from"
-            + " result because method is not defined on line number 11",
-        "Pruning \tat"
-            + " com.android.tools.r8.R8Command$Builder.setDisableVerticalClassMerging(R8Command.java:11)"
-            + " from result because method is not defined on line number 11",
-        "Pruning \tat"
-            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfigurationFiles$4(R8Command.java:11)"
-            + " from result because method is not defined on line number 11",
-        "Pruning \tat"
-            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:11)"
-            + " from result because method is not defined on line number 11",
-        "Pruning \tat"
-            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:11)"
-            + " from result because method is not defined on line number 11",
-        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:11) from"
-            + " result because method is not in range on line number 11",
-        "Pruning \tat"
-            + " com.android.tools.r8.R8Command$Builder.setDisableVerticalClassMerging(R8Command.java:1)"
-            + " from result because method is not defined on line number 1",
-        "Pruning \tat"
-            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfigurationFiles$4(R8Command.java:1)"
-            + " from result because method is not defined on line number 1",
-        "Pruning \tat"
-            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:1)"
-            + " from result because method is not defined on line number 1",
-        "Pruning \tat"
-            + " com.android.tools.r8.R8Command$Builder.lambda$addProguardConfiguration$6(R8Command.java:1)"
-            + " from result because method is not defined on line number 1",
-        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:1) from"
-            + " result because method is not defined on line number 1",
-        "Pruning \tat com.android.tools.r8.R8Command$Builder.makeCommand(R8Command.java:1) from"
-            + " result because method is not defined on line number 1",
         "com.android.tools.r8.CompilationFailedException: Compilation failed to complete",
         "\tat com.android.tools.r8.BaseCommand$Builder.build(BaseCommand.java:143)",
         "\tat com.android.tools.r8.R8TestBuilder.internalCompile(R8TestBuilder.java:104)",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
index 4d9418c..ce7a8f5 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/FoundMethodVerboseStackTrace.java
@@ -27,7 +27,7 @@
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.Foo com.android.tools.r8.naming.retrace.Main.main(java.lang.String[],"
+        "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo main(java.lang.String[],"
             + "com.android.Bar)(Main.java:102)");
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
new file mode 100644
index 0000000..3c22097
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, 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.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineSourceFileContextStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
+            + ".main(KotlinJavaSourceFileTestObject.java:1)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.joinLines(
+        "com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary ->"
+            + " com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary:",
+        "# {\"id\":\"sourceFile\",\"fileName\":\"KotlinJavaSourceFileTestLibrary.kt\"}",
+        "    void <init>() -> <init>",
+        "com.google.appreduce.remapper.KotlinJavaSourceFileTestObject ->"
+            + " com.google.appreduce.remapper.KotlinJavaSourceFileTestObject:",
+        "    void <init>() -> <init>",
+        "    1:1:void com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+            + ".throwsException():22:22 -> main",
+        "    1:1:void com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+            + ".callsThrowsException():19 -> main",
+        "    1:1:void main(java.lang.String[]):32 -> main",
+        "    2:7:void printStackTraceUpToMain(java.lang.Exception):19:24 -> main",
+        "    2:7:void main(java.lang.String[]):34 -> main");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+            + ".throwsException(KotlinJavaSourceFileTestLibrary.kt:22)",
+        "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+            + ".callsThrowsException(KotlinJavaSourceFileTestLibrary.kt:19)",
+        "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
+            + ".main(KotlinJavaSourceFileTestObject.java:32)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
new file mode 100644
index 0000000..2732b34
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, 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.stacktraces;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class PGStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime: java.lang.NullPointerException: Attempt"
+            + " to invoke virtual method 'boolean"
+            + " com.google.android.foo(com.google.android.foo.Data$Key)' on a null object"
+            + " reference",
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime:        at"
+            + " com.google.apps.sectionheader.SectionHeaderListController.onToolbarStateChanged(PG:586)",
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime:        at"
+            + " com.google.apps.Controller.onToolbarStateChanged(PG:1087)");
+  }
+
+  @Override
+  public String mapping() {
+    return "";
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime: java.lang.NullPointerException: Attempt"
+            + " to invoke virtual method 'boolean"
+            + " com.google.android.foo(com.google.android.foo.Data$Key)' on a null object"
+            + " reference",
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime:        at"
+            + " com.google.apps.sectionheader.SectionHeaderListController.onToolbarStateChanged(SectionHeaderListController.java:586)",
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime:        at"
+            + " com.google.apps.Controller.onToolbarStateChanged(Controller.java:1087)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
index 3eb9710..5a47079 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownMethodVerboseStackTrace.java
@@ -25,7 +25,7 @@
         "com.android.tools.r8.naming.retrace.Main -> a.a:",
         "    com.android.Foo main(java.lang.String[],com.android.Bar) -> a",
         "    com.android.Foo main(java.lang.String[]) -> b",
-        "    com.android.Bar main(com.android.Bar) -> b");
+        "    void main(com.android.Bar) -> b");
   }
 
   @Override
@@ -33,11 +33,11 @@
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.c(Main.java)",
-        "\tat com.android.Foo com.android.tools.r8.naming.retrace.Main.main("
+        "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo main("
             + "java.lang.String[])(Main.java)",
-        "\t<OR> at com.android.Bar com.android.tools.r8.naming.retrace.Main.main("
+        "\t<OR> at com.android.tools.r8.naming.retrace.Main.void main("
             + "com.android.Bar)(Main.java)",
-        "\tat com.android.Foo com.android.tools.r8.naming.retrace.Main.main("
+        "\tat com.android.tools.r8.naming.retrace.Main.com.android.Foo main("
             + "java.lang.String[],com.android.Bar)(Main.java)");
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 3cfbb22..d2c22e5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -37,8 +37,8 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.RetraceBase;
-import com.android.tools.r8.retrace.RetraceBaseImpl;
+import com.android.tools.r8.retrace.RetraceApi;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -471,7 +471,7 @@
     }
   }
 
-  public RetraceBase retrace() {
-    return RetraceBaseImpl.create(mapping);
+  public RetraceApi retrace() {
+    return Retracer.create(mapping);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index c5ca6c9..c3c961a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.retrace.RetraceBase;
+import com.android.tools.r8.retrace.RetraceApi;
 import com.android.tools.r8.retrace.RetraceMethodResult;
 
 public interface InstructionSubject {
@@ -125,18 +125,18 @@
     return lineNumberTable == null ? -1 : lineNumberTable.getLineForInstruction(this);
   }
 
-  default RetraceMethodResult retrace(RetraceBase retraceBase) {
+  default RetraceMethodResult retrace(RetraceApi retraceApi) {
     MethodSubject methodSubject = getMethodSubject();
     assert methodSubject.isPresent();
-    return retraceBase.retrace(methodSubject.asFoundMethodSubject().asMethodReference());
+    return retraceApi.retrace(methodSubject.asFoundMethodSubject().asMethodReference());
   }
 
-  default RetraceMethodResult retraceLinePosition(RetraceBase retraceBase) {
-    return retrace(retraceBase).narrowByLine(getLineNumber());
+  default RetraceMethodResult retraceLinePosition(RetraceApi retraceApi) {
+    return retrace(retraceApi).narrowByLine(getLineNumber());
   }
 
   default RetraceMethodResult retracePcPosition(
-      RetraceBase retraceBase, MethodSubject methodSubject) {
-    return retrace(retraceBase).narrowByLine(getOffset(methodSubject).offset);
+      RetraceApi retraceApi, MethodSubject methodSubject) {
+    return retrace(retraceApi).narrowByLine(getOffset(methodSubject).offset);
   }
 }