Merge commit 'f300db13a3e7d158ad40a5d675cc73ee285f75ac' into dev-release
diff --git a/build.gradle b/build.gradle
index f775822..bde2f60 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1091,6 +1091,7 @@
 task rawBuildLibraryDesugarConversions(type: Zip, dependsOn: downloadDeps) {
     from sourceSets.libraryDesugarConversions.output
     include "java/**/*.class"
+    include "desugar/sun/nio/fs/DesugarAndroid*.class"
     baseName 'library_desugar_conversions_raw'
     destinationDir file('build/tmp/desugaredlibrary')
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 37c4087..029ec75 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -168,7 +168,7 @@
         itemBuilder.setClassPattern(classNamePattern);
       }
       if (methodName != null) {
-        itemBuilder.setMembersPattern(
+        itemBuilder.setMemberPattern(
             KeepMethodPattern.builder().setNamePattern(methodName).build());
       }
       KeepTarget target = KeepTarget.builder().setItem(itemBuilder.build()).build();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index 0e96364..8307265 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepMembersPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern.KeepMethodNameExactPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
 import com.android.tools.r8.keepanno.ast.KeepPreconditions;
@@ -70,21 +70,21 @@
           if (!item.getExtendsPattern().isAny()) {
             throw new Unimplemented();
           }
-          writeMembers(item.getMembersPattern(), targetVisitor);
+          writeMember(item.getMemberPattern(), targetVisitor);
           targetVisitor.visitEnd();
         });
     arrayVisitor.visitEnd();
   }
 
-  private void writeMembers(KeepMembersPattern membersPattern, AnnotationVisitor targetVisitor) {
-    if (membersPattern.isNone()) {
+  private void writeMember(KeepMemberPattern memberPattern, AnnotationVisitor targetVisitor) {
+    if (memberPattern.isNone()) {
       // Default is "no methods".
       return;
     }
-    if (membersPattern.isAll()) {
+    if (memberPattern.isAll()) {
       throw new Unimplemented();
     }
-    KeepMethodPattern method = membersPattern.asMethod();
+    KeepMethodPattern method = memberPattern.asMethod();
     KeepMethodNameExactPattern exactMethodName = method.getNamePattern().asExact();
     if (exactMethodName != null) {
       targetVisitor.visit(Target.methodName, exactMethodName.getName());
@@ -95,7 +95,14 @@
       throw new Unimplemented();
     }
     if (!method.getReturnTypePattern().isAny()) {
-      throw new Unimplemented();
+      if (exactMethodName != null
+          && (exactMethodName.getName().equals("<init>")
+              || exactMethodName.getName().equals("<clinit>"))
+          && method.getReturnTypePattern().isVoid()) {
+        // constructors have implicit void return.
+      } else {
+        throw new Unimplemented();
+      }
     }
     if (!method.getParametersPattern().isAny()) {
       throw new Unimplemented();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index 5271837..89be76b 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -25,12 +25,13 @@
  *   CONDITION ::= ITEM_PATTERN
  *
  *   CONSEQUENCES ::= TARGET+
- *   TARGET ::= any | OPTIONS ITEM_PATTERN // TODO(b/248408342): What options are on target 'any'?
+ *   TARGET ::= OPTIONS ITEM_PATTERN
  *   OPTIONS ::= keep-all | OPTION+
  *   OPTION ::= shrinking | optimizing | obfuscating | access-modifying
  *
- *   ITEM_PATTERN ::=
- *     class QUALIFIED_CLASS_NAME_PATTERN extends EXTENDS_PATTERN { MEMBERS_PATTERN }
+ *   ITEM_PATTERN
+ *     ::= any
+ *       | class QUALIFIED_CLASS_NAME_PATTERN extends EXTENDS_PATTERN { MEMBER_PATTERN }
  *
  *   TYPE_PATTERN ::= any
  *   PACKAGE_PATTERN ::= any | exact package-name
@@ -38,7 +39,7 @@
  *   UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-name
  *   EXTENDS_PATTERN ::= any | QUALIFIED_CLASS_NAME_PATTERN
  *
- *   MEMBERS_PATTERN ::= none | all | METHOD_PATTERN
+ *   MEMBER_PATTERN ::= none | all | METHOD_PATTERN
  *
  *   METHOD_PATTERN
  *     ::= METHOD_ACCESS_PATTERN
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
index 68c5c17..50d3921 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
@@ -31,14 +31,14 @@
 
     private KeepQualifiedClassNamePattern classNamePattern;
     private KeepExtendsPattern extendsPattern = KeepExtendsPattern.any();
-    private KeepMembersPattern membersPattern = KeepMembersPattern.none();
+    private KeepMemberPattern memberPattern = KeepMemberPattern.none();
 
     private Builder() {}
 
     public Builder any() {
       classNamePattern = KeepQualifiedClassNamePattern.any();
       extendsPattern = KeepExtendsPattern.any();
-      membersPattern = KeepMembersPattern.all();
+      memberPattern = KeepMemberPattern.all();
       return this;
     }
 
@@ -52,8 +52,8 @@
       return this;
     }
 
-    public Builder setMembersPattern(KeepMembersPattern membersPattern) {
-      this.membersPattern = membersPattern;
+    public Builder setMemberPattern(KeepMemberPattern memberPattern) {
+      this.memberPattern = memberPattern;
       return this;
     }
 
@@ -61,29 +61,29 @@
       if (classNamePattern == null) {
         throw new KeepEdgeException("Class pattern must define a class name pattern.");
       }
-      return new KeepItemPattern(classNamePattern, extendsPattern, membersPattern);
+      return new KeepItemPattern(classNamePattern, extendsPattern, memberPattern);
     }
   }
 
   private final KeepQualifiedClassNamePattern qualifiedClassPattern;
   private final KeepExtendsPattern extendsPattern;
-  private final KeepMembersPattern membersPattern;
+  private final KeepMemberPattern memberPattern;
   // TODO: class annotations
 
   private KeepItemPattern(
       KeepQualifiedClassNamePattern qualifiedClassPattern,
       KeepExtendsPattern extendsPattern,
-      KeepMembersPattern membersPattern) {
+      KeepMemberPattern memberPattern) {
     assert qualifiedClassPattern != null;
     assert extendsPattern != null;
-    assert membersPattern != null;
+    assert memberPattern != null;
     this.qualifiedClassPattern = qualifiedClassPattern;
     this.extendsPattern = extendsPattern;
-    this.membersPattern = membersPattern;
+    this.memberPattern = memberPattern;
   }
 
   public boolean isAny() {
-    return qualifiedClassPattern.isAny() && extendsPattern.isAny() && membersPattern.isAll();
+    return qualifiedClassPattern.isAny() && extendsPattern.isAny() && memberPattern.isAll();
   }
 
   public KeepQualifiedClassNamePattern getClassNamePattern() {
@@ -94,8 +94,8 @@
     return extendsPattern;
   }
 
-  public KeepMembersPattern getMembersPattern() {
-    return membersPattern;
+  public KeepMemberPattern getMemberPattern() {
+    return memberPattern;
   }
 
   @Override
@@ -109,12 +109,12 @@
     KeepItemPattern that = (KeepItemPattern) obj;
     return qualifiedClassPattern.equals(that.qualifiedClassPattern)
         && extendsPattern.equals(that.extendsPattern)
-        && membersPattern.equals(that.membersPattern);
+        && memberPattern.equals(that.memberPattern);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(qualifiedClassPattern, extendsPattern, membersPattern);
+    return Objects.hash(qualifiedClassPattern, extendsPattern, memberPattern);
   }
 
   @Override
@@ -124,8 +124,8 @@
         + qualifiedClassPattern
         + ", extendsPattern="
         + extendsPattern
-        + ", membersPattern="
-        + membersPattern
+        + ", memberPattern="
+        + memberPattern
         + '}';
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
similarity index 84%
rename from src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java
rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
index 64e1b25..25a57ab 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
@@ -3,18 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+public abstract class KeepMemberPattern {
 
-public abstract class KeepMembersPattern {
-
-  public static KeepMembersPattern none() {
+  public static KeepMemberPattern none() {
     return None.getInstance();
   }
 
-  public static KeepMembersPattern all() {
+  public static KeepMemberPattern all() {
     return All.getInstance();
   }
 
-  private static class All extends KeepMembersPattern {
+  private static class All extends KeepMemberPattern {
 
     private static final All INSTANCE = new All();
 
@@ -43,7 +42,7 @@
     }
   }
 
-  private static class None extends KeepMembersPattern {
+  private static class None extends KeepMemberPattern {
 
     private static final None INSTANCE = new None();
 
@@ -72,7 +71,7 @@
     }
   }
 
-  KeepMembersPattern() {}
+  KeepMemberPattern() {}
 
   public boolean isAll() {
     return false;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
index bec8102..e00fedd 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
@@ -3,9 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern.KeepMethodNameExactPattern;
 import java.util.Objects;
 
-public final class KeepMethodPattern extends KeepMembersPattern {
+public final class KeepMethodPattern extends KeepMemberPattern {
 
   public static Builder builder() {
     return new Builder();
@@ -48,6 +49,15 @@
       if (namePattern == null) {
         throw new KeepEdgeException("Method pattern must declar a name pattern");
       }
+      KeepMethodReturnTypePattern returnTypePattern = this.returnTypePattern;
+      KeepMethodNameExactPattern exactName = namePattern.asExact();
+      if (exactName != null
+          && (exactName.getName().equals("<init>") || exactName.getName().equals("<clinit>"))) {
+        if (!this.returnTypePattern.isAny() && !this.returnTypePattern.isVoid()) {
+          throw new KeepEdgeException("Method constructor pattern must match 'void' type.");
+        }
+        returnTypePattern = KeepMethodReturnTypePattern.voidType();
+      }
       return new KeepMethodPattern(
           accessPattern, namePattern, returnTypePattern, parametersPattern);
     }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
index 111022b..6b7a212 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
@@ -7,10 +7,6 @@
 
 public class KeepTarget {
 
-  public static KeepTarget any() {
-    return KeepTarget.builder().setItem(KeepItemPattern.any()).build();
-  }
-
   public static class Builder {
 
     private KeepItemPattern item;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index 3ab882f..7c8c032 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepMembersPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern;
@@ -102,16 +102,16 @@
     if (!clazzPattern.getExtendsPattern().isAny()) {
       throw new Unimplemented();
     }
-    KeepMembersPattern members = clazzPattern.getMembersPattern();
-    if (members.isNone()) {
+    KeepMemberPattern member = clazzPattern.getMemberPattern();
+    if (member.isNone()) {
       return builder;
     }
-    if (members.isAll()) {
+    if (member.isAll()) {
       return builder.append(" { *; }");
     }
-    if (members.isMethod()) {
+    if (member.isMethod()) {
       builder.append(" {");
-      printMethod(builder.append(' '), members.asMethod());
+      printMethod(builder.append(' '), member.asMethod());
       return builder.append(" }");
     }
     throw new Unimplemented();
@@ -132,7 +132,7 @@
   private static StringBuilder printParameters(
       StringBuilder builder, KeepMethodParametersPattern parametersPattern) {
     if (parametersPattern.isAny()) {
-      return builder.append("(***)");
+      return builder.append("(...)");
     }
     return builder
         .append('(')
@@ -160,7 +160,7 @@
 
   private static StringBuilder printType(StringBuilder builder, KeepTypePattern typePattern) {
     if (typePattern.isAny()) {
-      return builder.append("*");
+      return builder.append("***");
     }
     throw new Unimplemented();
   }
@@ -232,7 +232,7 @@
 
     public boolean isMemberOnlyConsequent() {
       KeepItemPattern item = target.getItem();
-      return !item.isAny() && !item.getMembersPattern().isNone();
+      return !item.isAny() && !item.getMemberPattern().isNone();
     }
 
     public KeepQualifiedClassNamePattern getHolderPattern() {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
index 58601a6..c86857d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdge.Builder;
+import com.android.tools.r8.keepanno.ast.KeepEdgeException;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
@@ -125,17 +126,31 @@
     edgeBuilder.setConsequences(consequencesBuilder.build());
   }
 
+  private String getTypeNameForClassConstantElement(DeclaredType type) {
+    // The processor API does not expose the descriptor or typename, so we need to depend on the
+    // sun.tools internals to extract it. If not, this code will not work for inner classes as
+    // we cannot recover the $ separator.
+    try {
+      Object tsym = type.getClass().getField("tsym").get(type);
+      Object flatname = tsym.getClass().getField("flatname").get(tsym);
+      return flatname.toString();
+    } catch (NoSuchFieldException | IllegalAccessException e) {
+      throw new KeepEdgeException("Unable to obtain the class type name for: " + type);
+    }
+  }
+
   private void processTarget(KeepTarget.Builder builder, AnnotationMirror mirror) {
     KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
     AnnotationValue classConstantValue = getAnnotationValue(mirror, Target.classConstant);
     if (classConstantValue != null) {
       DeclaredType type = AnnotationClassValueVisitor.getType(classConstantValue);
-      itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(type.toString()));
+      String typeName = getTypeNameForClassConstantElement(type);
+      itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
     }
     AnnotationValue methodNameValue = getAnnotationValue(mirror, Target.methodName);
     if (methodNameValue != null) {
       String methodName = AnnotationStringValueVisitor.getString(methodNameValue);
-      itemBuilder.setMembersPattern(
+      itemBuilder.setMemberPattern(
           KeepMethodPattern.builder()
               .setNamePattern(KeepMethodNamePattern.exact(methodName))
               .build());
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidDefaultFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidDefaultFileSystemProvider.java
new file mode 100644
index 0000000..8de3f76
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidDefaultFileSystemProvider.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package desugar.sun.nio.fs;
+
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.spi.FileSystemProvider;
+
+public class DesugarAndroidDefaultFileSystemProvider {
+  private static final FileSystemProvider INSTANCE = DesugarAndroidFileSystemProvider.create();
+
+  private DesugarAndroidDefaultFileSystemProvider() {}
+
+  /** Returns the platform's default file system provider. */
+  public static FileSystemProvider instance() {
+    return INSTANCE;
+  }
+
+  /** Returns the platform's default file system. */
+  public static FileSystem theFileSystem() {
+    return INSTANCE.getFileSystem(URI.create("file:///"));
+  }
+}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
new file mode 100644
index 0000000..027d74f
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package desugar.sun.nio.fs;
+
+import java.adapter.AndroidVersionTest;
+import java.io.IOException;
+import java.nio.channels.DesugarChannels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Set;
+
+/** Linux implementation of {@link FileSystemProvider} for desugar support. */
+public class DesugarAndroidFileSystemProvider
+    extends desugar.sun.nio.fs.DesugarLinuxFileSystemProvider {
+
+  public static DesugarAndroidFileSystemProvider create() {
+    return new DesugarAndroidFileSystemProvider(System.getProperty("user.dir"), "/");
+  }
+
+  DesugarAndroidFileSystemProvider(String userDir, String rootDir) {
+    super(userDir, rootDir);
+  }
+
+  @Override
+  public SeekableByteChannel newByteChannel(
+      Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+    if (path.toFile().isDirectory()) {
+      throw new UnsupportedOperationException(
+          "The desugar library does not support creating a file channel on a directory: " + path);
+    }
+    // A FileChannel is a SeekableByteChannel.
+    return newFileChannel(path, options, attrs);
+  }
+
+  @Override
+  public FileChannel newFileChannel(
+      Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+    if (AndroidVersionTest.is26OrAbove) {
+      throw new RuntimeException("Above Api 26, the platform FileSystemProvider should be used.");
+    }
+    return DesugarChannels.openEmulatedFileChannel(path, options, attrs);
+  }
+}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java
deleted file mode 100644
index cf36b80..0000000
--- a/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package desugar.sun.nio.fs;
-
-import java.nio.file.spi.FileSystemProvider;
-
-public class DesugarDefaultFileSystemProvider {
-
-  public static FileSystemProvider instance() {
-    return null;
-  }
-}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarLinuxFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarLinuxFileSystemProvider.java
new file mode 100644
index 0000000..566bf12
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarLinuxFileSystemProvider.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package desugar.sun.nio.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Map;
+import java.util.Set;
+
+public class DesugarLinuxFileSystemProvider extends FileSystemProvider {
+
+  DesugarLinuxFileSystemProvider(String userDir, String rootDir) {
+    super();
+  }
+
+  @Override
+  public String getScheme() {
+    return null;
+  }
+
+  @Override
+  public FileSystem newFileSystem(URI uri, Map<String, ?> map) throws IOException {
+    return null;
+  }
+
+  @Override
+  public FileSystem getFileSystem(URI uri) {
+    return null;
+  }
+
+  @Override
+  public Path getPath(URI uri) {
+    return null;
+  }
+
+  @Override
+  public SeekableByteChannel newByteChannel(
+      Path path, Set<? extends OpenOption> set, FileAttribute<?>... fileAttributes)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public DirectoryStream<Path> newDirectoryStream(Path path, Filter<? super Path> filter)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public void createDirectory(Path path, FileAttribute<?>... fileAttributes) throws IOException {}
+
+  @Override
+  public void delete(Path path) throws IOException {}
+
+  @Override
+  public void copy(Path path, Path path1, CopyOption... copyOptions) throws IOException {}
+
+  @Override
+  public void move(Path path, Path path1, CopyOption... copyOptions) throws IOException {}
+
+  @Override
+  public boolean isSameFile(Path path, Path path1) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isHidden(Path path) throws IOException {
+    return false;
+  }
+
+  @Override
+  public FileStore getFileStore(Path path) throws IOException {
+    return null;
+  }
+
+  @Override
+  public void checkAccess(Path path, AccessMode... accessModes) throws IOException {}
+
+  @Override
+  public <V extends FileAttributeView> V getFileAttributeView(
+      Path path, Class<V> aClass, LinkOption... linkOptions) {
+    return null;
+  }
+
+  @Override
+  public <A extends BasicFileAttributes> A readAttributes(
+      Path path, Class<A> aClass, LinkOption... linkOptions) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Map<String, Object> readAttributes(Path path, String s, LinkOption... linkOptions)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public void setAttribute(Path path, String s, Object o, LinkOption... linkOptions)
+      throws IOException {}
+}
diff --git a/src/library_desugar/java/java/adapter/AndroidVersionTest.java b/src/library_desugar/java/java/adapter/AndroidVersionTest.java
new file mode 100644
index 0000000..065761e
--- /dev/null
+++ b/src/library_desugar/java/java/adapter/AndroidVersionTest.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package java.adapter;
+
+public class AndroidVersionTest {
+
+  public static final boolean is24OrAbove = setUp("java.util.StringJoiner");
+  public static final boolean is26OrAbove = setUp("java.nio.file.FileSystems");
+  public static final boolean isHeadfull = setUp("android.os.Build");
+
+  /**
+   * Answers true if the class is present, implying the SDK is at least at the level where the class
+   * was introduced.
+   */
+  private static boolean setUp(String className) {
+    try {
+      Class.forName(className);
+      return true;
+    } catch (ClassNotFoundException ignored) {
+    }
+    return false;
+  }
+}
diff --git a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
index e34e571..eea074f 100644
--- a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
+++ b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
@@ -6,7 +6,7 @@
 
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
-import desugar.sun.nio.fs.DesugarDefaultFileSystemProvider;
+import desugar.sun.nio.fs.DesugarAndroidDefaultFileSystemProvider;
 import j$.nio.file.FileSystems;
 import java.net.URI;
 import java.nio.file.FileSystem;
@@ -23,27 +23,18 @@
       INSTANCE.getFileSystem(URI.create("file:///"));
 
   private static FileSystemProvider getFileSystemProvider() {
-    // Note: this fails on non Android devices.
-    try {
+    if (AndroidVersionTest.is26OrAbove) {
       // On API 26 and above, FileSystems is present.
-      Class.forName("java.nio.file.FileSystems");
       j$.nio.file.FileSystem fileSystem = FileSystems.getDefault();
       j$.nio.file.spi.FileSystemProvider provider = fileSystem.provider();
       return j$.nio.file.spi.FileSystemProvider.wrap_convert(provider);
-    } catch (ClassNotFoundException ignored) {
-      // We reach this path is API < 26.
     }
-    // The DesugarDefaultFileSystemProvider requires the ThreadPolicy to be set to work correctly.
-    // We cannot set the ThreadPolicy in headless and it should not matter.
-    // In headless, android.os is absent so the following line will throw.
-    // In headfull, android.os is present and we set the thread policy.
-    try {
-      Class.forName("android.os.Build");
+    if (AndroidVersionTest.isHeadfull) {
+      // The DesugarDefaultFileSystemProvider requires the ThreadPolicy to be set to work correctly.
+      // We cannot set the ThreadPolicy in headless and it should not matter.
       setThreadPolicy();
-    } catch (ClassNotFoundException ignored) {
-      // Headless mode.
     }
-    return DesugarDefaultFileSystemProvider.instance();
+    return DesugarAndroidDefaultFileSystemProvider.instance();
   }
 
   private static void setThreadPolicy() {
diff --git a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
index da93736..8980540 100644
--- a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
+++ b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
@@ -17,13 +17,9 @@
   private HybridFileTypeDetector() {}
 
   public static FileTypeDetector create() {
-    try {
-      // On API 26 and above, java.nio.file.Files is present.
-      Class.forName("java.nio.file.Files");
-      return new PlatformFileTypeDetector();
-    } catch (ClassNotFoundException ignored) {
-      return DesugarDefaultFileTypeDetector.create();
-    }
+    return AndroidVersionTest.is26OrAbove
+        ? new PlatformFileTypeDetector()
+        : DesugarDefaultFileTypeDetector.create();
   }
 
   static class PlatformFileTypeDetector extends FileTypeDetector {
diff --git a/src/library_desugar/java/java/nio/channels/DesugarChannels.java b/src/library_desugar/java/java/nio/channels/DesugarChannels.java
new file mode 100644
index 0000000..4ca851d
--- /dev/null
+++ b/src/library_desugar/java/java/nio/channels/DesugarChannels.java
@@ -0,0 +1,242 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package java.nio.channels;
+
+import java.adapter.AndroidVersionTest;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileAttribute;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class DesugarChannels {
+
+  /** Special conversion for Channel to answer a converted FileChannel if required. */
+  public static Channel convertMaybeLegacyChannelFromLibrary(Channel raw) {
+    if (raw == null) {
+      return null;
+    }
+    if (raw instanceof FileChannel) {
+      return convertMaybeLegacyFileChannelFromLibrary((FileChannel) raw);
+    }
+    return raw;
+  }
+
+  /**
+   * Below Api 24 FileChannel does not implement SeekableByteChannel. When we get one from the
+   * library, we wrap it to implement the interface.
+   */
+  public static FileChannel convertMaybeLegacyFileChannelFromLibrary(FileChannel raw) {
+    if (raw == null) {
+      return null;
+    }
+    if (AndroidVersionTest.is24OrAbove) {
+      return raw;
+    }
+    return WrappedFileChannel.wrap(raw);
+  }
+
+  /**
+   * We unwrap when going to the library since we cannot intercept the calls to final methods in the
+   * library.
+   */
+  public static FileChannel convertMaybeLegacyFileChannelToLibrary(FileChannel raw) {
+    if (raw == null) {
+      return null;
+    }
+    if (raw instanceof WrappedFileChannel) {
+      return ((WrappedFileChannel) raw).delegate;
+    }
+    return raw;
+  }
+
+  static class WrappedFileChannel extends FileChannel implements SeekableByteChannel {
+
+    final FileChannel delegate;
+
+    public static FileChannel wrap(FileChannel channel) {
+      if (channel instanceof WrappedFileChannel) {
+        return channel;
+      }
+      return new WrappedFileChannel(channel);
+    }
+
+    private WrappedFileChannel(FileChannel delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public int read(ByteBuffer dst) throws IOException {
+      return delegate.read(dst);
+    }
+
+    @Override
+    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+      return delegate.read(dsts, offset, length);
+    }
+
+    @Override
+    public int write(ByteBuffer src) throws IOException {
+      return delegate.write(src);
+    }
+
+    @Override
+    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+      return delegate.write(srcs, offset, length);
+    }
+
+    @Override
+    public long position() throws IOException {
+      return delegate.position();
+    }
+
+    @Override
+    public FileChannel position(long newPosition) throws IOException {
+      return WrappedFileChannel.wrap(delegate.position(newPosition));
+    }
+
+    @Override
+    public long size() throws IOException {
+      return delegate.size();
+    }
+
+    @Override
+    public FileChannel truncate(long size) throws IOException {
+      return WrappedFileChannel.wrap(delegate.truncate(size));
+    }
+
+    @Override
+    public void force(boolean metaData) throws IOException {
+      delegate.force(metaData);
+    }
+
+    @Override
+    public long transferTo(long position, long count, WritableByteChannel target)
+        throws IOException {
+      return delegate.transferTo(position, count, target);
+    }
+
+    @Override
+    public long transferFrom(ReadableByteChannel src, long position, long count)
+        throws IOException {
+      return delegate.transferFrom(src, position, count);
+    }
+
+    @Override
+    public int read(ByteBuffer dst, long position) throws IOException {
+      return delegate.read(dst, position);
+    }
+
+    @Override
+    public int write(ByteBuffer src, long position) throws IOException {
+      return delegate.write(src, position);
+    }
+
+    @Override
+    public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
+      return delegate.map(mode, position, size);
+    }
+
+    @Override
+    public FileLock lock(long position, long size, boolean shared) throws IOException {
+      return delegate.lock(position, size, shared);
+    }
+
+    @Override
+    public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+      return delegate.tryLock(position, size, shared);
+    }
+
+    @Override
+    public void implCloseChannel() throws IOException {
+      // We cannot call the protected method, this should be effectively equivalent.
+      delegate.close();
+    }
+  }
+
+  /** The 2 open methods are present to be retargeted from FileChannel#open. */
+  public static FileChannel open(Path path, OpenOption... openOptions) throws IOException {
+    Set<OpenOption> openOptionSet = new HashSet<>();
+    Collections.addAll(openOptionSet, openOptions);
+    return open(path, openOptionSet);
+  }
+
+  public static FileChannel open(
+      Path path, Set<? extends OpenOption> openOptions, FileAttribute<?>... attrs)
+      throws IOException {
+    if (AndroidVersionTest.is26OrAbove) {
+      return FileChannel.open(path, openOptions, attrs);
+    }
+    return openEmulatedFileChannel(path, openOptions, attrs);
+  }
+
+  /**
+   * All FileChannel creation go through the FileSystemProvider which then comes here if the Api is
+   * strictly below 26, and to the plaform FileSystemProvider if the Api is above or equal to 26.
+   *
+   * <p>Below Api 26 there is no way to create a FileChannel, so we create instead an emulated
+   * version using RandomAccessFile which tries, with a best effort, to support all settings.
+   *
+   * <p>The FileAttributes are ignored.
+   */
+  public static FileChannel openEmulatedFileChannel(
+      Path path, Set<? extends OpenOption> openOptions, FileAttribute<?>... attrs)
+      throws IOException {
+
+    validateOpenOptions(path, openOptions);
+
+    RandomAccessFile randomAccessFile =
+        new RandomAccessFile(path.toFile(), getFileAccessModeText(openOptions));
+    if (openOptions.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
+      randomAccessFile.setLength(0);
+    }
+
+    if (!openOptions.contains(StandardOpenOption.APPEND)) {
+      // This one may be retargeted, below 24, to support SeekableByteChannel.
+      return randomAccessFile.getChannel();
+    }
+
+    // TODO(b/259056135): Consider subclassing UnsupportedOperationException for desugared library.
+    // RandomAccessFile does not support APPEND.
+    // We could hack a wrapper to support APPEND in simple cases such as Files.write().
+    throw new UnsupportedOperationException();
+  }
+
+  private static void validateOpenOptions(Path path, Set<? extends OpenOption> openOptions)
+      throws NoSuchFileException {
+    // Validations that resemble sun.nio.fs.UnixChannelFactory#newFileChannel.
+    if (openOptions.contains(StandardOpenOption.READ)
+        && openOptions.contains(StandardOpenOption.APPEND)) {
+      throw new IllegalArgumentException("READ + APPEND not allowed");
+    }
+    if (openOptions.contains(StandardOpenOption.APPEND)
+        && openOptions.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
+      throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
+    }
+    if (openOptions.contains(StandardOpenOption.APPEND) && !path.toFile().exists()) {
+      throw new NoSuchFileException(path.toString());
+    }
+  }
+
+  private static String getFileAccessModeText(Set<? extends OpenOption> options) {
+    if (!options.contains(StandardOpenOption.WRITE)) {
+      return "r";
+    }
+    if (options.contains(StandardOpenOption.SYNC)) {
+      return "rws";
+    }
+    if (options.contains(StandardOpenOption.DSYNC)) {
+      return "rwd";
+    }
+    return "rw";
+  }
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json b/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json
index 91f9a56..46a6562 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_legacy.json
@@ -2,13 +2,57 @@
   "configuration_format_version": 5,
   "group_id": "com.tools.android",
   "artifact_id": "desugar_jdk_libs",
-  "version": "1.2.4",
-  "required_compilation_api_level": 30,
+  "version": "1.2.5",
+  "required_compilation_api_level": 33,
   "synthesized_library_classes_package_prefix": "j$.",
   "support_all_callbacks_from_library": true,
   "common_flags": [
     {
-      "api_level_below_or_equal": 10000,
+      "api_level_below_or_equal": 32,
+      "rewrite_prefix": {
+        "java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
+      },
+      "retarget_lib_member": {
+        "java.util.stream.Collectors#filtering": "java.util.stream.DesugarCollectors",
+        "java.util.stream.Collectors#flatMapping": "java.util.stream.DesugarCollectors",
+        "java.util.stream.Collectors#toUnmodifiableList": "java.util.stream.DesugarCollectors",
+        "java.util.stream.Collectors#toUnmodifiableMap": "java.util.stream.DesugarCollectors",
+        "java.util.stream.Collectors#toUnmodifiableSet": "java.util.stream.DesugarCollectors"
+      }
+    },
+    {
+      "api_level_below_or_equal": 30,
+      "rewrite_prefix": {
+        "java.time.DesugarDuration": "j$.time.DesugarDuration",
+        "java.time.DesugarLocalTime": "j$.time.DesugarLocalTime"
+      },
+      "retarget_lib_member": {
+        "java.time.Duration#dividedBy": "java.time.DesugarDuration",
+        "java.time.Duration#toDaysPart": "java.time.DesugarDuration",
+        "java.time.Duration#toHoursPart": "java.time.DesugarDuration",
+        "java.time.Duration#toMillisPart": "java.time.DesugarDuration",
+        "java.time.Duration#toMinutesPart": "java.time.DesugarDuration",
+        "java.time.Duration#toNanosPart": "java.time.DesugarDuration",
+        "java.time.Duration#toSeconds": "java.time.DesugarDuration",
+        "java.time.Duration#toSecondsPart": "java.time.DesugarDuration",
+        "java.time.Duration#truncatedTo": "java.time.DesugarDuration",
+        "java.time.LocalTime#ofInstant": "java.time.DesugarLocalTime",
+        "java.time.LocalTime#toEpochSecond": "java.time.DesugarLocalTime"
+      }
+    },
+    {
+      "api_level_below_or_equal": 29,
+      "rewrite_prefix": {
+        "java.util.concurrent.Flow": "j$.util.concurrent.Flow"
+      },
+      "wrapper_conversion": [
+        "java.util.concurrent.Flow$Publisher",
+        "java.util.concurrent.Flow$Subscriber",
+        "java.util.concurrent.Flow$Subscription"
+      ]
+    },
+    {
+      "api_level_below_or_equal": 25,
       "rewrite_prefix": {
         "java.time.": "j$.time.",
         "java.util.Desugar": "j$.util.Desugar"
@@ -34,26 +78,10 @@
       }
     },
     {
-      "api_level_below_or_equal": 29,
-      "rewrite_prefix": {
-        "java.util.concurrent.Flow": "j$.util.concurrent.Flow"
-      },
-      "wrapper_conversion": [
-        "java.util.concurrent.Flow$Publisher",
-        "java.util.concurrent.Flow$Subscriber",
-        "java.util.concurrent.Flow$Subscription"
-      ]
-    },
-    {
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
         "java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic",
-        "java.util.function.": "j$.util.function."
-      }
-    },
-    {
-      "api_level_below_or_equal": 10000,
-      "rewrite_prefix": {
+        "java.util.function.": "j$.util.function.",
         "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
         "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
         "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
@@ -150,7 +178,7 @@
   ],
   "program_flags": [
     {
-      "api_level_below_or_equal": 10000,
+      "api_level_below_or_equal": 25,
       "retarget_lib_member": {
         "java.util.TimeZone#getTimeZone": "java.util.DesugarTimeZone",
         "java.util.Calendar#toInstant": "java.util.DesugarCalendar",
@@ -180,7 +208,7 @@
   ],
   "library_flags": [
     {
-      "api_level_below_or_equal": 10000,
+      "api_level_below_or_equal": 25,
       "rewrite_prefix": {
         "j$.time.": "java.time.",
         "java.lang.Desugar": "j$.lang.Desugar",
@@ -196,7 +224,7 @@
       }
     },
     {
-      "api_level_below_or_equal": 10000,
+      "api_level_below_or_equal": 23,
       "rewrite_prefix": {
         "java.util.AbstractList": "j$.util.AbstractList",
         "java.util.CollSer": "j$.util.CollSer",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index c97bd79..3295333 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -166,6 +166,7 @@
         "java.nio.channels.AsynchronousChannel": "j$.nio.channels.AsynchronousChannel",
         "java.nio.channels.AsynchronousFileChannel": "j$.nio.channels.AsynchronousFileChannel",
         "java.nio.channels.CompletionHandler": "j$.nio.channels.CompletionHandler",
+        "java.nio.channels.Desugar": "j$.nio.channels.Desugar",
         "java.nio.file.": "j$.nio.file."
       },
       "dont_rewrite_prefix": [
@@ -230,7 +231,8 @@
         "java.lang.Iterable java.nio.file.FileSystem#getRootDirectories()": [-1, "java.lang.Iterable java.nio.file.PathApiFlips#flipIterablePath(java.lang.Iterable)"],
         "java.util.Iterator java.nio.file.Path#iterator()": [-1, "java.util.Iterator java.nio.file.PathApiFlips#flipIteratorPath(java.util.Iterator)"],
         "java.nio.file.DirectoryStream java.nio.file.spi.FileSystemProvider#newDirectoryStream(java.nio.file.Path, java.nio.file.DirectoryStream$Filter)": [-1, "java.nio.file.DirectoryStream java.nio.file.PathApiFlips#flipDirectoryStreamPath(java.nio.file.DirectoryStream)", 1, "java.nio.file.DirectoryStream$Filter java.nio.file.PathApiFlips#flipDirectoryStreamFilterPath(java.nio.file.DirectoryStream$Filter)"],
-        "void java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])": [2, "java.lang.Object java.nio.file.FileApiFlips#flipMaybeFileTime(java.lang.Object)"]
+        "void java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])": [2, "java.lang.Object java.nio.file.FileApiFlips#flipMaybeFileTime(java.lang.Object)"],
+        "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "java.util.Set java.nio.file.FileApiFlips#flipOpenOptionSet(java.util.Set)"]
       },
       "wrapper_conversion": [
         "java.nio.channels.CompletionHandler",
@@ -279,8 +281,6 @@
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
         "java.io.DesugarBufferedReader": "j$.io.DesugarBufferedReader",
-        "java.nio.channels.FileChannel": "j$.nio.channels.FileChannel",
-        "java.nio.channels.SeekableByteChannel": "j$.nio.channels.SeekableByteChannel",
         "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
         "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
         "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
@@ -294,12 +294,10 @@
         "java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic",
         "java.util.stream.": "j$.util.stream."
       },
-      "dont_rewrite_prefix": [
-        "java.nio.channels.FileChannel$MapMode"
-      ],
       "maintain_prefix": [
         "java.util.function.",
-        "java.io.UncheckedIOException"
+        "java.io.UncheckedIOException",
+        "java.nio.channels.SeekableByteChannel"
       ],
       "emulate_interface": {
         "java.lang.Iterable": "j$.lang.Iterable",
@@ -347,13 +345,18 @@
         "java.util.stream.IntStream java.util.stream.IntStream#flatMap(java.util.function.IntFunction)": [0, "java.util.function.IntFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.IntFunction)"],
         "java.util.stream.LongStream java.util.stream.Stream#flatMapToLong(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
         "java.util.stream.LongStream java.util.stream.LongStream#flatMap(java.util.function.LongFunction)": [0, "java.util.function.LongFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.LongFunction)"],
-        "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.StackWalkerApiFlips#flipFunctionStream(java.util.function.Function)"]
+        "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.StackWalkerApiFlips#flipFunctionStream(java.util.function.Function)"],
+        "java.nio.channels.FileChannel java.nio.channels.FileLock#channel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+        "java.nio.channels.Channel java.nio.channels.FileLock#acquiredBy()": [-1, "java.nio.channels.Channel java.nio.channels.DesugarChannels#convertMaybeLegacyChannelFromLibrary(java.nio.channels.Channel)"],
+        "java.nio.channels.FileChannel java.io.RandomAccessFile#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+        "java.nio.channels.FileChannel java.io.FileInputStream#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+        "java.nio.channels.FileChannel java.io.FileOutputStream#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+        "void java.nio.channels.FileLock#<init>(java.nio.channels.FileChannel,long, long, boolean)": [0, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelToLibrary(java.nio.channels.FileChannel)"]
       },
       "never_outline_api": [
         "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)"
       ],
       "wrapper_conversion": [
-        "java.nio.channels.SeekableByteChannel",
         "java.util.PrimitiveIterator$OfDouble",
         "java.util.PrimitiveIterator$OfInt",
         "java.util.PrimitiveIterator$OfLong",
@@ -370,18 +373,6 @@
         "java.util.stream.LongStream",
         "java.util.stream.Stream"
       ],
-      "wrapper_conversion_excluding": {
-        "java.nio.channels.FileChannel": [
-          "long java.nio.channels.FileChannel#read(java.nio.ByteBuffer[])",
-          "long java.nio.channels.FileChannel#write(java.nio.ByteBuffer[])",
-          "java.nio.channels.FileLock java.nio.channels.FileChannel#lock()",
-          "java.nio.channels.FileLock java.nio.channels.FileChannel#tryLock()",
-          "void java.nio.channels.spi.AbstractInterruptibleChannel#close()",
-          "boolean java.nio.channels.spi.AbstractInterruptibleChannel#isOpen()",
-          "void java.nio.channels.spi.AbstractInterruptibleChannel#begin()",
-          "void java.nio.channels.spi.AbstractInterruptibleChannel#end(boolean)"
-        ]
-      },
       "custom_conversion": {
         "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions",
         "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
@@ -442,7 +433,9 @@
         "java.time.Instant java.util.Calendar#toInstant()": "java.util.DesugarCalendar",
         "java.util.Date java.util.Date#from(java.time.Instant)": "java.util.DesugarDate",
         "java.util.GregorianCalendar java.util.GregorianCalendar#from(java.time.ZonedDateTime)": "java.util.DesugarGregorianCalendar",
-        "java.util.TimeZone java.util.TimeZone#getTimeZone(java.lang.String)": "java.util.DesugarTimeZone"
+        "java.util.TimeZone java.util.TimeZone#getTimeZone(java.lang.String)": "java.util.DesugarTimeZone",
+        "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.nio.file.OpenOption[])": "java.nio.channels.DesugarChannels",
+        "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])": "java.nio.channels.DesugarChannels"
       }
     },
     {
@@ -483,7 +476,6 @@
     {
       "api_level_below_or_equal": 25,
       "rewrite_prefix": {
-        "java.nio.channels.Desugar": "j$.nio.channels.Desugar",
         "jdk.internal.": "j$.jdk.internal.",
         "sun.misc.Desugar": "j$.sun.misc.Desugar",
         "sun.nio.cs.": "j$.sun.nio.cs.",
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 6754dde..4649132 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -141,10 +141,12 @@
   private final Set<DexLibraryClass> libraryClassesToMock = Sets.newConcurrentHashSet();
   private final Set<DexType> seenTypes = Sets.newConcurrentHashSet();
   private final AndroidApiLevelCompute apiLevelCompute;
+  private final DexItemFactory factory;
 
   public ApiReferenceStubber(AppView<?> appView) {
     this.appView = appView;
     apiLevelCompute = appView.apiLevelCompute();
+    factory = appView.dexItemFactory();
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
@@ -228,6 +230,12 @@
         || libraryClass.getType().toDescriptorString().startsWith("Ljava/")) {
       return;
     }
+    // We cannot reliably create a stub that will have the same throwing
+    // behavior for all VMs. We only create stubs for exceptions to allow them being present in
+    // catch handlers. See b/b/258270051 for more information.
+    if (!isThrowable(libraryClass)) {
+      return;
+    }
     if (appView
         .options()
         .machineDesugaredLibrarySpecification
@@ -259,7 +267,6 @@
                               .setProto(factory.createProto(factory.voidType))
                               .setAccessFlags(MethodAccessFlags.createForClassInitializer())
                               .setCode(method -> throwExceptionCode));
-              // Based on b/138781768#comment57 there is no significant reason to synthesize fields.
               if (libraryClass.isInterface()) {
                 classBuilder.setInterface();
               }
@@ -269,4 +276,16 @@
             },
             ignored -> {});
   }
+
+  private boolean isThrowable(DexLibraryClass libraryClass) {
+    DexClass current = libraryClass;
+    while (current.getSuperType() != null) {
+      DexType superType = current.getSuperType();
+      if (superType == factory.throwableType) {
+        return true;
+      }
+      current = appView.definitionFor(current.getSuperType());
+    }
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 5869f46..65572c8 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -75,9 +75,8 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -112,7 +111,7 @@
   private final List<List<LocalVariableInfo>> localsAtLabel;
 
   private final StringBuilder builder = new StringBuilder();
-  private final ClassNameMapper mapper;
+  private final RetracerForCodePrinting retracer;
 
   private int nextInstructionIndex = 0;
   private final int instructionIndexSpace;
@@ -121,7 +120,7 @@
   public CfPrinter() {
     indent = "";
     labelToIndex = null;
-    mapper = null;
+    retracer = RetracerForCodePrinting.empty();
     instructionIndexSpace = 0;
 
     sortedLabels = Collections.emptyList();
@@ -130,12 +129,12 @@
 
   /** Entry for printing a complete code object. */
   public CfPrinter(CfCode code) {
-    this(code, null, null);
+    this(code, null, RetracerForCodePrinting.empty());
   }
 
   /** Entry for printing a complete method object. */
-  public CfPrinter(CfCode code, DexEncodedMethod method, ClassNameMapper mapper) {
-    this.mapper = mapper;
+  public CfPrinter(CfCode code, DexEncodedMethod method, RetracerForCodePrinting retracer) {
+    this.retracer = retracer;
     indent = "  ";
     instructionIndexSpace = ("" + code.getInstructions().size()).length();
     labelToIndex = new Reference2IntOpenHashMap<>();
@@ -770,56 +769,36 @@
   }
 
   private void appendDescriptor(DexType type) {
-    if (mapper != null) {
-      builder.append(DescriptorUtils.javaTypeToDescriptor(mapper.originalNameOf(type)));
-      return;
-    }
-    builder.append(type.toDescriptorString());
+    builder.append(retracer.toDescriptor(type));
   }
 
   private void appendType(DexType type) {
     if (type.isArrayType() || type.isClassType()) {
       appendClass(type);
     } else {
-      builder.append(type);
+      builder.append(retracer.toDescriptor(type));
     }
   }
 
   private void appendTypeElement(TypeElement type) {
-    builder.append(type.toString());
+    builder.append(type);
   }
 
   private void appendClass(DexType type) {
     assert type.isArrayType() || type.isClassType();
-    if (mapper == null) {
-      builder.append(type.getInternalName());
-    } else if (type == DexItemFactory.nullValueType) {
+    if (type == DexItemFactory.nullValueType) {
       builder.append("NULL");
     } else {
-      builder.append(
-          DescriptorUtils.descriptorToInternalName(
-              DescriptorUtils.javaTypeToDescriptor(mapper.originalNameOf(type))));
+      builder.append(retracer.toDescriptor(type));
     }
   }
 
   private void appendField(DexField field) {
-    if (mapper != null) {
-      builder.append(mapper.originalSignatureOf(field).toString());
-      return;
-    }
-    appendClass(field.holder);
-    builder.append('/').append(field.name);
+    builder.append(retracer.toDescriptor(field));
   }
 
   private void appendMethod(DexMethod method) {
-    if (mapper != null) {
-      MethodSignature signature = mapper.originalSignatureOf(method);
-      builder.append(mapper.originalNameOf(method.holder)).append('.');
-      builder.append(signature.name).append(signature.toDescriptor());
-      return;
-    }
-    builder.append(method.qualifiedName());
-    builder.append(method.proto.toDescriptorString());
+    builder.append(retracer.toDescriptor(method));
   }
 
   private String opcodeName(int opcode) {
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConst.java b/src/main/java/com/android/tools/r8/dex/code/DexConst.java
index 33fdff4..c47c264 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConst.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConst.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 
 public class DexConst extends DexFormat31i implements SingleConstant {
@@ -44,13 +44,13 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) + " (" + decodedValue() + ")");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) + "  # " + decodedValue());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConst16.java b/src/main/java/com/android/tools/r8/dex/code/DexConst16.java
index 1188df2..d676e03 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConst16.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConst16.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 
 public class DexConst16 extends DexFormat21s implements SingleConstant {
@@ -44,7 +44,7 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 4) + " (" + decodedValue() + ")");
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConst4.java b/src/main/java/com/android/tools/r8/dex/code/DexConst4.java
index 09ecd15..f1dd41c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConst4.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConst4.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 
 public class DexConst4 extends DexFormat11n implements SingleConstant {
@@ -44,13 +44,13 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(
         "v" + A + ", " + StringUtils.hexString(decodedValue(), 1) + " (" + decodedValue() + ")");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(
         "v" + A + ", " + StringUtils.hexString(decodedValue(), 2) + "  # " + decodedValue());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstHigh16.java b/src/main/java/com/android/tools/r8/dex/code/DexConstHigh16.java
index 2e732ed..82a6aa3 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstHigh16.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstHigh16.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 
 public class DexConstHigh16 extends DexFormat21h implements SingleConstant {
@@ -44,13 +44,13 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) + " (" + decodedValue() + ")");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) + "  # " + decodedValue());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java
index 87964c8..b909f64 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.nio.ShortBuffer;
 
@@ -58,12 +58,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java
index 68f49fb..15bee8c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.nio.ShortBuffer;
 
@@ -57,12 +57,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstString.java b/src/main/java/com/android/tools/r8/dex/code/DexConstString.java
index 0117785..e143d89 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstString.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstString.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.nio.ShortBuffer;
 
@@ -75,12 +75,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstStringJumbo.java b/src/main/java/com/android/tools/r8/dex/code/DexConstStringJumbo.java
index 725ef8a..e1811ba 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstStringJumbo.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstStringJumbo.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 
 public class DexConstStringJumbo extends DexFormat31c {
 
@@ -52,12 +52,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", \"" + BBBBBBBB.toString() + "\"");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", \"" + BBBBBBBB.toString() + "\"");
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstWide.java b/src/main/java/com/android/tools/r8/dex/code/DexConstWide.java
index 6fbd6f2..899e221 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstWide.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstWide.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 
 public class DexConstWide extends DexFormat51l implements WideConstant {
@@ -44,13 +44,13 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + " (" + decodedValue() + ")");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + "L  # " + decodedValue());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstWide16.java b/src/main/java/com/android/tools/r8/dex/code/DexConstWide16.java
index e30b886..2cd5ae3 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstWide16.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstWide16.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 
 public class DexConstWide16 extends DexFormat21s implements WideConstant {
@@ -44,13 +44,13 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + " (" + decodedValue() + ")");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + "L  # " + decodedValue());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstWide32.java b/src/main/java/com/android/tools/r8/dex/code/DexConstWide32.java
index b315cb9..9797a19 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstWide32.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstWide32.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 
 public class DexConstWide32 extends DexFormat31i implements WideConstant {
@@ -44,13 +44,13 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + " (" + decodedValue() + ")");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + "  # " + decodedValue());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstWideHigh16.java b/src/main/java/com/android/tools/r8/dex/code/DexConstWideHigh16.java
index 0eeca93..93b7f34 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstWideHigh16.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstWideHigh16.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 
 public class DexConstWideHigh16 extends DexFormat21h implements WideConstant {
@@ -44,13 +44,13 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + " (" + decodedValue() + ")");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(
         "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + "L  # " + decodedValue());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFillArrayData.java b/src/main/java/com/android/tools/r8/dex/code/DexFillArrayData.java
index de478d7..0a3974d 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFillArrayData.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFillArrayData.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.dex.code;
 
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 
 public class DexFillArrayData extends DexFormat31t {
 
@@ -41,7 +41,7 @@
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", :label_" + (getOffset() + BBBBBBBB));
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFillArrayDataPayload.java b/src/main/java/com/android/tools/r8/dex/code/DexFillArrayDataPayload.java
index 67d796c..228258d 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFillArrayDataPayload.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFillArrayDataPayload.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -91,8 +91,8 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
-    return super.toString(naming)
+  public String toString(RetracerForCodePrinting retracer) {
+    return super.toString(retracer)
         + "[FillArrayPayload], "
         + "width: "
         + element_width
@@ -101,7 +101,7 @@
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     builder.append("    ");
     builder.append(".array-data ");
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java
index ba9065a..ba29cbe 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import java.nio.ShortBuffer;
@@ -56,12 +56,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(formatRelativeOffset(AA));
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(":label_" + (getOffset() + AA));
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java
index 3cb41fb..174c89b 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import java.nio.ShortBuffer;
 
 abstract class DexFormat10x extends DexBase1Format {
@@ -33,12 +33,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(null);
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(null);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java
index fe02384..09e3fb6 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -70,7 +70,7 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + A + ", #" + B);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java
index b07478c..393ab88 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import java.nio.ShortBuffer;
@@ -56,12 +56,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA);
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java
index 633837d..979bcc7 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -64,12 +64,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + A + ", v" + B);
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + A + ", v" + B);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java
index 0c0e15e..a03b9c5 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import java.nio.ShortBuffer;
@@ -56,12 +56,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("" + AAAA + " " + formatRelativeOffset(AAAA));
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(":label_" + (getOffset() + AAAA));
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat21c.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat21c.java
index b47c6d7..4d25775 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat21c.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat21c.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.IndexedDexItem;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -52,13 +52,12 @@
   abstract void internalSubSpecify(StructuralSpecification<DexFormat21c<T>, ?> spec);
 
   @Override
-  public String toString(ClassNameMapper naming) {
-    return formatString(
-        "v" + AA + ", " + (naming == null ? BBBB.toString() : naming.originalNameOf(BBBB)));
+  public String toString(RetracerForCodePrinting retracer) {
+    return formatString("v" + AA + ", " + retracer.toDescriptor(BBBB));
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     // TODO(sgjesse): Add support for smali name mapping.
     return formatSmaliString("v" + AA + ", " + BBBB.toSmaliString());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java
index 97e3529..3d66ad2 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -67,12 +67,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", #" + BBBB);
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", " + StringUtils.hexString(BBBB, 4) + "  # " + BBBB);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java
index 1c80b33..93cd025 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -85,12 +85,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", " + formatRelativeOffset(BBBB));
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", :label_" + (getOffset() + BBBB));
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java
index 687d7e7..222fc09 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -71,12 +71,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", v" + BB + ", #" + CC);
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(
         "v" + AA + ", v" + BB + ", " + StringUtils.hexString(CC, 2) + "  # " + CC);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22c.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22c.java
index 2e1246c..68c7839 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22c.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22c.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.IndexedDexItem;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -57,13 +57,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
-    return formatString(
-        "v" + A + ", v" + B + ", " + (naming == null ? CCCC : naming.originalNameOf(CCCC)));
+  public String toString(RetracerForCodePrinting retracer) {
+    return formatString("v" + A + ", v" + B + ", " + retracer.toDescriptor(CCCC));
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     // TODO(sgjesse): Add support for smali name mapping.
     return formatSmaliString("v" + A + ", v" + B + ", " + CCCC.toSmaliString());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java
index e503467..53d247c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -71,12 +71,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + A + ", v" + B + ", #" + CCCC);
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(
         "v" + A + ", v" + B + ", " + StringUtils.hexString(CCCC, 4) + "  # " + CCCC);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java
index 17695d7..c276ca1 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -89,12 +89,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + A + ", v" + B + ", " + formatRelativeOffset(CCCC));
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + A + ", v" + B + ", :label_" + (getOffset() + CCCC));
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java
index 3c34540..3cd881d 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -66,12 +66,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", v" + (int) BBBB);
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", v" + (int) BBBB);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java
index df098d9..1e1b130 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -70,12 +70,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", v" + BB + ", v" + CC);
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", v" + BB + ", v" + CC);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java
index aa744ad..cfd2bf7 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import java.nio.ShortBuffer;
@@ -55,12 +55,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString(formatOffset(AAAAAAAA));
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString(":label_" + (getOffset() + AAAAAAAA));
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java
index 8023664..ae0ca6c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -69,9 +69,8 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
-    return formatString(
-        "v" + AA + ", " + (naming == null ? BBBBBBBB : naming.originalNameOf(BBBBBBBB)));
+  public String toString(RetracerForCodePrinting retracer) {
+    return formatString("v" + AA + ", " + retracer.toDescriptor(BBBBBBBB));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java
index 0795a92..942c5e2 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -65,7 +65,7 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", #" + BBBBBBBB);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java
index 320514d..7f26c21 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -80,7 +80,7 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", " + formatRelativeOffset(BBBBBBBB));
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java
index 00cf1f4..5389c2f 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -68,12 +68,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AAAA + ", v" + BBBB);
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AAAA + ", v" + BBBB);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat35c.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat35c.java
index 6e40c73..0f195c1 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat35c.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat35c.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.IndexedDexItem;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -94,20 +94,16 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     appendRegisterArguments(builder, " ");
     builder.append(" ");
-    if (naming == null) {
-      builder.append(BBBB.toSmaliString());
-    } else {
-      builder.append(naming.originalNameOf(BBBB));
-    }
+    builder.append(retracer.toDescriptor(BBBB));
     return formatString(builder.toString());
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     appendRegisterArguments(builder, ", ");
     builder.append(", ");
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat3rc.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat3rc.java
index b0c0204..3d8289c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat3rc.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat3rc.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.IndexedDexItem;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -71,20 +71,16 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     appendRegisterRange(builder);
     builder.append(" ");
-    if (naming == null) {
-      builder.append(BBBB.toSmaliString());
-    } else {
-      builder.append(naming.originalNameOf(BBBB));
-    }
+    builder.append(retracer.toDescriptor(BBBB));
     return formatString(builder.toString());
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     appendRegisterRange(builder);
     builder.append(", ");
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java
index 61a0257..2f4adc5 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java
@@ -11,12 +11,11 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
-import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -137,7 +136,7 @@
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     appendRegisterArguments(builder, ", ");
     builder.append(", ");
@@ -149,26 +148,16 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     appendRegisterArguments(builder, " ");
     builder.append(" ");
-    builder.append(itemToString(BBBB, naming));
+    builder.append(retracer.toDescriptor(BBBB));
     builder.append(", ");
-    builder.append(itemToString(HHHH, naming));
+    builder.append(retracer.toDescriptor(HHHH));
     return formatString(builder.toString());
   }
 
-  private String itemToString(IndexedDexItem indexedDexItem, ClassNameMapper naming) {
-    String str;
-    if (naming == null) {
-      str = indexedDexItem.toSmaliString();
-    } else {
-      str = naming.originalNameOf(indexedDexItem);
-    }
-    return str;
-  }
-
   private void appendRegisterArguments(StringBuilder builder, String separator) {
     builder.append("{ ");
     int[] values = new int[] {C, D, E, F, G};
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java
index b795316..8aa065a 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -90,25 +90,17 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     appendRegisterRange(builder);
     builder.append(" ");
-    if (naming == null) {
-      builder.append(BBBB.toSmaliString());
-    } else {
-      builder.append(naming.originalNameOf(BBBB));
-    }
-    if (naming == null) {
-      builder.append(HHHH.toSmaliString());
-    } else {
-      builder.append(naming.originalNameOf(HHHH));
-    }
+    builder.append(retracer.toDescriptor(BBBB));
+    builder.append(retracer.toDescriptor(HHHH));
     return formatString(builder.toString());
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     appendRegisterRange(builder);
     builder.append(", ");
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java
index 30ddee5..ddce5ad 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -65,7 +65,7 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     return formatString("v" + AA + ", #" + BBBBBBBBBBBBBBBB);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java b/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java
index c600656..8591fd9 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.ir.code.FieldMemberType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -144,18 +144,12 @@
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + dest + ", " + clazz.toSmaliString());
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
-    StringBuilder builder = new StringBuilder("v").append(dest).append(", ");
-    if (naming == null) {
-      builder.append(clazz.toSourceString());
-    } else {
-      builder.append(naming.originalNameOf(clazz));
-    }
-    return formatString(builder.toString());
+  public String toString(RetracerForCodePrinting retracer) {
+    return formatString("v" + dest + ", " + retracer.toDescriptor(clazz));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
index d5f8183..e6362d0 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.Equatable;
@@ -372,21 +372,21 @@
     throw new InternalCompilerError("Instruction " + payloadUser + " is not a payload user");
   }
 
-  public abstract String toSmaliString(ClassNameMapper naming);
+  public abstract String toSmaliString(RetracerForCodePrinting retracer);
 
   public String toSmaliString() {
-    return toSmaliString((ClassNameMapper) null);
+    return toSmaliString((RetracerForCodePrinting) null);
   }
 
-  public abstract String toString(ClassNameMapper naming);
+  public abstract String toString(RetracerForCodePrinting retracer);
 
-  public String toString(ClassNameMapper naming, DexInstruction payloadUser) {
+  public String toString(RetracerForCodePrinting retracer, DexInstruction payloadUser) {
     throw new InternalCompilerError("Instruction " + payloadUser + " is not a payload user");
   }
 
   @Override
   public String toString() {
-    return toString(null);
+    return toString(RetracerForCodePrinting.empty());
   }
 
   public abstract void write(
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java
index d34a783..ddaf074 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.nio.ShortBuffer;
 
@@ -85,13 +85,13 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     // TODO(christofferqa): Apply mapping to item.
     return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     // TODO(christofferqa): Apply mapping to item.
     return formatSmaliString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitch.java b/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitch.java
index 34029d8..c00a3b4 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitch.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitch.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.dex.code;
 
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 
 public class DexPackedSwitch extends DexFormat31t {
 
@@ -50,7 +50,7 @@
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", :label_" + (getOffset() + BBBBBBBB));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitchPayload.java b/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitchPayload.java
index d06f619..95f6eed 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitchPayload.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -102,12 +102,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
-    return toString(naming, null);
+  public String toString(RetracerForCodePrinting retracer) {
+    return toString(retracer, null);
   }
 
   @Override
-  public String toString(ClassNameMapper naming, DexInstruction payloadUser) {
+  public String toString(RetracerForCodePrinting retracer, DexInstruction payloadUser) {
     StringBuilder builder = new StringBuilder("[PackedSwitchPayload");
     if (payloadUser == null) {
       builder.append(" offsets relative to associated PackedSwitch");
@@ -123,7 +123,7 @@
       }
       StringUtils.appendLeftPadded(builder, (first_key + i) + " -> " + offsetString + "\n", 20);
     }
-    return super.toString(naming) + builder.toString();
+    return super.toString(retracer) + builder.toString();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java b/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java
index 955f67e..3101663 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -100,7 +100,7 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
+  public String toString(RetracerForCodePrinting retracer) {
     StringBuilder sb = new StringBuilder();
     sb.append("v").append(outRegister).append(" ");
     appendArguments(sb);
@@ -108,8 +108,8 @@
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
-    return toString(naming);
+  public String toSmaliString(RetracerForCodePrinting retracer) {
+    return toString(retracer);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitch.java b/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitch.java
index 4f15ab7..8dfabb6 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitch.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitch.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.dex.code;
 
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 
 public class DexSparseSwitch extends DexFormat31t {
 
@@ -49,7 +49,7 @@
   }
 
   @Override
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     return formatSmaliString("v" + AA + ", :label_" + (getOffset() + BBBBBBBB));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitchPayload.java b/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitchPayload.java
index 97d369a..838e4ea 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitchPayload.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -108,12 +108,12 @@
   }
 
   @Override
-  public String toString(ClassNameMapper naming) {
-    return toString(naming, null);
+  public String toString(RetracerForCodePrinting retracer) {
+    return toString(retracer, null);
   }
 
   @Override
-  public String toString(ClassNameMapper naming, DexInstruction payloadUser) {
+  public String toString(RetracerForCodePrinting retracer, DexInstruction payloadUser) {
     StringBuilder builder = new StringBuilder("[SparseSwitchPayload");
     if (payloadUser == null) {
       builder.append(" offsets relative to associated SparseSwitch");
@@ -129,7 +129,7 @@
       }
       StringUtils.appendLeftPadded(builder, keys[i] + " -> " + offsetString + "\n", 20);
     }
-    return super.toString(naming) + builder.toString();
+    return super.toString(retracer) + builder.toString();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 369e60c..4b70a1c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -275,8 +275,8 @@
   public boolean isSubtype(DexType subtype, DexType supertype) {
     assert subtype != null;
     assert supertype != null;
-    assert subtype.isClassType();
-    assert supertype.isClassType();
+    assert subtype.isClassType() : "subtype not a class: " + subtype;
+    assert supertype.isClassType() : "supertype not a class: " + supertype;
     return subtype == supertype || isStrictSubtypeOf(subtype, supertype);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 3061f1b..43ee151 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -12,11 +12,12 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.android.tools.r8.utils.Timing;
 import java.io.BufferedReader;
 import java.io.PrintStream;
@@ -34,6 +35,7 @@
   private final Kotlin kotlin;
   private final Timing timing = new Timing("AssemblyWriter");
   private final CompilationContext compilationContext;
+  private final RetracerForCodePrinting retracer;
 
   public AssemblyWriter(
       DexApplication application,
@@ -62,6 +64,8 @@
       this.appInfo = null;
     }
     kotlin = new Kotlin(application.dexItemFactory);
+    retracer =
+        RetracerForCodePrinting.create(application.getProguardMap(), application.options.reporter);
   }
 
   public static String getFileEnding() {
@@ -70,22 +74,17 @@
 
   @Override
   void writeClassHeader(DexProgramClass clazz, PrintStream ps) {
-    String clazzName;
-    if (application.getProguardMap() != null) {
-      clazzName = application.getProguardMap().originalNameOf(clazz.type);
-    } else {
-      clazzName = clazz.type.toSourceString();
-    }
+    String clazzName = retracer.toSourceString(clazz.getType());
     ps.println("# Bytecode for");
     ps.println("# Class: '" + clazzName + "'");
     if (writeAllClassInfo) {
       writeAnnotations(clazz, clazz.annotations(), ps);
       ps.println("# Flags: '" + clazz.accessFlags + "'");
       if (clazz.superType != application.dexItemFactory.objectType) {
-        ps.println("# Extends: '" + clazz.superType.toSourceString() + "'");
+        ps.println("# Extends: '" + retracer.toSourceString(clazz.superType) + "'");
       }
       for (DexType value : clazz.interfaces.values) {
-        ps.println("# Implements: '" + value.toSourceString() + "'");
+        ps.println("# Implements: '" + retracer.toSourceString(value) + "'");
       }
       if (!clazz.getInnerClasses().isEmpty()) {
         ps.println("# InnerClasses:");
@@ -93,10 +92,10 @@
           ps.println(
               "#  Outer: "
                   + (innerClassAttribute.getOuter() != null
-                      ? innerClassAttribute.getOuter().toSourceString()
+                      ? retracer.toSourceString(innerClassAttribute.getOuter())
                       : "-")
                   + ", inner: "
-                  + innerClassAttribute.getInner().toSourceString()
+                  + retracer.toSourceString(innerClassAttribute.getInner())
                   + ", inner name: "
                   + innerClassAttribute.getInnerName()
                   + ", access: "
@@ -107,10 +106,12 @@
       if (enclosingMethodAttribute != null) {
         ps.println("# EnclosingMethod:");
         if (enclosingMethodAttribute.getEnclosingClass() != null) {
-          ps.println("#  Class: " + enclosingMethodAttribute.getEnclosingClass().toSourceString());
+          ps.println(
+              "#  Class: " + retracer.toSourceString(enclosingMethodAttribute.getEnclosingClass()));
         } else {
           ps.println(
-              "#  Method: " + enclosingMethodAttribute.getEnclosingMethod().toSourceString());
+              "#  Method: "
+                  + retracer.toSourceString(enclosingMethodAttribute.getEnclosingMethod()));
         }
       }
     }
@@ -129,14 +130,9 @@
   @Override
   void writeField(DexEncodedField field, PrintStream ps) {
     if (writeFields) {
-      ClassNameMapper naming = application.getProguardMap();
-      FieldSignature fieldSignature =
-          naming != null
-              ? naming.originalSignatureOf(field.getReference())
-              : FieldSignature.fromDexField(field.getReference());
       writeAnnotations(null, field.annotations(), ps);
       ps.print(field.accessFlags + " ");
-      ps.print(fieldSignature);
+      ps.print(retracer.toSourceString(field.getReference()));
       if (field.isStatic() && field.hasExplicitStaticValue()) {
         ps.print(" = " + field.getStaticValue());
       }
@@ -152,15 +148,13 @@
   @Override
   void writeMethod(ProgramMethod method, PrintStream ps) {
     DexEncodedMethod definition = method.getDefinition();
-    ClassNameMapper naming = application.getProguardMap();
-    String methodName =
-        naming != null
-            ? naming.originalSignatureOf(method.getReference()).name
-            : method.getReference().name.toString();
     ps.println("#");
-    ps.println("# Method: '" + methodName + "':");
+    ps.println("# Method: '" + retracer.toSourceString(definition.getReference()) + "':");
     writeAnnotations(null, definition.annotations(), ps);
     ps.println("# " + definition.accessFlags);
+    if (!retracer.isEmpty()) {
+      ps.println("# Residual: '" + definition.getReference().toSourceString());
+    }
     ps.println("#");
     ps.println();
     if (!writeCode) {
@@ -171,7 +165,7 @@
       if (writeIR) {
         writeIR(method, ps);
       } else {
-        ps.println(code.toString(definition, naming));
+        ps.println(code.toString(definition, retracer));
       }
     }
   }
@@ -188,7 +182,7 @@
                 OptimizationFeedbackIgnore.getInstance(),
                 methodProcessor,
                 methodProcessingContext));
-    ps.println(printer.toString());
+    ps.println(printer);
   }
 
   private void writeAnnotations(
@@ -202,9 +196,19 @@
             assert clazz != null : "Kotlin metadata is a class annotation";
             KotlinMetadataWriter.writeKotlinMetadataAnnotation(prefix, annotation, ps, kotlin);
           } else {
-            String annotationString = annotation.toString();
+            StringBuilder sb = new StringBuilder();
+            sb.append(annotation.getVisibility());
+            sb.append(" ");
+            sb.append(retracer.toSourceString(annotation.getAnnotationType()));
+            sb.append(
+                StringUtils.join(
+                    ",",
+                    annotation.annotation.elements,
+                    element ->
+                        element.getName().toString() + " = " + getStringValue(element.getValue()),
+                    BraceType.SQUARE));
             ps.print(
-                new BufferedReader(new StringReader(annotationString))
+                new BufferedReader(new StringReader(sb.toString()))
                     .lines()
                     .collect(
                         Collectors.joining(
@@ -215,6 +219,26 @@
     }
   }
 
+  private String getStringValue(DexValue value) {
+    if (value.isDexValueType()) {
+      return retracer.toSourceString(value.asDexValueType().getValue());
+    } else if (value.isDexValueMethodHandle()) {
+      return retracer.toSourceString(value.asDexValueMethodHandle().value.asMethod());
+    } else if (value.isDexValueMethod()) {
+      return retracer.toSourceString(value.asDexValueMethod().value);
+    } else if (value.isDexItemBasedValueString()) {
+      return retracer.toSourceString(value.asDexItemBasedValueString().value);
+    } else if (value.isDexValueEnum()) {
+      return retracer.toSourceString(value.asDexValueEnum().value);
+    } else if (value.isDexValueField()) {
+      return retracer.toSourceString(value.asDexValueField().value);
+    } else if (value.isDexValueArray()) {
+      return "[" + value.asDexValueArray() + "]";
+    } else {
+      return value.toString();
+    }
+  }
+
   @Override
   void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
 
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index f415e4b..63d7d65 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -38,11 +38,11 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -783,8 +783,8 @@
   }
 
   @Override
-  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
-    return new CfPrinter(this, method, naming).toString();
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
+    return new CfPrinter(this, method, retracer).toString();
   }
 
   public ConstraintWithTarget computeInliningConstraint(
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 0edeb6f..1077fb9 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 
 public abstract class Code extends CachedHashValueDexItem {
@@ -71,7 +71,7 @@
   @Override
   public abstract String toString();
 
-  public abstract String toString(DexEncodedMethod method, ClassNameMapper naming);
+  public abstract String toString(DexEncodedMethod method, RetracerForCodePrinting retracer);
 
   public boolean isCfCode() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index 425c5d5..165ff81 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -28,10 +28,10 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.google.common.collect.ImmutableList;
 import java.nio.ShortBuffer;
@@ -391,7 +391,7 @@
   }
 
   @Override
-  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
     return toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 924590f..aff4bff 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -101,6 +101,10 @@
     return visibility + " " + annotation;
   }
 
+  public int getVisibility() {
+    return visibility;
+  }
+
   public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
     annotation.collectIndexedItems(appView, indexedItems);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
index a2be262..aa6aaec 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
@@ -71,4 +71,7 @@
     assert false;
   }
 
+  public DexString getName() {
+    return name;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index ad59604..7e55f8e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -27,9 +27,9 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.Equatable;
 import com.android.tools.r8.utils.structural.HashCodeVisitor;
@@ -449,14 +449,14 @@
 
   @Override
   public String toString() {
-    return toString(null, null);
+    return toString(null, RetracerForCodePrinting.empty());
   }
 
   @Override
-  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     if (method != null) {
-      builder.append(method.toSourceString()).append("\n");
+      builder.append(retracer.toSourceString(method.getReference())).append("\n");
     }
     builder.append("registers: ").append(registerSize);
     builder.append(", inputs: ").append(incomingRegisterSize);
@@ -495,9 +495,9 @@
       builder.append(": ");
       if (insn.isSwitchPayload()) {
         DexInstruction payloadUser = payloadUsers.get(insn.getOffset());
-        builder.append(insn.toString(naming, payloadUser));
+        builder.append(insn.toString(retracer, payloadUser));
       } else {
-        builder.append(insn.toString(naming));
+        builder.append(insn.toString(retracer));
       }
       builder.append('\n');
     }
@@ -542,7 +542,7 @@
     return current;
   }
 
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     // Find labeled targets.
     Map<Integer, DexInstruction> payloadUsers = new HashMap<>();
@@ -582,7 +582,7 @@
         DexInstruction payloadUser = payloadUsers.get(dex.getOffset());
         builder.append(dex.toSmaliString(payloadUser)).append('\n');
       } else {
-        builder.append(dex.toSmaliString(naming)).append('\n');
+        builder.append(dex.toSmaliString(retracer)).append('\n');
       }
     }
     if (tries.length > 0) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 985e215..318b353 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -56,7 +56,6 @@
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
@@ -65,6 +64,7 @@
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.Ordered;
@@ -902,7 +902,7 @@
     this.parameterAnnotationsList = parameterAnnotations;
   }
 
-  public String toSmaliString(ClassNameMapper naming) {
+  public String toSmaliString(RetracerForCodePrinting naming) {
     checkIfObsolete();
     StringBuilder builder = new StringBuilder();
     builder.append(".method ");
@@ -1226,7 +1226,7 @@
 
   public String codeToString() {
     checkIfObsolete();
-    return code == null ? "<no code>" : code.toString(this, null);
+    return code == null ? "<no code>" : code.toString(this, RetracerForCodePrinting.empty());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 36c45bc..e0e2bd6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -56,6 +57,10 @@
     return Reference.classFromDescriptor(toDescriptorString());
   }
 
+  public TypeReference asTypeReference() {
+    return Reference.typeFromDescriptor(toDescriptorString());
+  }
+
   public DynamicTypeWithUpperBound toDynamicType(AppView<AppInfoWithLiveness> appView) {
     return toDynamicType(appView, Nullability.maybeNull());
   }
diff --git a/src/main/java/com/android/tools/r8/graph/InvalidCode.java b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
index 2b9de26..b3016c9 100644
--- a/src/main/java/com/android/tools/r8/graph/InvalidCode.java
+++ b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
@@ -6,8 +6,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 
 public class InvalidCode extends Code {
 
@@ -48,7 +48,7 @@
   }
 
   @Override
-  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
     StringBuilder builder = new StringBuilder();
     if (method != null) {
       builder.append(method.toSourceString()).append("\n");
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index d18e823..3ab128b 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -63,7 +63,6 @@
 import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.position.TextPosition;
@@ -74,6 +73,7 @@
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringDiagnostic;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -295,8 +295,8 @@
   }
 
   @Override
-  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
-    return asCfCode().toString(method, naming);
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
+    return asCfCode().toString(method, retracer);
   }
 
   protected BiFunction<String, String, LazyCfCode> createCodeLocator(ReparseContext context) {
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index edf7d58..0eff9d1 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.Timing;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -31,7 +32,7 @@
     } catch (IOException e) {
       throw new CompilationError("Failed to generate smali sting", e);
     }
-    return new String(os.toByteArray(), StandardCharsets.UTF_8);
+    return os.toString(StandardCharsets.UTF_8);
   }
 
   public static String getFileEnding() {
@@ -67,7 +68,12 @@
   @Override
   void writeMethod(ProgramMethod method, PrintStream ps) {
     ps.append("\n");
-    ps.append(method.getDefinition().toSmaliString(application.getProguardMap()));
+    ps.append(
+        method
+            .getDefinition()
+            .toSmaliString(
+                RetracerForCodePrinting.create(
+                    application.getProguardMap(), application.options.reporter)));
     ps.append("\n");
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
index 789bb19..dc18cfe 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -19,8 +19,8 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import java.nio.ShortBuffer;
 import java.util.Objects;
@@ -237,7 +237,7 @@
   }
 
   @Override
-  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
     return "ThrowExceptionCode";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
index cac6f7a..661cefb 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -22,9 +22,9 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.google.common.collect.ImmutableList;
 import java.nio.ShortBuffer;
@@ -266,7 +266,7 @@
   }
 
   @Override
-  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
     return "ThrowNullCode";
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
index c9c8e34..abb1fbf 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
@@ -15,8 +15,8 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 
 public abstract class IncompleteHorizontalClassMergerCode extends Code {
 
@@ -80,7 +80,7 @@
   public abstract String toString();
 
   @Override
-  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
     return toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index 2e6cc06..6a030d7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -35,11 +35,11 @@
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.CfVersionUtils;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -311,7 +311,7 @@
     }
 
     @Override
-    public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
       throw new Unreachable();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 30c3b67..2a543a8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
@@ -29,6 +30,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
@@ -37,6 +39,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
@@ -210,7 +213,7 @@
     if (value.isPhi()) {
       return null;
     }
-    if (value.definition.isNewArrayEmpty()) {
+    if (value.definition.isNewArrayEmptyOrInvokeNewArray()) {
       return computeSingleEnumFieldValueForValuesArray(value);
     }
     if (value.definition.isNewInstance()) {
@@ -220,7 +223,7 @@
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
-    if (!value.definition.isNewArrayEmpty()) {
+    if (!value.definition.isNewArrayEmptyOrInvokeNewArray()) {
       return null;
     }
     AbstractValue valuesValue = computedValues.get(value);
@@ -241,26 +244,38 @@
   }
 
   private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
-    assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
-
     NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
-    if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
+    InvokeNewArray invokeNewArray = value.definition.asInvokeNewArray();
+    assert newArrayEmpty != null || invokeNewArray != null;
+
+    DexType arrayType = newArrayEmpty != null ? newArrayEmpty.type : invokeNewArray.getArrayType();
+    if (arrayType.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
       return null;
     }
     if (value.hasDebugUsers() || value.hasPhiUsers()) {
       return null;
     }
-    if (!newArrayEmpty.size().isConstNumber()) {
-      return null;
-    }
 
-    int valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
-    if (valuesSize == 0) {
-      // No need to compute the state of an empty array.
+    int valuesSize = newArrayEmpty != null ? newArrayEmpty.sizeIfConst() : invokeNewArray.size();
+    if (valuesSize < 1) {
+      // Array is empty or non-const size.
       return null;
     }
 
     ObjectState[] valuesState = new ObjectState[valuesSize];
+
+    if (invokeNewArray != null) {
+      // Populate array values from filled-new-array values.
+      List<Value> inValues = invokeNewArray.inValues();
+      for (int i = 0; i < valuesSize; ++i) {
+        if (!updateEnumValueState(valuesState, i, inValues.get(i))) {
+          return null;
+        }
+      }
+    }
+
+    // Populate / update array values from aput-object instructions, and find the static-put
+    // instruction.
     DexEncodedField valuesField = null;
     for (Instruction user : value.aliasedUsers()) {
       switch (user.opcode()) {
@@ -276,18 +291,9 @@
           if (index < 0 || index >= valuesSize) {
             return null;
           }
-          ObjectState objectState = computeEnumInstanceObjectState(arrayPut.value());
-          if (objectState == null || objectState.isEmpty()) {
-            // We need the state of all fields for the analysis to be valuable.
+          if (!updateEnumValueState(valuesState, index, arrayPut.value())) {
             return null;
           }
-          if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
-            return null;
-          }
-          if (valuesState[index] != null) {
-            return null;
-          }
-          valuesState[index] = objectState;
           break;
 
         case ASSUME:
@@ -328,24 +334,34 @@
         .createSingleFieldValue(valuesField.getReference(), new EnumValuesObjectState(valuesState));
   }
 
-  private ObjectState computeEnumInstanceObjectState(Value value) {
+  private boolean updateEnumValueState(ObjectState[] valuesState, int index, Value value) {
     Value root = value.getAliasedValue();
     if (root.isPhi()) {
-      return ObjectState.empty();
+      return false;
     }
     Instruction definition = root.getDefinition();
-    if (definition.isNewInstance()) {
-      return computeObjectState(definition.outValue());
-    }
     if (definition.isStaticGet()) {
       // Enums with many instance rely on staticGets to set the $VALUES data instead of directly
       // keeping the values in registers, due to the max capacity of the redundant field load
       // elimination. The capacity has already been increased, so that this case is extremely
       // uncommon (very large enums).
       // TODO(b/169050248): We could consider analysing these to answer the object state here.
-      return ObjectState.empty();
+      return false;
     }
-    return ObjectState.empty();
+    ObjectState objectState =
+        definition.isNewInstance() ? computeObjectState(definition.outValue()) : null;
+    if (objectState == null || objectState.isEmpty()) {
+      // We need the state of all fields for the analysis to be valuable.
+      return false;
+    }
+    if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+      return false;
+    }
+    if (valuesState[index] != null) {
+      return false;
+    }
+    valuesState[index] = objectState;
+    return true;
   }
 
   private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 74a3fb5..b4b2359 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -46,6 +47,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -316,17 +318,37 @@
     Value sizeValue =
         instructionIterator.insertConstIntInstruction(code, appView.options(), objects.size());
     Value newObjectsValue = code.createValue(objectArrayType);
-    instructionIterator.add(
-        new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
 
     // Populate the `objects` array.
-    for (int i = 0; i < objects.size(); i++) {
-      Value indexValue = instructionIterator.insertConstIntInstruction(code, appView.options(), i);
-      Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
-      instructionIterator.add(materializingInstruction);
+    var rewriteOptions = appView.options().rewriteArrayOptions();
+    if (rewriteOptions.canUseFilledNewArrayOfObjects()
+        && objects.size() < rewriteOptions.maxRangeInputs) {
+      List<Value> arrayValues = new ArrayList<>(objects.size());
+      for (int i = 0; i < objects.size(); i++) {
+        Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+        instructionIterator.add(materializingInstruction);
+        arrayValues.add(materializingInstruction.outValue());
+      }
       instructionIterator.add(
-          new ArrayPut(
-              MemberType.OBJECT, newObjectsValue, indexValue, materializingInstruction.outValue()));
+          new InvokeNewArray(
+              appView.dexItemFactory().objectArrayType, newObjectsValue, arrayValues));
+    } else {
+      instructionIterator.add(
+          new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
+
+      // Populate the `objects` array.
+      for (int i = 0; i < objects.size(); i++) {
+        Value indexValue =
+            instructionIterator.insertConstIntInstruction(code, appView.options(), i);
+        Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+        instructionIterator.add(materializingInstruction);
+        instructionIterator.add(
+            new ArrayPut(
+                MemberType.OBJECT,
+                newObjectsValue,
+                indexValue,
+                materializingInstruction.outValue()));
+      }
     }
 
     // Pass the newly created `objects` array to RawMessageInfo.<init>(...) or
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index fbe3ff5..c25c456 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -302,18 +303,30 @@
    */
   private static ThrowingIterator<Value, InvalidRawMessageInfoException> createObjectIterator(
       Value objectsValue) throws InvalidRawMessageInfoException {
-    if (objectsValue.isPhi() || !objectsValue.definition.isNewArrayEmpty()) {
+    if (objectsValue.isPhi()) {
       throw new InvalidRawMessageInfoException();
     }
 
     NewArrayEmpty newArrayEmpty = objectsValue.definition.asNewArrayEmpty();
-    int expectedArraySize = objectsValue.uniqueUsers().size() - 1;
+    InvokeNewArray invokeNewArray = objectsValue.definition.asInvokeNewArray();
 
-    // Verify that the size is correct.
+    if (newArrayEmpty == null && invokeNewArray == null) {
+      throw new InvalidRawMessageInfoException();
+    }
+    // Verify that the array is used in only one spot.
+    if (invokeNewArray != null) {
+      if (objectsValue.uniqueUsers().size() != 1) {
+        throw new InvalidRawMessageInfoException();
+      }
+      return ThrowingIterator.fromIterator(invokeNewArray.inValues().iterator());
+    }
+
     Value sizeValue = newArrayEmpty.size().getAliasedValue();
-    if (sizeValue.isPhi()
-        || !sizeValue.definition.isConstNumber()
-        || sizeValue.definition.asConstNumber().getIntValue() != expectedArraySize) {
+    if (sizeValue.isPhi() || !sizeValue.definition.isConstNumber()) {
+      throw new InvalidRawMessageInfoException();
+    }
+    int arraySize = sizeValue.definition.asConstNumber().getIntValue();
+    if (arraySize != objectsValue.uniqueUsers().size() - 1) {
       throw new InvalidRawMessageInfoException();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
index e06cdbee..99f92eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -73,20 +73,25 @@
    * <p>Use with caution!
    */
   public static void removeArrayAndTransitiveInputsIfNotUsed(IRCode code, Instruction definition) {
-    Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
     if (definition.isConstNumber()) {
       // No need to explicitly remove `null`, it will be removed by ordinary dead code elimination
       // anyway.
       assert definition.asConstNumber().isZero();
       return;
     }
-
-    if (definition.isNewArrayEmpty()) {
-      Value arrayValue = definition.outValue();
-      if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
-        return;
-      }
-
+    Value arrayValue = definition.outValue();
+    if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
+      return;
+    }
+    if (!definition.isNewArrayEmptyOrInvokeNewArray()) {
+      assert false;
+      return;
+    }
+    Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
+    InvokeNewArray invokeNewArray = definition.asInvokeNewArray();
+    if (invokeNewArray != null) {
+      worklist.add(definition);
+    } else if (definition.isNewArrayEmpty()) {
       for (Instruction user : arrayValue.uniqueUsers()) {
         // If we encounter an Assume instruction here, we also need to consider indirect users.
         assert !user.isAssume();
@@ -95,11 +100,10 @@
         }
         worklist.add(user);
       }
-      internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
-      return;
+    } else {
+      assert false;
     }
-
-    assert false;
+    internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index c99ee11..8892da0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1038,6 +1038,10 @@
     return false;
   }
 
+  public boolean isNewArrayEmptyOrInvokeNewArray() {
+    return isNewArrayEmpty() || isInvokeNewArray();
+  }
+
   public NewArrayEmpty asNewArrayEmpty() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index a2e815f..f0715b5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -221,4 +221,9 @@
   void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
     registry.registerTypeReference(type);
   }
+
+  // Returns the number of elements in the array.
+  public int size() {
+    return inValues.size();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c6d527f..a829247 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -49,10 +49,6 @@
     return super.toString() + " " + type.toString();
   }
 
-  public Value dest() {
-    return outValue;
-  }
-
   public Value size() {
     return inValues.get(0);
   }
@@ -60,7 +56,7 @@
   @Override
   public void buildDex(DexBuilder builder) {
     int size = builder.allocatedRegister(size(), getNumber());
-    int dest = builder.allocatedRegister(dest(), getNumber());
+    int dest = builder.allocatedRegister(outValue, getNumber());
     builder.add(this, new DexNewArray(dest, size, type));
   }
 
@@ -171,4 +167,10 @@
   void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
     registry.registerTypeReference(type);
   }
+
+  // Returns the size of the array if it is known, -1 otherwise.
+  public int sizeIfConst() {
+    Value size = size();
+    return size.isConstNumber() ? size.getConstInstruction().asConstNumber().getIntValue() : -1;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 178255b..40f9d67 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -170,7 +170,9 @@
 
   private DexMethod ensureApiGenericConversion(
       DexMethod conversion, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
-    assert !appView.options().isDesugaredLibraryCompilation();
+    if (appView.appInfoForDesugaring().resolveMethod(conversion, false).isSingleResolution()) {
+      return conversion;
+    }
     ClasspathMethod classpathMethod =
         appView
             .getSyntheticItems()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
index 7a669c2..ae3214b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
@@ -14,6 +14,8 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiConsumer;
@@ -29,12 +31,14 @@
   private final Set<DexString> descriptorDontRewritePrefix;
   private final Map<DexString, Map<DexString, DexString>> descriptorDifferentPrefix;
   private final Set<DexString> usedPrefix = Sets.newIdentityHashSet();
+  private final boolean jdk11Legacy;
 
   public HumanToMachinePrefixConverter(
       AppInfoWithClassHierarchy appInfo,
       MachineRewritingFlags.Builder builder,
       String synthesizedPrefix,
       boolean libraryCompilation,
+      String identifier,
       HumanRewritingFlags rewritingFlags) {
     this.appInfo = appInfo;
     this.builder = builder;
@@ -45,6 +49,7 @@
     this.descriptorMaintainPrefix = convertPrefixSet(rewritingFlags.getMaintainPrefix());
     this.descriptorDifferentPrefix =
         convertRewriteDifferentPrefix(rewritingFlags.getRewriteDerivedPrefix());
+    this.jdk11Legacy = identifier.startsWith("com.tools.android:desugar_jdk_libs:1.2.");
   }
 
   public void convertPrefixFlags(
@@ -63,12 +68,27 @@
   }
 
   private void warnIfUnusedPrefix(BiConsumer<String, Set<DexString>> warnConsumer) {
-    Set<DexString> prefixes = Sets.newIdentityHashSet();
-    prefixes.addAll(descriptorPrefix.keySet());
-    prefixes.addAll(descriptorMaintainPrefix);
-    prefixes.addAll(descriptorDifferentPrefix.keySet());
-    prefixes.removeAll(usedPrefix);
-    warnConsumer.accept("The following prefixes do not match any type: ", prefixes);
+    Set<DexString> unused = Sets.newIdentityHashSet();
+    unused.addAll(descriptorPrefix.keySet());
+    unused.addAll(descriptorMaintainPrefix);
+    unused.addAll(descriptorDifferentPrefix.keySet());
+    unused.removeAll(usedPrefix);
+    if (jdk11Legacy) {
+      // We have to allow duplicate because of the jdk11 legacy configuration, since there is no
+      // api_greater_or_equal in legacy, we're bound to have duplicates.
+      // If we have x.y. -> w.z and x. -> w., then if one is used the other is used.
+      List<DexString> duplicates = new ArrayList<>();
+      for (DexString prefix : unused) {
+        descriptorPrefix.forEach(
+            (k, v) -> {
+              if (!unused.contains(k) && (k.startsWith(prefix) || prefix.startsWith(k))) {
+                duplicates.add(prefix);
+              }
+            });
+      }
+      duplicates.forEach(unused::remove);
+    }
+    warnConsumer.accept("The following prefixes do not match any type: ", unused);
   }
 
   public DexType convertJavaNameToDesugaredLibrary(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index 2773de6..48f3aaa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -57,12 +57,13 @@
 
     MachineTopLevelFlags machineTopLevelFlags = convertTopLevelFlags(humanSpec.getTopLevelFlags());
     String synthesizedPrefix = machineTopLevelFlags.getSynthesizedLibraryClassesPackagePrefix();
+    String identifier = machineTopLevelFlags.getIdentifier();
     Map<ApiLevelRange, MachineRewritingFlags> commonFlags =
-        convertRewritingFlagMap(humanSpec.getCommonFlags(), synthesizedPrefix, true);
+        convertRewritingFlagMap(humanSpec.getCommonFlags(), synthesizedPrefix, true, identifier);
     Map<ApiLevelRange, MachineRewritingFlags> programFlags =
-        convertRewritingFlagMap(humanSpec.getProgramFlags(), synthesizedPrefix, false);
+        convertRewritingFlagMap(humanSpec.getProgramFlags(), synthesizedPrefix, false, identifier);
     Map<ApiLevelRange, MachineRewritingFlags> libraryFlags =
-        convertRewritingFlagMap(humanSpec.getLibraryFlags(), synthesizedPrefix, true);
+        convertRewritingFlagMap(humanSpec.getLibraryFlags(), synthesizedPrefix, true, identifier);
 
     MultiAPILevelMachineDesugaredLibrarySpecification machineSpec =
         new MultiAPILevelMachineDesugaredLibrarySpecification(
@@ -74,13 +75,15 @@
   private Map<ApiLevelRange, MachineRewritingFlags> convertRewritingFlagMap(
       Map<ApiLevelRange, HumanRewritingFlags> libFlags,
       String synthesizedPrefix,
-      boolean interpretAsLibraryCompilation) {
+      boolean interpretAsLibraryCompilation,
+      String identifier) {
     Map<ApiLevelRange, MachineRewritingFlags> map = new HashMap<>();
     libFlags.forEach(
         (range, flags) ->
             map.put(
                 range,
-                convertRewritingFlags(flags, synthesizedPrefix, interpretAsLibraryCompilation)));
+                convertRewritingFlags(
+                    flags, synthesizedPrefix, interpretAsLibraryCompilation, identifier)));
     return map;
   }
 
@@ -99,7 +102,8 @@
         convertRewritingFlags(
             humanSpec.getRewritingFlags(),
             humanSpec.getSynthesizedLibraryClassesPackagePrefix(),
-            humanSpec.isLibraryCompilation());
+            humanSpec.isLibraryCompilation(),
+            humanSpec.getIdentifier());
     MachineTopLevelFlags topLevelFlags = convertTopLevelFlags(humanSpec.getTopLevelFlags());
     timing.end();
     return new MachineDesugaredLibrarySpecification(
@@ -117,7 +121,10 @@
   }
 
   private MachineRewritingFlags convertRewritingFlags(
-      HumanRewritingFlags rewritingFlags, String synthesizedPrefix, boolean libraryCompilation) {
+      HumanRewritingFlags rewritingFlags,
+      String synthesizedPrefix,
+      boolean libraryCompilation,
+      String identifier) {
     timing.begin("convert rewriting flags");
     MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder();
     DesugaredLibraryAmender.run(
@@ -135,7 +142,7 @@
     new HumanToMachineEmulatedInterfaceConverter(appInfo)
         .convertEmulatedInterfaces(rewritingFlags, appInfo, builder, this::warnMissingReferences);
     new HumanToMachinePrefixConverter(
-            appInfo, builder, synthesizedPrefix, libraryCompilation, rewritingFlags)
+            appInfo, builder, synthesizedPrefix, libraryCompilation, identifier, rewritingFlags)
         .convertPrefixFlags(rewritingFlags, this::warnMissingDexString);
     new HumanToMachineWrapperConverter(appInfo)
         .convertWrappers(rewritingFlags, builder, this::warnMissingReferences);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
index c6ea5b0..2acd508 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
@@ -27,7 +27,7 @@
     } else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.R)) {
       levelType = app.dexItemFactory.createType("Ljava/util/concurrent/Flow;");
     } else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.N)) {
-      levelType = app.dexItemFactory.createType("Ljava/util/function/Supplier;");
+      levelType = app.dexItemFactory.createType("Ljava/util/StringJoiner;");
     } else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.T)) {
       levelType = app.dexItemFactory.createType("Ljava/lang/invoke/VarHandle;");
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 6314197..1d4e435 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -16,7 +16,6 @@
 
 import com.android.tools.r8.algorithms.scc.SCC;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
@@ -55,7 +54,6 @@
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DebugLocalWrite;
@@ -84,6 +82,7 @@
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -138,6 +137,7 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
@@ -162,7 +162,6 @@
     FALSE
   }
 
-  private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE;
   // This constant was determined by experimentation.
   private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
 
@@ -2100,16 +2099,21 @@
     }
   }
 
-  private short[] computeArrayFilledData(ConstInstruction[] values, int size, int elementSize) {
-    if (values == null) {
-      return null;
+  private short[] computeArrayFilledData(Value[] values, int size, int elementSize) {
+    for (Value v : values) {
+      if (!v.isConstant()) {
+        return null;
+      }
     }
     if (elementSize == 1) {
       short[] result = new short[(size + 1) / 2];
       for (int i = 0; i < size; i += 2) {
-        short value = (short) (values[i].asConstNumber().getIntValue() & 0xFF);
+        short value =
+            (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
         if (i + 1 < size) {
-          value |= (short) ((values[i + 1].asConstNumber().getIntValue() & 0xFF) << 8);
+          value |=
+              (short)
+                  ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
         }
         result[i / 2] = value;
       }
@@ -2119,7 +2123,7 @@
     int shortsPerConstant = elementSize / 2;
     short[] result = new short[size * shortsPerConstant];
     for (int i = 0; i < size; i++) {
-      long value = values[i].asConstNumber().getRawValue();
+      long value = values[i].getConstInstruction().asConstNumber().getRawValue();
       for (int part = 0; part < shortsPerConstant; part++) {
         result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
       }
@@ -2127,40 +2131,45 @@
     return result;
   }
 
-  private ConstInstruction[] computeConstantArrayValues(
-      NewArrayEmpty newArray, BasicBlock block, int size) {
-    if (size > MAX_FILL_ARRAY_SIZE) {
-      return null;
-    }
-    ConstInstruction[] values = new ConstInstruction[size];
+  private Value[] computeArrayValues(LinearFlowInstructionListIterator it, int size) {
+    NewArrayEmpty newArrayEmpty = it.next().asNewArrayEmpty();
+    assert newArrayEmpty != null;
+    Value arrayValue = newArrayEmpty.outValue();
+
+    // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify
+    // if types require a cast.
+    // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g.
+    // if aput-object throws a ClassCastException if given an object that does not implement the
+    // desired interface, then we could add check-cast instructions for arguments we're not sure
+    // about.
+    DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory);
+    boolean needsTypeCheck =
+        !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
+
+    Value[] values = new Value[size];
     int remaining = size;
-    Set<Instruction> users = newArray.outValue().uniqueUsers();
-    Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
-    // We allow the array instantiations to cross block boundaries as long as it hasn't encountered
-    // an instruction instance that can throw an exception.
-    InstructionIterator it = block.iterator();
-    it.nextUntil(i -> i == newArray);
-    do {
-      visitedBlocks.add(block);
+    Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
       while (it.hasNext()) {
         Instruction instruction = it.next();
-        // If we encounter an instruction that can throw an exception we need to bail out of the
-        // optimization so that we do not transform half-initialized arrays into fully initialized
-        // arrays on exceptional edges. If the block has no handlers it is not observable so
-        // we perform the rewriting.
-        if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+      // If we encounter an instruction that can throw an exception we need to bail out of the
+      // optimization so that we do not transform half-initialized arrays into fully initialized
+      // arrays on exceptional edges. If the block has no handlers it is not observable so
+      // we perform the rewriting.
+      // TODO(b/246971330): Allow simplification when all users of the array are in the same
+      // try/catch.
+      if (instruction.getBlock().hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
           return null;
         }
         if (!users.contains(instruction)) {
           continue;
         }
-        // If the initialization sequence is broken by another use we cannot use a
-        // fill-array-data instruction.
-        if (!instruction.isArrayPut()) {
+      ArrayPut arrayPut = instruction.asArrayPut();
+      // If the initialization sequence is broken by another use we cannot use a fill-array-data
+      // instruction.
+      if (arrayPut == null || arrayPut.array() != arrayValue) {
           return null;
         }
-        ArrayPut arrayPut = instruction.asArrayPut();
-        if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) {
+      if (!arrayPut.index().isConstNumber()) {
           return null;
         }
         int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
@@ -2170,40 +2179,38 @@
         if (values[index] != null) {
           return null;
         }
-        ConstInstruction value = arrayPut.value().getConstInstruction();
+      Value value = arrayPut.value();
+      if (needsTypeCheck && !value.isAlwaysNull(appView)) {
+        DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
+        if (elementType.isArrayType()) {
+          if (elementType != valueDexType) {
+            return null;
+          }
+        } else if (valueDexType.isArrayType()) {
+          // isSubtype asserts for this case.
+          return null;
+        } else if (valueDexType.isNullValueType()) {
+          // Assume instructions can cause value.isAlwaysNull() == false while the DexType is
+          // null.
+          // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
+          // that hits this case.
+        } else {
+          // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
+          // library types (which this helper does not do).
+          if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
+            return null;
+          }
+        }
+      }
         values[index] = value;
         --remaining;
         if (remaining == 0) {
           return values;
         }
       }
-      BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
-      block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
-      it = block != null ? block.iterator() : null;
-    } while (it != null);
     return null;
   }
 
-  private boolean allowNewFilledArrayConstruction(Instruction instruction) {
-    if (!(instruction instanceof NewArrayEmpty)) {
-      return false;
-    }
-    NewArrayEmpty newArray = instruction.asNewArrayEmpty();
-    if (!newArray.size().isConstant()) {
-      return false;
-    }
-    assert newArray.size().isConstNumber();
-    int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-    if (size < 1) {
-      return false;
-    }
-    if (newArray.type.isPrimitiveArrayType()) {
-      return true;
-    }
-    return newArray.type == dexItemFactory.stringArrayType
-        && options.canUseFilledNewArrayOfObjects();
-  }
-
   /**
    * Replace new-array followed by stores of constants to all entries with new-array
    * and fill-array-data / filled-new-array.
@@ -2212,6 +2219,11 @@
     if (options.isGeneratingClassFiles()) {
       return;
     }
+    InternalOptions.RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
+    boolean canUseForStrings = rewriteOptions.canUseFilledNewArrayOfStrings();
+    boolean canUseForObjects = rewriteOptions.canUseFilledNewArrayOfObjects();
+    boolean canUseForArrays = rewriteOptions.canUseFilledNewArrayOfArrays();
+
     for (BasicBlock block : code.blocks) {
       // Map from the array value to the number of array put instruction to remove for that value.
       Map<Value, Instruction> instructionToInsertForArray = new HashMap<>();
@@ -2220,31 +2232,56 @@
       InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
-        if (instruction.getLocalInfo() != null || !allowNewFilledArrayConstruction(instruction)) {
+        NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
+        if (newArrayEmpty == null || !newArrayEmpty.size().isConstant()) {
           continue;
         }
-        NewArrayEmpty newArray = instruction.asNewArrayEmpty();
-        int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-        ConstInstruction[] values = computeConstantArrayValues(newArray, block, size);
+        if (instruction.getLocalInfo() != null) {
+          continue;
+        }
+        int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+        if (size < 1 || size > rewriteOptions.maxFillArrayDataInputs) {
+          continue;
+        }
+        DexType arrayType = newArrayEmpty.type;
+        if (!arrayType.isPrimitiveArrayType()) {
+          if (arrayType == dexItemFactory.stringArrayType) {
+            if (!canUseForStrings) {
+              continue;
+            }
+          } else if (!canUseForObjects) {
+            continue;
+          } else if (!canUseForArrays && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
+            continue;
+          }
+        }
+
+        Value[] values =
+            computeArrayValues(
+                new LinearFlowInstructionListIterator(code, block, it.previousIndex()), size);
         if (values == null) {
           continue;
         }
-        if (newArray.type == dexItemFactory.stringArrayType) {
+        // filled-new-array is implemented only for int[] and Object[].
+        // For int[], using filled-new-array is usually smaller than filled-array-data.
+        // filled-new-array supports up to 5 registers before it's filled-new-array/range.
+        if (!arrayType.isPrimitiveArrayType()
+            || (arrayType == dexItemFactory.intArrayType && size <= 5)) {
           // Don't replace with filled-new-array if it requires more than 200 consecutive registers.
-          if (size > 200) {
+          if (size > rewriteOptions.maxRangeInputs) {
             continue;
           }
-          List<Value> stringValues = new ArrayList<>(size);
-          for (ConstInstruction value : values) {
-            stringValues.add(value.outValue());
+          // block.hasCatchHandlers() is fine here since new-array-filled replaces new-array-empty
+          // and computeArrayValues already checks that no throwing instructions exist between the
+          // original new-array-empty and the final aput-object (where the new-array-filled will be
+          // positioned).
+          Value invokeValue =
+              code.createValue(newArrayEmpty.getOutType(), newArrayEmpty.getLocalInfo());
+          InvokeNewArray invoke = new InvokeNewArray(arrayType, invokeValue, Arrays.asList(values));
+          for (Value value : newArrayEmpty.inValues()) {
+            value.removeUser(newArrayEmpty);
           }
-          Value invokeValue = code.createValue(newArray.getOutType(), newArray.getLocalInfo());
-          InvokeNewArray invoke =
-              new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
-          for (Value value : newArray.inValues()) {
-            value.removeUser(newArray);
-          }
-          newArray.outValue().replaceUsers(invokeValue);
+          newArrayEmpty.outValue().replaceUsers(invokeValue);
           it.removeOrReplaceByDebugLocalRead();
           instructionToInsertForArray.put(invokeValue, invoke);
           storesToRemoveForArray.put(invokeValue, size);
@@ -2254,26 +2291,33 @@
           if (size == 1) {
             continue;
           }
-          int elementSize = newArray.type.elementSizeForPrimitiveArrayType();
+          // TODO(b/246971330): Allow simplification when all users of the array are in the same
+          // try/catch.
+          if (block.hasCatchHandlers()) {
+            // NewArrayFilledData can throw, so creating one as done below would add a second
+            // throwing instruction to the same block (the first one being NewArrayEmpty).
+            continue;
+          }
+          int elementSize = arrayType.elementSizeForPrimitiveArrayType();
           short[] contents = computeArrayFilledData(values, size, elementSize);
           if (contents == null) {
             continue;
           }
-          if (block.hasCatchHandlers()) {
-            continue;
-          }
-          int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
+          int arraySize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+          // fill-array-data requires the new-array-empty instruction to remain, as it does not
+          // itself create an array.
           NewArrayFilledData fillArray =
-              new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents);
-          fillArray.setPosition(newArray.getPosition());
+              new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, arraySize, contents);
+          fillArray.setPosition(newArrayEmpty.getPosition());
           it.add(fillArray);
-          storesToRemoveForArray.put(newArray.outValue(), size);
+          storesToRemoveForArray.put(newArrayEmpty.outValue(), size);
         }
       }
       // Second pass: remove all the array put instructions for the array for which we have
       // inserted a fill array data instruction instead.
       if (!storesToRemoveForArray.isEmpty()) {
         Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
+        int numInstructionsInserted = 0;
         do {
           visitedBlocks.add(block);
           it = block.listIterator(code);
@@ -2295,6 +2339,7 @@
                     // last removed put at which point we are now adding the construction.
                     construction.setPosition(instruction.getPosition());
                     it.add(construction);
+                    numInstructionsInserted += 1;
                   }
                 }
               }
@@ -2303,6 +2348,7 @@
           BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
           block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
         } while (block != null);
+        assert numInstructionsInserted == instructionToInsertForArray.size();
       }
     }
     assert code.isConsistentSSA(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index 2cab3fb..4a99fb4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -66,12 +66,12 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.outliner.OutlineCollection;
 import com.android.tools.r8.ir.optimize.outliner.Outliner;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -1830,7 +1830,7 @@
     }
 
     @Override
-    public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
       return null;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index a649af6..6fb553c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -18,6 +18,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
@@ -68,6 +69,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.MemberType;
@@ -1060,6 +1062,10 @@
             instruction.asInstancePut(), code, context, enumClass, enumValue);
       case INVOKE_DIRECT:
       case INVOKE_INTERFACE:
+        return analyzeInvokeUser(instruction.asInvokeMethod(), code, context, enumClass, enumValue);
+      case INVOKE_NEW_ARRAY:
+        return analyzeInvokeNewArrayUser(
+            instruction.asInvokeNewArray(), code, context, enumClass, enumValue);
       case INVOKE_STATIC:
       case INVOKE_SUPER:
       case INVOKE_VIRTUAL:
@@ -1130,6 +1136,40 @@
     return Reason.INVALID_ARRAY_PUT;
   }
 
+  private Reason analyzeInvokeNewArrayUser(
+      InvokeNewArray invokeNewArray,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    // MyEnum[] array = new MyEnum[] { MyEnum.A }; is valid.
+    // We need to prove that the value to put in and the array have correct types.
+    TypeElement arrayType = invokeNewArray.getOutType();
+    assert arrayType.isArrayType();
+
+    ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
+    if (arrayBaseType == null) {
+      assert false;
+      return Reason.INVALID_INVOKE_NEW_ARRAY;
+    }
+    if (arrayBaseType.getClassType() != enumClass.type) {
+      return Reason.INVALID_INVOKE_NEW_ARRAY;
+    }
+
+    for (Value value : invokeNewArray.inValues()) {
+      TypeElement valueBaseType = value.getType();
+      if (valueBaseType.isArrayType()) {
+        assert valueBaseType.asArrayType().getBaseType().isClassType();
+        assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
+        valueBaseType = valueBaseType.asArrayType().getBaseType();
+      }
+      if (!arrayBaseType.equalUpToNullability(valueBaseType)) {
+        return Reason.INVALID_INVOKE_NEW_ARRAY;
+      }
+    }
+    return Reason.ELIGIBLE;
+  }
+
   private Reason analyzeCheckCastUser(
       CheckCast checkCast,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
index 6e187ae..4e7801c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -19,9 +19,9 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxerImpl;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 
 /**
  * A special code object used by enum unboxing that supplies IR from an existing method
@@ -128,7 +128,7 @@
   }
 
   @Override
-  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
     return toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 3e17a0a..7cb0290 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -32,6 +32,8 @@
       new StringReason("IMPLICIT_UP_CAST_IN_RETURN");
   public static final Reason INVALID_FIELD_PUT = new StringReason("INVALID_FIELD_PUT");
   public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
+  public static final Reason INVALID_INVOKE_NEW_ARRAY =
+      new StringReason("INVALID_INVOKE_NEW_ARRAY");
   public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
   public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
   public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT");
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index 8c28b9e..ad0b54c 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -21,8 +21,8 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SourceCode;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import java.util.function.Consumer;
 
 public abstract class AbstractSynthesizedCode extends Code {
@@ -73,7 +73,7 @@
 
   @Override
   public final String toString() {
-    return toString(null, null);
+    return toString(null, RetracerForCodePrinting.empty());
   }
 
   @Override
@@ -101,7 +101,7 @@
   }
 
   @Override
-  public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
+  public final String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
     return this.getClass().getSimpleName();
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 3e4078b..6e14907 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -424,12 +425,16 @@
 
   // Perform a conservative evaluation of an array content of dex type values from its construction
   // until its use at a given instruction.
-  private static DexType[] evaluateTypeArrayContentFromConstructionToUse(
-      NewArrayEmpty newArray,
-      List<CheckCast> aliases,
-      int size,
-      Instruction user,
-      DexItemFactory factory) {
+  private static DexTypeList evaluateTypeArrayContentFromConstructionToUse(
+      NewArrayEmpty newArray, List<CheckCast> aliases, Instruction user, DexItemFactory factory) {
+    int size = newArray.sizeIfConst();
+    if (size < 0) {
+      return null;
+    } else if (size == 0) {
+      // TODO: We should likely still scan to ensure no ArrayPut instructions exist.
+      return DexTypeList.empty();
+    }
+
     DexType[] values = new DexType[size];
     int remaining = size;
     Set<Instruction> users = Sets.newIdentityHashSet();
@@ -453,7 +458,7 @@
         if (instruction == user) {
           // Return the array content if all elements are known when hitting the user for which
           // the content was requested.
-          return remaining == 0 ? values : null;
+          return remaining == 0 ? new DexTypeList(values) : null;
         }
         // Any other kinds of use besides array-put mean that the array escapes and its content
         // could be altered.
@@ -498,6 +503,21 @@
     return null;
   }
 
+  private static DexTypeList evaluateTypeArrayContent(
+      InvokeNewArray newArray, DexItemFactory factory) {
+    List<Value> arrayValues = newArray.inValues();
+    int size = arrayValues.size();
+    DexType[] values = new DexType[size];
+    for (int i = 0; i < size; ++i) {
+      DexType type = getTypeFromConstClassOrBoxedPrimitive(arrayValues.get(i), factory);
+      if (type == null) {
+        return null;
+      }
+      values[i] = type;
+    }
+    return new DexTypeList(values);
+  }
+
   /**
    * Visits all {@link ArrayPut}'s with the given {@param classListValue} as array and {@link Class}
    * as value. Then collects all corresponding {@link DexType}s so as to determine reflective cases.
@@ -551,30 +571,13 @@
     }
 
     // Make sure this Value refers to a new array.
-    if (!classListValue.definition.isNewArrayEmpty()
-        || !classListValue.definition.asNewArrayEmpty().size().isConstant()) {
+    if (classListValue.definition.isNewArrayEmpty()) {
+      return evaluateTypeArrayContentFromConstructionToUse(
+          classListValue.definition.asNewArrayEmpty(), aliases, invoke, factory);
+    } else if (classListValue.definition.isInvokeNewArray()) {
+      return evaluateTypeArrayContent(classListValue.definition.asInvokeNewArray(), factory);
+    } else {
       return null;
     }
-
-    int size =
-        classListValue
-            .definition
-            .asNewArrayEmpty()
-            .size()
-            .getConstInstruction()
-            .asConstNumber()
-            .getIntValue();
-    if (size == 0) {
-      return DexTypeList.empty();
-    }
-
-    DexType[] arrayContent =
-        evaluateTypeArrayContentFromConstructionToUse(
-            classListValue.definition.asNewArrayEmpty(), aliases, size, invoke, factory);
-
-    if (arrayContent == null) {
-      return null;
-    }
-    return new DexTypeList(arrayContent);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index 0dbea46..2e6fb53 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -87,11 +87,11 @@
 
   private DexMethod getReboundMethodReference(DexMethod method) {
     DexMethod rebound = nonReboundMethodReferenceToDefinitionMap.get(method);
+    assert method != rebound;
     while (rebound != null) {
       method = rebound;
       rebound = nonReboundMethodReferenceToDefinitionMap.get(method);
     }
-    assert method != rebound;
     return method;
   }
 
diff --git a/src/main/java/com/android/tools/r8/references/FieldReference.java b/src/main/java/com/android/tools/r8/references/FieldReference.java
index dab6fd8..90f4b91 100644
--- a/src/main/java/com/android/tools/r8/references/FieldReference.java
+++ b/src/main/java/com/android/tools/r8/references/FieldReference.java
@@ -65,6 +65,14 @@
 
   @Override
   public String toString() {
-    return getHolderClass().toString() + getFieldName() + ":" + getFieldType().getDescriptor();
+    return getHolderClass() + getFieldName() + ":" + getFieldType().getDescriptor();
+  }
+
+  public String toSourceString() {
+    return getFieldType().getTypeName()
+        + " "
+        + getHolderClass().getTypeName()
+        + "."
+        + getFieldName();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/references/MethodReference.java b/src/main/java/com/android/tools/r8/references/MethodReference.java
index 5ef8596..3339916 100644
--- a/src/main/java/com/android/tools/r8/references/MethodReference.java
+++ b/src/main/java/com/android/tools/r8/references/MethodReference.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.KeepForRetraceApi;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import java.util.List;
@@ -78,8 +77,7 @@
   }
 
   public String getMethodDescriptor() {
-    return StringUtils.join(
-            "", ListUtils.map(getFormalTypes(), TypeReference::getDescriptor), BraceType.PARENS)
+    return StringUtils.join("", getFormalTypes(), TypeReference::getDescriptor, BraceType.PARENS)
         + (getReturnType() == null ? "V" : getReturnType().getDescriptor());
   }
 
@@ -87,4 +85,16 @@
   public String toString() {
     return getHolderClass() + getMethodName() + getMethodDescriptor();
   }
+
+  public String toSourceString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append(returnType == null ? "void" : returnType.getTypeName());
+    sb.append(" ");
+    sb.append(holderClass.getTypeName());
+    sb.append(".");
+    sb.append(methodName);
+    sb.append(
+        StringUtils.join(", ", getFormalTypes(), TypeReference::getTypeName, BraceType.PARENS));
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceTypeElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceTypeElement.java
new file mode 100644
index 0000000..2df0a4c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeElement.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetraceTypeElement extends RetraceElement<RetraceTypeResult> {
+
+  RetracedTypeReference getType();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
index 30a4c5c..7925ff6 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
@@ -5,21 +5,6 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
 
 @Keep
-public interface RetraceTypeResult {
-
-  Stream<Element> stream();
-
-  RetraceTypeResult forEach(Consumer<Element> resultConsumer);
-
-  boolean isAmbiguous();
-
-  @Keep
-  interface Element {
-
-    RetracedTypeReference getType();
-  }
-}
+public interface RetraceTypeResult extends RetraceResult<RetraceTypeElement> {}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedClassReference.java b/src/main/java/com/android/tools/r8/retrace/RetracedClassReference.java
index 99e85be..5e6b29b 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedClassReference.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedClassReference.java
@@ -12,6 +12,8 @@
 
   String getTypeName();
 
+  String getDescriptor();
+
   String getBinaryName();
 
   RetracedTypeReference getRetracedType();
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java
index 6242c3b..d1ee952 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java
@@ -4,61 +4,94 @@
 
 package com.android.tools.r8.retrace.internal;
 
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetraceTypeElement;
 import com.android.tools.r8.retrace.RetraceTypeResult;
 import com.android.tools.r8.retrace.RetracedTypeReference;
 import com.android.tools.r8.retrace.Retracer;
+import com.android.tools.r8.utils.ListUtils;
+import java.util.Collections;
+import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 public class RetraceTypeResultImpl implements RetraceTypeResult {
 
   private final TypeReference obfuscatedType;
+  private final List<RetracedTypeReference> retracedTypeReferences;
   private final Retracer retracer;
 
-  private RetraceTypeResultImpl(TypeReference obfuscatedType, Retracer retracer) {
+  private RetraceTypeResultImpl(
+      TypeReference obfuscatedType,
+      List<RetracedTypeReference> retracedTypeReferences,
+      Retracer retracer) {
     this.obfuscatedType = obfuscatedType;
+    this.retracedTypeReferences = retracedTypeReferences;
     this.retracer = retracer;
   }
 
   static RetraceTypeResultImpl create(TypeReference obfuscatedType, Retracer retracer) {
-    return new RetraceTypeResultImpl(obfuscatedType, retracer);
+    // Handle void and primitive types as single element results.
+    return new RetraceTypeResultImpl(
+        obfuscatedType, retraceTypeReference(obfuscatedType, retracer), retracer);
+  }
+
+  private static List<RetracedTypeReference> retraceTypeReference(
+      TypeReference obfuscatedType, Retracer retracer) {
+    if (obfuscatedType == null) {
+      return Collections.emptyList();
+    } else if (obfuscatedType.isPrimitive()) {
+      return Collections.singletonList(RetracedTypeReferenceImpl.create(obfuscatedType));
+    } else if (obfuscatedType.isArray()) {
+      int dimensions = obfuscatedType.asArray().getDimensions();
+      List<RetracedTypeReference> baseTypeRetraceResult =
+          retraceTypeReference(obfuscatedType.asArray().getBaseType(), retracer);
+      return ListUtils.map(
+          baseTypeRetraceResult,
+          retraceTypeReference ->
+              RetracedTypeReferenceImpl.create(
+                  Reference.array(retraceTypeReference.getTypeReference(), dimensions)));
+    } else {
+      assert obfuscatedType.isClass();
+      return retracer.retraceClass(obfuscatedType.asClass()).stream()
+          .map(clazz -> clazz.getRetracedClass().getRetracedType())
+          .collect(Collectors.toList());
+    }
   }
 
   @Override
-  public Stream<Element> stream() {
-    // Handle void and primitive types as single element results.
-    if (obfuscatedType == null || obfuscatedType.isPrimitive()) {
-      return Stream.of(new ElementImpl(RetracedTypeReferenceImpl.create(obfuscatedType)));
-    }
-    if (obfuscatedType.isArray()) {
-      int dimensions = obfuscatedType.asArray().getDimensions();
-      return retracer.retraceType(obfuscatedType.asArray().getBaseType()).stream()
-          .map(
-              baseElement ->
-                  new ElementImpl(
-                      RetracedTypeReferenceImpl.create(baseElement.getType().toArray(dimensions))));
-    }
-    return retracer.retraceClass(obfuscatedType.asClass()).stream()
-        .map(classElement -> new ElementImpl(classElement.getRetracedClass().getRetracedType()));
+  public Stream<RetraceTypeElement> stream() {
+    List<RetraceTypeElement> map =
+        ListUtils.map(
+            retracedTypeReferences,
+            retracedTypeReference -> new ElementImpl(this, retracedTypeReference));
+    return map.stream();
   }
 
   @Override
   public boolean isAmbiguous() {
-    return false;
+    return retracedTypeReferences.size() > 1;
   }
 
   @Override
-  public RetraceTypeResultImpl forEach(Consumer<Element> resultConsumer) {
+  public void forEach(Consumer<RetraceTypeElement> resultConsumer) {
     stream().forEach(resultConsumer);
-    return this;
   }
 
-  public static class ElementImpl implements RetraceTypeResult.Element {
+  @Override
+  public boolean isEmpty() {
+    return retracedTypeReferences.size() == 0;
+  }
 
+  public static class ElementImpl implements RetraceTypeElement {
+
+    private final RetraceTypeResult typeResult;
     private final RetracedTypeReference retracedType;
 
-    public ElementImpl(RetracedTypeReference retracedType) {
+    private ElementImpl(RetraceTypeResult typeResult, RetracedTypeReference retracedType) {
+      this.typeResult = typeResult;
       this.retracedType = retracedType;
     }
 
@@ -66,5 +99,15 @@
     public RetracedTypeReference getType() {
       return retracedType;
     }
+
+    @Override
+    public RetraceTypeResult getParentResult() {
+      return typeResult;
+    }
+
+    @Override
+    public boolean isCompilerSynthesized() {
+      return false;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassReferenceImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassReferenceImpl.java
index ef23ef1..d036009 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassReferenceImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassReferenceImpl.java
@@ -26,6 +26,11 @@
   }
 
   @Override
+  public String getDescriptor() {
+    return classReference.getDescriptor();
+  }
+
+  @Override
   public String getBinaryName() {
     return classReference.getBinaryName();
   }
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 a8b5e71..c907105 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
@@ -15,8 +15,8 @@
 import com.android.tools.r8.retrace.RetraceStackTraceElementProxy;
 import com.android.tools.r8.retrace.RetraceStackTraceElementProxyResult;
 import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
+import com.android.tools.r8.retrace.RetraceTypeElement;
 import com.android.tools.r8.retrace.RetraceTypeResult;
-import com.android.tools.r8.retrace.RetraceTypeResult.Element;
 import com.android.tools.r8.retrace.RetracedClassReference;
 import com.android.tools.r8.retrace.RetracedFieldReference;
 import com.android.tools.r8.retrace.RetracedMethodReference;
@@ -272,7 +272,8 @@
     } else {
       TypeReference typeReference = Reference.typeFromTypeName(elementOrReturnType);
       RetraceTypeResult retraceTypeResult = retracer.retraceType(typeReference);
-      List<Element> retracedElements = retraceTypeResult.stream().collect(Collectors.toList());
+      List<RetraceTypeElement> retracedElements =
+          retraceTypeResult.stream().collect(Collectors.toList());
       return resultBuilder
           .setResultStream(
               currentResult.stream()
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 213819d..93d387e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -104,7 +104,9 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
@@ -5079,25 +5081,43 @@
       return;
     }
     Value parametersValue = constructorDefinition.inValues().get(1);
-    if (parametersValue.isPhi() || !parametersValue.definition.isNewArrayEmpty()) {
+    if (parametersValue.isPhi()) {
       // Give up, we can't tell which constructor is being invoked.
       return;
     }
-
-    Value parametersSizeValue = parametersValue.definition.asNewArrayEmpty().size();
-    if (parametersSizeValue.isPhi() || !parametersSizeValue.definition.isConstNumber()) {
-      // Give up, we can't tell which constructor is being invoked.
+    NewArrayEmpty newArrayEmpty = parametersValue.definition.asNewArrayEmpty();
+    InvokeNewArray invokeNewArray = parametersValue.definition.asInvokeNewArray();
+    int parametersSize =
+        newArrayEmpty != null
+            ? newArrayEmpty.sizeIfConst()
+            : invokeNewArray != null ? invokeNewArray.size() : -1;
+    if (parametersSize < 0) {
       return;
     }
 
     ProgramMethod initializer = null;
 
-    int parametersSize = parametersSizeValue.definition.asConstNumber().getIntValue();
     if (parametersSize == 0) {
       initializer = clazz.getProgramDefaultInitializer();
     } else {
       DexType[] parameterTypes = new DexType[parametersSize];
       int missingIndices = parametersSize;
+
+      if (newArrayEmpty != null) {
+        missingIndices = parametersSize;
+      } else {
+        missingIndices = 0;
+        List<Value> values = invokeNewArray.inValues();
+        for (int i = 0; i < parametersSize; ++i) {
+          DexType type =
+              ConstantValueUtils.getDexTypeRepresentedByValueForTracing(values.get(i), appView);
+          if (type == null) {
+            return;
+          }
+          parameterTypes[i] = type;
+        }
+      }
+
       for (Instruction user : parametersValue.uniqueUsers()) {
         if (user.isArrayPut()) {
           ArrayPut arrayPutInstruction = user.asArrayPut();
@@ -5159,20 +5179,31 @@
     }
 
     Value interfacesValue = invoke.arguments().get(1);
-    if (interfacesValue.isPhi() || !interfacesValue.definition.isNewArrayEmpty()) {
+    if (interfacesValue.isPhi()) {
       // Give up, we can't tell which interfaces the proxy implements.
       return;
     }
 
-    WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
-    for (Instruction user : interfacesValue.uniqueUsers()) {
-      if (!user.isArrayPut()) {
-        continue;
+    InvokeNewArray invokeNewArray = interfacesValue.definition.asInvokeNewArray();
+    NewArrayEmpty newArrayEmpty = interfacesValue.definition.asNewArrayEmpty();
+    List<Value> values;
+    if (invokeNewArray != null) {
+      values = invokeNewArray.inValues();
+    } else if (newArrayEmpty != null) {
+      values = new ArrayList<>(interfacesValue.uniqueUsers().size());
+      for (Instruction user : interfacesValue.uniqueUsers()) {
+        ArrayPut arrayPut = user.asArrayPut();
+        if (arrayPut != null) {
+          values.add(arrayPut.value());
+        }
       }
+    } else {
+      return;
+    }
 
-      ArrayPut arrayPut = user.asArrayPut();
-      DexType type =
-          ConstantValueUtils.getDexTypeRepresentedByValueForTracing(arrayPut.value(), appView);
+    WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
+    for (Value value : values) {
+      DexType type = ConstantValueUtils.getDexTypeRepresentedByValueForTracing(value, appView);
       if (type == null || !type.isClassType()) {
         continue;
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 7170817..57726f6 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -156,6 +156,11 @@
     if (clazz == null) {
       return;
     }
+    if (clazz.isLibraryClass()) {
+      // TODO(b/259204069): Mitigation of invalid interface removal.
+      // It would be nice to integrate this with the api database.
+      return;
+    }
     for (DexType itf : clazz.interfaces) {
       if (interfaces.remove(itf) && interfaces.isEmpty()) {
         return;
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index cdac93d..443b9e7 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -4,12 +4,11 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.structural.Ordered;
 import java.util.Optional;
 
-/**
- * Android dex version
- */
-public enum DexVersion {
+/** Android dex version */
+public enum DexVersion implements Ordered<DexVersion> {
   V35(35, new byte[] {'0', '3', '5'}),
   V37(37, new byte[] {'0', '3', '7'}),
   V38(38, new byte[] {'0', '3', '8'}),
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index e653a7a..98d5a27 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -816,6 +816,7 @@
 
   public boolean debug = false;
 
+  private final RewriteArrayOptions rewriteArrayOptions = new RewriteArrayOptions();
   private final CallSiteOptimizationOptions callSiteOptimizationOptions =
       new CallSiteOptimizationOptions();
   private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
@@ -850,6 +851,10 @@
 
   public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON;
 
+  public RewriteArrayOptions rewriteArrayOptions() {
+    return rewriteArrayOptions;
+  }
+
   public CallSiteOptimizationOptions callSiteOptimizationOptions() {
     return callSiteOptimizationOptions;
   }
@@ -1383,6 +1388,42 @@
         System.getProperty("com.android.tools.r8.lambdaClassFieldsNotFinal") == null;
   }
 
+  public class RewriteArrayOptions {
+    // Arbitrary limit of number of inputs to new-filled-array/range.
+    // The technical limit is 255 (Constants.U8BIT_MAX).
+    public int maxRangeInputs = 200;
+    // Arbitrary limit of number of inputs to fill-array-data.
+    public int maxFillArrayDataInputs = 8 * 1024;
+
+    // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
+    // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
+    // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
+    // removed during the jelly-bean release cycle and is not there from kitkat.
+    //
+    // Buggy code that accidentally call code that only works on primitives arrays.
+    //
+    // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
+    public boolean canUseFilledNewArrayOfStrings() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.K);
+    }
+
+    // When adding support for emitting new-filled-array for non-String types, ART 6.0.1 had issues.
+    // https://ci.chromium.org/ui/p/r8/builders/ci/linux-android-6.0.1/6507/overview
+    // It somehow had a new-array-filled return null.
+    public boolean canUseFilledNewArrayOfObjects() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.N);
+    }
+
+    // Dalvik doesn't handle new-filled-array with arrays as values. It fails with:
+    // W(629880) VFY: [Ljava/lang/Integer; is not instance of Ljava/lang/Integer;  (dalvikvm)
+    public boolean canUseFilledNewArrayOfArrays() {
+      assert isGeneratingDex();
+      return hasFeaturePresentFrom(AndroidApiLevel.L);
+    }
+  }
+
   public class CallSiteOptimizationOptions {
 
     private boolean enabled = true;
@@ -2387,19 +2428,6 @@
     return hasFeaturePresentFrom(AndroidApiLevel.J);
   }
 
-  // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
-  // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
-  // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
-  // removed during the jelly-bean release cycle and is not there from kitkat.
-  //
-  // Buggy code that accidentally call code that only works on primitives arrays.
-  //
-  // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
-  public boolean canUseFilledNewArrayOfObjects() {
-    assert isGeneratingDex();
-    return hasFeaturePresentFrom(AndroidApiLevel.K);
-  }
-
   // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
   // where an aget-wide instruction using the same register for the array
   // and the first register of the result could lead to the wrong exception
diff --git a/src/main/java/com/android/tools/r8/utils/RetracerForCodePrinting.java b/src/main/java/com/android/tools/r8/utils/RetracerForCodePrinting.java
new file mode 100644
index 0000000..1c5f9b3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/RetracerForCodePrinting.java
@@ -0,0 +1,176 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.IndexedDexItem;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.retrace.RetraceClassElement;
+import com.android.tools.r8.retrace.RetraceElement;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceResult;
+import com.android.tools.r8.retrace.RetracedFieldReference;
+import com.android.tools.r8.retrace.RetracedFieldReference.KnownRetracedFieldReference;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedMethodReference.KnownRetracedMethodReference;
+import com.android.tools.r8.retrace.Retracer;
+import com.android.tools.r8.retrace.internal.MappingSupplierInternalImpl;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
+import java.util.function.Function;
+
+public class RetracerForCodePrinting {
+
+  private static final RetracerForCodePrinting EMPTY = new RetracerForCodePrinting(null);
+
+  public static RetracerForCodePrinting empty() {
+    return EMPTY;
+  }
+
+  private final Retracer retracer;
+
+  private RetracerForCodePrinting(Retracer retracer) {
+    this.retracer = retracer;
+  }
+
+  public static RetracerForCodePrinting create(
+      ClassNameMapper classNameMapper, DiagnosticsHandler handler) {
+    return classNameMapper == null
+        ? empty()
+        : new RetracerForCodePrinting(
+            RetracerImpl.createInternal(
+                MappingSupplierInternalImpl.createInternal(classNameMapper), handler));
+  }
+
+  public <T extends RetraceElement<?>> String joinAmbiguousResults(
+      RetraceResult<T> retraceResult, Function<T, String> nameToString) {
+    return StringUtils.join(" <OR> ", retraceResult.stream(), nameToString);
+  }
+
+  private String typeToString(
+      DexType type,
+      Function<DexType, String> noRetraceString,
+      Function<RetraceClassElement, String> retraceResult) {
+    return retracer == null
+        ? noRetraceString.apply(type)
+        : joinAmbiguousResults(retracer.retraceClass(type.asClassReference()), retraceResult);
+  }
+
+  public String toSourceString(DexType type) {
+    return typeToString(
+        type, DexType::toSourceString, element -> element.getRetracedClass().getTypeName());
+  }
+
+  public String toDescriptor(DexType type) {
+    return typeToString(
+        type, DexType::toDescriptorString, element -> element.getRetracedClass().getDescriptor());
+  }
+
+  private String retraceMethodToString(
+      DexMethod method,
+      Function<DexMethod, String> noRetraceString,
+      Function<KnownRetracedMethodReference, String> knownToString,
+      Function<RetracedMethodReference, String> unknownToString) {
+    if (retracer == null) {
+      return noRetraceString.apply(method);
+    }
+    // TODO(b/169953605): Use retracer.retraceMethod() when we have enough information.
+    MethodReference methodReference = method.asMethodReference();
+    RetraceMethodResult retraceMethodResult =
+        retracer
+            .retraceClass(methodReference.getHolderClass())
+            .lookupMethod(methodReference.getMethodName());
+    return joinAmbiguousResults(
+        retraceMethodResult,
+        element -> {
+          if (element.isUnknown()) {
+            return unknownToString.apply(element.getRetracedMethod());
+          } else {
+            return knownToString.apply(element.getRetracedMethod().asKnown());
+          }
+        });
+  }
+
+  public String toSourceString(DexMethod method) {
+    return retraceMethodToString(
+        method,
+        DexMethod::toSourceString,
+        knownRetracedMethodReference ->
+            knownRetracedMethodReference.getMethodReference().toSourceString(),
+        unknown -> unknown.getHolderClass().getTypeName() + " " + unknown.getMethodName());
+  }
+
+  public String toDescriptor(DexMethod method) {
+    return retraceMethodToString(
+        method,
+        m -> m.asMethodReference().toString(),
+        knownRetracedMethodReference ->
+            knownRetracedMethodReference.getMethodReference().toString(),
+        unknown -> unknown.getHolderClass().getDescriptor() + unknown.getMethodName());
+  }
+
+  private String retraceFieldToString(
+      DexField field,
+      Function<DexField, String> noRetraceString,
+      Function<KnownRetracedFieldReference, String> knownToString,
+      Function<RetracedFieldReference, String> unknownToString) {
+    if (retracer == null) {
+      return noRetraceString.apply(field);
+    }
+    // TODO(b/169953605): Use retracer.retraceField() when we have enough information.
+    FieldReference fieldReference = field.asFieldReference();
+    return joinAmbiguousResults(
+        retracer
+            .retraceClass(fieldReference.getHolderClass())
+            .lookupField(fieldReference.getFieldName()),
+        element -> {
+          if (element.isUnknown()) {
+            return unknownToString.apply(element.getField());
+          } else {
+            return knownToString.apply(element.getField().asKnown());
+          }
+        });
+  }
+
+  public String toSourceString(DexField field) {
+    return retraceFieldToString(
+        field,
+        f -> f.asFieldReference().toSourceString(),
+        known -> known.getFieldReference().toSourceString(),
+        unknown -> unknown.getHolderClass().getDescriptor() + " " + unknown.getFieldName());
+  }
+
+  public String toDescriptor(DexField field) {
+    return retraceFieldToString(
+        field,
+        f -> f.asFieldReference().toString(),
+        known -> known.getFieldReference().toString(),
+        unknown -> unknown.getHolderClass().getDescriptor() + unknown.getFieldName());
+  }
+
+  public String toDescriptor(IndexedDexItem item) {
+    if (!(item instanceof DexReference)) {
+      return item.toString();
+    }
+    return ((DexReference) item).apply(this::toDescriptor, this::toDescriptor, this::toDescriptor);
+  }
+
+  public String toSourceString(IndexedDexItem item) {
+    if (!(item instanceof DexReference)) {
+      return item.toSourceString();
+    }
+    return ((DexReference) item)
+        .apply(this::toSourceString, this::toSourceString, this::toSourceString);
+  }
+
+  public boolean isEmpty() {
+    return this == EMPTY;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index f1e4e18..69cef74 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -15,6 +15,8 @@
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class StringUtils {
   public static char[] EMPTY_CHAR_ARRAY = {};
@@ -143,6 +145,15 @@
     return join(separator, iterable, fn, BraceType.NONE);
   }
 
+  public static <T> String join(String separator, Stream<T> stream, Function<T, String> fn) {
+    return join(separator, stream.collect(Collectors.toList()), fn, BraceType.NONE);
+  }
+
+  public static <T> String join(
+      String separator, T[] elements, Function<T, String> fn, BraceType brace) {
+    return join(separator, Arrays.asList(elements), fn, brace);
+  }
+
   public static <T> String join(String separator, Iterable<T> iterable, BraceType brace) {
     return join(separator, iterable, Object::toString, brace);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
index 50d23c9..7d41ae1 100644
--- a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 
@@ -32,4 +33,18 @@
     }
     return result;
   }
+
+  public static <T, E extends Exception> ThrowingIterator<T, E> fromIterator(Iterator<T> it) {
+    return new ThrowingIterator<>() {
+      @Override
+      public boolean hasNext() {
+        return it.hasNext();
+      }
+
+      @Override
+      public T next() throws E {
+        return it.next();
+      }
+    };
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 0230b46..5c73e63 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.keepanno.utils.Unimplemented;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.shaking.FilteredClassPath;
@@ -32,6 +33,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -66,6 +68,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -712,12 +715,28 @@
   private static final Path DX = getDxExecutablePath();
 
   private static Path getDexVmPath(DexVm vm) {
-    return Paths.get(
-        TOOLS,
-        "linux",
-        vm.getVersion() == DexVm.Version.DEFAULT
-            ? "art"
-            : "art-" + vm.getVersion());
+    DexVm.Version version = vm.getVersion();
+    Path base = Paths.get(TOOLS, "linux");
+    switch (version) {
+      case DEFAULT:
+        return base.resolve("art");
+      case V4_0_4:
+      case V4_4_4:
+      case V5_1_1:
+      case V6_0_1:
+      case V7_0_0:
+      case V8_1_0:
+      case V9_0_0:
+      case V10_0_0:
+        return base.resolve("art-" + version);
+      case V12_0_0:
+        return base.resolve("host").resolve("art-12.0.0-beta4");
+      case V13_0_0:
+      case MASTER:
+        return base.resolve("host").resolve("art-" + version);
+      default:
+        throw new Unreachable();
+    }
   }
 
   private static Path getDexVmLibPath(DexVm vm) {
@@ -733,7 +752,25 @@
   }
 
   private static String getArchString(DexVm vm) {
-    return vm.isOlderThanOrEqual(DexVm.ART_5_1_1_HOST) ? "arm" : "arm64";
+    switch (vm.getVersion()) {
+      case V4_0_4:
+      case V4_4_4:
+      case V5_1_1:
+        return "arm";
+      case V6_0_1:
+      case V7_0_0:
+      case V8_1_0:
+      case DEFAULT:
+      case V9_0_0:
+      case V10_0_0:
+        return "arm64";
+      case V12_0_0:
+      case V13_0_0:
+      case MASTER:
+        return "x86_64";
+      default:
+        throw new Unimplemented();
+    }
   }
 
   private static Path getProductBootImagePath(DexVm vm) {
@@ -1057,6 +1094,10 @@
     }
   }
 
+  public static DexVersion getDexFileVersionForVm(DexVm vm) {
+    return DexVersion.getDexVersion(getMinApiLevelForDexVm(vm));
+  }
+
   private static String getPlatform() {
     return System.getProperty("os.name");
   }
@@ -1992,34 +2033,56 @@
     }
   }
 
-  public static ProcessResult runDex2OatRaw(Path file, Path outFile, DexVm vm) throws IOException {
-    // TODO(jmhenaff): find a way to run this on windows (push dex and run on device/emulator?)
+  // Checked in VMs for which dex2oat should work specified in decreasing order.
+  private static List<DexVm> SUPPORTED_DEX2OAT_VMS =
+      ImmutableList.of(DexVm.ART_12_0_0_HOST, DexVm.ART_6_0_1_HOST);
+
+  public static ProcessResult runDex2OatRaw(Path file, Path outFile, DexVm targetVm)
+      throws IOException {
     Assume.assumeTrue(ToolHelper.isDex2OatSupported());
-    Assume.assumeFalse(
-        "b/144975341",
-        vm.version == DexVm.Version.V10_0_0
-            || vm.version == DexVm.Version.V12_0_0
-            || vm.version == DexVm.Version.V13_0_0);
-    if (vm.isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
-      // Run default dex2oat for tests on dalvik runtimes.
-      vm = DexVm.ART_DEFAULT;
-    }
     assert Files.exists(file);
     assert ByteStreams.toByteArray(Files.newInputStream(file)).length > 0;
+    assert SUPPORTED_DEX2OAT_VMS.stream()
+        .sorted(Comparator.comparing(DexVm::getVersion).reversed())
+        .collect(Collectors.toList())
+        .equals(SUPPORTED_DEX2OAT_VMS);
+    DexVersion requiredDexFileVersion = getDexFileVersionForVm(targetVm);
+    DexVm vm = null;
+    for (DexVm supported : SUPPORTED_DEX2OAT_VMS) {
+      DexVersion supportedDexFileVersion = getDexFileVersionForVm(supported);
+      // This and remaining VMs can't compile code at the required DEX version.
+      if (supportedDexFileVersion.isLessThan(requiredDexFileVersion)) {
+        break;
+      }
+      // Find the "oldest" supported VM. Only consider VMs older than targetVm if no VM matched.
+      if (supported.isNewerThanOrEqual(targetVm) || vm == null) {
+        vm = supported;
+      }
+    }
+    if (vm == null) {
+      throw new Unimplemented("Unable to find a supported dex2oat for VM " + vm);
+    }
     List<String> command = new ArrayList<>();
-    command.add(getDex2OatPath(vm).toString());
-    command.add("--android-root=" + getProductPath(vm) + "/system");
+    command.add(getDex2OatPath(vm).toAbsolutePath().toString());
+    command.add("--android-root=" + getProductPath(vm).toAbsolutePath() + "/system");
     command.add("--runtime-arg");
     command.add("-verbose:verifier");
     command.add("--runtime-arg");
     command.add("-Xnorelocate");
     command.add("--dex-file=" + file.toAbsolutePath());
     command.add("--oat-file=" + outFile.toAbsolutePath());
-    // TODO(zerny): Create a proper interface for invoking dex2oat. Hardcoding arch here is a hack!
     command.add("--instruction-set=" + getArchString(vm));
+    if (vm.version.equals(DexVm.Version.V12_0_0)) {
+      command.add(
+          "--boot-image="
+              + getDexVmPath(vm).toAbsolutePath()
+              + "/apex/art_boot_images/javalib/boot.art");
+    }
     ProcessBuilder builder = new ProcessBuilder(command);
+    builder.directory(getDexVmPath(vm).toFile());
     builder.environment().put("LD_LIBRARY_PATH", getDexVmLibPath(vm).toString());
-    return runProcess(builder);
+    ProcessResult processResult = runProcess(builder);
+    return processResult;
   }
 
   public static ProcessResult runProguardRaw(
@@ -2192,8 +2255,15 @@
 
   public static ProcessResult runProcess(ProcessBuilder builder, PrintStream out)
       throws IOException {
+    boolean printCwd = builder.directory() != null;
+    if (printCwd) {
+      out.println("(cd " + builder.directory().toString() + "; ");
+    }
     String command = String.join(" ", builder.command());
     out.println(command);
+    if (printCwd) {
+      out.println(")");
+    }
     return drainProcessOutputStreams(builder.start(), command);
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockAbstractMethodOnBaseToOutlineTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockAbstractMethodOnBaseToOutlineTest.java
new file mode 100644
index 0000000..380702e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockAbstractMethodOnBaseToOutlineTest.java
@@ -0,0 +1,186 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelMockAbstractMethodOnBaseToOutlineTest extends TestBase {
+
+  private final AndroidApiLevel subMockLevel = AndroidApiLevel.M;
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private boolean isGreaterOrEqualToMockLevel() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(subMockLevel);
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addProgramClasses(Main.class)
+        .addLibraryClasses(
+            LibraryClass.class, OtherLibraryClass.class, SubLibraryClassAtLaterApiLevel.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(setMockApiLevelForClass(LibraryClass.class, AndroidApiLevel.B))
+        .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, AndroidApiLevel.B))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClass.class.getDeclaredMethod("foo"), AndroidApiLevel.B))
+        .apply(
+            setMockApiLevelForMethod(
+                OtherLibraryClass.class.getDeclaredMethod("baz"), subMockLevel))
+        .apply(setMockApiLevelForClass(SubLibraryClassAtLaterApiLevel.class, subMockLevel))
+        .apply(
+            setMockApiLevelForDefaultInstanceInitializer(
+                SubLibraryClassAtLaterApiLevel.class, subMockLevel))
+        .apply(
+            setMockApiLevelForMethod(
+                SubLibraryClassAtLaterApiLevel.class.getDeclaredMethod("foo"), subMockLevel));
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .addBootClasspathClasses(LibraryClass.class)
+        .applyIf(
+            isGreaterOrEqualToMockLevel(),
+            b ->
+                b.addBootClasspathClasses(
+                    OtherLibraryClass.class, SubLibraryClassAtLaterApiLevel.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(runResult -> checkOutput(runResult, false));
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .addBootClasspathClasses(LibraryClass.class)
+        .applyIf(
+            isGreaterOrEqualToMockLevel(),
+            b ->
+                b.addBootClasspathClasses(
+                    OtherLibraryClass.class, SubLibraryClassAtLaterApiLevel.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(runResult -> checkOutput(runResult, true));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .compile()
+        .inspect(this::inspect)
+        .addBootClasspathClasses(LibraryClass.class)
+        .applyIf(
+            isGreaterOrEqualToMockLevel(),
+            b ->
+                b.addBootClasspathClasses(
+                    OtherLibraryClass.class, SubLibraryClassAtLaterApiLevel.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(runResult -> checkOutput(runResult, true));
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult, boolean isRelease) {
+    if (isGreaterOrEqualToMockLevel()) {
+      runResult.assertSuccessWithOutputLines(
+          "OtherLibraryClass::foo", "SubLibraryClassAtLaterApiLevel::foo");
+    } else {
+      runResult.assertSuccessWithOutputLines("NoClassDefFoundError");
+    }
+    runResult.applyIf(
+        !isGreaterOrEqualToMockLevel()
+            && parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0),
+        result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(SubLibraryClassAtLaterApiLevel.class), isAbsent());
+  }
+
+  public abstract static class LibraryClass {
+
+    public abstract void foo();
+  }
+
+  public static class SubLibraryClassAtLaterApiLevel extends LibraryClass {
+
+    @Override
+    public void foo() {
+      System.out.println("SubLibraryClassAtLaterApiLevel::foo");
+    }
+  }
+
+  public static class OtherLibraryClass {
+
+    public static void baz() {
+      System.out.println("OtherLibraryClass::foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      try {
+        OtherLibraryClass.baz();
+        if (AndroidBuildVersion.VERSION >= 23) {
+          callSubFoo(new SubLibraryClassAtLaterApiLevel());
+        } else if (System.currentTimeMillis() == 0) {
+          callSubFoo(new Object());
+        }
+      } catch (NoClassDefFoundError e) {
+        System.out.println("NoClassDefFoundError");
+      }
+    }
+
+    @NeverInline
+    private static void callSubFoo(Object o) {
+      ((SubLibraryClassAtLaterApiLevel) o).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassCheckCastTest.java
similarity index 66%
copy from src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
copy to src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassCheckCastTest.java
index b80d79c..05c405f 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassCheckCastTest.java
@@ -1,13 +1,14 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -17,7 +18,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -28,7 +28,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class ApiModelMockClassLoadingTest extends TestBase {
+public class ApiModelMockClassCheckCastTest extends TestBase {
 
   private final AndroidApiLevel mockLevel = AndroidApiLevel.M;
 
@@ -36,7 +36,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
   private boolean isGreaterOrEqualToMockLevel() {
@@ -54,9 +54,17 @@
   }
 
   @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+    testForJvm()
+        .addProgramClasses(Main.class, TestClass.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
   public void testD8Debug() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    testForD8()
+    testForD8(parameters.getBackend())
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
@@ -68,8 +76,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    testForD8()
+    testForD8(parameters.getBackend())
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
         .compile()
@@ -94,12 +101,10 @@
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
     if (isGreaterOrEqualToMockLevel()) {
-      runResult.assertSuccessWithOutputLines("Hello World!");
-    } else if (parameters.isDexRuntime()
-        && parameters.asDexRuntime().getVm().isEqualTo(DexVm.ART_4_4_4_HOST)) {
-      runResult.assertSuccessWithOutputLines("ClassNotFoundException");
+      runResult.assertSuccessWithOutputLines("false", "checkcast caused ClassCastException");
     } else {
-      runResult.assertSuccessWithOutputLines("NoClassDefFoundError");
+      runResult.assertSuccessWithOutputLines(
+          "instanceof caused NoClassDefFoundError", "checkcast caused NoClassDefFoundError");
     }
     runResult.applyIf(
         !isGreaterOrEqualToMockLevel()
@@ -109,7 +114,7 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+    assertThat(inspector.clazz(LibraryClass.class), isAbsent());
   }
 
   // Only present from api level 23.
@@ -118,14 +123,22 @@
   public static class TestClass {
 
     @NeverInline
-    public static void test() {
+    public static void testInstanceOf(Object o) {
       try {
-        Class.forName(LibraryClass.class.getName());
-        System.out.println("Hello World!");
-      } catch (ExceptionInInitializerError | NoClassDefFoundError er) {
-        System.out.println("NoClassDefFoundError");
-      } catch (ClassNotFoundException e) {
-        System.out.println("ClassNotFoundException");
+        System.out.println(o instanceof LibraryClass);
+      } catch (NoClassDefFoundError ex) {
+        System.out.println("instanceof caused NoClassDefFoundError");
+      }
+    }
+
+    @NeverInline
+    public static void testCheckCast(Object o) {
+      try {
+        System.out.println(((LibraryClass) o).getClass().getName());
+      } catch (NoClassDefFoundError e) {
+        System.out.println("checkcast caused NoClassDefFoundError");
+      } catch (ClassCastException e) {
+        System.out.println("checkcast caused ClassCastException");
       }
     }
   }
@@ -133,7 +146,15 @@
   public static class Main {
 
     public static void main(String[] args) {
-      TestClass.test();
+      if (System.currentTimeMillis() > 0) {
+        Object o = new Object();
+        TestClass.testInstanceOf(o);
+        TestClass.testCheckCast(o);
+      } else {
+        LibraryClass libraryClass = new LibraryClass();
+        TestClass.testInstanceOf(libraryClass);
+        TestClass.testCheckCast(libraryClass);
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassForNameTest.java
similarity index 77%
copy from src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
copy to src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassForNameTest.java
index b80d79c..0b76660 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassForNameTest.java
@@ -5,9 +5,10 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -17,7 +18,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -28,7 +28,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class ApiModelMockClassLoadingTest extends TestBase {
+public class ApiModelMockClassLoadingByClassForNameTest extends TestBase {
 
   private final AndroidApiLevel mockLevel = AndroidApiLevel.M;
 
@@ -36,7 +36,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
   private boolean isGreaterOrEqualToMockLevel() {
@@ -54,9 +54,17 @@
   }
 
   @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+    testForJvm()
+        .addProgramClasses(Main.class, TestClass.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
   public void testD8Debug() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    testForD8()
+    testForD8(parameters.getBackend())
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
@@ -68,8 +76,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    testForD8()
+    testForD8(parameters.getBackend())
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
         .compile()
@@ -95,11 +102,8 @@
   private void checkOutput(SingleTestRunResult<?> runResult) {
     if (isGreaterOrEqualToMockLevel()) {
       runResult.assertSuccessWithOutputLines("Hello World!");
-    } else if (parameters.isDexRuntime()
-        && parameters.asDexRuntime().getVm().isEqualTo(DexVm.ART_4_4_4_HOST)) {
-      runResult.assertSuccessWithOutputLines("ClassNotFoundException");
     } else {
-      runResult.assertSuccessWithOutputLines("NoClassDefFoundError");
+      runResult.assertSuccessWithOutputLines("ClassNotFoundException");
     }
     runResult.applyIf(
         !isGreaterOrEqualToMockLevel()
@@ -109,7 +113,7 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+    assertThat(inspector.clazz(LibraryClass.class), isAbsent());
   }
 
   // Only present from api level 23.
@@ -120,9 +124,14 @@
     @NeverInline
     public static void test() {
       try {
-        Class.forName(LibraryClass.class.getName());
+        String className =
+            "com.android.tools.r8.apimodel.ApiModelMockClassLoadingByClassForNameTest$LibraryClass"
+                + (System.currentTimeMillis() == 0 ? "asdf" : "");
+        Class.forName(className);
         System.out.println("Hello World!");
-      } catch (ExceptionInInitializerError | NoClassDefFoundError er) {
+      } catch (ExceptionInInitializerError er) {
+        System.out.println("ExceptionInInitializerError");
+      } catch (NoClassDefFoundError er) {
         System.out.println("NoClassDefFoundError");
       } catch (ClassNotFoundException e) {
         System.out.println("ClassNotFoundException");
@@ -134,6 +143,9 @@
 
     public static void main(String[] args) {
       TestClass.test();
+      if (System.currentTimeMillis() == 0) {
+        System.out.println(LibraryClass.class.getName());
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassReferenceTest.java
similarity index 80%
rename from src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassReferenceTest.java
index b80d79c..9074fcc 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassReferenceTest.java
@@ -1,13 +1,14 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -17,7 +18,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -28,7 +28,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class ApiModelMockClassLoadingTest extends TestBase {
+public class ApiModelMockClassLoadingByClassReferenceTest extends TestBase {
 
   private final AndroidApiLevel mockLevel = AndroidApiLevel.M;
 
@@ -36,7 +36,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
   private boolean isGreaterOrEqualToMockLevel() {
@@ -54,9 +54,17 @@
   }
 
   @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+    testForJvm()
+        .addProgramClasses(Main.class, TestClass.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
   public void testD8Debug() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    testForD8()
+    testForD8(parameters.getBackend())
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
@@ -68,8 +76,7 @@
 
   @Test
   public void testD8Release() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    testForD8()
+    testForD8(parameters.getBackend())
         .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
         .compile()
@@ -94,10 +101,7 @@
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
     if (isGreaterOrEqualToMockLevel()) {
-      runResult.assertSuccessWithOutputLines("Hello World!");
-    } else if (parameters.isDexRuntime()
-        && parameters.asDexRuntime().getVm().isEqualTo(DexVm.ART_4_4_4_HOST)) {
-      runResult.assertSuccessWithOutputLines("ClassNotFoundException");
+      runResult.assertSuccessWithOutputLines(typeName(LibraryClass.class));
     } else {
       runResult.assertSuccessWithOutputLines("NoClassDefFoundError");
     }
@@ -109,7 +113,7 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+    assertThat(inspector.clazz(LibraryClass.class), isAbsent());
   }
 
   // Only present from api level 23.
@@ -120,12 +124,9 @@
     @NeverInline
     public static void test() {
       try {
-        Class.forName(LibraryClass.class.getName());
-        System.out.println("Hello World!");
+        System.out.println(new LibraryClass().getClass().getName());
       } catch (ExceptionInInitializerError | NoClassDefFoundError er) {
         System.out.println("NoClassDefFoundError");
-      } catch (ClassNotFoundException e) {
-        System.out.println("ClassNotFoundException");
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
index cff8d68..ac98967 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
@@ -6,11 +6,9 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
 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 static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -21,7 +19,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -133,18 +130,7 @@
             .compile()
             .writeToZip();
 
-    if (isGreaterOrEqualToMockLevel()) {
-      assertFalse(globals.hasGlobals());
-    } else if (outputMode == OutputMode.DexIndexed) {
-      assertTrue(globals.hasGlobals());
-      assertTrue(globals.isSingleGlobal());
-    } else {
-      assertTrue(globals.hasGlobals());
-      // The TestClass does reference the mock and should have globals.
-      assertNotNull(globals.getProvider(Reference.classFromClass(TestClass.class)));
-      // The Main class does not have references to the mock and should have no globals.
-      assertNull(globals.getProvider(Reference.classFromClass(Main.class)));
-    }
+    assertFalse(globals.hasGlobals());
 
     testForD8()
         .debug()
@@ -172,7 +158,7 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+    assertThat(inspector.clazz(LibraryClass.class), isAbsent());
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockDalvikVerifyErrorTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockDalvikVerifyErrorTest.java
index 50edd76..1a5cd80 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockDalvikVerifyErrorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockDalvikVerifyErrorTest.java
@@ -7,7 +7,8 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -109,7 +110,7 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+    assertThat(inspector.clazz(LibraryClass.class), isAbsent());
   }
 
   public abstract static class LibraryClass {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
index 322b30f..5d32ca1 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
@@ -6,7 +6,8 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -97,7 +98,7 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+    assertThat(inspector.clazz(LibraryClass.class), isAbsent());
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
index 409cd9b..0ca565f 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
@@ -10,7 +10,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.OutputMode;
@@ -19,7 +18,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -102,17 +100,8 @@
     paths.add(runD8ForClass(TestCallingFoo.class, testCallingFooGlobals, mode));
     paths.add(runD8ForClass(TestCallingBar.class, testCallingBarGlobals, mode));
     assertFalse(mainGlobals.hasGlobals());
-    if (isGreaterOrEqualToMockLevel()) {
-      assertFalse(testCallingFooGlobals.hasGlobals());
-      assertFalse(testCallingBarGlobals.hasGlobals());
-    } else {
-      // The TestCallingX does reference the mock and should have globals.
-      assertNotNull(
-          testCallingFooGlobals.getProvider(Reference.classFromClass(TestCallingFoo.class)));
-      assertNotNull(
-          testCallingBarGlobals.getProvider(Reference.classFromClass(TestCallingBar.class)));
-    }
-
+    assertFalse(testCallingFooGlobals.hasGlobals());
+    assertFalse(testCallingBarGlobals.hasGlobals());
     testForD8()
         .setMode(mode)
         .addProgramFiles(paths)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
index b2611de..01681c4 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
@@ -8,10 +8,8 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.OutputMode;
@@ -20,7 +18,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -103,17 +100,8 @@
     paths.add(runD8ForClass(TestCallingFoo.class, testCallingFooGlobals, mode));
     paths.add(runD8ForClass(TestCallingBar.class, testCallingBarGlobals, mode));
     assertFalse(mainGlobals.hasGlobals());
-    if (isGreaterOrEqualToMockLevel()) {
-      assertFalse(testCallingFooGlobals.hasGlobals());
-      assertFalse(testCallingBarGlobals.hasGlobals());
-    } else {
-      // The TestCallingX does reference the mock and should have globals.
-      assertNotNull(
-          testCallingFooGlobals.getProvider(Reference.classFromClass(TestCallingFoo.class)));
-      assertNotNull(
-          testCallingBarGlobals.getProvider(Reference.classFromClass(TestCallingBar.class)));
-    }
-
+    assertFalse(testCallingFooGlobals.hasGlobals());
+    assertFalse(testCallingBarGlobals.hasGlobals());
     testForD8()
         .setMode(mode)
         .addProgramFiles(paths)
@@ -132,13 +120,7 @@
 
   private void inspect(CodeInspector inspector) {
     ClassSubject libraryClassSubject = inspector.clazz(LibraryClass.class);
-    if (isGreaterOrEqualToMockLevel()) {
-      assertThat(libraryClassSubject, isAbsent());
-    } else {
-      assertThat(libraryClassSubject, isPresent());
-      assertThat(libraryClassSubject.uniqueMethodWithOriginalName("foo"), isAbsent());
-      assertThat(libraryClassSubject.uniqueMethodWithOriginalName("bar"), isAbsent());
-    }
+    assertThat(libraryClassSubject, isAbsent());
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
index 5d4fc8f..a45f683 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
@@ -6,7 +6,8 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -121,9 +122,9 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(lowerMockApiLevel);
-    verifyThat(inspector, parameters, LibraryInterface.class).stubbedUntil(lowerMockApiLevel);
-    verifyThat(inspector, parameters, OtherLibraryClass.class).stubbedUntil(mockApiLevel);
+    assertThat(inspector.clazz(LibraryClass.class), isAbsent());
+    assertThat(inspector.clazz(LibraryInterface.class), isAbsent());
+    assertThat(inspector.clazz(OtherLibraryClass.class), isAbsent());
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
index 4bb470c..37e361c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
@@ -116,11 +116,7 @@
     } else {
       verifyThat(inspector, parameters, methodOn23).isNotOutlinedFrom(mainMethod);
     }
-    if (canStub) {
-      verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(libraryApiLevel);
-    } else {
-      assertThat(inspector.clazz(LibraryClass.class), isAbsent());
-    }
+    assertThat(inspector.clazz(LibraryClass.class), isAbsent());
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
index 3504527..01894d1 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
@@ -7,6 +7,8 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -104,7 +106,7 @@
   }
 
   private void inspect(CodeInspector inspector) throws Exception {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(libraryClassLevel);
+    assertThat(inspector.clazz(LibraryClass.class), isAbsent());
     verifyThat(inspector, parameters, apiMethod())
         .isOutlinedFromUntil(
             Main.class.getDeclaredMethod("main", String[].class), libraryMethodLevel);
diff --git a/src/test/java/com/android/tools/r8/apimodel/NewInstanceToAbstractClassReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/NewInstanceToAbstractClassReferenceTest.java
new file mode 100644
index 0000000..b733845
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/NewInstanceToAbstractClassReferenceTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NewInstanceToAbstractClassReferenceTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(
+            transformer(A.class).setAccessFlags(ClassAccessFlags::setAbstract).transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrowsIf(parameters.isCfRuntime(), InstantiationError.class)
+        .applyIf(
+            parameters.isDexRuntime(),
+            result -> {
+              if (parameters.getDexRuntimeVersion().isDalvik()) {
+                result.assertStderrMatches(
+                    containsString(
+                        "VFY: new-instance on interface or abstract class " + descriptor(A.class)));
+
+              } else if (parameters.getDexRuntimeVersion().isOlderThan(Version.V7_0_0)) {
+                result.assertStderrMatches(
+                    containsString(
+                        "Verification failed on class "
+                            + typeName(NewInstanceToAbstractClassReferenceTest.class)));
+              } else {
+                result.assertStderrMatches(not(containsString("Verification failed")));
+              }
+            });
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 1f9e6c7..7799ba7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -1021,7 +1021,7 @@
     assertThat(method, isPresent());
     assertThat(
         method.getMethod().getCode().asCfCode().toString(),
-        containsString("invokeinterface classmerging.MergeDefaultMethodIntoClassTest$A.f()V"));
+        containsString("invokeinterface Lclassmerging/MergeDefaultMethodIntoClassTest$A;f()V"));
 
     runTestOnInput(
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
index 812eb63..b00d78b 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -10,7 +10,9 @@
 import com.android.tools.r8.dex.code.DexConst4;
 import com.android.tools.r8.dex.code.DexConstClass;
 import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.dex.code.DexInvokeVirtual;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
 import com.android.tools.r8.dex.code.DexNewArray;
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.graph.DexCode;
@@ -93,16 +95,29 @@
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof DexConst4);
-    assertTrue(code.instructions[1] instanceof DexNewArray);
-    assertTrue(code.instructions[2] instanceof DexConst4);
-    assertTrue(code.instructions[3] instanceof DexConstClass);
-    assertTrue(code.instructions[4] instanceof DexAputObject);
-    assertTrue(code.instructions[5] instanceof DexConstClass);
-    assertTrue(code.instructions[6] instanceof DexConstString);
-    assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
-    assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
-    assertTrue(code.instructions[8] instanceof DexReturnVoid);
+
+    // Accept either array construction style (differs based on minSdkVersion).
+    if (code.instructions[1] instanceof DexFilledNewArray) {
+      assertTrue(code.instructions[0] instanceof DexConstClass);
+      assertTrue(code.instructions[1] instanceof DexFilledNewArray);
+      assertTrue(code.instructions[2] instanceof DexMoveResultObject);
+      assertTrue(code.instructions[3] instanceof DexConstClass);
+      assertTrue(code.instructions[4] instanceof DexConstString);
+      assertNotEquals("foo", code.instructions[4].asConstString().getString().toString());
+      assertTrue(code.instructions[5] instanceof DexInvokeVirtual);
+      assertTrue(code.instructions[6] instanceof DexReturnVoid);
+    } else {
+      assertTrue(code.instructions[0] instanceof DexConst4);
+      assertTrue(code.instructions[1] instanceof DexNewArray);
+      assertTrue(code.instructions[2] instanceof DexConst4);
+      assertTrue(code.instructions[3] instanceof DexConstClass);
+      assertTrue(code.instructions[4] instanceof DexAputObject);
+      assertTrue(code.instructions[5] instanceof DexConstClass);
+      assertTrue(code.instructions[6] instanceof DexConstString);
+      assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
+      assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
+      assertTrue(code.instructions[8] instanceof DexReturnVoid);
+    }
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index 76bd656..2c99723 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -80,7 +80,11 @@
           "java.util.stream.DoubleStream"
               + " java.util.stream.DoubleStream.dropWhile(java.util.function.DoublePredicate)",
           // FileStore.getBlockSize() was added in 33 which confuses the required library (30).
-          "long java.nio.file.FileStore.getBlockSize()");
+          "long java.nio.file.FileStore.getBlockSize()",
+          // The call is present but unreachable above 26.
+          "java.nio.channels.FileChannel"
+              + " java.nio.channels.DesugarChannels.openEmulatedFileChannel(java.nio.file.Path,"
+              + " java.util.Set, java.nio.file.attribute.FileAttribute[])");
 
   private final TestParameters parameters;
   private final CompilationSpecification compilationSpecification;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index a68f3ce..4602235 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -514,8 +514,7 @@
       if (clazzType.startsWith("java.")
           && !doesNotNeedWrapper(clazzType, customConversions, maintainType)
           // FileChannel is there since B but it needs wrapping due to recently added interfaces.
-          && (!preDesugarTypes.contains(clazz)
-              || clazzType.equals("java.nio.channels.FileChannel"))) {
+          && !preDesugarTypes.contains(clazz)) {
         additions.accept(clazz);
         return true;
       }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 535da2a..5e6c723 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -148,23 +148,23 @@
             .streamInstructions()
             .filter(instr -> instr.isInvokeInterface() || instr.isInvokeStatic())
             .collect(Collectors.toList());
-    assertInvokeStaticMatching(invokes, 0, "Set$-EL;->spliterator");
-    assertInvokeStaticMatching(invokes, 1, "Collection$-EL;->stream");
-    assertInvokeInterfaceMatching(invokes, 2, "Set;->iterator");
-    assertInvokeStaticMatching(invokes, 3, "Collection$-EL;->stream");
-    assertInvokeStaticMatching(invokes, 4, "Set$-EL;->spliterator");
-    assertInvokeInterfaceMatching(invokes, 8, "Iterator;->remove");
-    assertInvokeStaticMatching(invokes, 9, "DesugarArrays;->spliterator");
-    assertInvokeStaticMatching(invokes, 10, "DesugarArrays;->spliterator");
-    assertInvokeStaticMatching(invokes, 11, "DesugarArrays;->stream");
-    assertInvokeStaticMatching(invokes, 12, "DesugarArrays;->stream");
-    assertInvokeStaticMatching(invokes, 13, "Collection$-EL;->stream");
-    assertInvokeStaticMatching(invokes, 14, "IntStream$-CC;->range");
-    assertInvokeStaticMatching(invokes, 16, "Comparator$-CC;->comparingInt");
-    assertInvokeStaticMatching(invokes, 17, "List$-EL;->sort");
-    assertInvokeStaticMatching(invokes, 19, "Comparator$-CC;->comparingInt");
-    assertInvokeStaticMatching(invokes, 20, "List$-EL;->sort");
-    assertInvokeStaticMatching(invokes, 21, "Collection$-EL;->stream");
+    assertInvokeStaticMatching(invokes, 0, "Set$-EL;spliterator");
+    assertInvokeStaticMatching(invokes, 1, "Collection$-EL;stream");
+    assertInvokeInterfaceMatching(invokes, 2, "Set;iterator");
+    assertInvokeStaticMatching(invokes, 3, "Collection$-EL;stream");
+    assertInvokeStaticMatching(invokes, 4, "Set$-EL;spliterator");
+    assertInvokeInterfaceMatching(invokes, 8, "Iterator;remove");
+    assertInvokeStaticMatching(invokes, 9, "DesugarArrays;spliterator");
+    assertInvokeStaticMatching(invokes, 10, "DesugarArrays;spliterator");
+    assertInvokeStaticMatching(invokes, 11, "DesugarArrays;stream");
+    assertInvokeStaticMatching(invokes, 12, "DesugarArrays;stream");
+    assertInvokeStaticMatching(invokes, 13, "Collection$-EL;stream");
+    assertInvokeStaticMatching(invokes, 14, "IntStream$-CC;range");
+    assertInvokeStaticMatching(invokes, 16, "Comparator$-CC;comparingInt");
+    assertInvokeStaticMatching(invokes, 17, "List$-EL;sort");
+    assertInvokeStaticMatching(invokes, 19, "Comparator$-CC;comparingInt");
+    assertInvokeStaticMatching(invokes, 20, "List$-EL;sort");
+    assertInvokeStaticMatching(invokes, 21, "Collection$-EL;stream");
     assertEquals(22, invokes.size());
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileChannelTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileChannelTest.java
new file mode 100644
index 0000000..6c5c149
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileChannelTest.java
@@ -0,0 +1,234 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FileChannelTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "true",
+          "true",
+          "true",
+          "true",
+          "Hello World! ",
+          "Hello World! ",
+          "Bye bye. ",
+          "Hello World! ",
+          "Bye bye. ",
+          "Hello World! ",
+          "The monkey eats...",
+          "Bananas!",
+          "Bananas!",
+          "Bananas!");
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        // Skip Android 4.4.4 due to missing libjavacrypto.
+        getTestParameters()
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build(),
+        ImmutableList.of(JDK11_PATH),
+        DEFAULT_SPECIFICATIONS);
+  }
+
+  public FileChannelTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void test() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) throws IOException {
+      instanceTest();
+      fisTest();
+      fosTest();
+      fileChannelOpen();
+    }
+
+    /**
+     * These check look obvious but they are not on low Api level due to the interface injection.
+     */
+    @SuppressWarnings("all")
+    private static void instanceTest() throws IOException {
+      Path tmp = Files.createTempFile("tmp", ".txt");
+      System.out.println(
+          new FileInputStream(tmp.toFile()).getChannel() instanceof SeekableByteChannel);
+      System.out.println(
+          new FileOutputStream(tmp.toFile()).getChannel() instanceof SeekableByteChannel);
+      System.out.println(
+          new RandomAccessFile(tmp.toFile(), "rw").getChannel() instanceof SeekableByteChannel);
+      System.out.println(
+          Files.newByteChannel(tmp, StandardOpenOption.READ) instanceof SeekableByteChannel);
+    }
+
+    private static void fosTest() throws IOException {
+      String toWrite = "The monkey eats...";
+      Path tmp = Files.createTempFile("fos", ".txt");
+
+      ByteBuffer byteBuffer = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8));
+      FileOutputStream fos = new FileOutputStream(tmp.toFile());
+      FileChannel channel = fos.getChannel();
+      channel.write(byteBuffer);
+
+      List<String> lines = Files.readAllLines(tmp);
+      System.out.println(lines.get(0));
+    }
+
+    private static void fileChannelOpen() throws IOException {
+      fileChannelOpenTest();
+      fileChannelOpenSetTest();
+      fileChannelOpenLockTest();
+    }
+
+    private static void fileChannelOpenLockTest() throws IOException {
+      Path tmp = Files.createTempFile("lock", ".txt");
+      String contents = "Bananas!";
+      Files.write(tmp, contents.getBytes(StandardCharsets.UTF_8));
+      FileChannel fc = FileChannel.open(tmp, StandardOpenOption.READ);
+      ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length());
+      fc.read(byteBuffer);
+      System.out.println(new String(byteBuffer.array()));
+      fc.close();
+    }
+
+    private static void fileChannelOpenTest() throws IOException {
+      Path tmp = Files.createTempFile("a", ".txt");
+      String contents = "Bananas!";
+      Files.write(tmp, contents.getBytes(StandardCharsets.UTF_8));
+      FileChannel fc = FileChannel.open(tmp, StandardOpenOption.READ, StandardOpenOption.WRITE);
+      ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length());
+      // Extra indirection through the lock.
+      fc.lock().channel().read(byteBuffer);
+      System.out.println(new String(byteBuffer.array()));
+      fc.close();
+    }
+
+    private static void fileChannelOpenSetTest() throws IOException {
+      Path tmp = Files.createTempFile("b", ".txt");
+      String contents = "Bananas!";
+      Files.write(tmp, contents.getBytes(StandardCharsets.UTF_8));
+      Set<OpenOption> options = new HashSet<>();
+      options.add(StandardOpenOption.READ);
+      FileChannel fc = FileChannel.open(tmp, options);
+      ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length());
+      fc.read(byteBuffer);
+      System.out.println(new String(byteBuffer.array()));
+      fc.close();
+    }
+
+    private static void fisTest() throws IOException {
+      fisOwner();
+      fisNotOwner(true);
+      fisNotOwner(false);
+      fisOwnerTryResources();
+    }
+
+    private static void fisNotOwner(boolean closeFirst) throws IOException {
+      String toWrite = "Hello World! ";
+      String toWriteFIS = "Bye bye. ";
+      Path tmp = Files.createTempFile("tmp", ".txt");
+      Files.write(tmp, (toWrite + toWriteFIS).getBytes(StandardCharsets.UTF_8));
+
+      ByteBuffer byteBuffer = ByteBuffer.allocate(toWrite.length());
+      ByteBuffer byteBufferFIS = ByteBuffer.allocate(toWriteFIS.length());
+      FileInputStream fileInputStream = new FileInputStream(tmp.toFile());
+      FileDescriptor fd = fileInputStream.getFD();
+      FileInputStream fis2 = new FileInputStream(fd);
+      fileInputStream.getChannel().read(byteBuffer);
+      fis2.getChannel().read(byteBufferFIS);
+
+      if (closeFirst) {
+        fileInputStream.close();
+        fis2.close();
+      } else {
+        fis2.close();
+        fileInputStream.close();
+      }
+
+      System.out.println(new String(byteBuffer.array()));
+      System.out.println(new String(byteBufferFIS.array()));
+    }
+
+    private static void fisOwner() throws IOException {
+      String toWrite = "Hello World! ";
+      Path tmp = Files.createTempFile("tmp", ".txt");
+      Files.write(tmp, toWrite.getBytes(StandardCharsets.UTF_8));
+
+      ByteBuffer byteBuffer = ByteBuffer.allocate(toWrite.length());
+      FileInputStream fileInputStream = new FileInputStream(tmp.toFile());
+      fileInputStream.getChannel().read(byteBuffer);
+      fileInputStream.close();
+
+      System.out.println(new String(byteBuffer.array()));
+    }
+
+    private static void fisOwnerTryResources() throws IOException {
+      String toWrite = "Hello World! ";
+      Path tmp = Files.createTempFile("tmp", ".txt");
+      Files.write(tmp, toWrite.getBytes(StandardCharsets.UTF_8));
+
+      ByteBuffer byteBuffer = ByteBuffer.allocate(toWrite.length());
+      try (FileInputStream fileInputStream = new FileInputStream(tmp.toFile())) {
+        fileInputStream.getChannel().read(byteBuffer);
+      }
+
+      System.out.println(new String(byteBuffer.array()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
index e6015ad..fb9ca6a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
@@ -58,21 +58,6 @@
           "tmp",
           "/",
           "true");
-  private static final String EXPECTED_RESULT_DESUGARING_FILE_SYSTEM_PLATFORM_CHANNEL =
-      StringUtils.lines(
-          "bytes written: 11",
-          "String written: Hello World",
-          "bytes read: 11",
-          "String read: Hello World",
-          "unsupported",
-          "unsupported",
-          "null",
-          "true",
-          "unsupported",
-          "j$.nio.file.attribute",
-          "tmp",
-          "/",
-          "true");
   private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING =
       StringUtils.lines(
           "bytes written: 11",
@@ -136,9 +121,7 @@
           ? EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING
           : EXPECTED_RESULT_PLATFORM_FILE_SYSTEM;
     }
-    return libraryDesugaringSpecification.hasNioChannelDesugaring(parameters)
-        ? EXPECTED_RESULT_DESUGARING_FILE_SYSTEM
-        : EXPECTED_RESULT_DESUGARING_FILE_SYSTEM_PLATFORM_CHANNEL;
+    return EXPECTED_RESULT_DESUGARING_FILE_SYSTEM;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/JavaTimeJDK11Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/JavaTimeJDK11Test.java
index 09bb322..d6724b5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/JavaTimeJDK11Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/JavaTimeJDK11Test.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_LEGACY;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -69,7 +70,7 @@
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withDexRuntimes().withAllApiLevels().build(),
-        ImmutableList.of(JDK11, JDK11_PATH),
+        ImmutableList.of(JDK11, JDK11_PATH, JDK11_LEGACY),
         DEFAULT_SPECIFICATIONS);
   }
 
@@ -101,7 +102,8 @@
         .forEach(
             i -> {
               if (i.isInvoke()) {
-                if (libraryDesugaringSpecification.hasTimeDesugaring(parameters)) {
+                if (libraryDesugaringSpecification.hasTimeDesugaring(parameters)
+                    && libraryDesugaringSpecification != JDK11_LEGACY) {
                   checkInvokeTime(i, "j$.time.Duration", "j$.time.LocalTime");
                   return;
                 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/NewCollectorsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/NewCollectorsTest.java
index 4c77e47..84e5f4b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/NewCollectorsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/NewCollectorsTest.java
@@ -6,7 +6,9 @@
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_LEGACY;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -46,7 +48,7 @@
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withDexRuntimes().withAllApiLevels().build(),
-        ImmutableList.of(JDK11, JDK11_PATH),
+        ImmutableList.of(JDK11, JDK11_PATH, JDK11_LEGACY),
         DEFAULT_SPECIFICATIONS);
   }
 
@@ -76,7 +78,11 @@
     if (libraryDesugaringSpecification.hasEmulatedInterfaceDesugaring(parameters)) {
       // Collectors is not present, all calls to the j$ version.
       assertTrue(anyStaticInvokeToHolder(methodSubject, "j$.util.stream.Collectors"));
-      assertFalse(anyStaticInvokeToHolder(methodSubject, "j$.util.stream.DesugarCollectors"));
+      // In JDK11_LEGACY DesugarCollectors is used whenever possible, in other specifications,
+      // it is used only when needed.
+      assertEquals(
+          libraryDesugaringSpecification == JDK11_LEGACY,
+          anyStaticInvokeToHolder(methodSubject, "j$.util.stream.DesugarCollectors"));
       assertFalse(anyStaticInvokeToHolder(methodSubject, "java.util.stream.Collectors"));
       return;
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
index 1e56f81..7de441c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -206,7 +206,7 @@
       if (artProcessResult.exitCode != 0) {
         System.out.println(artProcessResult);
       }
-      assertEquals(0, artProcessResult.exitCode);
+      assertEquals(artProcessResult.stderr, 0, artProcessResult.exitCode);
       assertProgramsEqual(
           "The output of R8/JVM in-process and R8/ART external differ.",
           outputThroughCf,
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
index 6245fec..3d76b78 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
@@ -37,7 +37,7 @@
   public static Descriptor JDK11_DESCRIPTOR = new Descriptor(24, 26, -1, 10000, -1);
   public static Descriptor EMPTY_DESCRIPTOR_24 = new Descriptor(-1, -1, -1, 24, -1);
   public static Descriptor JDK11_PATH_DESCRIPTOR = new Descriptor(24, 26, 26, 10000, -1);
-  public static Descriptor JDK11_LEGACY_DESCRIPTOR = new Descriptor(10000, 10000, -1, 10000, 24);
+  public static Descriptor JDK11_LEGACY_DESCRIPTOR = new Descriptor(24, 26, -1, 32, 24);
 
   private static class Descriptor {
 
@@ -166,7 +166,7 @@
           // The legacy specification is not using the undesugared JAR.
           DESUGARED_JDK_11_LIB_JAR,
           "jdk11/desugar_jdk_libs_legacy.json",
-          AndroidApiLevel.R,
+          AndroidApiLevel.T,
           JDK11_LEGACY_DESCRIPTOR,
           LEGACY);
   public static final LibraryDesugaringSpecification RELEASED_1_0_9 =
diff --git a/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
index c8ea9ec..6242204 100644
--- a/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
@@ -6,11 +6,10 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Paths;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -20,7 +19,7 @@
   private static final String JAR = "third_party/framework/framework_14082017.jar";
 
   @Test
-  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  @Ignore("b/259195080")
   public void verifyDebugBuild() throws Exception {
     runAndCheckVerification(
         D8Command.builder()
@@ -32,7 +31,7 @@
   }
 
   @Test
-  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  @Ignore("b/259195080")
   public void verifyReleaseBuild() throws Exception {
     runAndCheckVerification(
         D8Command.builder()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
index 1e43777..7141ad08 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
@@ -53,8 +53,7 @@
         .addProgramClasses(I.class, A.class)
         .addProgramClassFileData(getTransformedMain())
         .addKeepMainRule(Main.class)
-        // Keep get() to prevent that we optimize it into having static return type A.
-        .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+        .addKeepMethodRules(Reference.methodFromMethod(Main.class.getDeclaredMethod("get")))
         .addOptionsModification(
             options ->
                 options
diff --git a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
index 0f07d76..d9a6eaf 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -109,7 +109,7 @@
                     .addTarget(
                         target(
                             buildClassItem(CLASS)
-                                .setMembersPattern(defaultInitializerPattern())
+                                .setMemberPattern(defaultInitializerPattern())
                                 .build()))
                     .build())
             .build();
@@ -146,7 +146,7 @@
                     .addTarget(
                         target(
                             buildClassItem(CLASS)
-                                .setMembersPattern(defaultInitializerPattern())
+                                .setMemberPattern(defaultInitializerPattern())
                                 .build()))
                     .build())
             .build();
@@ -169,7 +169,7 @@
     return KeepItemPattern.builder().setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
   }
 
-  private KeepMembersPattern defaultInitializerPattern() {
+  private KeepMemberPattern defaultInitializerPattern() {
     return KeepMethodPattern.builder()
         .setNamePattern(KeepMethodNamePattern.initializer())
         .setParametersPattern(KeepMethodParametersPattern.none())
diff --git a/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java b/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java
new file mode 100644
index 0000000..94001ec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.keeprules;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
+import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepRuleExtractorTest extends TestBase {
+
+  private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
+  private static final String EXPECTED = KeepSourceEdges.getExpected(SOURCE);
+  private static final Path KEEP_ANNO_PATH =
+      Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public KeepRuleExtractorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    List<String> rules = getKeepRulesForClass(SOURCE);
+    testForR8(parameters.getBackend())
+        .addClasspathFiles(KEEP_ANNO_PATH)
+        .addProgramClassesAndInnerClasses(SOURCE)
+        .addKeepRules(rules)
+        .addKeepMainRule(SOURCE)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), SOURCE)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private List<String> getKeepRulesForClass(Class<?> clazz) throws IOException {
+    Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
+    List<String> rules = new ArrayList<>();
+    KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
+    keepEdges.forEach(extractor::extract);
+    return rules;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java b/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
index eb7b616..83f60a3 100644
--- a/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
@@ -36,6 +36,7 @@
   private static final Path KEEP_ANNO_PATH =
       Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
   private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
+  private static final String EXPECTED = KeepSourceEdges.getExpected(SOURCE);
 
   private final TestParameters parameters;
 
@@ -63,6 +64,12 @@
     checkSynthesizedKeepEdgeClass(inspector, out);
     // The source is added as a classpath name but not part of the compilation unit output.
     assertThat(inspector.clazz(SOURCE), isAbsent());
+
+    testForJvm()
+        .addProgramClasses(SOURCE, KeepClassAndDefaultConstructorSource.A.class)
+        .addProgramFiles(out)
+        .run(parameters.getRuntime(), SOURCE)
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
@@ -78,7 +85,7 @@
     testForJvm()
         .addProgramFiles(out)
         .run(parameters.getRuntime(), SOURCE)
-        .assertSuccessWithOutputLines("A is alive!")
+        .assertSuccessWithOutput(EXPECTED)
         .inspect(
             inspector -> {
               assertThat(inspector.clazz(SOURCE), isPresent());
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
index ca83a46..b9fb2b6 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
@@ -9,9 +9,11 @@
 @KeepEdge(
     consequences = {
       // Keep the class to allow lookup of it.
-      @KeepTarget(classConstant = KeepClassAndDefaultConstructorSource.class),
+      @KeepTarget(classConstant = KeepClassAndDefaultConstructorSource.A.class),
       // Keep the default constructor.
-      @KeepTarget(classConstant = KeepClassAndDefaultConstructorSource.class, methodName = "<init>")
+      @KeepTarget(
+          classConstant = KeepClassAndDefaultConstructorSource.A.class,
+          methodName = "<init>")
     })
 public class KeepClassAndDefaultConstructorSource {
 
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
index b6369de..5637edf 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepTarget;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.Collections;
 import java.util.Set;
 
@@ -27,8 +28,19 @@
     throw new RuntimeException();
   }
 
+  public static String getExpected(Class<?> clazz) {
+    if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
+      return getKeepClassAndDefaultConstructorSourceExpected();
+    }
+    throw new RuntimeException();
+  }
+
+  public static String getKeepClassAndDefaultConstructorSourceExpected() {
+    return StringUtils.lines("A is alive!");
+  }
+
   public static Set<KeepEdge> getKeepClassAndDefaultConstructorSourceEdges() {
-    Class<?> clazz = KeepClassAndDefaultConstructorSource.class;
+    Class<?> clazz = KeepClassAndDefaultConstructorSource.A.class;
     // Build the class target.
     KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
     KeepItemPattern classItem = KeepItemPattern.builder().setClassPattern(name).build();
@@ -37,10 +49,7 @@
     KeepMethodPattern constructorMethod =
         KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact("<init>")).build();
     KeepItemPattern constructorItem =
-        KeepItemPattern.builder()
-            .setClassPattern(name)
-            .setMembersPattern(constructorMethod)
-            .build();
+        KeepItemPattern.builder().setClassPattern(name).setMemberPattern(constructorMethod).build();
     KeepTarget constructorTarget = KeepTarget.builder().setItem(constructorItem).build();
     // The consequet set is the class an its constructor.
     KeepConsequences consequences =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index 9fd2a2e..87dc848 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -133,9 +133,7 @@
     if (kotlinParameters.isNewerThan(KOTLINC_1_7_0)) {
       assertThat(
           compileResult.stderr,
-          containsString(
-              "the feature \"references to synthetic java properties\" is only available since"
-                  + " language version 1.9"));
+          containsString("the feature \"references to synthetic java properties\""));
     } else {
       assertThat(
           compileResult.stderr,
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 83ea780..c544c9e 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -16,10 +16,12 @@
 import com.android.tools.r8.dex.code.DexConst4;
 import com.android.tools.r8.dex.code.DexConstClass;
 import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.dex.code.DexInvokeDirect;
 import com.android.tools.r8.dex.code.DexInvokeStatic;
 import com.android.tools.r8.dex.code.DexInvokeVirtual;
 import com.android.tools.r8.dex.code.DexIputObject;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
 import com.android.tools.r8.dex.code.DexNewArray;
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.dex.code.DexSgetObject;
@@ -639,7 +641,8 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep class R { *; }");
-    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+    CodeInspector inspector =
+        compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -647,19 +650,33 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    checkInstructions(
-        code,
-        ImmutableList.of(
-            DexInvokeDirect.class,
-            DexConst4.class,
-            DexNewArray.class,
-            DexConst4.class,
-            DexConstClass.class,
-            DexAputObject.class,
-            DexConstString.class,
-            DexInvokeStatic.class,
-            DexReturnVoid.class));
-    DexConstString constString = (DexConstString) code.instructions[6];
+    // Accept either array construction style (differs based on minSdkVersion).
+    if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConstClass.class,
+              DexFilledNewArray.class,
+              DexMoveResultObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    } else {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConst4.class,
+              DexNewArray.class,
+              DexConst4.class,
+              DexConstClass.class,
+              DexAputObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    }
+    DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
     assertEquals("foo", constString.getString().toString());
   }
 
@@ -700,7 +717,8 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class R { *; }");
-    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+    CodeInspector inspector =
+        compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -708,19 +726,33 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    checkInstructions(
-        code,
-        ImmutableList.of(
-            DexInvokeDirect.class,
-            DexConst4.class,
-            DexNewArray.class,
-            DexConst4.class,
-            DexConstClass.class,
-            DexAputObject.class,
-            DexConstString.class,
-            DexInvokeStatic.class,
-            DexReturnVoid.class));
-    DexConstString constString = (DexConstString) code.instructions[6];
+    // Accept either array construction style (differs based on minSdkVersion).
+    if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConstClass.class,
+              DexFilledNewArray.class,
+              DexMoveResultObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    } else {
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              DexInvokeDirect.class,
+              DexConst4.class,
+              DexNewArray.class,
+              DexConst4.class,
+              DexConstClass.class,
+              DexAputObject.class,
+              DexConstString.class,
+              DexInvokeStatic.class,
+              DexReturnVoid.class));
+    }
+    DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
     assertNotEquals("foo", constString.getString().toString());
   }
 
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
new file mode 100644
index 0000000..3b7a115
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.proto;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ProtoNormalizationDestinationOverrideLibraryTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String[] EXPECTED = new String[] {"LibraryMethod421337"};
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, ProgramClass.class, X.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryClasses(LibraryClass.class)
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/258720808): We should not fail compilation.
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClasses(Main.class, ProgramClass.class, X.class)
+                .addDefaultRuntimeLibrary(parameters)
+                .addLibraryClasses(LibraryClass.class)
+                .setMinApi(parameters.getApiLevel())
+                .addKeepMainRule(Main.class)
+                .addDontObfuscate()
+                .enableInliningAnnotations()
+                .enableNoMethodStaticizingAnnotations()
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      diagnostics.assertErrorMessageThatMatches(
+                          containsString(
+                              "went from not overriding a library method to overriding a library"
+                                  + " method"));
+                    }));
+  }
+
+  public static class LibraryClass {
+
+    public void foo(int i1, int i2, String bar) {
+      System.out.println(bar + i1 + i2);
+    }
+
+    public static void callFoo(LibraryClass clazz) {
+      clazz.foo(42, 1337, "LibraryMethod");
+    }
+  }
+
+  public static class ProgramClass extends LibraryClass {
+    @NeverInline
+    @NoMethodStaticizing
+    public void foo(String bar, int i1, int i2) {
+      System.out.println(i1 + i2 + bar);
+    }
+  }
+
+  // Needs to have a class name that is after lexicographically than ProgramClass.
+  public static class X {
+    @NeverInline
+    public void foo(int i1, String bar, int i2) {
+      System.out.println(i1 + bar + i2);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ProgramClass programClass = new ProgramClass();
+      X otherProgramClass = new X();
+      if (args.length == 1) {
+        programClass.foo("Hello World", 1, 1);
+        otherProgramClass.foo(1, "Hello World", 1);
+      } else if (args.length > 1) {
+        programClass.foo("Goodbye World", 2, 2);
+        otherProgramClass.foo(2, "Goodbye World", 2);
+      }
+      LibraryClass.callFoo(programClass);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
index ba776e4..c179493 100644
--- a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
@@ -33,6 +33,20 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private final String[] EXPECTED =
+      new String[] {"A::foo", "B", "A", "B::foo", "B", "A", "B::foo", "B", "A"};
+
+  private final String[] R8_EXPECTED =
+      new String[] {"A::foo", "B", "A", "A::foo", "B", "A", "B::foo", "B", "A"};
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
@@ -40,20 +54,23 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
-        // TODO(b/173398086): uniqueMethodWithName() does not work with proto changes.
         .addDontObfuscate()
+        .addKeepClassAndMembersRules(B.class)
         .setMinApi(parameters.getApiLevel())
         .compile()
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/258720808): We should not produce incorrect results.
+        .assertSuccessWithOutputLines(R8_EXPECTED)
         .inspect(
             inspector -> {
-              ClassSubject aClassSubject = inspector.clazz(A.class);
-              assertThat(aClassSubject, isPresent());
-
               ClassSubject bClassSubject = inspector.clazz(B.class);
               assertThat(bClassSubject, isPresent());
 
-              TypeSubject aTypeSubject = aClassSubject.asTypeSubject();
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
               TypeSubject bTypeSubject = bClassSubject.asTypeSubject();
+              TypeSubject aTypeSubject = aClassSubject.asTypeSubject();
 
               MethodSubject fooMethodSubject = aClassSubject.uniqueMethodWithOriginalName("foo");
               assertThat(fooMethodSubject, isPresent());
@@ -62,12 +79,10 @@
               // TODO(b/173398086): Consider rewriting B.foo(B, A) to B.foo(A, B, C) instead of
               //  B.foo$1(A, B).
               MethodSubject otherFooMethodSubject =
-                  bClassSubject.uniqueMethodWithOriginalName("foo$1");
+                  bClassSubject.uniqueMethodWithOriginalName("foo");
               assertThat(otherFooMethodSubject, isPresent());
               assertThat(otherFooMethodSubject, hasParameters(aTypeSubject, bTypeSubject));
-            })
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("A", "B", "A", "B");
+            });
   }
 
   static class Main {
@@ -75,16 +90,18 @@
     public static void main(String[] args) {
       A a = new A();
       B b = new B();
+      a.foo(b, a);
       a.foo(a, b);
-      b.foo(b, a);
+      b.foo(a, b);
     }
   }
 
   @NoVerticalClassMerging
-  static class A {
+  static class B {
 
     @NeverInline
     public void foo(A a, B b) {
+      System.out.println("B::foo");
       System.out.println(a);
       System.out.println(b);
     }
@@ -95,10 +112,11 @@
     }
   }
 
-  static class B extends A {
+  static class A extends B {
 
     @NeverInline
     public void foo(B b, A a) {
+      System.out.println("A::foo");
       System.out.println(a);
       System.out.println(b);
     }
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
index c233d49..e665c12 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
@@ -41,7 +41,8 @@
           RetracePartitionRoundTripTest.ApiTest.class,
           RetracePartitionJoinNoMetadataTest.ApiTest.class,
           RetracePartitionSerializedObfuscatedKeyTest.ApiTest.class,
-          RetracePartitionRoundTripInlineTest.ApiTest.class);
+          RetracePartitionRoundTripInlineTest.ApiTest.class,
+          RetraceApiTypeResultTest.ApiTest.class);
 
   public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
       ImmutableList.of(RetraceApiResidualSignatureTest.ApiTest.class);
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java
new file mode 100644
index 0000000..4d76e2c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceTypeElement;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiTypeResultTest extends RetraceApiTestBase {
+
+  public RetraceApiTypeResultTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final TypeReference minifiedName = Reference.typeFromTypeName("a[]");
+    private final TypeReference original = Reference.typeFromTypeName("some.Class[]");
+
+    private static final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: '1.0' }\nsome.Class -> a:\n";
+
+    @Test
+    public void testRetraceClassArray() {
+      List<RetraceTypeElement> collect =
+          Retracer.createDefault(
+                  ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {})
+              .retraceType(minifiedName)
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(1, collect.size());
+      assertEquals(original, collect.get(0).getType().getTypeReference());
+    }
+
+    @Test
+    public void testRetracePrimitiveArray() {
+      TypeReference intArr = Reference.typeFromTypeName("int[][]");
+      List<RetraceTypeElement> collect =
+          Retracer.createDefault(
+                  ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {})
+              .retraceType(intArr)
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(1, collect.size());
+      assertEquals(intArr, collect.get(0).getType().getTypeReference());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
index 4a76a6a..513dc73 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.AppView;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -33,7 +35,7 @@
     this.parameters = parameters;
   }
 
-  private static final String[] expectedOutput = {"3"};
+  private static final String[] expectedOutput = {"3", "2"};
 
   @Test
   public void d8() throws Exception {
@@ -44,7 +46,7 @@
         .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(expectedOutput)
-        .inspect(this::assertNoArrayLength);
+        .inspect(i -> inspect(i, true));
   }
 
   @Test
@@ -54,32 +56,52 @@
         .addProgramClasses(Main.class)
         .addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(expectedOutput)
-        .inspect(this::assertNoArrayLength);
+        .inspect(i -> inspect(i, false));
   }
 
   private void transformArray(IRCode irCode, AppView<?> appView) {
-    if (irCode.context().getReference().getName().toString().contains("main")) {
       new CodeRewriter(appView).simplifyArrayConstruction(irCode);
+    String name = irCode.context().getReference().getName().toString();
+    if (name.contains("filledArrayData")) {
       assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));
+    } else if (name.contains("filledNewArray")) {
+      assertTrue(irCode.streamInstructions().anyMatch(Instruction::isInvokeNewArray));
     }
   }
 
-  private void assertNoArrayLength(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector, boolean d8) {
     ClassSubject mainClass = inspector.clazz(Main.class);
     assertTrue(mainClass.isPresent());
-    assertTrue(
-        mainClass.mainMethod().streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    MethodSubject filledArrayData = mainClass.uniqueMethodWithOriginalName("filledArrayData");
+    assertTrue(filledArrayData.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    if (!d8) {
+      MethodSubject filledNewArray = mainClass.uniqueMethodWithOriginalName("filledNewArray");
+      assertTrue(filledNewArray.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+    }
   }
 
   public static final class Main {
+    @NeverInline
+    public static void filledArrayData() {
+      short[] values = new short[3];
+      values[0] = 5;
+      values[1] = 6;
+      values[2] = 1;
+      System.out.println(values.length);
+    }
+
+    @NeverInline
+    public static void filledNewArray() {
+      int[] values = new int[] {7, 8};
+      System.out.println(values.length);
+    }
+
     public static void main(String[] args) {
-      int[] ints = new int[3];
-      ints[0] = 5;
-      ints[1] = 6;
-      ints[2] = 1;
-      System.out.println(ints.length);
+      filledArrayData();
+      filledNewArray();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
new file mode 100644
index 0000000..939753f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
@@ -0,0 +1,774 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.code.DexFillArrayData;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.dex.code.DexFilledNewArrayRange;
+import com.android.tools.r8.dex.code.DexNewArray;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.beust.jcommander.internal.Lists;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimplifyArrayConstructionTest extends TestBase {
+  @Parameters(name = "{0}, mode = {1}")
+  public static Iterable<?> data() {
+    return buildParameters(
+        getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
+        CompilationMode.values());
+  }
+
+  private final TestParameters parameters;
+  private final CompilationMode compilationMode;
+
+  public SimplifyArrayConstructionTest(TestParameters parameters, CompilationMode compilationMode) {
+    this.parameters = parameters;
+    this.compilationMode = compilationMode;
+  }
+
+  private static final Class<?>[] DEX_ARRAY_INSTRUCTIONS = {
+    DexNewArray.class, DexFilledNewArray.class, DexFilledNewArrayRange.class, DexFillArrayData.class
+  };
+
+  private static final String[] EXPECTED_OUTPUT = {
+    "[a]",
+    "[a, 1, null]",
+    "[1, null]",
+    "[1, null, 2]",
+    "[1, null, 2]",
+    "[1]",
+    "[1, 2]",
+    "[1, 2, 3, 4, 5]",
+    "[1]",
+    "[a, 1, null, d, e, f]",
+    "[1, null, 3, null, null, 6]",
+    "[1, 2, 3, 4, 5, 6]",
+    "[true, false]",
+    "[1, 2]",
+    "[1, 2]",
+    "[1, 2]",
+    "[1.0, 2.0]",
+    "[1.0, 2.0]",
+    "[]",
+    "[]",
+    "[true]",
+    "[1]",
+    "[1]",
+    "[1]",
+    "[1.0]",
+    "[1.0]",
+    "[0, 1]",
+    "[1, null]",
+    "[a]",
+    "[0, 1]",
+    "200",
+    "[0, 1, 2, 3, 4]",
+    "[4, 0, 0, 0, 0]",
+    "[4, 1, 2, 3, 4]",
+    "[0, 1, 2]",
+    "[0]",
+    "[0, 1, 2]",
+    "[1, 2, 3]",
+    "[1, 2, 3, 4, 5, 6]",
+    "[0]",
+    "[null, null]",
+  };
+
+  private static final byte[] TRANSFORMED_MAIN = transformedMain();
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(compilationMode == CompilationMode.DEBUG);
+    testForRuntime(
+            parameters.getRuntime(),
+            d8TestBuilder ->
+                d8TestBuilder.setMinApi(parameters.getApiLevel()).setMode(compilationMode))
+        .addProgramClassFileData(TRANSFORMED_MAIN)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+        .inspect(inspector -> inspect(inspector, false));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options ->
+                options
+                    .getOpenClosedInterfacesOptions()
+                    .suppressSingleOpenInterface(Reference.classFromClass(Serializable.class)))
+        .setMode(compilationMode)
+        .addProgramClassFileData(TRANSFORMED_MAIN)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .addKeepAnnotation()
+        .addKeepRules("-keepclassmembers class * { @com.android.tools.r8.Keep *; }")
+        .addKeepRules("-assumenosideeffects class * { *** assumedNullField return null; }")
+        .addKeepRules("-assumenosideeffects class * { *** assumedNonNullField return _NONNULL_; }")
+        .addDontObfuscate()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+        .inspect(inspector -> inspect(inspector, true));
+  }
+
+  private static byte[] transformedMain() {
+    try {
+      return transformer(Main.class)
+          .transformMethodInsnInMethod(
+              "interfaceArrayWithRawObject",
+              (opcode, owner, name, descriptor, isInterface, visitor) -> {
+                if (name.equals("getObjectThatImplementsSerializable")) {
+                  visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface);
+                } else {
+                  visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                }
+              })
+          .setReturnType(
+              ClassFileTransformer.MethodPredicate.onName("getObjectThatImplementsSerializable"),
+              Object.class.getTypeName())
+          .transform();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private void inspect(CodeInspector inspector, boolean isR8) {
+    if (parameters.isCfRuntime()) {
+      return;
+    }
+    ClassSubject mainClass = inspector.clazz(Main.class);
+    assertTrue(mainClass.isPresent());
+
+    MethodSubject stringArrays = mainClass.uniqueMethodWithOriginalName("stringArrays");
+    MethodSubject referenceArraysNoCasts =
+        mainClass.uniqueMethodWithOriginalName("referenceArraysNoCasts");
+    MethodSubject referenceArraysWithSubclasses =
+        mainClass.uniqueMethodWithOriginalName("referenceArraysWithSubclasses");
+    MethodSubject interfaceArrayWithRawObject =
+        mainClass.uniqueMethodWithOriginalName("interfaceArrayWithRawObject");
+
+    MethodSubject phiFilledNewArray = mainClass.uniqueMethodWithOriginalName("phiFilledNewArray");
+    MethodSubject intsThatUseFilledNewArray =
+        mainClass.uniqueMethodWithOriginalName("intsThatUseFilledNewArray");
+    MethodSubject twoDimensionalArrays =
+        mainClass.uniqueMethodWithOriginalName("twoDimensionalArrays");
+    MethodSubject objectArraysFilledNewArrayRange =
+        mainClass.uniqueMethodWithOriginalName("objectArraysFilledNewArrayRange");
+    MethodSubject arraysThatUseFilledData =
+        mainClass.uniqueMethodWithOriginalName("arraysThatUseFilledData");
+    MethodSubject arraysThatUseNewArrayEmpty =
+        mainClass.uniqueMethodWithOriginalName("arraysThatUseNewArrayEmpty");
+    MethodSubject reversedArray = mainClass.uniqueMethodWithOriginalName("reversedArray");
+    MethodSubject arrayWithCorrectCountButIncompleteCoverage =
+        mainClass.uniqueMethodWithOriginalName("arrayWithCorrectCountButIncompleteCoverage");
+    MethodSubject arrayWithExtraInitialPuts =
+        mainClass.uniqueMethodWithOriginalName("arrayWithExtraInitialPuts");
+    MethodSubject catchHandlerThrowing =
+        mainClass.uniqueMethodWithOriginalName("catchHandlerThrowing");
+    MethodSubject catchHandlerNonThrowingFilledNewArray =
+        mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFilledNewArray");
+    MethodSubject catchHandlerNonThrowingFillArrayData =
+        mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFillArrayData");
+    MethodSubject assumedValues = mainClass.uniqueMethodWithOriginalName("assumedValues");
+
+    assertArrayTypes(arraysThatUseNewArrayEmpty, DexNewArray.class);
+    assertArrayTypes(intsThatUseFilledNewArray, DexFilledNewArray.class);
+    assertFilledArrayData(arraysThatUseFilledData);
+
+    if (compilationMode == CompilationMode.DEBUG) {
+      // The explicit assignments can't be collapsed without breaking the debugger's ability to
+      // visit each line.
+      assertArrayTypes(reversedArray, DexNewArray.class);
+    } else {
+      assertArrayTypes(reversedArray, DexFilledNewArray.class);
+    }
+
+    // Cannot use filled-new-array of String before K.
+    if (parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) {
+      assertArrayTypes(stringArrays, DexNewArray.class);
+    } else {
+      assertArrayTypes(stringArrays, DexFilledNewArray.class);
+    }
+    // Cannot use filled-new-array of Object before L.
+    if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+      assertArrayTypes(referenceArraysNoCasts, DexNewArray.class);
+      assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+      assertArrayTypes(phiFilledNewArray, DexNewArray.class);
+      assertArrayTypes(objectArraysFilledNewArrayRange, DexNewArray.class);
+      assertArrayTypes(twoDimensionalArrays, DexNewArray.class);
+      assertArrayTypes(assumedValues, DexNewArray.class);
+    } else {
+      assertArrayTypes(referenceArraysNoCasts, DexFilledNewArray.class);
+      // TODO(b/246971330): Add support for arrays with subtypes.
+      if (isR8) {
+        assertArrayTypes(referenceArraysWithSubclasses, DexFilledNewArray.class);
+      } else {
+        assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+      }
+
+      // TODO(b/246971330): Add support for arrays whose values have conditionals.
+      // assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
+
+      assertArrayTypes(objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class);
+
+      if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
+        assertArrayTypes(twoDimensionalArrays, DexFilledNewArray.class);
+      } else {
+        // No need to assert this case. If it's wrong, Dalvik verify errors cause test failures.
+      }
+
+      assertArrayTypes(assumedValues, DexFilledNewArray.class);
+    }
+    // filled-new-array fails verification when types of parameters are not subclasses (aput-object
+    // does not).
+    assertArrayTypes(interfaceArrayWithRawObject, DexNewArray.class);
+
+    // These could be optimized to use InvokeNewArray, but they seem like rare code patterns so we
+    // haven't bothered.
+    assertArrayTypes(arrayWithExtraInitialPuts, DexNewArray.class);
+    assertArrayTypes(arrayWithCorrectCountButIncompleteCoverage, DexNewArray.class);
+
+    assertArrayTypes(catchHandlerThrowing, DexNewArray.class);
+    assertArrayTypes(catchHandlerNonThrowingFillArrayData, DexNewArray.class);
+    assertArrayTypes(catchHandlerNonThrowingFilledNewArray, DexFilledNewArray.class);
+  }
+
+  private static Predicate<InstructionSubject> isInstruction(List<Class<?>> allowlist) {
+    return (ins) -> allowlist.contains(ins.asDexInstruction().getInstruction().getClass());
+  }
+
+  private static Predicate<InstructionSubject> isInstruction(Class<?> clazz) {
+    return isInstruction(Arrays.asList(clazz));
+  }
+
+  private static void assertArrayTypes(MethodSubject method, Class<?> allowedArrayInst) {
+    assertTrue(method.isPresent());
+    List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+    disallowedClasses.remove(allowedArrayInst);
+
+    assertTrue(method.streamInstructions().anyMatch(isInstruction(allowedArrayInst)));
+    assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+  }
+
+  private static void assertFilledArrayData(MethodSubject method) {
+    assertTrue(method.isPresent());
+    List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+    disallowedClasses.remove(DexFillArrayData.class);
+    disallowedClasses.remove(DexNewArray.class);
+
+    assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+    assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isArrayPut));
+    long numNewArray = method.streamInstructions().filter(InstructionSubject::isNewArray).count();
+    long numFillArray =
+        method.streamInstructions().filter(isInstruction(DexFillArrayData.class)).count();
+    assertEquals(numNewArray, numFillArray);
+  }
+
+  public static final class Main {
+    static final String assumedNonNullField = null;
+    static final String assumedNullField = null;
+
+    public static void main(String[] args) {
+      stringArrays();
+      referenceArraysNoCasts();
+      referenceArraysWithSubclasses();
+      interfaceArrayWithRawObject();
+      phiFilledNewArray();
+      intsThatUseFilledNewArray();
+      twoDimensionalArrays();
+      objectArraysFilledNewArrayRange();
+      arraysThatUseFilledData();
+      arraysThatUseNewArrayEmpty();
+      reversedArray();
+      arrayWithCorrectCountButIncompleteCoverage();
+      arrayWithExtraInitialPuts();
+      catchHandlerThrowing();
+      catchHandlerNonThrowingFilledNewArray();
+      catchHandlerNonThrowingFillArrayData();
+      arrayIntoAnotherArray();
+      assumedValues();
+    }
+
+    @NeverInline
+    private static void stringArrays() {
+      // Test exact class, no null.
+      String[] stringArr = {"a"};
+      System.out.println(Arrays.toString(stringArr));
+    }
+
+    @NeverInline
+    private static void referenceArraysNoCasts() {
+      // Tests that no type info is needed when array type is Object[].
+      Object[] objectArr = {"a", 1, null};
+      System.out.println(Arrays.toString(objectArr));
+      // Test that interface arrays work when we have the exact interface already.
+      Serializable[] interfaceArr = {getSerializable(1), null};
+      System.out.println(Arrays.toString(interfaceArr));
+    }
+
+    @Keep
+    private static Serializable getSerializable(Integer value) {
+      return value;
+    }
+
+    @NeverInline
+    private static void referenceArraysWithSubclasses() {
+      Serializable[] interfaceArr = {1, null, 2};
+      System.out.println(Arrays.toString(interfaceArr));
+      Number[] objArray = {1, null, 2};
+      System.out.println(Arrays.toString(objArray));
+    }
+
+    @NeverInline
+    private static void interfaceArrayWithRawObject() {
+      // Interfaces can use filled-new-array only when we know types implement the interface.
+      Serializable[] arr = new Serializable[1];
+      // Transformed from `I get()` to `Object get()`.
+      arr[0] = getObjectThatImplementsSerializable();
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @Keep
+    private static /*Object*/ Serializable getObjectThatImplementsSerializable() {
+      return 1;
+    }
+
+    @NeverInline
+    private static void reversedArray() {
+      int[] arr = new int[5];
+      arr[4] = 4;
+      arr[3] = 3;
+      arr[2] = 2;
+      arr[1] = 1;
+      arr[0] = 0;
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void arrayWithCorrectCountButIncompleteCoverage() {
+      int[] arr = new int[5];
+      arr[0] = 0;
+      arr[0] = 1;
+      arr[0] = 2;
+      arr[0] = 3;
+      arr[0] = 4;
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void arrayWithExtraInitialPuts() {
+      int[] arr = new int[5];
+      arr[0] = 0;
+      arr[0] = 1;
+      arr[0] = 2;
+      arr[0] = 3;
+      arr[0] = 4;
+      arr[1] = 1;
+      arr[2] = 2;
+      arr[3] = 3;
+      arr[4] = 4;
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void catchHandlerNonThrowingFilledNewArray() {
+      try {
+        int[] arr1 = {1, 2, 3};
+        System.currentTimeMillis();
+        System.out.println(Arrays.toString(arr1));
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    @NeverInline
+    private static void catchHandlerNonThrowingFillArrayData() {
+      try {
+        int[] arr = {1, 2, 3, 4, 5, 6};
+        System.currentTimeMillis();
+        System.out.println(Arrays.toString(arr));
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    @NeverInline
+    private static void catchHandlerThrowing() {
+      int[] arr1 = new int[3];
+      arr1[0] = 0;
+      arr1[1] = 1;
+      // Since the array is used in only one spot, and that spot is not within the try/catch, it
+      // should be safe to use filled-new-array, but we don't.
+      try {
+        System.currentTimeMillis();
+        arr1[2] = 2;
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+      System.out.println(Arrays.toString(arr1));
+
+      try {
+        // Test filled-new-array with a throwing instruction before the last array-put.
+        int[] arr2 = new int[1];
+        System.currentTimeMillis();
+        arr2[0] = 0;
+        System.out.println(Arrays.toString(arr2));
+
+        // Test filled-array-data with a throwing instruction before the last array-put.
+        short[] arr3 = new short[3];
+        arr3[0] = 0;
+        arr3[1] = 1;
+        System.currentTimeMillis();
+        arr3[2] = 2;
+        System.out.println(Arrays.toString(arr3));
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    @NeverInline
+    private static void arrayIntoAnotherArray() {
+      // Tests that our computeValues() method does not assume all array-put-object users have
+      // the array as the target.
+      int[] intArr = new int[1];
+      Object[] objArr = new Object[2];
+      objArr[0] = intArr;
+      System.out.println(Arrays.toString((int[]) objArr[0]));
+    }
+
+    @NeverInline
+    private static void assumedValues() {
+      Object[] arr = {assumedNonNullField, assumedNullField};
+      System.out.println(Arrays.toString(arr));
+    }
+
+    @NeverInline
+    private static void phiFilledNewArray() {
+      // The presence of ? should not affect use of filled-new-array.
+      Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3};
+      System.out.println(Arrays.toString(phiArray));
+    }
+
+    @NeverInline
+    private static void intsThatUseFilledNewArray() {
+      // Up to 5 ints uses filled-new-array rather than filled-array-data.
+      int[] intArr = {1, 2, 3, 4, 5};
+      System.out.println(Arrays.toString(intArr));
+    }
+
+    @NeverInline
+    private static void twoDimensionalArrays() {
+      Integer[][] twoDimensions = {new Integer[] {1}, null};
+      System.out.println(Arrays.toString(Arrays.asList(twoDimensions).get(0)));
+    }
+
+    @NeverInline
+    private static void objectArraysFilledNewArrayRange() {
+      // 6 or more elements use /range variant.
+      Object[] objectArr = {"a", 1, null, "d", "e", "f"};
+      System.out.println(Arrays.toString(objectArr));
+      Serializable[] interfaceArr = {
+        getSerializable(1), null, getSerializable(3), null, null, getSerializable(6)
+      };
+      System.out.println(Arrays.toString(interfaceArr));
+    }
+
+    @NeverInline
+    private static void arraysThatUseFilledData() {
+      // For int[], <= 5 elements should use NewArrayFilledData (otherwise NewFilledArray is used).
+      int[] intArr = {1, 2, 3, 4, 5, 6};
+      // For other primitives, > 1 element leads to fill-array-data.
+      System.out.println(Arrays.toString(intArr));
+      boolean[] boolArr = {true, false};
+      System.out.println(Arrays.toString(boolArr));
+      byte[] byteArr = {1, 2};
+      System.out.println(Arrays.toString(byteArr));
+      char[] charArr = {'1', '2'};
+      System.out.println(Arrays.toString(charArr));
+      long[] longArr = {1, 2};
+      System.out.println(Arrays.toString(longArr));
+      float[] floatArr = {1, 2};
+      System.out.println(Arrays.toString(floatArr));
+      double[] doubleArr = {1, 2};
+      System.out.println(Arrays.toString(doubleArr));
+    }
+
+    @NeverInline
+    private static void arraysThatUseNewArrayEmpty() {
+      // int/object of size zero should not use filled-new-array.
+      int[] intArr = {};
+      System.out.println(Arrays.toString(intArr));
+      String[] strArr = {};
+      System.out.println(Arrays.toString(strArr));
+
+      // Other primitives with size <= 1 should not use filled-array-data.
+      boolean[] boolArr = {true};
+      System.out.println(Arrays.toString(boolArr));
+      byte[] byteArr = {1};
+      System.out.println(Arrays.toString(byteArr));
+      char[] charArr = {'1'};
+      System.out.println(Arrays.toString(charArr));
+      long[] longArr = {1};
+      System.out.println(Arrays.toString(longArr));
+      float[] floatArr = {1};
+      System.out.println(Arrays.toString(floatArr));
+      double[] doubleArr = {1};
+      System.out.println(Arrays.toString(doubleArr));
+
+      // Array used before all members are set.
+      int[] readArray = new int[2];
+      readArray[0] = (int) (System.currentTimeMillis() / System.nanoTime());
+      readArray[1] = readArray[0] + 1;
+      System.out.println(Arrays.toString(readArray));
+
+      // Array does not have all elements set (we could make this work, but likely this is rare).
+      Integer[] partialArray = new Integer[2];
+      partialArray[0] = 1;
+      System.out.println(Arrays.toString(partialArray));
+
+      // Non-constant array size.
+      int trickyZero = (int) (System.currentTimeMillis() / System.nanoTime());
+      Object[] nonConstSize = new Object[trickyZero + 1];
+      nonConstSize[0] = "a";
+      System.out.println(Arrays.toString(nonConstSize));
+
+      // Non-constant index.
+      Object[] nonConstIndex = new Object[2];
+      nonConstIndex[trickyZero] = 0;
+      nonConstIndex[trickyZero + 1] = 1;
+      System.out.println(Arrays.toString(nonConstIndex));
+
+      // Exceeds our (arbitrary) size limit for /range.
+      String[] bigArr = new String[201];
+      bigArr[0] = "0";
+      bigArr[1] = "1";
+      bigArr[2] = "2";
+      bigArr[3] = "3";
+      bigArr[4] = "4";
+      bigArr[5] = "5";
+      bigArr[6] = "6";
+      bigArr[7] = "7";
+      bigArr[8] = "8";
+      bigArr[9] = "9";
+      bigArr[10] = "10";
+      bigArr[11] = "11";
+      bigArr[12] = "12";
+      bigArr[13] = "13";
+      bigArr[14] = "14";
+      bigArr[15] = "15";
+      bigArr[16] = "16";
+      bigArr[17] = "17";
+      bigArr[18] = "18";
+      bigArr[19] = "19";
+      bigArr[20] = "20";
+      bigArr[21] = "21";
+      bigArr[22] = "22";
+      bigArr[23] = "23";
+      bigArr[24] = "24";
+      bigArr[25] = "25";
+      bigArr[26] = "26";
+      bigArr[27] = "27";
+      bigArr[28] = "28";
+      bigArr[29] = "29";
+      bigArr[30] = "30";
+      bigArr[31] = "31";
+      bigArr[32] = "32";
+      bigArr[33] = "33";
+      bigArr[34] = "34";
+      bigArr[35] = "35";
+      bigArr[36] = "36";
+      bigArr[37] = "37";
+      bigArr[38] = "38";
+      bigArr[39] = "39";
+      bigArr[40] = "40";
+      bigArr[41] = "41";
+      bigArr[42] = "42";
+      bigArr[43] = "43";
+      bigArr[44] = "44";
+      bigArr[45] = "45";
+      bigArr[46] = "46";
+      bigArr[47] = "47";
+      bigArr[48] = "48";
+      bigArr[49] = "49";
+      bigArr[50] = "50";
+      bigArr[51] = "51";
+      bigArr[52] = "52";
+      bigArr[53] = "53";
+      bigArr[54] = "54";
+      bigArr[55] = "55";
+      bigArr[56] = "56";
+      bigArr[57] = "57";
+      bigArr[58] = "58";
+      bigArr[59] = "59";
+      bigArr[60] = "60";
+      bigArr[61] = "61";
+      bigArr[62] = "62";
+      bigArr[63] = "63";
+      bigArr[64] = "64";
+      bigArr[65] = "65";
+      bigArr[66] = "66";
+      bigArr[67] = "67";
+      bigArr[68] = "68";
+      bigArr[69] = "69";
+      bigArr[70] = "70";
+      bigArr[71] = "71";
+      bigArr[72] = "72";
+      bigArr[73] = "73";
+      bigArr[74] = "74";
+      bigArr[75] = "75";
+      bigArr[76] = "76";
+      bigArr[77] = "77";
+      bigArr[78] = "78";
+      bigArr[79] = "79";
+      bigArr[80] = "80";
+      bigArr[81] = "81";
+      bigArr[82] = "82";
+      bigArr[83] = "83";
+      bigArr[84] = "84";
+      bigArr[85] = "85";
+      bigArr[86] = "86";
+      bigArr[87] = "87";
+      bigArr[88] = "88";
+      bigArr[89] = "89";
+      bigArr[90] = "90";
+      bigArr[91] = "91";
+      bigArr[92] = "92";
+      bigArr[93] = "93";
+      bigArr[94] = "94";
+      bigArr[95] = "95";
+      bigArr[96] = "96";
+      bigArr[97] = "97";
+      bigArr[98] = "98";
+      bigArr[99] = "99";
+      bigArr[100] = "100";
+      bigArr[101] = "101";
+      bigArr[102] = "102";
+      bigArr[103] = "103";
+      bigArr[104] = "104";
+      bigArr[105] = "105";
+      bigArr[106] = "106";
+      bigArr[107] = "107";
+      bigArr[108] = "108";
+      bigArr[109] = "109";
+      bigArr[110] = "110";
+      bigArr[111] = "111";
+      bigArr[112] = "112";
+      bigArr[113] = "113";
+      bigArr[114] = "114";
+      bigArr[115] = "115";
+      bigArr[116] = "116";
+      bigArr[117] = "117";
+      bigArr[118] = "118";
+      bigArr[119] = "119";
+      bigArr[120] = "120";
+      bigArr[121] = "121";
+      bigArr[122] = "122";
+      bigArr[123] = "123";
+      bigArr[124] = "124";
+      bigArr[125] = "125";
+      bigArr[126] = "126";
+      bigArr[127] = "127";
+      bigArr[128] = "128";
+      bigArr[129] = "129";
+      bigArr[130] = "130";
+      bigArr[131] = "131";
+      bigArr[132] = "132";
+      bigArr[133] = "133";
+      bigArr[134] = "134";
+      bigArr[135] = "135";
+      bigArr[136] = "136";
+      bigArr[137] = "137";
+      bigArr[138] = "138";
+      bigArr[139] = "139";
+      bigArr[140] = "140";
+      bigArr[141] = "141";
+      bigArr[142] = "142";
+      bigArr[143] = "143";
+      bigArr[144] = "144";
+      bigArr[145] = "145";
+      bigArr[146] = "146";
+      bigArr[147] = "147";
+      bigArr[148] = "148";
+      bigArr[149] = "149";
+      bigArr[150] = "150";
+      bigArr[151] = "151";
+      bigArr[152] = "152";
+      bigArr[153] = "153";
+      bigArr[154] = "154";
+      bigArr[155] = "155";
+      bigArr[156] = "156";
+      bigArr[157] = "157";
+      bigArr[158] = "158";
+      bigArr[159] = "159";
+      bigArr[160] = "160";
+      bigArr[161] = "161";
+      bigArr[162] = "162";
+      bigArr[163] = "163";
+      bigArr[164] = "164";
+      bigArr[165] = "165";
+      bigArr[166] = "166";
+      bigArr[167] = "167";
+      bigArr[168] = "168";
+      bigArr[169] = "169";
+      bigArr[170] = "170";
+      bigArr[171] = "171";
+      bigArr[172] = "172";
+      bigArr[173] = "173";
+      bigArr[174] = "174";
+      bigArr[175] = "175";
+      bigArr[176] = "176";
+      bigArr[177] = "177";
+      bigArr[178] = "178";
+      bigArr[179] = "179";
+      bigArr[180] = "180";
+      bigArr[181] = "181";
+      bigArr[182] = "182";
+      bigArr[183] = "183";
+      bigArr[184] = "184";
+      bigArr[185] = "185";
+      bigArr[186] = "186";
+      bigArr[187] = "187";
+      bigArr[188] = "188";
+      bigArr[189] = "189";
+      bigArr[190] = "190";
+      bigArr[191] = "191";
+      bigArr[192] = "192";
+      bigArr[193] = "193";
+      bigArr[194] = "194";
+      bigArr[195] = "195";
+      bigArr[196] = "196";
+      bigArr[197] = "197";
+      bigArr[198] = "198";
+      bigArr[199] = "199";
+      bigArr[200] = "200";
+      System.out.println(Arrays.asList(bigArr).get(200));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodTest.java
new file mode 100644
index 0000000..939b14a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideDefaultMethodTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.librarymethodoverride;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideDefaultMethodTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(ProgramI.class, ProgramClass.class, Main.class)
+        .addLibraryClasses(LibraryI.class, LibraryClass.class)
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryI.class, LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("ProgramI::foo");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(ProgramI.class, ProgramClass.class, Main.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addOptionsModification(
+            options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+        .addLibraryClasses(LibraryI.class, LibraryClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .compile()
+        .addBootClasspathClasses(LibraryI.class, LibraryClass.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("ProgramI::foo");
+  }
+
+  private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
+    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+    DexProgramClass clazz =
+        appInfo
+            .definitionFor(dexItemFactory.createType(descriptor(ProgramI.class)))
+            .asProgramClass();
+    DexEncodedMethod method =
+        clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
+    assertTrue(method.isLibraryMethodOverride().isTrue());
+  }
+
+  public interface LibraryI {
+
+    void foo();
+  }
+
+  public static class LibraryClass {
+
+    public static void callI(LibraryI i) {
+      i.foo();
+    }
+  }
+
+  public interface ProgramI extends LibraryI {
+
+    @Override
+    default void foo() {
+      System.out.println("ProgramI::foo");
+    }
+  }
+
+  public static class ProgramClass implements ProgramI {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      LibraryClass.callI(new ProgramClass());
+    }
+  }
+}
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index 023f1c9..e86ba01 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-ec4f1b68e126b5b5e8a53078c74c79c65e190cf2
\ No newline at end of file
+8eea4fa493bb7cb63d11e8d2a0ac34775726825e
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 63d02c6..08f453e 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -140,12 +140,31 @@
         + github_account + '/' + GITHUB_REPRO, checkout_dir)
   git_utils.GitCheckout(desugar_jdk_libs_hash, checkout_dir)
 
-def GetJavaEnv():
+def GetJavaEnv(androidHomeTemp):
   java_env = dict(os.environ, JAVA_HOME = jdk.GetJdk11Home())
   java_env['PATH'] = java_env['PATH'] + os.pathsep + os.path.join(jdk.GetJdk11Home(), 'bin')
   java_env['GRADLE_OPTS'] = '-Xmx1g'
+  java_env['ANDROID_HOME'] = androidHomeTemp
   return java_env
 
+def setUpFakeAndroidHome(androidHomeTemp):
+  # Bazel will check if 30 is present then extract android.jar from 32.
+  # We copy android.jar from third_party to mimic repository structure.
+  subpath = os.path.join(androidHomeTemp, "platforms")
+  cmd = ["mkdir", subpath]
+  subprocess.check_call(cmd)
+  subpath30 = os.path.join(subpath, "android-30")
+  cmd = ["mkdir", subpath30]
+  subprocess.check_call(cmd)
+  subpath = os.path.join(subpath, "android-32")
+  cmd = ["mkdir", subpath]
+  subprocess.check_call(cmd)
+  dest = os.path.join(subpath, "android.jar")
+  sha = os.path.join(utils.THIRD_PARTY, "android_jar", "lib-v32.tar.gz.sha1")
+  utils.DownloadFromGoogleCloudStorage(sha)
+  src = os.path.join(utils.THIRD_PARTY, "android_jar", "lib-v32", "android.jar")
+  cmd = ["cp", src, dest]
+  subprocess.check_call(cmd)
 
 def BuildDesugaredLibrary(checkout_dir, variant, version = None):
   if not variant in MAVEN_RELEASE_TARGET_MAP:
@@ -153,19 +172,22 @@
   if variant != 'jdk8' and variant != 'jdk11_legacy' and version is None:
     raise Exception('Variant ' + variant + ' require version for undesugaring')
   with utils.ChangedWorkingDirectory(checkout_dir):
-    bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
-    cmd = [
+    with utils.TempDir() as androidHomeTemp:
+      setUpFakeAndroidHome(androidHomeTemp)
+      javaEnv = GetJavaEnv(androidHomeTemp)
+      bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
+      cmd = [
         bazel,
         '--bazelrc=/dev/null',
         'build',
         '--spawn_strategy=local',
         '--verbose_failures',
         MAVEN_RELEASE_TARGET_MAP[variant]]
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd, env=GetJavaEnv())
-    cmd = [bazel, 'shutdown']
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd, env=GetJavaEnv())
+      utils.PrintCmd(cmd)
+      subprocess.check_call(cmd, env=javaEnv)
+      cmd = [bazel, 'shutdown']
+      utils.PrintCmd(cmd)
+      subprocess.check_call(cmd, env=javaEnv)
 
     # Locate the library jar and the maven zip with the jar from the
     # bazel build.
diff --git a/tools/dex2oat.py b/tools/dex2oat.py
index 04422cf..10c4732 100755
--- a/tools/dex2oat.py
+++ b/tools/dex2oat.py
@@ -12,16 +12,21 @@
 
 LINUX_DIR = os.path.join(utils.TOOLS_DIR, 'linux')
 
+LATEST = '12.0.0'
+
 VERSIONS = [
-  'default',
-  '9.0.0',
-  '8.1.0',
-  '7.0.0',
+  '12.0.0',
+  # TODO(b/258170524): Fix the broken dex2oat versions.
+  # 'default',
+  # '9.0.0',
+  # '8.1.0',
+  # '7.0.0',
   '6.0.1',
-  '5.1.1',
+  # '5.1.1',
 ]
 
 DIRS = {
+  '12.0.0': 'host/art-12.0.0-beta4',
   'default': 'art',
   '9.0.0': 'art-9.0.0',
   '8.1.0': 'art-8.1.0',
@@ -31,6 +36,7 @@
 }
 
 PRODUCTS = {
+  '12.0.0': 'redfin',
   'default': 'angler',
   '9.0.0': 'marlin',
   '8.1.0': 'marlin',
@@ -40,6 +46,7 @@
 }
 
 ARCHS = {
+  '12.0.0': 'x86_64',
   'default': 'arm64',
   '9.0.0': 'arm64',
   '8.1.0': 'arm64',
@@ -58,12 +65,16 @@
   'all',
 ]
 
+BOOT_IMAGE = {
+  '12.0.0': 'apex/art_boot_images/javalib/boot.art'
+}
+
 def ParseOptions():
   parser = optparse.OptionParser()
   parser.add_option('--version',
-                    help='Version of dex2oat. (defaults to latest, eg, tools/linux/art).',
+                    help='Version of dex2oat. (defaults to latest: ' + LATEST + ').',
                     choices=VERSIONS,
-                    default='default')
+                    default=LATEST)
   parser.add_option('--all',
                     help='Run dex2oat on all possible versions',
                     default=False,
@@ -96,7 +107,9 @@
     print("")
   return 0
 
-def run(dexfile, oatfile=None, version='default', verbose=[]):
+def run(dexfile, oatfile=None, version=None, verbose=[]):
+  if not version:
+    version = LATEST
   # dex2oat accepts non-existent dex files, check here instead
   if not os.path.exists(dexfile):
     raise Exception('DEX file not found: "{}"'.format(dexfile))
@@ -117,9 +130,12 @@
     ]
     for flag in verbose:
       cmd += ['--runtime-arg', '-verbose:' + flag]
+    if version in BOOT_IMAGE:
+      cmd += ['--boot-image=' + BOOT_IMAGE[version]]
     env = {"LD_LIBRARY_PATH": os.path.join(base, 'lib')}
     utils.PrintCmd(cmd)
-    subprocess.check_call(cmd, env = env)
+    with utils.ChangedWorkingDirectory(base):
+      subprocess.check_call(cmd, env = env)
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/linux/README.art-versions b/tools/linux/README.art-versions
index 5e38ebd..611c06e 100644
--- a/tools/linux/README.art-versions
+++ b/tools/linux/README.art-versions
@@ -115,6 +115,10 @@
      --art-dir host/art-12.0.0-beta4 \
      --android-product redfin
 
+The dex2oat tool expects to find core-oj.dex in out/host/linux-x86 so copy it.
+
+(cd tools/linux/host; cp -r apex out/host/linux-x86/apex)
+
 (cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-12.0.0-beta4)
 
 art-10.0.0 (Android Q)
diff --git a/tools/linux/host/art-12.0.0-beta4.tar.gz.sha1 b/tools/linux/host/art-12.0.0-beta4.tar.gz.sha1
index b95d909..9f80774 100644
--- a/tools/linux/host/art-12.0.0-beta4.tar.gz.sha1
+++ b/tools/linux/host/art-12.0.0-beta4.tar.gz.sha1
@@ -1 +1 @@
-9b6d0b061669e0fb1b09ba92c3650d5c7bdc9e85
\ No newline at end of file
+df7267e9eff9cc1812b5a7b4111e7175d5e186e9
\ No newline at end of file