Merge commit 'e93002edb8c0bd7207fb1f33fe8d2354079abfb1' into dev-release
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index eb5f45e..79ec8a5 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -3,95 +3,78 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.ProgramResource.Kind;
+import static com.android.tools.r8.utils.StringUtils.quote;
+
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.dex.VDexParser;
-import com.android.tools.r8.dex.VDexReader;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MarkerInfoConsumerDataImpl;
+import com.android.tools.r8.utils.MarkerInfoImpl;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.io.ByteStreams;
 import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
+import java.io.PrintStream;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.concurrent.ExecutionException;
+import java.util.Comparator;
+import java.util.List;
 
+/**
+ * A tool to extract markers generated by the R8 compiler and related tools.
+ *
+ * <p>The tool is run by defining a consumer and then building the command, such as:
+ *
+ * <pre>
+ *   ExtractMarker.run(ExtractMarkerCommand.builder()
+ *       .addProgramFiles(inputPathA, inputPathB)
+ *       .setMarkerInfoConsumer(new MyInfoConsumer())
+ *       .build());
+ * </pre>
+ */
+@Keep
 public class ExtractMarker {
-  public static class VdexOrigin extends Origin {
 
-    private final int index;
+  private static class MarkerInfoPrintConsumer implements MarkerInfoConsumer {
 
-    public VdexOrigin(Origin vdexOrigin, int index) {
-      super(vdexOrigin);
-      this.index = index;
+    private final PrintStream stream;
+
+    public MarkerInfoPrintConsumer(PrintStream stream) {
+      this.stream = stream;
+    }
+
+    private void printRow(Origin inputOrigin, String rawMarker) {
+      stream.print(inputOrigin.toString());
+      stream.print(": ");
+      stream.print(rawMarker);
+      stream.println();
     }
 
     @Override
-    public String part() {
-      return Integer.toString(index);
-    }
-  }
-
-  public static Collection<Marker> extractMarkerFromDexFile(Path file)
-      throws IOException, ResourceException {
-    AndroidApp.Builder appBuilder = AndroidApp.builder();
-    addDexResources(appBuilder, file);
-    return extractMarker(appBuilder.build());
-  }
-
-  public static Collection<Marker> extractMarkerFromJarFile(Path file) throws IOException {
-    return extractMarker(AndroidApp.builder().addProgramFile(file).build());
-  }
-
-  public static int extractDexSize(Path file) throws IOException, ResourceException {
-    AndroidApp.Builder appBuilder = AndroidApp.builder();
-    addDexResources(appBuilder, file);
-    int size = 0;
-    for (ProgramResource resource : appBuilder.build().computeAllProgramResources()) {
-      if (resource.getKind() == Kind.DEX) {
-        try (InputStream input = resource.getByteStream()) {
-          size += ByteStreams.toByteArray(input).length;
+    public void acceptMarkerInfo(MarkerInfoConsumerData data) {
+      if (data.hasMarkers()) {
+        for (MarkerInfo marker : data.getMarkers()) {
+          printRow(data.getInputOrigin(), marker.getRawEncoding());
         }
+      } else {
+        printRow(data.getInputOrigin(), quote("no marker"));
       }
     }
-    return size;
+
+    @Override
+    public void finished() {}
   }
 
-  public static Collection<Marker> extractMarkerFromDexProgramData(byte[] data) throws IOException {
-    AndroidApp app = AndroidApp.builder().addDexProgramData(data, Origin.unknown()).build();
-    return extractMarker(app);
-  }
+  private static class InterceptedException extends RuntimeException {
+    private final CompilationFailedException compilationFailedException;
 
-  public static Collection<Marker> extractMarkerFromClassProgramData(byte[] data)
-      throws IOException {
-    AndroidApp app = AndroidApp.builder().addClassProgramData(data, Origin.unknown()).build();
-    return extractMarker(app);
-  }
-
-  private static void addDexResources(AndroidApp.Builder appBuilder, Path file) throws IOException {
-    if (FileUtils.isVDexFile(file)) {
-      PathOrigin vdexOrigin = new PathOrigin(file);
-      try (InputStream fileInputStream = Files.newInputStream(file)) {
-        VDexReader vdexReader = new VDexReader(vdexOrigin, fileInputStream);
-        VDexParser vDexParser = new VDexParser(vdexReader);
-        int index = 0;
-        for (byte[] bytes : vDexParser.getDexFiles()) {
-          appBuilder.addDexProgramData(bytes, new VdexOrigin(vdexOrigin, index));
-          index++;
-        }
-      }
-    } else {
-      appBuilder.addProgramFiles(file);
+    private InterceptedException(CompilationFailedException e) {
+      this.compilationFailedException = e;
     }
   }
 
@@ -103,70 +86,67 @@
     return dexApp.dexItemFactory.extractMarkers();
   }
 
-  public static void main(String[] args)
-      throws IOException, ExecutionException, ResourceException {
+  private static void extractForConsumer(
+      MarkerInfoConsumer consumer, Reporter reporter, Origin origin, AndroidApp.Builder builder) {
+    List<Marker> markers = new ArrayList<>();
+    try {
+      ExceptionUtils.withCompilationHandler(
+          reporter, () -> markers.addAll(extractMarker(builder.build())));
+    } catch (CompilationFailedException e) {
+      throw new InterceptedException(e);
+    }
+    // Ensure the markers are sorted, so we have deterministic callback/print order.
+    markers.sort(Comparator.comparing(Marker::toString));
+    List<MarkerInfo> infos = ListUtils.map(markers, MarkerInfoImpl::new);
+    consumer.acceptMarkerInfo(new MarkerInfoConsumerDataImpl(origin, infos));
+  }
+
+  /**
+   * Main API entry for the extract marker tool.
+   *
+   * @param command Extract marker command specification.
+   */
+  public static void run(ExtractMarkerCommand command) throws CompilationFailedException {
+    MarkerInfoConsumer consumer = command.getMarkerInfoConsumer();
+    Reporter reporter = new Reporter(command.getDiagnosticsHandler());
+    try {
+      command.forEachEntry(
+          (path, origin) ->
+              extractForConsumer(
+                  consumer, reporter, origin, AndroidApp.builder().addProgramFile(path)),
+          (data, origin) ->
+              extractForConsumer(
+                  consumer, reporter, origin, AndroidApp.builder().addDexProgramData(data, origin)),
+          (data, origin) ->
+              extractForConsumer(
+                  consumer,
+                  reporter,
+                  origin,
+                  AndroidApp.builder().addClassProgramData(data, origin)));
+    } catch (InterceptedException e) {
+      throw e.compilationFailedException;
+    }
+  }
+
+  private static void run(String[] args) throws CompilationFailedException {
+    PrintStream out = System.out;
     ExtractMarkerCommand.Builder builder = ExtractMarkerCommand.parse(args);
-    ExtractMarkerCommand command = builder.build();
+    MarkerInfoConsumer consumer = new MarkerInfoPrintConsumer(out);
+    ExtractMarkerCommand command = builder.setMarkerInfoConsumer(consumer).build();
     if (command.isPrintHelp()) {
       System.out.println(ExtractMarkerCommand.USAGE_MESSAGE);
       return;
     }
+    run(command);
+  }
 
-    Path cwd = Paths.get(System.getProperty("user.dir"));
-    // Dex code is not needed for getting the marker. VDex files typically contains quickened byte
-    // codes which cannot be read, and we want to get the marker from vdex files as well.
-    int d8Count = 0;
-    int r8Count = 0;
-    int otherCount = 0;
-    for (Path programFile : command.getProgramFiles()) {
-      Collection<Marker> markers;
-      try {
-        markers = extractMarkerFromDexFile(programFile);
-      } catch (CompilationError e) {
-        System.out.println(
-            "Failed to read dex/vdex file `" + programFile + "`: '" + e.getMessage() + "'");
-        continue;
-      }
-      System.out.print("In file: " + cwd.toAbsolutePath().relativize(programFile.toAbsolutePath()));
-      System.out.println(", " + extractDexSize(programFile) + " bytes:");
-      for (Marker marker : markers) {
-        if (marker == null) {
-          otherCount++;
-          if (!command.getIncludeOther()) {
-            continue;
-          }
-        } else {
-          if (marker.isD8()) {
-            d8Count++;
-          } else {
-            r8Count++;
-          }
-        }
-        if (command.getCSV()) {
-          System.out.print("\"" + programFile + "\"");
-          System.out.print(", ");
-          if (marker == null) {
-            System.out.print("\"no marker\"");
-          } else {
-            System.out.print("\"" + (marker.isD8() ? "D8" : "R8") + "\"");
-          }
-          System.out.print(", ");
-          System.out.print(extractDexSize(programFile));
-        } else {
-          if (command.getVerbose()) {
-            System.out.print(programFile);
-            System.out.print(": ");
-          }
-          System.out.print(marker == null ? "D8/R8 marker not found" : marker);
-        }
-        System.out.println();
-      }
-    }
-    if (command.getSummary()) {
-      System.out.println("D8: " + d8Count);
-      System.out.println("R8: " + r8Count);
-      System.out.println("Other: " + otherCount);
-      System.out.println("Total: " + (d8Count + r8Count + otherCount));
-    }
+  /**
+   * Command-line entry to the extract marker tool.
+   *
+   * <p>See {@link ExtractMarkerCommand#USAGE_MESSAGE} or run with {@code --help} for usage
+   * information.
+   */
+  public static void main(String[] args) throws Exception {
+    ExceptionUtils.withMainProgramHandler(() -> run(args));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java b/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java
index a2b44dc..ac084da 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarkerCommand.java
@@ -4,22 +4,35 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
-import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import java.util.function.BiConsumer;
 
-class ExtractMarkerCommand {
+/** Immutable command structure for an invocation of the {@link ExtractMarker} tool. */
+@Keep
+public class ExtractMarkerCommand {
 
+  /** Builder for constructing a {@link ExtractMarkerCommand}. */
+  @Keep
   public static class Builder {
     private boolean printHelp = false;
-    private boolean includeOther = true;
-    private boolean verbose;
-    private boolean summary;
-    private boolean csv;
     private final List<Path> programFiles = new ArrayList<>();
+    private final List<Pair<Origin, byte[]>> dexData = new ArrayList<>();
+    private final List<Pair<Origin, byte[]>> cfData = new ArrayList<>();
+    private MarkerInfoConsumer consumer;
+    private DiagnosticsHandler handler;
+
+    Builder(DiagnosticsHandler handler) {
+      this.handler = handler;
+    }
 
     public Builder setPrintHelp(boolean printHelp) {
       this.printHelp = printHelp;
@@ -30,28 +43,40 @@
       return printHelp;
     }
 
-    public Builder setIncludeOther(boolean includeOther) {
-      this.includeOther = includeOther;
+    /**
+     * Add program files to extract marker information from.
+     *
+     * <p>All program files supported by the input and output of D8/R8 can be passed here.
+     */
+    public Builder addProgramFiles(Path... programFiles) {
+      return addProgramFiles(Arrays.asList(programFiles));
+    }
+
+    /**
+     * Add program files to extract marker information from.
+     *
+     * <p>All program files supported by the input and output of D8/R8 can be passed here.
+     */
+    public Builder addProgramFiles(Collection<Path> programFiles) {
+      this.programFiles.addAll(programFiles);
       return this;
     }
 
-    public Builder setVerbose(boolean verbose) {
-      this.verbose = verbose;
+    /** Add dex encoded bytes to extract marker information from. */
+    public Builder addDexProgramData(byte[] data, Origin origin) {
+      dexData.add(new Pair<>(origin, data));
       return this;
     }
 
-    public Builder setSummary(boolean summary) {
-      this.summary = summary;
+    /** Add classfile encoded bytes to extract marker information from. */
+    public Builder addClassProgramData(byte[] data, Origin origin) {
+      cfData.add(new Pair<>(origin, data));
       return this;
     }
 
-    public Builder setCSV(boolean csv) {
-      this.csv = csv;
-      return this;
-    }
-
-    public Builder addProgramFile(Path programFile) {
-      programFiles.add(programFile);
+    /** Set the callback to obtain the collected marker information. */
+    public Builder setMarkerInfoConsumer(MarkerInfoConsumer consumer) {
+      this.consumer = consumer;
       return this;
     }
 
@@ -60,24 +85,27 @@
       if (isPrintHelp()) {
         return new ExtractMarkerCommand(isPrintHelp());
       }
-      return new ExtractMarkerCommand(includeOther, verbose, summary, csv, programFiles);
+      return new ExtractMarkerCommand(handler, consumer, programFiles, dexData, cfData);
     }
   }
 
-  static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
-      "Usage: extractmarker [options] <input-files>",
-      " where <input-files> are dex or vdex files",
-      "  --no-other              # Only show information for D8 or R8 processed files.",
-      "  --verbose               # More verbose output.",
-      "  --summary               # Print summary at the end.",
-      "  --csv                   # Output in CSV format.",
-      "  --help                  # Print this message."));
+  static final String USAGE_MESSAGE =
+      String.join(
+          "\n",
+          ImmutableList.of(
+              "Usage: extractmarker [options] <input-files>",
+              " where <input-files> are D8 supported input/output files and options are:",
+              "  --help                  # Print this message."));
 
   public static Builder builder() {
-    return new Builder();
+    return builder(new DiagnosticsHandler() {});
   }
 
-  public static Builder parse(String[] args) throws IOException {
+  public static Builder builder(DiagnosticsHandler handler) {
+    return new Builder(handler);
+  }
+
+  public static Builder parse(String[] args) {
     Builder builder = builder();
     parse(args, builder);
     return builder;
@@ -86,74 +114,65 @@
   private static void parse(String[] args, Builder builder) {
     for (int i = 0; i < args.length; i++) {
       String arg = args[i].trim();
-      if (arg.length() == 0) {
-        continue;
-      } else if (arg.equals("--no-other")) {
-        builder.setIncludeOther(false);
-      } else if (arg.equals("--verbose")) {
-        builder.setVerbose(true);
-      } else if (arg.equals("--summary")) {
-        builder.setSummary(true);
-      } else if (arg.equals("--csv")) {
-        builder.setCSV(true);
-      } else if (arg.equals("--help")) {
+      if (arg.equals("--help")) {
         builder.setPrintHelp(true);
       } else {
         if (arg.startsWith("--")) {
           throw new CompilationError("Unknown option: " + arg);
         }
-        builder.addProgramFile(Paths.get(arg));
+        builder.addProgramFiles(Paths.get(arg));
       }
     }
   }
 
   private final boolean printHelp;
-  private final boolean includeOther;
-  private final boolean verbose;
-  private final boolean summary;
-  private final boolean csv;
+  private final DiagnosticsHandler handler;
+  private final MarkerInfoConsumer consumer;
   private final List<Path> programFiles;
+  private final List<Pair<Origin, byte[]>> dexData;
+  private final List<Pair<Origin, byte[]>> cfData;
 
-  private ExtractMarkerCommand(boolean includeOther, boolean verbose, boolean summary,
-      boolean csv, List<Path> programFiles) {
+  private ExtractMarkerCommand(
+      DiagnosticsHandler handler,
+      MarkerInfoConsumer consumer,
+      List<Path> programFiles,
+      List<Pair<Origin, byte[]>> dexData,
+      List<Pair<Origin, byte[]>> cfData) {
     this.printHelp = false;
-    this.includeOther = includeOther;
-    this.verbose = verbose;
-    this.summary = summary;
-    this.csv = csv;
+    this.handler = handler;
+    this.consumer = consumer;
     this.programFiles = programFiles;
+    this.dexData = dexData;
+    this.cfData = cfData;
   }
 
   private ExtractMarkerCommand(boolean printHelp) {
     this.printHelp = printHelp;
-    this.includeOther = true;
-    this.verbose = false;
-    this.summary = false;
-    this.csv = false;
-    programFiles = ImmutableList.of();
+    handler = null;
+    consumer = null;
+    programFiles = null;
+    dexData = null;
+    cfData = null;
   }
 
   public boolean isPrintHelp() {
     return printHelp;
   }
 
-  public List<Path> getProgramFiles() {
-    return programFiles;
+  public MarkerInfoConsumer getMarkerInfoConsumer() {
+    return consumer;
   }
 
-  public boolean getIncludeOther() {
-    return includeOther;
+  public DiagnosticsHandler getDiagnosticsHandler() {
+    return handler;
   }
 
-  public boolean getVerbose() {
-    return verbose;
-  }
-
-  public boolean getSummary() {
-    return summary;
-  }
-
-  public boolean getCSV() {
-    return csv;
+  public void forEachEntry(
+      BiConsumer<Path, Origin> onProgramFile,
+      BiConsumer<byte[], Origin> onDexData,
+      BiConsumer<byte[], Origin> onCfData) {
+    programFiles.forEach(path -> onProgramFile.accept(path, new PathOrigin(path)));
+    dexData.forEach(pair -> onDexData.accept(pair.getSecond(), pair.getFirst()));
+    cfData.forEach(pair -> onCfData.accept(pair.getSecond(), pair.getFirst()));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/MarkerInfo.java b/src/main/java/com/android/tools/r8/MarkerInfo.java
new file mode 100644
index 0000000..32578c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/MarkerInfo.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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;
+
+/** Information present in a given marker. */
+@Keep
+public interface MarkerInfo {
+
+  /** Get the tool that has generated the marker. */
+  String getTool();
+
+  /** True of the generating tool was the R8 compiler. */
+  boolean isR8();
+
+  /** True of the generating tool was the D8 compiler. */
+  boolean isD8();
+
+  /** True of the generating tool was the L8 compiler. */
+  boolean isL8();
+
+  /** Get the min-api specified by the marker, or -1 if not defined. */
+  int getMinApi();
+
+  /**
+   * Get the raw encoding of the marker used by the compilers.
+   *
+   * <p>Warning: The format of this encoding may be subject to change and its concrete encoding
+   * should not be assumed. For usages of concrete information in the marker the structured API on
+   * {@link MarkerInfo} should be used.
+   */
+  String getRawEncoding();
+}
diff --git a/src/main/java/com/android/tools/r8/MarkerInfoConsumer.java b/src/main/java/com/android/tools/r8/MarkerInfoConsumer.java
new file mode 100644
index 0000000..48e4a30
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/MarkerInfoConsumer.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2023, 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;
+
+/** Interface for consumers of the marker information. */
+@Keep
+public interface MarkerInfoConsumer {
+
+  void acceptMarkerInfo(MarkerInfoConsumerData data);
+
+  void finished();
+}
diff --git a/src/main/java/com/android/tools/r8/MarkerInfoConsumerData.java b/src/main/java/com/android/tools/r8/MarkerInfoConsumerData.java
new file mode 100644
index 0000000..1b2d500
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/MarkerInfoConsumerData.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2023, 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;
+
+import com.android.tools.r8.origin.Origin;
+import java.util.Collection;
+
+/** Information about which markers are present in a given input. */
+@Keep
+public interface MarkerInfoConsumerData {
+
+  Origin getInputOrigin();
+
+  boolean hasMarkers();
+
+  Collection<MarkerInfo> getMarkers();
+}
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 47bcc73..7af0129 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -807,6 +807,9 @@
     expect(':');
     skipWhitespace();
     int to = parseNumber();
+    if (from > to) {
+      throw new ParseException("From is larger than to in range: " + from + ":" + to);
+    }
     return nonCardinalRangeCache.get(from, to);
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ExtractMarkerUtils.java b/src/main/java/com/android/tools/r8/utils/ExtractMarkerUtils.java
new file mode 100644
index 0000000..b8d0bb0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ExtractMarkerUtils.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2023, 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.utils;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ExtractMarker;
+import com.android.tools.r8.ExtractMarkerCommand;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+// Internal tools for accessing marker info.
+public class ExtractMarkerUtils {
+
+  public static Collection<Marker> extractMarkersFromFile(Path file)
+      throws CompilationFailedException {
+    return extractMarkers(ExtractMarkerCommand.builder().addProgramFiles(file));
+  }
+
+  public static Collection<Marker> extractMarkerFromDexProgramData(byte[] data)
+      throws CompilationFailedException {
+    return extractMarkers(ExtractMarkerCommand.builder().addDexProgramData(data, Origin.unknown()));
+  }
+
+  public static Collection<Marker> extractMarkerFromClassProgramData(byte[] data)
+      throws CompilationFailedException {
+    return extractMarkers(
+        ExtractMarkerCommand.builder().addClassProgramData(data, Origin.unknown()));
+  }
+
+  private static List<Marker> extractMarkers(ExtractMarkerCommand.Builder builder)
+      throws CompilationFailedException {
+    List<Marker> markers = new ArrayList<>();
+    ExtractMarker.run(
+        builder.setMarkerInfoConsumer(new MarkerInfoToInternalMarkerConsumer(markers)).build());
+    return markers;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/MarkerInfoConsumerDataImpl.java b/src/main/java/com/android/tools/r8/utils/MarkerInfoConsumerDataImpl.java
new file mode 100644
index 0000000..7a844a1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/MarkerInfoConsumerDataImpl.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2023, 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.utils;
+
+import com.android.tools.r8.MarkerInfo;
+import com.android.tools.r8.MarkerInfoConsumerData;
+import com.android.tools.r8.origin.Origin;
+import java.util.Collection;
+import java.util.List;
+
+public class MarkerInfoConsumerDataImpl implements MarkerInfoConsumerData {
+
+  private final Origin origin;
+  private final List<MarkerInfo> markerInfos;
+
+  public MarkerInfoConsumerDataImpl(Origin origin, List<MarkerInfo> markerInfos) {
+    this.origin = origin;
+    this.markerInfos = markerInfos;
+  }
+
+  @Override
+  public Origin getInputOrigin() {
+    return origin;
+  }
+
+  @Override
+  public boolean hasMarkers() {
+    return !markerInfos.isEmpty();
+  }
+
+  @Override
+  public Collection<MarkerInfo> getMarkers() {
+    return markerInfos;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/MarkerInfoImpl.java b/src/main/java/com/android/tools/r8/utils/MarkerInfoImpl.java
new file mode 100644
index 0000000..bcbbeeb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/MarkerInfoImpl.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, 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.utils;
+
+import com.android.tools.r8.MarkerInfo;
+import com.android.tools.r8.dex.Marker;
+
+public class MarkerInfoImpl implements MarkerInfo {
+
+  private final Marker marker;
+
+  public MarkerInfoImpl(Marker marker) {
+    this.marker = marker;
+  }
+
+  Marker getInternalMarker() {
+    return marker;
+  }
+
+  @Override
+  public String getTool() {
+    return marker.getTool().toString();
+  }
+
+  @Override
+  public boolean isR8() {
+    return marker.isR8();
+  }
+
+  @Override
+  public boolean isD8() {
+    return marker.isD8();
+  }
+
+  @Override
+  public boolean isL8() {
+    return marker.isL8();
+  }
+
+  @Override
+  public int getMinApi() {
+    return marker.hasMinApi() ? marker.getMinApi().intValue() : -1;
+  }
+
+  @Override
+  public String getRawEncoding() {
+    return marker.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/MarkerInfoToInternalMarkerConsumer.java b/src/main/java/com/android/tools/r8/utils/MarkerInfoToInternalMarkerConsumer.java
new file mode 100644
index 0000000..099d9b1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/MarkerInfoToInternalMarkerConsumer.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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.utils;
+
+import com.android.tools.r8.MarkerInfo;
+import com.android.tools.r8.MarkerInfoConsumer;
+import com.android.tools.r8.MarkerInfoConsumerData;
+import com.android.tools.r8.dex.Marker;
+import java.util.List;
+
+/** Consumer to convert the marker interface to the internal marker class. */
+public class MarkerInfoToInternalMarkerConsumer implements MarkerInfoConsumer {
+
+  private final List<Marker> markers;
+
+  public MarkerInfoToInternalMarkerConsumer(List<Marker> markers) {
+    this.markers = markers;
+  }
+
+  @Override
+  public void acceptMarkerInfo(MarkerInfoConsumerData data) {
+    if (data.hasMarkers()) {
+      for (MarkerInfo marker : data.getMarkers()) {
+        MarkerInfoImpl impl = (MarkerInfoImpl) marker;
+        markers.add(impl.getInternalMarker());
+      }
+    }
+  }
+
+  @Override
+  public void finished() {}
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 11cebfd..487451f 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExtractMarkerUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -137,7 +138,7 @@
         input.toString());
     assertEquals(0, ToolHelper.forkD8(working, "@flags.txt").exitCode);
     assertTrue(Files.exists(output));
-    Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+    Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(output);
     assertEquals(1, markers.size());
     Marker marker = markers.iterator().next();
     assertEquals(24, marker.getMinApi().intValue());
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 6ed73d8..1f0ef3c 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ExtractMarkerUtils;
 import java.nio.file.Paths;
 import java.util.Collection;
 import java.util.Set;
@@ -68,7 +69,7 @@
                     Marker marker;
                     try {
                       Collection<Marker> markers =
-                          ExtractMarker.extractMarkerFromDexProgramData(data.copyByteData());
+                          ExtractMarkerUtils.extractMarkerFromDexProgramData(data.copyByteData());
                       assertEquals(1, markers.size());
                       marker = markers.iterator().next();
                     } catch (Exception e) {
@@ -109,7 +110,7 @@
                     Marker marker;
                     try {
                       Collection<Marker> markers =
-                          ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+                          ExtractMarkerUtils.extractMarkerFromClassProgramData(data.copyByteData());
                       assertEquals(1, markers.size());
                       marker = markers.iterator().next();
                     } catch (Exception e) {
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index a2c7122..e2d8b20 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ExtractMarkerUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -97,7 +98,7 @@
             .setOutput(output, OutputMode.DexIndexed)
             .build());
     assertMarkersMatch(
-        ExtractMarker.extractMarkerFromDexFile(output),
+        ExtractMarkerUtils.extractMarkersFromFile(output),
         ImmutableList.of(markerTool(Tool.L8), markerTool(Tool.D8)));
   }
 
@@ -112,7 +113,7 @@
             .addDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
             .setOutput(output, OutputMode.ClassFile)
             .build());
-    assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(output), markerTool(Tool.L8));
+    assertMarkersMatch(ExtractMarkerUtils.extractMarkersFromFile(output), markerTool(Tool.L8));
   }
 
   private List<String> buildCommand(int minAPI, Path output) {
@@ -140,7 +141,7 @@
     L8Command l8Command = parse(command.toArray(new String[0]));
     L8.run(l8Command);
     assertMarkersMatch(
-        ExtractMarker.extractMarkerFromDexFile(output),
+        ExtractMarkerUtils.extractMarkersFromFile(output),
         ImmutableList.of(markerTool(Tool.L8), markerTool(Tool.D8)));
   }
 
@@ -151,7 +152,7 @@
     command.add("--classfile");
     L8Command l8Command = parse(command.toArray(new String[0]));
     L8.run(l8Command);
-    assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(output), markerTool(Tool.L8));
+    assertMarkersMatch(ExtractMarkerUtils.extractMarkersFromFile(output), markerTool(Tool.L8));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/MarkersTest.java b/src/test/java/com/android/tools/r8/MarkersTest.java
index d5a61db..bd3a45d 100644
--- a/src/test/java/com/android/tools/r8/MarkersTest.java
+++ b/src/test/java/com/android/tools/r8/MarkersTest.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ExtractMarkerUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
@@ -90,7 +91,7 @@
       builder.addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown());
     }
     L8.run(builder.build());
-    Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+    Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(output);
     JsonObject jsonObject =
         new JsonParser()
             .parse(
@@ -138,7 +139,7 @@
     } else {
       D8.run(builder.build());
     }
-    Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+    Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(output);
     Matcher<Marker> matcher =
         allOf(
             markerTool(Tool.D8),
@@ -167,11 +168,11 @@
     if (noCfMarkerForDesugaredCode) {
       ToolHelper.runD8(
           builder, options -> options.desugarSpecificOptions().noCfMarkerForDesugaredCode = true);
-      Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+      Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(output);
       assertTrue(markers.isEmpty());
     } else {
       D8.run(builder.build());
-      Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+      Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(output);
       Matcher<Marker> matcher =
           allOf(
               markerTool(Tool.D8),
@@ -206,7 +207,7 @@
     } else {
       R8.run(builder.build());
     }
-    Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+    Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(output);
     Matcher<Marker> matcher =
         allOf(
             markerTool(Tool.R8),
@@ -238,7 +239,7 @@
     } else {
       R8.run(builder.build());
     }
-    Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+    Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(output);
     Matcher<Marker> matcher =
         allOf(
             markerTool(Tool.R8),
@@ -267,12 +268,12 @@
     if (noCfMarkerForDesugaredCode) {
       ToolHelper.runD8(
           builder, options -> options.desugarSpecificOptions().noCfMarkerForDesugaredCode = true);
-      Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(d8DesugaredOutput);
+      Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(d8DesugaredOutput);
       assertTrue(markers.isEmpty());
     } else {
       D8.run(builder.build());
       assertMarkersMatch(
-          ExtractMarker.extractMarkerFromDexFile(d8DesugaredOutput),
+          ExtractMarkerUtils.extractMarkersFromFile(d8DesugaredOutput),
           allOf(
               markerTool(Tool.D8),
               markerCompilationMode(compilationSpecification.getProgramCompilationMode()),
@@ -292,7 +293,7 @@
             .setOutput(output, OutputMode.ClassFile)
             .build());
     assertMarkersMatch(
-        ExtractMarker.extractMarkerFromDexFile(output),
+        ExtractMarkerUtils.extractMarkersFromFile(output),
         allOf(
             markerTool(Tool.R8),
             markerCompilationMode(compilationSpecification.getProgramCompilationMode()),
diff --git a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
index f0b68de..f827134 100644
--- a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ExtractMarkerUtils;
 import com.android.tools.r8.utils.VersionProperties;
 import java.nio.file.Paths;
 import java.util.Collection;
@@ -71,7 +72,7 @@
                     Marker marker;
                     try {
                       Collection<Marker> markers =
-                          ExtractMarker.extractMarkerFromDexProgramData(data.copyByteData());
+                          ExtractMarkerUtils.extractMarkerFromDexProgramData(data.copyByteData());
                       assertEquals(1, markers.size());
                       marker = markers.iterator().next();
                     } catch (Exception e) {
@@ -116,7 +117,7 @@
                     Marker marker;
                     try {
                       Collection<Marker> markers =
-                          ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+                          ExtractMarkerUtils.extractMarkerFromClassProgramData(data.copyByteData());
                       assertEquals(1, markers.size());
                       marker = markers.iterator().next();
                     } catch (Exception e) {
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 57fda02..152992d 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ExtractMarkerUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.ApiModelTestingOptions;
@@ -199,7 +200,7 @@
         input.toString());
     assertEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
     assertTrue(Files.exists(output));
-    Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+    Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(output);
     assertEquals(1, markers.size());
     Marker marker = markers.iterator().next();
     assertEquals(24, marker.getMinApi().intValue());
diff --git a/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java b/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
index 32f0871..f4c1a3b 100644
--- a/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.utils.ExtractMarkerUtils;
 import java.util.Collection;
 import java.util.Set;
 import org.junit.Test;
@@ -44,7 +45,7 @@
         int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       try {
         Collection<Marker> markers =
-            ExtractMarker.extractMarkerFromDexProgramData(data.copyByteData());
+            ExtractMarkerUtils.extractMarkerFromDexProgramData(data.copyByteData());
         assertEquals(1, markers.size());
         marker = markers.iterator().next();
       } catch (Exception e) {
@@ -70,7 +71,7 @@
     public void accept(ByteDataView data, String descriptors, DiagnosticsHandler handler) {
       try {
         Collection<Marker> markers =
-            ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+            ExtractMarkerUtils.extractMarkerFromClassProgramData(data.copyByteData());
         assertEquals(1, markers.size());
         marker = markers.iterator().next();
       } catch (Exception e) {
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index c2d4a30..1bbaf31 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
 import com.android.tools.r8.compilerapi.diagnostics.ProguardKeepRuleDiagnosticsApiTest;
 import com.android.tools.r8.compilerapi.diagnostics.UnsupportedFeaturesDiagnosticApiTest;
+import com.android.tools.r8.compilerapi.extractmarker.ExtractMarkerApiTest;
 import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
 import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
 import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
@@ -52,15 +53,16 @@
           CommandLineParserTest.ApiTest.class,
           EnableMissingLibraryApiModelingTest.ApiTest.class,
           AndroidPlatformBuildApiTest.ApiTest.class,
-          UnsupportedFeaturesDiagnosticApiTest.ApiTest.class);
-
-  private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(
+          UnsupportedFeaturesDiagnosticApiTest.ApiTest.class,
           ArtProfilesForRewritingApiTest.ApiTest.class,
           StartupProfileApiTest.ApiTest.class,
           ClassConflictResolverTest.ApiTest.class,
           ProguardKeepRuleDiagnosticsApiTest.ApiTest.class,
-          SyntheticContextsConsumerTest.ApiTest.class);
+          SyntheticContextsConsumerTest.ApiTest.class,
+          ExtractMarkerApiTest.ApiTest.class);
+
+  private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
+      ImmutableList.of();
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/extractmarker/ExtractMarkerApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/extractmarker/ExtractMarkerApiTest.java
new file mode 100644
index 0000000..20e2bd4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/extractmarker/ExtractMarkerApiTest.java
@@ -0,0 +1,195 @@
+// Copyright (c) 2023, 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.compilerapi.extractmarker;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringStartsWith.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ExtractMarker;
+import com.android.tools.r8.ExtractMarkerCommand;
+import com.android.tools.r8.MarkerInfo;
+import com.android.tools.r8.MarkerInfoConsumer;
+import com.android.tools.r8.MarkerInfoConsumerData;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.IntBox;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+
+public class ExtractMarkerApiTest extends CompilerApiTestRunner {
+
+  public ExtractMarkerApiTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class SomeClass {}
+
+  public static class SomeOrigin extends Origin {
+    private final String name;
+
+    public SomeOrigin(String name) {
+      super(Origin.root());
+      this.name = name;
+    }
+
+    @Override
+    public String part() {
+      return name;
+    }
+  }
+
+  @Test
+  public void test() throws Exception {
+
+    Path inputFile = ToolHelper.getClassFileForTestClass(SomeClass.class);
+    byte[] inputCf = ToolHelper.getClassAsBytes(SomeClass.class);
+    Box<byte[]> inputDex = new Box<>();
+
+    // Compile DEX code for use as byte input.
+    testForD8(Backend.DEX)
+        .addProgramClasses(SomeClass.class)
+        .setMinApi(1)
+        .setProgramConsumer(
+            new DexIndexedConsumer() {
+              byte[] bytes = null;
+
+              @Override
+              public void accept(
+                  int fileIndex,
+                  ByteDataView data,
+                  Set<String> descriptors,
+                  DiagnosticsHandler handler) {
+                assertEquals(0, fileIndex);
+                assertNull(bytes);
+                bytes = data.copyByteData();
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                assertNotNull(bytes);
+                inputDex.set(bytes);
+              }
+            })
+        .compile();
+
+    assertNotNull(inputDex.get());
+
+    Origin originDex = new SomeOrigin("dex bytes");
+    Origin originCf = new SomeOrigin("cf bytes");
+    IntBox calls = new IntBox(0);
+    new ApiTest(ApiTest.PARAMETERS)
+        .run(
+            inputFile,
+            inputDex.get(),
+            originDex,
+            inputCf,
+            originCf,
+            (origin, marker) -> {
+              calls.increment();
+              if (origin == originDex) {
+                assertEquals("D8", marker.getTool());
+                assertTrue(marker.isD8());
+                assertFalse(marker.isR8());
+                assertFalse(marker.isL8());
+                assertEquals(1, marker.getMinApi());
+                assertThat(marker.getRawEncoding(), startsWith("~~D8{"));
+              } else {
+                assertTrue(origin == originCf || origin.equals(new PathOrigin(inputFile)));
+                // The CF input file and bytes have no marker as they are javac generated.
+                assertNull(marker);
+              }
+            });
+    assertEquals(4, calls.get());
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void run(
+        Path inputFile,
+        byte[] inputDex,
+        Origin dexOrigin,
+        byte[] inputCf,
+        Origin cfOrigin,
+        BiConsumer<Origin, MarkerInfo> consumer)
+        throws Exception {
+      ExtractMarkerCommand.Builder builder = ExtractMarkerCommand.builder();
+      if (inputFile != null) {
+        builder.addProgramFiles(inputFile).addProgramFiles(Collections.singleton(inputFile));
+      }
+      if (inputDex != null) {
+        builder.addDexProgramData(inputDex, dexOrigin);
+      }
+      if (inputCf != null) {
+        builder.addClassProgramData(inputCf, cfOrigin);
+      }
+      builder.setMarkerInfoConsumer(
+          new MarkerInfoConsumer() {
+            @Override
+            public void acceptMarkerInfo(MarkerInfoConsumerData data) {
+              if (data.hasMarkers()) {
+                data.getMarkers()
+                    .forEach(
+                        marker -> {
+                          consumer.accept(data.getInputOrigin(), marker);
+                        });
+              } else {
+                consumer.accept(data.getInputOrigin(), null);
+              }
+            }
+
+            @Override
+            public void finished() {}
+          });
+      ExtractMarker.run(builder.build());
+    }
+
+    @Test
+    public void test() throws Exception {
+      byte[] inputCf = getBytesForClass(getMockClass());
+      run(
+          null,
+          null,
+          null,
+          inputCf,
+          Origin.root(),
+          (origin, marker) -> {
+            // Marker is null here but use all currently defined methods on marker info to force
+            // binary compatible usage.
+            if (marker != null) {
+              String tool = marker.getTool();
+              boolean d8 = marker.isD8();
+              boolean r8 = marker.isR8();
+              boolean l8 = marker.isL8();
+              int minApi = marker.getMinApi();
+              String raw = marker.getRawEncoding();
+            }
+          });
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index 26a5169..642b0f8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -18,7 +18,6 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.ExtractMarker;
 import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
@@ -27,6 +26,7 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ExtractMarkerUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -110,7 +110,7 @@
             markerCompilationMode(CompilationMode.RELEASE),
             not(markerIsDesugared()),
             not(markerHasDesugaredLibraryIdentifier()));
-    assertMarkersMatch(ExtractMarker.extractMarkerFromJarFile(shrunkenLib), libraryMatcher);
+    assertMarkersMatch(ExtractMarkerUtils.extractMarkersFromFile(shrunkenLib), libraryMatcher);
 
     // Build an app with the R8 compiled library.
     Path app =
@@ -135,7 +135,8 @@
             markerHasDesugaredLibraryIdentifier(
                 libraryDesugaringSpecification.hasAnyDesugaring(parameters)));
     assertMarkersMatch(
-        ExtractMarker.extractMarkerFromDexFile(app), ImmutableList.of(libraryMatcher, d8Matcher));
+        ExtractMarkerUtils.extractMarkersFromFile(app),
+        ImmutableList.of(libraryMatcher, d8Matcher));
   }
 
   @Test
@@ -157,7 +158,7 @@
             markerIsDesugared(),
             markerMinApi(parameters.getApiLevel()),
             not(markerHasDesugaredLibraryIdentifier()));
-    assertMarkersMatch(ExtractMarker.extractMarkerFromJarFile(desugaredLibCf), markerMatcher);
+    assertMarkersMatch(ExtractMarkerUtils.extractMarkersFromFile(desugaredLibCf), markerMatcher);
 
     Path desugaredLibDex =
         testForD8()
@@ -178,7 +179,7 @@
             not(markerHasDesugaredLibraryIdentifier()));
     List<Matcher<Marker>> markerMatcherAfterDex = ImmutableList.of(markerMatcher, dexMarkerMatcher);
     assertMarkersMatch(
-        ExtractMarker.extractMarkerFromJarFile(desugaredLibDex), markerMatcherAfterDex);
+        ExtractMarkerUtils.extractMarkersFromFile(desugaredLibDex), markerMatcherAfterDex);
 
     // Build an app using library desugaring merging with library not using library desugaring.
     Path app;
@@ -203,7 +204,7 @@
               markerMinApi(parameters.getApiLevel()),
               markerHasDesugaredLibraryIdentifier()));
     }
-    assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(app), expectedMarkers);
+    assertMarkersMatch(ExtractMarkerUtils.extractMarkersFromFile(app), expectedMarkers);
   }
 
   private boolean expectError() {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
index cf890cf..130f0e5 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
@@ -11,7 +11,6 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.ExtractMarker;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.ResourceException;
@@ -20,6 +19,7 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ExtractMarkerUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -109,9 +109,9 @@
     Path basePath = compiledWithFeature.getBasePath();
     Path feature1Path = compiledWithFeature.getFeature1Path();
     Path feature2Path = compiledWithFeature.getFeature2Path();
-    Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(basePath);
-    Collection<Marker> feature1Markers = ExtractMarker.extractMarkerFromDexFile(feature1Path);
-    Collection<Marker> feature2Markers = ExtractMarker.extractMarkerFromDexFile(feature2Path);
+    Collection<Marker> markers = ExtractMarkerUtils.extractMarkersFromFile(basePath);
+    Collection<Marker> feature1Markers = ExtractMarkerUtils.extractMarkersFromFile(feature1Path);
+    Collection<Marker> feature2Markers = ExtractMarkerUtils.extractMarkersFromFile(feature2Path);
 
     assertEquals(markers.size(), 1);
     assertEquals(feature1Markers.size(), 1);
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 c8ce59b..d06cbe6 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -7,6 +7,7 @@
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
@@ -46,6 +47,8 @@
 import com.android.tools.r8.retrace.stacktraces.InlineSourceFileContextStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineSourceFileStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InvalidMinifiedRangeStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InvalidOriginalRangeStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MapVersionWarningStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
@@ -457,6 +460,22 @@
     runRetraceTest(new TrailingWhitespaceStackTrace());
   }
 
+  @Test
+  public void testInvalidMinifiedRangeStackTrace() {
+    assumeFalse(external);
+    assertThrows(
+        InvalidMappingFileException.class,
+        () -> runRetraceTest(new InvalidMinifiedRangeStackTrace()));
+  }
+
+  @Test
+  public void testInvalidOriginalRangeStackTrace() {
+    assumeFalse(external);
+    assertThrows(
+        InvalidMappingFileException.class,
+        () -> runRetraceTest(new InvalidOriginalRangeStackTrace()));
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidMinifiedRangeStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidMinifiedRangeStackTrace.java
new file mode 100644
index 0000000..ad16a40
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidMinifiedRangeStackTrace.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2023, 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.Collections;
+import java.util.List;
+
+public class InvalidMinifiedRangeStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:3)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> com.android.tools.r8.naming.retrace.Main:",
+        "    5:3:void main(java.lang.String[]) -> main");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidOriginalRangeStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidOriginalRangeStackTrace.java
new file mode 100644
index 0000000..3b8fa63
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InvalidOriginalRangeStackTrace.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2023, 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.Collections;
+import java.util.List;
+
+public class InvalidOriginalRangeStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:3)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> com.android.tools.r8.naming.retrace.Main:",
+        "    2:5:void main(java.lang.String[]):5:2 -> main");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/third_party/bazel.tar.gz.sha1 b/third_party/bazel.tar.gz.sha1
index 956036b..dbc680e 100644
--- a/third_party/bazel.tar.gz.sha1
+++ b/third_party/bazel.tar.gz.sha1
@@ -1 +1 @@
-2533d70cd7892c7597bb6aa57e812d07e2a72c46
\ No newline at end of file
+4c5816530d149bc83aab1e26f7342cfb595523e8
\ No newline at end of file
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index 8a2887d..b83c4a2 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-42f88c3f809026d506e15c14804dd584bc0b9474
\ No newline at end of file
+ae9c0faab45cba310dbe57a3ce0dcba7bed355a3
\ No newline at end of file