[Retrace] Update retrace and parsers to better support google3 users

This change helps using and modifying bits and pieces of retrace
without exposing many of the internals. The major needs this CL
addresses are:

- The ability to not give in the stack trace before hand
- The ability to pass in a custom regular expression

Bug: 178699279
Change-Id: I8df1f28ff7cd7b043cdab893253f4a45c15cbf5a
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 76fad7d..0b02310 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
 import static com.android.tools.r8.utils.ExceptionUtils.failWithFakeEntry;
 
 import com.android.tools.r8.Diagnostic;
@@ -12,12 +11,11 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
-import com.android.tools.r8.retrace.internal.PlainStackTraceVisitor;
+import com.android.tools.r8.retrace.internal.PlainStackTraceLineParser;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
-import com.android.tools.r8.retrace.internal.RetraceRegularExpression;
-import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
-import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -33,13 +31,13 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Scanner;
-import java.util.Set;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * A retrace tool for obfuscated stack traces.
@@ -48,14 +46,7 @@
  * tool.
  */
 @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 class Retrace<T, ST extends StackTraceElementProxy<T, ST>> {
 
   public static final String USAGE_MESSAGE =
       StringUtils.lines(
@@ -68,7 +59,6 @@
     Builder builder = RetraceCommand.builder(diagnosticsHandler);
     boolean hasSetProguardMap = false;
     boolean hasSetStackTrace = false;
-    boolean hasSetRegularExpression = false;
     boolean hasSetQuiet = false;
     while (context.head() != null) {
       Boolean help = OptionsParsing.tryParseBoolean(context, "--help");
@@ -97,7 +87,6 @@
       String regex = OptionsParsing.tryParseSingle(context, "--regex", "r");
       if (regex != null && !regex.isEmpty()) {
         builder.setRegularExpression(regex);
-        hasSetRegularExpression = true;
         continue;
       }
       if (!hasSetProguardMap) {
@@ -123,9 +112,6 @@
     if (!hasSetStackTrace) {
       builder.setStackTrace(getStackTraceFromStandardInput(hasSetQuiet));
     }
-    if (!hasSetRegularExpression) {
-      builder.setRegularExpression(DEFAULT_REGULAR_EXPRESSION);
-    }
     return builder;
   }
 
@@ -158,6 +144,96 @@
     }
   }
 
+  private final StackTraceLineParser<T, ST> stackTraceLineParser;
+  private final StackTraceElementProxyRetracer<T, ST> proxyRetracer;
+  private final DiagnosticsHandler diagnosticsHandler;
+  private final boolean isVerbose;
+
+  Retrace(
+      StackTraceLineParser<T, ST> stackTraceLineParser,
+      StackTraceElementProxyRetracer<T, ST> proxyRetracer,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean isVerbose) {
+    this.stackTraceLineParser = stackTraceLineParser;
+    this.proxyRetracer = proxyRetracer;
+    this.diagnosticsHandler = diagnosticsHandler;
+    this.isVerbose = isVerbose;
+  }
+
+  public static <T, ST extends StackTraceElementProxy<T, ST>> Retrace<T, ST> createRetrace(
+      StackTraceLineParser<T, ST> stackTraceLineParser,
+      StackTraceElementProxyRetracer<T, ST> proxyRetracer,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean isVerbose) {
+    return new Retrace<>(stackTraceLineParser, proxyRetracer, diagnosticsHandler, isVerbose);
+  }
+
+  /**
+   * Retraces a stack frame and calls the consumer for each retraced line
+   *
+   * @param stackTrace the stack trace to be retrace
+   * @param retracedFrameConsumer the consumer to accept the retraced stack trace.
+   */
+  public void retraceStackTrace(List<T> stackTrace, Consumer<List<List<T>>> retracedFrameConsumer) {
+    ListUtils.forEachWithIndex(
+        stackTrace,
+        (line, lineNumber) -> {
+          if (line == null) {
+            diagnosticsHandler.error(
+                RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
+            throw new RetraceAbortException();
+          }
+        });
+    stackTrace.forEach(line -> retracedFrameConsumer.accept(retraceFrame(line)));
+  }
+
+  /**
+   * Retraces a stack trace frame with support for splitting up ambiguous results.
+   *
+   * @param stackTraceFrame The frame to retrace that can give rise to ambiguous results
+   * @return A collection of retraced frame where each entry in the outer list is ambiguous
+   */
+  public List<List<T>> retraceFrame(T stackTraceFrame) {
+    Map<RetraceStackTraceProxy<T, ST>, List<T>> ambiguousBlocks = new HashMap<>();
+    List<RetraceStackTraceProxy<T, ST>> ambiguousKeys = new ArrayList<>();
+    ST parsedLine = stackTraceLineParser.parse(stackTraceFrame);
+    proxyRetracer
+        .retrace(parsedLine)
+        .forEach(
+            retracedElement -> {
+              if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
+                ambiguousKeys.add(retracedElement);
+                ambiguousBlocks.put(retracedElement, new ArrayList<>());
+              }
+              ambiguousBlocks
+                  .get(ListUtils.last(ambiguousKeys))
+                  .add(parsedLine.toRetracedItem(retracedElement, isVerbose));
+            });
+    Collections.sort(ambiguousKeys);
+    List<List<T>> retracedList = new ArrayList<>();
+    ambiguousKeys.forEach(key -> retracedList.add(ambiguousBlocks.get(key)));
+    return retracedList;
+  }
+
+  /**
+   * Utility method for tracing a single line that also retraces ambiguous lines without being able
+   * to distinguish them. For retracing with ambiguous results separated, use {@link #retraceFrame}
+   *
+   * @param stackTraceLine the stack trace line to retrace
+   * @return the retraced stack trace line
+   */
+  public List<T> retraceLine(T stackTraceLine) {
+    ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
+    return proxyRetracer
+        .retrace(parsedLine)
+        .map(
+            retraceFrame -> {
+              retraceFrame.getOriginalItem().toRetracedItem(retraceFrame, isVerbose);
+              return parsedLine.toRetracedItem(retraceFrame, isVerbose);
+            })
+        .collect(Collectors.toList());
+  }
+
   /**
    * The main entry point for running retrace.
    *
@@ -167,110 +243,33 @@
     try {
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       timing.begin("Read proguard map");
+      RetraceOptions options = command.getOptions();
+      DiagnosticsHandler diagnosticsHandler = options.getDiagnosticsHandler();
       Retracer retracer =
-          Retracer.createDefault(command.proguardMapProducer, command.diagnosticsHandler);
-      timing.end();
-      timing.begin("Parse and Retrace");
-      StackTraceVisitor<StackTraceElementStringProxy> stackTraceVisitor =
-          command.regularExpression != null
-              ? new RetraceRegularExpression(command.stackTrace, command.regularExpression)
-              : new PlainStackTraceVisitor(command.stackTrace, command.diagnosticsHandler);
+          Retracer.createDefault(options.getProguardMapProducer(), diagnosticsHandler);
       timing.end();
       timing.begin("Report result");
-      command.retracedStackTraceConsumer.accept(
-          runInternal(stackTraceVisitor, retracer, command.isVerbose));
+      StringRetrace stringRetrace =
+          new StringRetrace(
+              options.getRegularExpression() == null
+                  ? new PlainStackTraceLineParser()
+                  : new StackTraceRegularExpressionParser(options.getRegularExpression()),
+              StackTraceElementProxyRetracer.createDefault(retracer),
+              diagnosticsHandler,
+              options.isVerbose());
+      command
+          .getRetracedStackTraceConsumer()
+          .accept(stringRetrace.retrace(command.getStackTrace()));
       timing.end();
       if (command.printTimes()) {
         timing.report();
       }
     } catch (InvalidMappingFileException e) {
-      command.diagnosticsHandler.error(new ExceptionDiagnostic(e));
+      command.getOptions().getDiagnosticsHandler().error(new ExceptionDiagnostic(e));
       throw e;
     }
   }
 
-  /**
-   * Entry point for running retrace with default parsing on a stack trace
-   *
-   * @param retracer the retracer used to parse each stack trace frame.
-   * @param stackTrace the stack trace to be parsed.
-   * @param isVerbose if the output should embed verbose information.
-   * @return the retraced strings in flat format
-   */
-  public static List<String> run(Retracer retracer, List<String> stackTrace, boolean isVerbose) {
-    return runInternal(
-        new RetraceRegularExpression(stackTrace, DEFAULT_REGULAR_EXPRESSION), retracer, isVerbose);
-  }
-
-  private static List<String> runInternal(
-      StackTraceVisitor<StackTraceElementStringProxy> stackTraceVisitor,
-      Retracer retracer,
-      boolean isVerbose) {
-    List<String> retracedStrings = new ArrayList<>();
-    Box<StackTraceElementStringProxy> lastReportedFrame = new Box<>();
-    Set<String> seenSetForLastReportedFrame = new HashSet<>();
-    run(
-        stackTraceVisitor,
-        StackTraceElementProxyRetracer.createDefault(retracer),
-        (stackTraceElement, frames) -> {
-          frames.forEach(
-              frame -> {
-                StackTraceElementStringProxy originalItem = frame.getOriginalItem();
-                boolean newFrame =
-                    lastReportedFrame.getAndSet(stackTraceElement) != stackTraceElement;
-                if (newFrame) {
-                  seenSetForLastReportedFrame.clear();
-                }
-                String retracedString = originalItem.toRetracedItem(frame, isVerbose);
-                if (seenSetForLastReportedFrame.add(retracedString)) {
-                  if (frame.isAmbiguous() && !newFrame) {
-                    int firstCharIndex = firstNonWhiteSpaceCharacterFromIndex(retracedString, 0);
-                    retracedString =
-                        retracedString.substring(0, firstCharIndex)
-                            + "<OR> "
-                            + retracedString.substring(firstCharIndex);
-                  }
-                  retracedStrings.add(retracedString);
-                }
-              });
-        });
-    return retracedStrings;
-  }
-
-  /**
-   * @param stackTraceVisitor the stack trace visitor.
-   * @param proxyRetracer the retracer used to parse each stack trace frame.
-   * @param resultConsumer consumer to accept each parsed stack trace frame. The stack-trace element
-   *     is guaranteed to be unique per line.
-   */
-  public static <T extends StackTraceElementProxy<?>> void run(
-      StackTraceVisitor<T> stackTraceVisitor,
-      StackTraceElementProxyRetracer<T> proxyRetracer,
-      BiConsumer<T, List<RetraceStackTraceProxy<T>>> resultConsumer) {
-    stackTraceVisitor.forEach(
-        stackTraceElement -> {
-          Box<List<RetraceStackTraceProxy<T>>> currentList = new Box<>();
-          Map<RetraceStackTraceProxy<T>, List<RetraceStackTraceProxy<T>>> ambiguousBlocks =
-              new HashMap<>();
-          proxyRetracer
-              .retrace(stackTraceElement)
-              .forEach(
-                  retracedElement -> {
-                    if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
-                      List<RetraceStackTraceProxy<T>> block = new ArrayList<>();
-                      ambiguousBlocks.put(retracedElement, block);
-                      currentList.set(block);
-                    }
-                    currentList.get().add(retracedElement);
-                  });
-          ambiguousBlocks.keySet().stream()
-              .sorted()
-              .forEach(
-                  topFrame ->
-                      resultConsumer.accept(stackTraceElement, ambiguousBlocks.get(topFrame)));
-        });
-  }
-
   public static void run(String[] args) throws RetraceFailedException {
     // To be compatible with standard retrace and remapper, we translate -arg into --arg.
     String[] mappedArgs = new String[args.length];
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index e346e3c..66819e7 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -6,35 +6,30 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
 import java.util.List;
 import java.util.function.Consumer;
 
 @Keep
 public class RetraceCommand {
 
-  final boolean isVerbose;
-  final String regularExpression;
-  final DiagnosticsHandler diagnosticsHandler;
-  final ProguardMapProducer proguardMapProducer;
-  final List<String> stackTrace;
-  final Consumer<List<String>> retracedStackTraceConsumer;
+  private final List<String> stackTrace;
+  private final Consumer<List<String>> retracedStackTraceConsumer;
+  // Not inheriting to allow for static builder methods.
+  private final RetraceOptions options;
 
   private RetraceCommand(
-      boolean isVerbose,
       String regularExpression,
       DiagnosticsHandler diagnosticsHandler,
       ProguardMapProducer proguardMapProducer,
       List<String> stackTrace,
-      Consumer<List<String>> retracedStackTraceConsumer) {
-    this.isVerbose = isVerbose;
-    this.regularExpression = regularExpression;
-    this.diagnosticsHandler = diagnosticsHandler;
-    this.proguardMapProducer = proguardMapProducer;
+      Consumer<List<String>> retracedStackTraceConsumer,
+      boolean isVerbose) {
+    options =
+        new RetraceOptions(regularExpression, diagnosticsHandler, proguardMapProducer, isVerbose);
     this.stackTrace = stackTrace;
     this.retracedStackTraceConsumer = retracedStackTraceConsumer;
 
-    assert this.diagnosticsHandler != null;
-    assert this.proguardMapProducer != null;
     assert this.stackTrace != null;
     assert this.retracedStackTraceConsumer != null;
   }
@@ -47,6 +42,18 @@
     return System.getProperty("com.android.tools.r8.printmemory") != null;
   }
 
+  public List<String> getStackTrace() {
+    return stackTrace;
+  }
+
+  public Consumer<List<String>> getRetracedStackTraceConsumer() {
+    return retracedStackTraceConsumer;
+  }
+
+  public RetraceOptions getOptions() {
+    return options;
+  }
+
   /**
    * Utility method for obtaining a RetraceCommand builder.
    *
@@ -61,12 +68,13 @@
     return new Builder(new DiagnosticsHandler() {});
   }
 
+  @Keep
   public static class Builder {
 
     private boolean isVerbose;
     private final DiagnosticsHandler diagnosticsHandler;
     private ProguardMapProducer proguardMapProducer;
-    private String regularExpression;
+    private String regularExpression = StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
     private List<String> stackTrace;
     private Consumer<List<String>> retracedStackTraceConsumer;
 
@@ -92,7 +100,8 @@
 
     /**
      * Set a regular expression for parsing the incoming text. The Regular expression must not use
-     * naming groups and has special wild cards according to proguard retrace.
+     * naming groups and has special wild cards according to proguard retrace. Note, this will
+     * override the default regular expression.
      *
      * @param regularExpression The regular expression to use.
      */
@@ -136,13 +145,12 @@
         throw new RuntimeException("RetracedStackConsumer not specified");
       }
       return new RetraceCommand(
-          isVerbose,
           regularExpression,
           diagnosticsHandler,
           proguardMapProducer,
           stackTrace,
-          retracedStackTraceConsumer);
+          retracedStackTraceConsumer,
+          isVerbose);
     }
   }
-
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
index d2a6827..54c8519 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
@@ -43,9 +43,4 @@
   public static RetraceInvalidStackTraceLineDiagnostics createNull(int lineNumber) {
     return new RetraceInvalidStackTraceLineDiagnostics(lineNumber, NULL_STACK_TRACE_LINE_MESSAGE);
   }
-
-  static RetraceInvalidStackTraceLineDiagnostics createParse(int lineNumber, String line) {
-    return new RetraceInvalidStackTraceLineDiagnostics(
-        lineNumber, String.format(PARSE_STACK_TRACE_LINE_MESSAGE, line));
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java b/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
new file mode 100644
index 0000000..a8e5394
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2021, 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.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
+
+/**
+ * The base options for running retrace with support for continuously retrace strings without
+ * parsing the proguard map multiple times.
+ */
+@Keep
+public class RetraceOptions {
+
+  private final boolean isVerbose;
+  private final String regularExpression;
+  private final DiagnosticsHandler diagnosticsHandler;
+  private final ProguardMapProducer proguardMapProducer;
+
+  RetraceOptions(
+      String regularExpression,
+      DiagnosticsHandler diagnosticsHandler,
+      ProguardMapProducer proguardMapProducer,
+      boolean isVerbose) {
+    this.regularExpression = regularExpression;
+    this.diagnosticsHandler = diagnosticsHandler;
+    this.proguardMapProducer = proguardMapProducer;
+    this.isVerbose = isVerbose;
+
+    assert diagnosticsHandler != null;
+    assert proguardMapProducer != null;
+  }
+
+  public boolean isVerbose() {
+    return isVerbose;
+  }
+
+  public String getRegularExpression() {
+    return regularExpression;
+  }
+
+  public DiagnosticsHandler getDiagnosticsHandler() {
+    return diagnosticsHandler;
+  }
+
+  public ProguardMapProducer getProguardMapProducer() {
+    return proguardMapProducer;
+  }
+
+  /** Utility method for obtaining a builder with a default diagnostics handler. */
+  public static Builder builder() {
+    return builder(new DiagnosticsHandler() {});
+  }
+
+  /** Utility method for obtaining a builder. */
+  public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
+    return new Builder(diagnosticsHandler);
+  }
+
+  @Keep
+  public static class Builder {
+
+    private boolean isVerbose;
+    private final DiagnosticsHandler diagnosticsHandler;
+    private ProguardMapProducer proguardMapProducer;
+    private String regularExpression = StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
+
+    Builder(DiagnosticsHandler diagnosticsHandler) {
+      this.diagnosticsHandler = diagnosticsHandler;
+    }
+
+    /** Set if the produced stack trace should have additional information. */
+    public Builder setVerbose(boolean verbose) {
+      this.isVerbose = verbose;
+      return this;
+    }
+
+    /**
+     * Set a producer for the proguard mapping contents.
+     *
+     * @param producer Producer for
+     */
+    public Builder setProguardMapProducer(ProguardMapProducer producer) {
+      this.proguardMapProducer = producer;
+      return this;
+    }
+
+    /**
+     * Set a regular expression for parsing the incoming text. The Regular expression must not use
+     * naming groups and has special wild cards according to proguard retrace. Note, this will
+     * override the default regular expression.
+     *
+     * @param regularExpression The regular expression to use.
+     */
+    public Builder setRegularExpression(String regularExpression) {
+      this.regularExpression = regularExpression;
+      return this;
+    }
+
+    public RetraceOptions build() {
+      if (this.diagnosticsHandler == null) {
+        throw new RuntimeException("DiagnosticsHandler not specified");
+      }
+      if (this.proguardMapProducer == null) {
+        throw new RuntimeException("ProguardMapSupplier not specified");
+      }
+      return new RetraceOptions(
+          regularExpression, diagnosticsHandler, proguardMapProducer, isVerbose);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java
index d9d1375..4eef2fc 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceProxy.java
@@ -8,8 +8,8 @@
 import java.util.List;
 
 @Keep
-public interface RetraceStackTraceProxy<T extends StackTraceElementProxy<?>>
-    extends Comparable<RetraceStackTraceProxy<T>> {
+public interface RetraceStackTraceProxy<T, ST extends StackTraceElementProxy<T, ST>>
+    extends Comparable<RetraceStackTraceProxy<T, ST>> {
 
   boolean isAmbiguous();
 
@@ -29,7 +29,7 @@
 
   boolean hasMethodArguments();
 
-  T getOriginalItem();
+  ST getOriginalItem();
 
   RetracedClass getRetracedClass();
 
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
index 09c1a8f..7cffce0 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.references.ClassReference;
 
 @Keep
-public abstract class StackTraceElementProxy<T> {
+public abstract class StackTraceElementProxy<T, ST extends StackTraceElementProxy<T, ST>> {
 
   public abstract boolean hasClassName();
 
@@ -37,4 +37,6 @@
   public abstract String getFieldOrReturnType();
 
   public abstract String getMethodArguments();
+
+  public abstract T toRetracedItem(RetraceStackTraceProxy<T, ST> retracedProxy, boolean verbose);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
index 17de5f1..18be7e7 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
@@ -9,12 +9,12 @@
 import java.util.stream.Stream;
 
 @Keep
-public interface StackTraceElementProxyRetracer<T extends StackTraceElementProxy<?>> {
+public interface StackTraceElementProxyRetracer<T, ST extends StackTraceElementProxy<T, ST>> {
 
-  Stream<RetraceStackTraceProxy<T>> retrace(T element);
+  Stream<RetraceStackTraceProxy<T, ST>> retrace(ST element);
 
-  static <T extends StackTraceElementProxy<?>> StackTraceElementProxyRetracer<T> createDefault(
-      Retracer retracer) {
+  static <T, ST extends StackTraceElementProxy<T, ST>>
+      StackTraceElementProxyRetracer<T, ST> createDefault(Retracer retracer) {
     return new StackTraceElementProxyRetracerImpl<>(retracer);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceLineParser.java b/src/main/java/com/android/tools/r8/retrace/StackTraceLineParser.java
new file mode 100644
index 0000000..92d1dca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceLineParser.java
@@ -0,0 +1,20 @@
+// 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;
+import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
+import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
+
+@Keep
+public interface StackTraceLineParser<T, ST extends StackTraceElementProxy<T, ST>> {
+
+  ST parse(T stackTraceLine);
+
+  static StackTraceLineParser<String, StackTraceElementStringProxy> createRegularExpressionParser(
+      String regularExpression) {
+    return new StackTraceRegularExpressionParser(regularExpression);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java
deleted file mode 100644
index bf2de73..0000000
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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;
-import java.util.function.Consumer;
-
-@Keep
-public interface StackTraceVisitor<T extends StackTraceElementProxy<?>> {
-
-  void forEach(Consumer<T> consumer);
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
new file mode 100644
index 0000000..957e357
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2021, 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 com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * Specialized Retrace class for retracing string retraces, with special handling for appending
+ * additional information into the strings, such as OR's for ambiguous lines.
+ */
+@Keep
+public class StringRetrace extends Retrace<String, StackTraceElementStringProxy> {
+
+  StringRetrace(
+      StackTraceLineParser<String, StackTraceElementStringProxy> stackTraceLineParser,
+      StackTraceElementProxyRetracer<String, StackTraceElementStringProxy> proxyRetracer,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean isVerbose) {
+    super(stackTraceLineParser, proxyRetracer, diagnosticsHandler, isVerbose);
+  }
+
+  /**
+   * Default entry point for creating a retrace designed for string input and output.
+   *
+   * @param command the command with information about creating the StringRetrace
+   * @return a StringRetrace object
+   */
+  public static StringRetrace create(RetraceOptions command) {
+    Retracer retracer =
+        Retracer.createDefault(command.getProguardMapProducer(), command.getDiagnosticsHandler());
+    return new StringRetrace(
+        StackTraceLineParser.createRegularExpressionParser(command.getRegularExpression()),
+        StackTraceElementProxyRetracer.createDefault(retracer),
+        command.getDiagnosticsHandler(),
+        command.isVerbose());
+  }
+
+  /**
+   * Retraces a list of stack-traces strings and returns a list. Ambiguous and inline frames will be
+   * appended automatically to the retraced string.
+   *
+   * @param stackTrace the incoming stack trace
+   * @return the retraced stack trace
+   */
+  public List<String> retrace(List<String> stackTrace) {
+    List<String> retracedStrings = new ArrayList<>();
+    retraceStackTrace(stackTrace, result -> joinAmbiguousLines(result, retracedStrings::add));
+    return retracedStrings;
+  }
+
+  /**
+   * Retraces a single stack trace line and returns the potential list of original frames
+   *
+   * @param stackTraceLine the stack trace line to retrace
+   * @return the retraced frames
+   */
+  public List<String> retrace(String stackTraceLine) {
+    List<String> result = new ArrayList<>();
+    joinAmbiguousLines(retraceFrame(stackTraceLine), result::add);
+    return result;
+  }
+
+  private void joinAmbiguousLines(
+      List<List<String>> retracedResult, Consumer<String> joinedConsumer) {
+    assert !retracedResult.isEmpty();
+    List<String> initialResult = retracedResult.get(0);
+    initialResult.forEach(joinedConsumer);
+    if (retracedResult.size() <= 1) {
+      // The result is not ambiguous.
+      return;
+    }
+    Set<String> reportedFrames = new HashSet<>(initialResult);
+    for (int i = 1; i < retracedResult.size(); i++) {
+      List<String> ambiguousResult = retracedResult.get(i);
+      assert !ambiguousResult.isEmpty();
+      String topFrame = ambiguousResult.get(0);
+      if (reportedFrames.add(topFrame)) {
+        ambiguousResult.forEach(
+            retracedString -> {
+              int firstCharIndex = firstNonWhiteSpaceCharacterFromIndex(retracedString, 0);
+              retracedString =
+                  retracedString.substring(0, firstCharIndex)
+                      + "<OR> "
+                      + retracedString.substring(firstCharIndex);
+              joinedConsumer.accept(retracedString);
+            });
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceLineParser.java
similarity index 87%
rename from src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
rename to src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceLineParser.java
index a632f1c..ddaf675 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceLineParser.java
@@ -7,34 +7,21 @@
 import static com.android.tools.r8.retrace.internal.RetraceUtils.firstCharFromIndex;
 import static com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
 
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.retrace.RetraceInvalidStackTraceLineDiagnostics;
-import com.android.tools.r8.retrace.StackTraceVisitor;
+import com.android.tools.r8.retrace.StackTraceLineParser;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassNameType;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder;
 import com.android.tools.r8.utils.DescriptorUtils;
-import java.util.List;
-import java.util.function.Consumer;
 
-public final class PlainStackTraceVisitor
-    implements StackTraceVisitor<StackTraceElementStringProxy> {
+public final class PlainStackTraceLineParser
+    implements StackTraceLineParser<String, StackTraceElementStringProxy> {
 
-  private final List<String> stackTrace;
-  private final DiagnosticsHandler diagnosticsHandler;
-
-  public PlainStackTraceVisitor(List<String> stackTrace, DiagnosticsHandler diagnosticsHandler) {
-    this.stackTrace = stackTrace;
-    this.diagnosticsHandler = diagnosticsHandler;
-  }
+  public PlainStackTraceLineParser() {}
 
   @Override
-  public void forEach(Consumer<StackTraceElementStringProxy> consumer) {
-    for (int i = 0; i < stackTrace.size(); i++) {
-      consumer.accept(parseLine(i + 1, stackTrace.get(i)));
-    }
+  public StackTraceElementStringProxy parse(String stackTraceLine) {
+    return parseLine(stackTraceLine);
   }
 
-
   /**
    * Captures a stack trace line of the following formats:
    *
@@ -193,11 +180,7 @@
     }
   }
 
-  private StackTraceElementStringProxy parseLine(int lineNumber, String line) {
-    if (line == null) {
-      diagnosticsHandler.error(RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
-      throw new RetraceAbortException();
-    }
+  private StackTraceElementStringProxy parseLine(String line) {
     // Most lines are 'at lines' so attempt to parse it first.
     StackTraceElementStringProxy parsedLine = AtLine.tryParse(line);
     if (parsedLine != null) {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
index c3453b5..ad01ee2 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -27,8 +27,8 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-public class StackTraceElementProxyRetracerImpl<T extends StackTraceElementProxy<?>>
-    implements StackTraceElementProxyRetracer<T> {
+public class StackTraceElementProxyRetracerImpl<T, ST extends StackTraceElementProxy<T, ST>>
+    implements StackTraceElementProxyRetracer<T, ST> {
 
   private final Retracer retracer;
 
@@ -37,9 +37,11 @@
   }
 
   @Override
-  public Stream<RetraceStackTraceProxy<T>> retrace(T element) {
+  public Stream<RetraceStackTraceProxy<T, ST>> retrace(ST element) {
     if (!element.hasClassName()) {
-      return Stream.of(RetraceStackTraceProxyImpl.builder(element).build());
+      RetraceStackTraceProxyImpl.Builder<T, ST> builder =
+          RetraceStackTraceProxyImpl.builder(element);
+      return Stream.of(builder.build());
     }
     RetraceClassResult classResult = retracer.retraceClass(element.getClassReference());
     if (element.hasMethodName()) {
@@ -51,8 +53,8 @@
     }
   }
 
-  private Stream<RetraceStackTraceProxy<T>> retraceClassOrType(
-      T element, RetraceClassResult classResult) {
+  private Stream<RetraceStackTraceProxy<T, ST>> retraceClassOrType(
+      ST element, RetraceClassResult classResult) {
     return classResult.stream()
         .flatMap(
             classElement ->
@@ -62,7 +64,7 @@
                             retracedMethodArguments(element)
                                 .map(
                                     argumentsConsumer -> {
-                                      RetraceStackTraceProxyImpl.Builder<T> proxy =
+                                      RetraceStackTraceProxyImpl.Builder<T, ST> proxy =
                                           RetraceStackTraceProxyImpl.builder(element)
                                               .setRetracedClass(classElement.getRetracedClass())
                                               .setAmbiguous(classResult.isAmbiguous())
@@ -79,8 +81,8 @@
                                     })));
   }
 
-  private Stream<RetraceStackTraceProxy<T>> retraceMethod(
-      T element, RetraceClassResult classResult) {
+  private Stream<RetraceStackTraceProxy<T, ST>> retraceMethod(
+      ST element, RetraceClassResult classResult) {
     return retraceFieldOrReturnType(element)
         .flatMap(
             fieldOrReturnTypeConsumer ->
@@ -95,11 +97,11 @@
                           return frameResult.stream()
                               .flatMap(
                                   frameElement -> {
-                                    List<RetraceStackTraceProxyImpl<T>> retracedProxies =
+                                    List<RetraceStackTraceProxy<T, ST>> retracedProxies =
                                         new ArrayList<>();
                                     frameElement.visitFrames(
                                         (frame, index) -> {
-                                          RetraceStackTraceProxyImpl.Builder<T> proxy =
+                                          RetraceStackTraceProxyImpl.Builder<T, ST> proxy =
                                               RetraceStackTraceProxyImpl.builder(element)
                                                   .setRetracedClass(frame.getHolderClass())
                                                   .setRetracedMethod(frame)
@@ -126,8 +128,8 @@
                         }));
   }
 
-  private Stream<RetraceStackTraceProxy<T>> retraceField(
-      T element, RetraceClassResult classResult) {
+  private Stream<RetraceStackTraceProxy<T, ST>> retraceField(
+      ST element, RetraceClassResult classResult) {
     return retraceFieldOrReturnType(element)
         .flatMap(
             fieldOrReturnTypeConsumer ->
@@ -139,7 +141,7 @@
                           return retraceFieldResult.stream()
                               .map(
                                   fieldElement -> {
-                                    RetraceStackTraceProxyImpl.Builder<T> proxy =
+                                    RetraceStackTraceProxyImpl.Builder<T, ST> proxy =
                                         RetraceStackTraceProxyImpl.builder(element)
                                             .setRetracedClass(
                                                 fieldElement.getField().getHolderClass())
@@ -159,8 +161,8 @@
                         }));
   }
 
-  private Stream<Consumer<RetraceStackTraceProxyImpl.Builder<T>>> retraceFieldOrReturnType(
-      T element) {
+  private Stream<Consumer<RetraceStackTraceProxyImpl.Builder<T, ST>>> retraceFieldOrReturnType(
+      ST element) {
     if (!element.hasFieldOrReturnType()) {
       return Stream.of(noEffect -> {});
     }
@@ -182,8 +184,8 @@
     }
   }
 
-  private Stream<Consumer<RetraceStackTraceProxyImpl.Builder<T>>> retracedMethodArguments(
-      T element) {
+  private Stream<Consumer<RetraceStackTraceProxyImpl.Builder<T, ST>>> retracedMethodArguments(
+      ST element) {
     if (!element.hasMethodArguments()) {
       return Stream.of(noEffect -> {});
     }
@@ -225,10 +227,10 @@
                 });
   }
 
-  public static class RetraceStackTraceProxyImpl<T extends StackTraceElementProxy<?>>
-      implements RetraceStackTraceProxy<T> {
+  public static class RetraceStackTraceProxyImpl<T, ST extends StackTraceElementProxy<T, ST>>
+      implements RetraceStackTraceProxy<T, ST> {
 
-    private final T originalItem;
+    private final ST originalItem;
     private final RetracedClass retracedClass;
     private final RetracedMethod retracedMethod;
     private final RetracedField retracedField;
@@ -240,7 +242,7 @@
     private final boolean isTopFrame;
 
     private RetraceStackTraceProxyImpl(
-        T originalItem,
+        ST originalItem,
         RetracedClass retracedClass,
         RetracedMethod retracedMethod,
         RetracedField retracedField,
@@ -309,7 +311,7 @@
     }
 
     @Override
-    public T getOriginalItem() {
+    public ST getOriginalItem() {
       return originalItem;
     }
 
@@ -343,7 +345,8 @@
       return sourceFile;
     }
 
-    private static <T extends StackTraceElementProxy<?>> Builder<T> builder(T originalElement) {
+    private static <T, ST extends StackTraceElementProxy<T, ST>> Builder<T, ST> builder(
+        ST originalElement) {
       return new Builder<>(originalElement);
     }
 
@@ -353,7 +356,7 @@
     }
 
     @Override
-    public int compareTo(RetraceStackTraceProxy<T> other) {
+    public int compareTo(RetraceStackTraceProxy<T, ST> other) {
       int classCompare = Boolean.compare(hasRetracedClass(), other.hasRetracedClass());
       if (classCompare != 0) {
         return classCompare;
@@ -395,9 +398,9 @@
       return 0;
     }
 
-    private static class Builder<T extends StackTraceElementProxy<?>> {
+    private static class Builder<T, ST extends StackTraceElementProxy<T, ST>> {
 
-      private final T originalElement;
+      private final ST originalElement;
       private RetracedClass classContext;
       private RetracedMethod methodContext;
       private RetracedField retracedField;
@@ -408,56 +411,56 @@
       private boolean isAmbiguous;
       private boolean isTopFrame;
 
-      private Builder(T originalElement) {
+      private Builder(ST originalElement) {
         this.originalElement = originalElement;
       }
 
-      private Builder<T> setRetracedClass(RetracedClass retracedClass) {
+      private Builder<T, ST> setRetracedClass(RetracedClass retracedClass) {
         this.classContext = retracedClass;
         return this;
       }
 
-      private Builder<T> setRetracedMethod(RetracedMethod methodElement) {
+      private Builder<T, ST> setRetracedMethod(RetracedMethod methodElement) {
         this.methodContext = methodElement;
         return this;
       }
 
-      private Builder<T> setRetracedField(RetracedField retracedField) {
+      private Builder<T, ST> setRetracedField(RetracedField retracedField) {
         this.retracedField = retracedField;
         return this;
       }
 
-      private Builder<T> setRetracedFieldOrReturnType(RetracedType retracedType) {
+      private Builder<T, ST> setRetracedFieldOrReturnType(RetracedType retracedType) {
         this.fieldOrReturnType = retracedType;
         return this;
       }
 
-      private Builder<T> setRetracedMethodArguments(List<RetracedType> arguments) {
+      private Builder<T, ST> setRetracedMethodArguments(List<RetracedType> arguments) {
         this.methodArguments = arguments;
         return this;
       }
 
-      private Builder<T> setSourceFile(String sourceFile) {
+      private Builder<T, ST> setSourceFile(String sourceFile) {
         this.sourceFile = sourceFile;
         return this;
       }
 
-      private Builder<T> setLineNumber(int lineNumber) {
+      private Builder<T, ST> setLineNumber(int lineNumber) {
         this.lineNumber = lineNumber;
         return this;
       }
 
-      private Builder<T> setAmbiguous(boolean ambiguous) {
+      private Builder<T, ST> setAmbiguous(boolean ambiguous) {
         this.isAmbiguous = ambiguous;
         return this;
       }
 
-      private Builder<T> setTopFrame(boolean topFrame) {
+      private Builder<T, ST> setTopFrame(boolean topFrame) {
         isTopFrame = topFrame;
         return this;
       }
 
-      private RetraceStackTraceProxyImpl<T> build() {
+      private RetraceStackTraceProxy<T, ST> build() {
         RetracedClass retracedClass = classContext;
         if (methodContext != null) {
           retracedClass = methodContext.getHolderClass();
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index 6decd5e..272f19d 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.retrace.internal;
 
 import static com.android.tools.r8.retrace.internal.RetraceUtils.methodDescriptionFromRetraceMethod;
+import static com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassStringIndex.NO_INDEX;
 import static com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StringIndex.noIndex;
 
 import com.android.tools.r8.references.ClassReference;
@@ -19,7 +20,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public final class StackTraceElementStringProxy extends StackTraceElementProxy<String> {
+public final class StackTraceElementStringProxy
+    extends StackTraceElementProxy<String, StackTraceElementStringProxy> {
 
   private final String line;
   private final List<StringIndex> orderedIndices;
@@ -133,8 +135,9 @@
     return hasMethodArguments() ? getEntryInLine(methodArguments) : null;
   }
 
+  @Override
   public String toRetracedItem(
-      RetraceStackTraceProxy<StackTraceElementStringProxy> retracedProxy, boolean verbose) {
+      RetraceStackTraceProxy<String, StackTraceElementStringProxy> retracedProxy, boolean verbose) {
     StringBuilder sb = new StringBuilder();
     int lastSeenIndex = 0;
     for (StringIndex index : orderedIndices) {
@@ -315,9 +318,6 @@
 
   static class StringIndex {
 
-    private static final ClassStringIndex NO_INDEX =
-        new ClassStringIndex(-1, -1, null, ClassNameType.TYPENAME);
-
     static ClassStringIndex noIndex() {
       return NO_INDEX;
     }
@@ -325,20 +325,14 @@
     protected final int startIndex;
     protected final int endIndex;
     private final TriFunction<
-            RetraceStackTraceProxy<StackTraceElementStringProxy>,
-            StackTraceElementStringProxy,
-            Boolean,
-            String>
+            RetraceStackTraceProxy<String, ?>, StackTraceElementStringProxy, Boolean, String>
         retracedString;
 
     private StringIndex(
         int startIndex,
         int endIndex,
         TriFunction<
-                RetraceStackTraceProxy<StackTraceElementStringProxy>,
-                StackTraceElementStringProxy,
-                Boolean,
-                String>
+                RetraceStackTraceProxy<String, ?>, StackTraceElementStringProxy, Boolean, String>
             retracedString) {
       this.startIndex = startIndex;
       this.endIndex = endIndex;
@@ -352,16 +346,16 @@
 
   static final class ClassStringIndex extends StringIndex {
 
+    static final ClassStringIndex NO_INDEX =
+        new ClassStringIndex(-1, -1, null, ClassNameType.TYPENAME);
+
     private final ClassNameType classNameType;
 
     private ClassStringIndex(
         int startIndex,
         int endIndex,
         TriFunction<
-                RetraceStackTraceProxy<StackTraceElementStringProxy>,
-                StackTraceElementStringProxy,
-                Boolean,
-                String>
+                RetraceStackTraceProxy<String, ?>, StackTraceElementStringProxy, Boolean, String>
             retracedString,
         ClassNameType classNameType) {
       super(startIndex, endIndex, retracedString);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
similarity index 85%
rename from src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
rename to src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
index 5790d52..fc8d5d9 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
@@ -5,24 +5,31 @@
 package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.retrace.StackTraceVisitor;
+import com.android.tools.r8.retrace.StackTraceLineParser;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassNameType;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class RetraceRegularExpression implements StackTraceVisitor<StackTraceElementStringProxy> {
+public class StackTraceRegularExpressionParser
+    implements StackTraceLineParser<String, StackTraceElementStringProxy> {
 
-  private final List<String> stackTrace;
-  private final String regularExpression;
+  // 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(?::.*)?)";
+
+  private final Pattern compiledPattern;
 
   private static final int NO_MATCH = -1;
 
   private final SourceFileLineNumberGroup sourceFileLineNumberGroup =
       new SourceFileLineNumberGroup();
+  private final List<RegularExpressionGroupHandler> handlers;
   private final TypeNameGroup typeNameGroup = new TypeNameGroup();
   private final BinaryNameGroup binaryNameGroup = new BinaryNameGroup();
   private final SourceFileGroup sourceFileGroup = new SourceFileGroup();
@@ -35,39 +42,35 @@
   private static final String CAPTURE_GROUP_PREFIX = "captureGroup";
   private static final int FIRST_CAPTURE_GROUP_INDEX = 0;
 
-  public RetraceRegularExpression(List<String> stackTrace, String regularExpression) {
-    this.stackTrace = stackTrace;
-    this.regularExpression = regularExpression;
+  public StackTraceRegularExpressionParser() {
+    this(DEFAULT_REGULAR_EXPRESSION);
+  }
+
+  public StackTraceRegularExpressionParser(String regularExpression) {
+    handlers = new ArrayList<>();
+    StringBuilder refinedRegularExpressionBuilder = new StringBuilder();
+    registerGroups(
+        regularExpression, refinedRegularExpressionBuilder, handlers, FIRST_CAPTURE_GROUP_INDEX);
+    compiledPattern = Pattern.compile(refinedRegularExpressionBuilder.toString());
   }
 
   @Override
-  public void forEach(Consumer<StackTraceElementStringProxy> consumer) {
-    List<RegularExpressionGroupHandler> handlers = new ArrayList<>();
-    StringBuilder refinedRegularExpressionBuilder = new StringBuilder();
-    registerGroups(
-        this.regularExpression,
-        refinedRegularExpressionBuilder,
-        handlers,
-        FIRST_CAPTURE_GROUP_INDEX);
-    String refinedRegularExpression = refinedRegularExpressionBuilder.toString();
-    Pattern compiledPattern = Pattern.compile(refinedRegularExpression);
-    for (String string : stackTrace) {
-      StackTraceElementStringProxyBuilder proxyBuilder =
-          StackTraceElementStringProxy.builder(string);
-      Matcher matcher = compiledPattern.matcher(string);
-      if (matcher.matches()) {
-        boolean seenMatchedClassHandler = false;
-        for (RegularExpressionGroupHandler handler : handlers) {
-          if (seenMatchedClassHandler && handler.isClassHandler()) {
-            continue;
-          }
-          if (handler.matchHandler(proxyBuilder, matcher)) {
-            seenMatchedClassHandler |= handler.isClassHandler();
-          }
+  public StackTraceElementStringProxy parse(String stackTraceLine) {
+    StackTraceElementStringProxyBuilder proxyBuilder =
+        StackTraceElementStringProxy.builder(stackTraceLine);
+    Matcher matcher = compiledPattern.matcher(stackTraceLine);
+    if (matcher.matches()) {
+      boolean seenMatchedClassHandler = false;
+      for (RegularExpressionGroupHandler handler : handlers) {
+        if (seenMatchedClassHandler && handler.isClassHandler()) {
+          continue;
+        }
+        if (handler.matchHandler(proxyBuilder, matcher)) {
+          seenMatchedClassHandler |= handler.isClassHandler();
         }
       }
-      consumer.accept(proxyBuilder.build());
     }
+    return proxyBuilder.build();
   }
 
   private int registerGroups(
@@ -166,9 +169,6 @@
     }
   }
 
-  private static final String anyNonDigitLetterCharWithMarkers = "\\p{L}\\p{M}*+";
-  private static final String anyDigit = "\\p{N}";
-
   // TODO(b/145731185): Extend support for identifiers with strings inside back ticks.
   private static final String javaIdentifierSegment =
       "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
@@ -376,4 +376,3 @@
     }
   }
 }
-
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 10cd22a..f4c8deb 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -145,4 +145,14 @@
     }
     return result;
   }
+
+  public static <T> void forEachWithIndex(List<T> items, ReferenceAndIntConsumer<T> consumer) {
+    for (int i = 0; i < items.size(); i++) {
+      consumer.accept(items.get(i), i);
+    }
+  }
+
+  public interface ReferenceAndIntConsumer<T> {
+    void accept(T item, int index);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java
index c8b98d7..1034b16 100644
--- a/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/internal/retrace/RetraceTests.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.internal.retrace;
 
-import static com.android.tools.r8.retrace.Retrace.DEFAULT_REGULAR_EXPRESSION;
+import static com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.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 94abc05..84bc19c 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.Retrace.DEFAULT_REGULAR_EXPRESSION;
+import static com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
index 2e75b1d..d217689 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.Retrace.DEFAULT_REGULAR_EXPRESSION;
+import static com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
 import static junit.framework.TestCase.assertEquals;
 import static org.junit.Assume.assumeFalse;
 
diff --git a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
index fb7f280..9bfcffa 100644
--- a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 
@@ -106,8 +107,7 @@
     StackTrace originalStackTrace = runResult.getOriginalStackTrace();
     StackTrace retracedStackTrace =
         originalStackTrace.retrace(
-            runResult.proguardMap(),
-            useRegularExpression ? Retrace.DEFAULT_REGULAR_EXPRESSION : null);
+            runResult.proguardMap(), useRegularExpression ? DEFAULT_REGULAR_EXPRESSION : null);
     runResult.inspectFailure(inspector -> consumer.accept(retracedStackTrace, inspector));
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
rename to src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
index 4f79193..aeaed43 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.Retrace.DEFAULT_REGULAR_EXPRESSION;
+import static com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
 import static junit.framework.TestCase.assertEquals;
 
 import com.android.tools.r8.TestBase;
@@ -25,14 +25,14 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class RetraceRegularExpressionTests extends TestBase {
+public class StackTraceRegularExpressionParserTests extends TestBase {
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withNoneRuntime().build();
   }
 
-  public RetraceRegularExpressionTests(TestParameters parameters) {}
+  public StackTraceRegularExpressionParserTests(TestParameters parameters) {}
 
   @Test
   public void ensureNotMatchingOnLiteral() {