Merge commit 'edf8439d2bc78bfc7e9b6a5052edffda49bcaba3' into dev-release
diff --git a/build.gradle b/build.gradle
index c47befd..b7c7830 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1939,6 +1939,11 @@
         systemProperty 'runtimes', project.property('runtimes')
     }
 
+    if (project.hasProperty('horizontalClassMerging')) {
+        println "NOTE: Running with horizontal class merging"
+        systemProperty 'com.android.tools.r8.horizontalClassMerging', 'true'
+    }
+
     if (project.hasProperty('slow_tests')) {
         systemProperty 'slow_tests', project.property('slow_tests')
     }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 9e462dd..84bc9b3 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -398,6 +398,7 @@
     // Assert some of R8 optimizations are disabled.
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
+    assert !internal.enableHorizontalClassMerging;
     assert !internal.enableStaticClassMerging;
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index db24f23..221df4c 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -167,6 +167,7 @@
     // Assert some of R8 optimizations are disabled.
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
+    assert !internal.enableHorizontalClassMerging;
     assert !internal.enableStaticClassMerging;
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index c0efa2c..2f53841 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -207,7 +207,10 @@
       addType(field.holder);
       Set<DexField> typeFields = fields.get(field.holder);
       if (typeFields != null) {
-        assert baseField != null;
+        if (baseField == null) {
+          System.out.println(field.toSourceString());
+        }
+        assert baseField != null : field.toSourceString();
         if (!allowObfuscation) {
           noObfuscationTypes.add(field.holder);
         }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 7c78db1..0375b6b 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -845,6 +845,7 @@
             ? LineNumberOptimization.ON
             : LineNumberOptimization.OFF;
 
+    assert proguardConfiguration.isOptimizing() || !internal.enableHorizontalClassMerging;
     assert internal.enableStaticClassMerging || !proguardConfiguration.isOptimizing();
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
     assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
@@ -854,6 +855,7 @@
       internal.getProguardConfiguration().getKeepAttributes().localVariableTypeTable = true;
       internal.enableInlining = false;
       internal.enableClassInlining = false;
+      internal.enableHorizontalClassMerging = false;
       internal.enableStaticClassMerging = false;
       internal.enableVerticalClassMerging = false;
       internal.enableClassStaticizer = false;
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 15c2dce..1cd6718 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -145,6 +145,7 @@
       if (failedToCreateDelegate) {
         return;
       }
+      ensureDelegate(handler);
       if (delegate != null) {
         delegate.finished(handler);
         delegate = null;
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index f079277..ddc81b6 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.compatproguard.CompatProguard;
 import com.android.tools.r8.dexsplitter.DexSplitter;
 import com.android.tools.r8.relocator.RelocatorCommandLine;
+import com.android.tools.r8.tracereferences.TraceReferences;
 import java.util.Arrays;
 
 /**
@@ -75,6 +76,9 @@
       case "relocator":
         RelocatorCommandLine.main(shift((args)));
         break;
+      case "tracereferences":
+        TraceReferences.main(shift((args)));
+        break;
       default:
         runDefault(args);
         break;
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
index edbffbe..fa55609 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
@@ -24,7 +24,7 @@
   @Override
   public boolean equals(Object o) {
     return this == o
-        || (o instanceof ClassGraphNode && ((ClassGraphNode) o).reference == reference);
+        || (o instanceof ClassGraphNode && ((ClassGraphNode) o).reference.equals(reference));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
index b091613..836058f 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
@@ -24,7 +24,7 @@
   @Override
   public boolean equals(Object o) {
     return this == o
-        || (o instanceof FieldGraphNode && ((FieldGraphNode) o).reference == reference);
+        || (o instanceof FieldGraphNode && ((FieldGraphNode) o).reference.equals(reference));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
index 94fe8e7..7a92a91 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
@@ -24,7 +24,7 @@
   @Override
   public boolean equals(Object o) {
     return this == o
-        || (o instanceof MethodGraphNode && ((MethodGraphNode) o).reference == reference);
+        || (o instanceof MethodGraphNode && ((MethodGraphNode) o).reference.equals(reference));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 4bb68b9..ed19598 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -15,62 +15,53 @@
 public class AccessControl {
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz, ProgramMethod context, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isClassAccessible(
-        clazz, context.getHolder(), appView.appInfo().getClassToFeatureSplitMap());
+      DexClass clazz,
+      ProgramDefinition context,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isClassAccessible(clazz, context, appView.appInfo().getClassToFeatureSplitMap());
   }
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz, DexProgramClass context, ClassToFeatureSplitMap classToFeatureSplitMap) {
-    if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getType())) {
+      DexClass clazz, ProgramDefinition context, ClassToFeatureSplitMap classToFeatureSplitMap) {
+    if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
       return OptionalBool.FALSE;
     }
     if (clazz.isProgramClass()
-        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
+        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
+            clazz.asProgramClass(), context.getContextClass())) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
   }
 
-  public static OptionalBool isMethodAccessible(
-      DexEncodedMethod method,
-      DexClass holder,
-      ProgramMethod context,
+  /** Intentionally package-private, use {@link MemberResolutionResult#isAccessibleFrom}. */
+  static OptionalBool isMemberAccessible(
+      SuccessfulMemberResolutionResult<?, ?> resolutionResult,
+      ProgramDefinition context,
+      AppInfoWithClassHierarchy appInfo) {
+    return isMemberAccessible(
+        resolutionResult.getResolutionPair(),
+        resolutionResult.getInitialResolutionHolder(),
+        context,
+        appInfo);
+  }
+
+  public static OptionalBool isMemberAccessible(
+      DexClassAndMember<?, ?> member,
+      DexClass initialResolutionHolder,
+      ProgramDefinition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isMethodAccessible(method, holder, context.getHolder(), appView.appInfo());
+    return isMemberAccessible(member, initialResolutionHolder, context, appView.appInfo());
   }
 
-  public static OptionalBool isMethodAccessible(
-      DexEncodedMethod method,
-      DexClass holder,
-      DexProgramClass context,
+  public static OptionalBool isMemberAccessible(
+      DexClassAndMember<?, ?> member,
+      DexClass initialResolutionHolder,
+      ProgramDefinition context,
       AppInfoWithClassHierarchy appInfo) {
-    return isMemberAccessible(method.accessFlags, holder, context, appInfo);
-  }
-
-  public static OptionalBool isFieldAccessible(
-      DexEncodedField field,
-      DexClass holder,
-      ProgramMethod context,
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isFieldAccessible(field, holder, context.getHolder(), appView.appInfo());
-  }
-
-  public static OptionalBool isFieldAccessible(
-      DexEncodedField field,
-      DexClass holder,
-      DexProgramClass context,
-      AppInfoWithClassHierarchy appInfo) {
-    return isMemberAccessible(field.accessFlags, holder, context, appInfo);
-  }
-
-  private static OptionalBool isMemberAccessible(
-      AccessFlags<?> memberFlags,
-      DexClass holder,
-      DexProgramClass context,
-      AppInfoWithClassHierarchy appInfo) {
+    AccessFlags<?> memberFlags = member.getDefinition().getAccessFlags();
     OptionalBool classAccessibility =
-        isClassAccessible(holder, context, appInfo.getClassToFeatureSplitMap());
+        isClassAccessible(initialResolutionHolder, context, appInfo.getClassToFeatureSplitMap());
     if (classAccessibility.isFalse()) {
       return OptionalBool.FALSE;
     }
@@ -78,15 +69,16 @@
       return classAccessibility;
     }
     if (memberFlags.isPrivate()) {
-      if (!isNestMate(holder, context)) {
+      if (!isNestMate(member.getHolder(), context.getContextClass())) {
         return OptionalBool.FALSE;
       }
       return classAccessibility;
     }
-    if (holder.getType().isSamePackage(context.getType())) {
+    if (member.getHolderType().isSamePackage(context.getContextType())) {
       return classAccessibility;
     }
-    if (memberFlags.isProtected() && appInfo.isSubtype(context.getType(), holder.getType())) {
+    if (memberFlags.isProtected()
+        && appInfo.isSubtype(context.getContextType(), member.getHolderType())) {
       return classAccessibility;
     }
     return OptionalBool.FALSE;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index cde4638..dede4e0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -54,7 +54,7 @@
   private EnclosingMethodAttribute enclosingMethod;
 
   /** InnerClasses table. If this class is an inner class, it will have an entry here. */
-  private final List<InnerClassAttribute> innerClasses;
+  private List<InnerClassAttribute> innerClasses;
 
   private NestHostClassAttribute nestHost;
   private final List<NestMemberClassAttribute> nestMembers;
@@ -723,6 +723,10 @@
     return innerClasses;
   }
 
+  public void setInnerClasses(List<InnerClassAttribute> innerClasses) {
+    this.innerClasses = innerClasses;
+  }
+
   public EnclosingMethodAttribute getEnclosingMethodAttribute() {
     return enclosingMethod;
   }
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 24776d0..c30f69c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -509,6 +509,11 @@
     return accessFlags.isPublic();
   }
 
+  public boolean isProtectedMethod() {
+    checkIfObsolete();
+    return accessFlags.isProtected();
+  }
+
   public boolean isPrivateMethod() {
     checkIfObsolete();
     return accessFlags.isPrivate();
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 68da447..b5fe6b8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -494,6 +494,11 @@
   }
 
   @Override
+  public DexProgramClass getContextClass() {
+    return this;
+  }
+
+  @Override
   public DexType getContextType() {
     return getType();
   }
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 500da1e..f21e965 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
@@ -42,7 +41,8 @@
 
   // Bundletool is merging classes that may originate from a build with an old version of R8.
   // Allow merging of classes that use names from older versions of R8.
-  private static List<String> OLD_SYNTHESIZED_NAMES = ImmutableList.of("$r8$java8methods$utility");
+  private static List<String> OLD_SYNTHESIZED_NAMES =
+      ImmutableList.of("$r8$backportedMethods$utility", "$r8$java8methods$utility");
 
   public final DexString descriptor;
   private String toStringCache = null;
@@ -327,7 +327,6 @@
         || name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
         || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME) // Global singleton.
         || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME) // Global singleton.
-        || name.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX) // Shared on reference.
         || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME); // Global singleton.
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index 486996e..ecb09ca 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.utils.OptionalBool;
 
 public abstract class FieldResolutionResult
-    implements MemberResolutionResult<DexEncodedField, DexField> {
+    extends MemberResolutionResult<DexEncodedField, DexField> {
 
   public static FailedFieldResolutionResult failure() {
     return FailedFieldResolutionResult.INSTANCE;
@@ -21,9 +21,6 @@
     return null;
   }
 
-  public abstract OptionalBool isAccessibleFrom(
-      ProgramMethod context, AppInfoWithClassHierarchy appInfo);
-
   public boolean isSuccessfulResolution() {
     return false;
   }
@@ -85,14 +82,15 @@
       return resolvedField;
     }
 
+    @Override
     public DexClassAndField getResolutionPair() {
       return DexClassAndField.create(resolvedHolder, resolvedField);
     }
 
     @Override
-    public OptionalBool isAccessibleFrom(ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
-      return AccessControl.isFieldAccessible(
-          resolvedField, initialResolutionHolder, context.getHolder(), appInfo);
+    public OptionalBool isAccessibleFrom(
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
+      return AccessControl.isMemberAccessible(this, context, appInfo);
     }
 
     @Override
@@ -121,7 +119,8 @@
     private static final FailedFieldResolutionResult INSTANCE = new FailedFieldResolutionResult();
 
     @Override
-    public OptionalBool isAccessibleFrom(ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+    public OptionalBool isAccessibleFrom(
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.FALSE;
     }
 
@@ -140,7 +139,8 @@
     private static final UnknownFieldResolutionResult INSTANCE = new UnknownFieldResolutionResult();
 
     @Override
-    public OptionalBool isAccessibleFrom(ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+    public OptionalBool isAccessibleFrom(
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.FALSE;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
index da70e09..01294be 100644
--- a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
@@ -4,10 +4,20 @@
 
 package com.android.tools.r8.graph;
 
-public interface MemberResolutionResult<
+import com.android.tools.r8.utils.OptionalBool;
+
+public abstract class MemberResolutionResult<
     D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
 
-  boolean isSuccessfulMemberResolutionResult();
+  public abstract boolean isSuccessfulMemberResolutionResult();
 
-  SuccessfulMemberResolutionResult<D, R> asSuccessfulMemberResolutionResult();
+  public abstract SuccessfulMemberResolutionResult<D, R> asSuccessfulMemberResolutionResult();
+
+  public abstract OptionalBool isAccessibleFrom(
+      ProgramDefinition context, AppInfoWithClassHierarchy appInfo);
+
+  public final OptionalBool isAccessibleFrom(
+      ProgramDefinition context, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isAccessibleFrom(context, appView.appInfo());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 91edb06..883b0a6 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -6,6 +6,8 @@
 
 public interface ProgramDefinition {
 
+  DexProgramClass getContextClass();
+
   DexType getContextType();
 
   DexDefinition getDefinition();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMember.java b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
index 328c71f..fc55509 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMember.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
@@ -8,7 +8,14 @@
     extends ProgramDefinition {
 
   @Override
+  default DexProgramClass getContextClass() {
+    return getHolder();
+  }
+
+  @Override
   D getDefinition();
 
+  DexProgramClass getHolder();
+
   DexType getHolderType();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 82b02c4..4db00fc 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -18,8 +18,7 @@
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
-public abstract class ResolutionResult
-    implements MemberResolutionResult<DexEncodedMethod, DexMethod> {
+public abstract class ResolutionResult extends MemberResolutionResult<DexEncodedMethod, DexMethod> {
 
   /**
    * Returns true if resolution succeeded *and* the resolved method has a known definition.
@@ -75,21 +74,8 @@
     return null;
   }
 
-  public abstract OptionalBool isAccessibleFrom(
-      DexProgramClass context, AppInfoWithClassHierarchy appInfo);
-
-  public final OptionalBool isAccessibleFrom(
-      ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
-    return isAccessibleFrom(context.getHolder(), appInfo);
-  }
-
   public abstract OptionalBool isAccessibleForVirtualDispatchFrom(
-      DexProgramClass context, AppInfoWithClassHierarchy appInfo);
-
-  public final OptionalBool isAccessibleForVirtualDispatchFrom(
-      ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
-    return isAccessibleForVirtualDispatchFrom(context.getHolder(), appInfo);
-  }
+      ProgramDefinition context, AppInfoWithClassHierarchy appInfo);
 
   public abstract boolean isVirtualTarget();
 
@@ -183,6 +169,7 @@
           : null;
     }
 
+    @Override
     public DexClassAndMethod getResolutionPair() {
       return DexClassAndMethod.create(resolvedHolder, resolvedMethod);
     }
@@ -210,14 +197,13 @@
 
     @Override
     public OptionalBool isAccessibleFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
-      return AccessControl.isMethodAccessible(
-          resolvedMethod, initialResolutionHolder, context, appInfo);
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
+      return AccessControl.isMemberAccessible(this, context, appInfo);
     }
 
     @Override
     public OptionalBool isAccessibleForVirtualDispatchFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       if (resolvedMethod.isVirtualMethod()) {
         return isAccessibleFrom(context, appInfo);
       }
@@ -781,13 +767,13 @@
 
     @Override
     public OptionalBool isAccessibleFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.TRUE;
     }
 
     @Override
     public OptionalBool isAccessibleForVirtualDispatchFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.TRUE;
     }
 
@@ -816,13 +802,13 @@
 
     @Override
     public OptionalBool isAccessibleFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.FALSE;
     }
 
     @Override
     public OptionalBool isAccessibleForVirtualDispatchFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.FALSE;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java b/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java
index 84d89d0..4a94338 100644
--- a/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java
@@ -12,4 +12,6 @@
   DexClass getResolvedHolder();
 
   D getResolvedMember();
+
+  DexClassAndMember<D, R> getResolutionPair();
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index 37e1690..345905b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -98,13 +98,15 @@
     DexEncodedMethod firstConstructor = constructors.iterator().next();
     DexProto oldProto = firstConstructor.getProto();
 
-    if (isTrivialMerge()) {
+    if (isTrivialMerge() && syntheticArgumentClass == null) {
       return oldProto;
     }
 
     List<DexType> parameters = new ArrayList<>();
     Collections.addAll(parameters, oldProto.parameters.values);
-    parameters.add(dexItemFactory.intType);
+    if (!isTrivialMerge()) {
+      parameters.add(dexItemFactory.intType);
+    }
     if (syntheticArgumentClass != null) {
       parameters.add(syntheticArgumentClass.getArgumentClass());
     }
@@ -171,7 +173,15 @@
     if (isTrivialMerge()) {
       // The constructor does not require the additional argument, just map it like a regular
       // method.
-      lensBuilder.mapMethod(constructors.iterator().next().method, newConstructorReference);
+      DexEncodedMethod oldConstructor = constructors.iterator().next();
+      if (addExtraNull) {
+        List<ExtraParameter> extraParameters = new LinkedList<>();
+        extraParameters.add(new ExtraUnusedNullParameter());
+        lensBuilder.mapMergedConstructor(
+            oldConstructor.method, newConstructorReference, extraParameters);
+      } else {
+        lensBuilder.mapMethod(oldConstructor.method, newConstructorReference);
+      }
     } else {
       // Map each old constructor to the newly synthesized constructor in the graph lens.
       for (DexEncodedMethod oldConstructor : constructors) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index d33b15d..1886a48 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
+import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations;
+import com.android.tools.r8.horizontalclassmerging.policies.NoClassesWithInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.NoFields;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
@@ -17,6 +19,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoStaticClassInitializer;
 import com.android.tools.r8.horizontalclassmerging.policies.NotEntryPoint;
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
 import com.android.tools.r8.horizontalclassmerging.policies.PreventChangingVisibility;
 import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDex;
 import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
@@ -50,10 +53,13 @@
             new NoFields(),
             // TODO(b/166071504): Allow merging of classes that implement interfaces.
             new NoInterfaces(),
+            new NoClassesWithInterfaces(),
             new NoAnnotations(),
+            new NoClassesOrMembersWithAnnotations(),
             new NoInnerClasses(),
             new NoStaticClassInitializer(),
             new NoKeepRules(appView),
+            new NotVerticallyMergedIntoSubtype(appView),
             new NoRuntimeTypeChecks(classMergingEnqueuerExtension),
             new NotEntryPoint(appView.dexItemFactory()),
             new PreventMergeIntoMainDex(appView, mainDexTracingResult),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
index f96a787..9190247 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
@@ -10,6 +10,6 @@
 public class NoAnnotations extends SingleClassPolicy {
   @Override
   public boolean canMerge(DexProgramClass program) {
-    return !program.hasClassOrMemberAnnotations();
+    return !program.isAnnotation();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java
new file mode 100644
index 0000000..f9d2106
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+
+public class NoClassesOrMembersWithAnnotations extends SingleClassPolicy {
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !program.hasClassOrMemberAnnotations();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesWithInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesWithInterfaces.java
new file mode 100644
index 0000000..56e6756
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesWithInterfaces.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+
+public class NoClassesWithInterfaces extends SingleClassPolicy {
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return program.interfaces.isEmpty();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
index 7b194a2..63df0de 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
@@ -11,6 +11,6 @@
 
   @Override
   public boolean canMerge(DexProgramClass program) {
-    return program.interfaces.isEmpty();
+    return !program.isInterface();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
new file mode 100644
index 0000000..67afbc3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class NotVerticallyMergedIntoSubtype extends SingleClassPolicy {
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public NotVerticallyMergedIntoSubtype(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    if (appView.verticallyMergedClasses() == null) {
+      return true;
+    }
+    return !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(program.type);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index b7e359e..3c9bd86 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
-import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -14,6 +13,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -80,12 +80,9 @@
   @Override
   public boolean isMaterializableInContext(
       AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
-    return AccessControl.isFieldAccessible(
-            appView.appInfo().resolveField(field).getResolvedField(),
-            appView.definitionForHolder(field),
-            context.getHolder(),
-            appView.appInfo())
-        .isTrue();
+    SuccessfulFieldResolutionResult resolutionResult =
+        appView.appInfo().resolveField(field).asSuccessfulResolution();
+    return resolutionResult != null && resolutionResult.isAccessibleFrom(context, appView).isTrue();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index da1a799..6dc372e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -56,10 +56,6 @@
 
 public final class BackportedMethodRewriter {
 
-  // Don't change this name, at least not without adding special-casing in DexType to support
-  // merging old dex code in Bundletool.
-  public static final String UTILITY_CLASS_NAME_PREFIX = "$r8$backportedMethods$utility";
-
   private final AppView<?> appView;
   private final RewritableMethods rewritableMethods;
   private final boolean enabled;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 7f697a9..e58eeba 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -1002,18 +1002,26 @@
     // classes if needed.
     AppInfo appInfo = appView.appInfo();
     MainDexClasses mainDexClasses = appInfo.getMainDexClasses();
-    processInterfaces(builder, flavour)
-        .forEach(
-            (interfaceClass, synthesizedClass) -> {
-              // Don't need to optimize synthesized class since all of its methods
-              // are just moved from interfaces and don't need to be re-processed.
-              builder.addSynthesizedClass(synthesizedClass);
-              boolean addToMainDexClasses =
-                  interfaceClass.isProgramClass()
-                      && mainDexClasses.contains(interfaceClass.asProgramClass());
-              appInfo.addSynthesizedClass(synthesizedClass, addToMainDexClasses);
-            });
-
+    InterfaceProcessorNestedGraphLens.Builder graphLensBuilder =
+        InterfaceProcessorNestedGraphLens.builder();
+    Map<DexClass, DexProgramClass> classMapping =
+        processInterfaces(builder, flavour, graphLensBuilder);
+    InterfaceProcessorNestedGraphLens graphLens =
+        graphLensBuilder.build(appView.dexItemFactory(), appView.graphLens());
+    if (appView.enableWholeProgramOptimizations() && graphLens != null) {
+      appView.setGraphLens(graphLens);
+    }
+    classMapping.forEach(
+        (interfaceClass, synthesizedClass) -> {
+          // Don't need to optimize synthesized class since all of its methods
+          // are just moved from interfaces and don't need to be re-processed.
+          builder.addSynthesizedClass(synthesizedClass);
+          boolean addToMainDexClasses =
+              interfaceClass.isProgramClass()
+                  && mainDexClasses.contains(interfaceClass.asProgramClass());
+          appInfo.addSynthesizedClass(synthesizedClass, addToMainDexClasses);
+        });
+    new InterfaceMethodRewriterFixup(appView, graphLens).run();
     if (appView.options().isDesugaredLibraryCompilation()) {
       renameEmulatedInterfaces();
     }
@@ -1036,9 +1044,10 @@
         && clazz.isInterface() == mustBeInterface;
   }
 
-  private Map<DexClass, DexProgramClass> processInterfaces(Builder<?> builder, Flavor flavour) {
-    InterfaceProcessorNestedGraphLens.Builder graphLensBuilder =
-        InterfaceProcessorNestedGraphLens.builder();
+  private Map<DexClass, DexProgramClass> processInterfaces(
+      Builder<?> builder,
+      Flavor flavour,
+      InterfaceProcessorNestedGraphLens.Builder graphLensBuilder) {
     InterfaceProcessor processor = new InterfaceProcessor(appView, this);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, true)) {
@@ -1049,9 +1058,6 @@
       DexProgramClass dispatchClass = processor.process(entry.getKey(), entry.getValue());
       dispatchClass.forEachProgramMethod(synthesizedMethods::add);
     }
-    if (appView.enableWholeProgramOptimizations()) {
-      appView.setGraphLens(graphLensBuilder.build(appView.dexItemFactory(), appView.graphLens()));
-    }
     return processor.syntheticClasses;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java
new file mode 100644
index 0000000..995f693
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
+
+class InterfaceMethodRewriterFixup {
+  private final AppView<?> appView;
+  private final InterfaceProcessorNestedGraphLens graphLens;
+
+  InterfaceMethodRewriterFixup(AppView<?> appView, InterfaceProcessorNestedGraphLens graphLens) {
+    this.appView = appView;
+    this.graphLens = graphLens;
+  }
+
+  void run() {
+    if (graphLens == null) {
+      return;
+    }
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.getEnclosingMethodAttribute() != null
+          && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
+        // Only relevant to rewrite for local or anonymous classes, as the interface
+        // desugaring only moves methods to the companion class.
+        InnerClassAttribute innerClassAttributeForThisClass =
+            clazz.getInnerClassAttributeForThisClass();
+        if (innerClassAttributeForThisClass != null
+            && innerClassAttributeForThisClass.getOuter() == null) {
+          clazz.setEnclosingMethodAttribute(
+              fixupEnclosingMethodAttribute(clazz.getEnclosingMethodAttribute()));
+        }
+      }
+    }
+  }
+
+  private EnclosingMethodAttribute fixupEnclosingMethodAttribute(
+      EnclosingMethodAttribute enclosingMethodAttribute) {
+    DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
+    DexMethod mappedEnclosingMethod = fixupDexMethodForEnclosingMethod(enclosingMethod);
+    return mappedEnclosingMethod != enclosingMethod
+        ? new EnclosingMethodAttribute(mappedEnclosingMethod)
+        : enclosingMethodAttribute;
+  }
+
+  private DexMethod fixupDexMethodForEnclosingMethod(DexMethod method) {
+    if (method == null) {
+      return null;
+    }
+    // Map default methods to their companion methods.
+    DexMethod mappedMethod = graphLens.getExtraOriginalMethodSignatures().inverse().get(method);
+    if (mappedMethod != null) {
+      return mappedMethod;
+    }
+    // For other methods moved to the companion class use the lens mapping.
+    return graphLens.getRenamedMethodSignature(method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 67d5b2a..20c3ae4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -428,6 +428,10 @@
       this.extraOriginalMethodSignatures = tmp;
     }
 
+    public BiMap<DexMethod, DexMethod> getExtraOriginalMethodSignatures() {
+      return extraOriginalMethodSignatures;
+    }
+
     @Override
     public boolean isInterfaceProcessorLens() {
       return true;
@@ -476,11 +480,12 @@
       }
 
       @Override
-      public GraphLens build(DexItemFactory dexItemFactory, GraphLens previousLens) {
+      public InterfaceProcessorNestedGraphLens build(
+          DexItemFactory dexItemFactory, GraphLens previousLens) {
         if (originalFieldSignatures.isEmpty()
             && originalMethodSignatures.isEmpty()
             && extraOriginalMethodSignatures.isEmpty()) {
-          return previousLens;
+          return null;
         }
         return new InterfaceProcessorNestedGraphLens(
             typeMap,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index ca93336..273f7b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -65,7 +65,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -983,7 +982,8 @@
                   .appInfo()
                   .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
                   .asSingleResolution();
-          if (resolutionResult == null) {
+          if (resolutionResult == null
+              || resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) {
             continue;
           }
 
@@ -994,16 +994,6 @@
             continue;
           }
 
-          OptionalBool methodAccessible =
-              AccessControl.isMethodAccessible(
-                  singleTarget.getDefinition(),
-                  singleTarget.getHolder().asDexClass(),
-                  context,
-                  appView);
-          if (!methodAccessible.isTrue()) {
-            continue;
-          }
-
           DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
           WhyAreYouNotInliningReporter whyAreYouNotInliningReporter =
               oracle.isForcedInliningOracle()
@@ -1022,6 +1012,16 @@
             continue;
           }
 
+          DexType downcastTypeOrNull = getDowncastTypeIfNeeded(strategy, invoke, singleTarget);
+          if (downcastTypeOrNull != null) {
+            DexClass downcastClass = appView.definitionFor(downcastTypeOrNull, context);
+            if (downcastClass == null
+                || AccessControl.isClassAccessible(downcastClass, context, appView)
+                    .isPossiblyFalse()) {
+              continue;
+            }
+          }
+
           if (!inlineeStack.isEmpty()
               && !strategy.allowInliningOfInvokeInInlinee(
                   action, inlineeStack.size(), whyAreYouNotInliningReporter)) {
@@ -1069,12 +1069,7 @@
           iterator.previous();
           strategy.markInlined(inlinee);
           iterator.inlineInvoke(
-              appView,
-              code,
-              inlinee.code,
-              blockIterator,
-              blocksToRemove,
-              getDowncastTypeIfNeeded(strategy, invoke, singleTarget));
+              appView, code, inlinee.code, blockIterator, blocksToRemove, downcastTypeOrNull);
 
           if (inlinee.reason == Reason.SINGLE_CALLER) {
             feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index ec1f830..b917507 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -60,7 +60,6 @@
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
@@ -292,7 +291,8 @@
                   .appInfo()
                   .resolveMethod(invokeMethod.getInvokedMethod(), invokeMethod.getInterfaceBit())
                   .asSingleResolution();
-          if (resolutionResult == null) {
+          if (resolutionResult == null
+              || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
             return user; // Not eligible.
           }
 
@@ -311,13 +311,11 @@
             return user; // Not eligible.
           }
 
-          OptionalBool methodAccessible =
-              AccessControl.isMethodAccessible(
-                  singleTargetMethod, singleTarget.getHolder().asDexClass(), method, appView);
-
-          if (!methodAccessible.isTrue()) {
-            return user; // Not eligible.
+          if (AccessControl.isClassAccessible(singleTarget.getHolder(), method, appView)
+              .isPossiblyFalse()) {
+            continue;
           }
+
           // Eligible constructor call (for new instance roots only).
           if (user.isInvokeDirect()) {
             InvokeDirect invoke = user.asInvokeDirect();
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index b8022b9..5d3f09b 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -330,8 +331,8 @@
       return;
     }
 
-    DexEncodedField resolvedField = resolutionResult.getResolvedField();
-    if (resolvedField.field == field) {
+    DexClassAndField resolvedField = resolutionResult.getResolutionPair();
+    if (resolvedField.getReference() == field) {
       assert false;
       return;
     }
@@ -341,7 +342,7 @@
     boolean accessibleInAllContexts = true;
     for (ProgramMethod context : contexts) {
       boolean inaccessibleInContext =
-          AccessControl.isFieldAccessible(
+          AccessControl.isMemberAccessible(
                   resolvedField, resolutionResult.getResolvedHolder(), context, appView)
               .isPossiblyFalse();
       if (inaccessibleInContext) {
@@ -353,7 +354,8 @@
     if (accessibleInAllContexts) {
       builder.map(
           field,
-          lens.lookupField(validTargetFor(resolvedField.field, field, DexClass::lookupField)));
+          lens.lookupField(
+              validTargetFor(resolvedField.getReference(), field, DexClass::lookupField)));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/references/ArrayReference.java b/src/main/java/com/android/tools/r8/references/ArrayReference.java
index 7946c2f..919bbd7 100644
--- a/src/main/java/com/android/tools/r8/references/ArrayReference.java
+++ b/src/main/java/com/android/tools/r8/references/ArrayReference.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.Objects;
 
 /** Reference to an array type. */
 @Keep
@@ -13,7 +14,8 @@
 
   private final int dimensions;
   private final TypeReference baseType;
-  private String descriptor;
+  // Consider removing the descriptor field as dimensions and baseType encode the same information.
+  private final String descriptor;
 
   private ArrayReference(int dimensions, TypeReference baseType, String descriptor) {
     assert dimensions > 0;
@@ -71,11 +73,18 @@
 
   @Override
   public boolean equals(Object o) {
-    return this == o;
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof ArrayReference)) {
+      return false;
+    }
+    ArrayReference other = (ArrayReference) o;
+    return dimensions == other.dimensions && baseType.equals(other.baseType);
   }
 
   @Override
   public int hashCode() {
-    return System.identityHashCode(this);
+    return Objects.hash(dimensions, baseType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/references/ClassReference.java b/src/main/java/com/android/tools/r8/references/ClassReference.java
index 9c0f479..3e30319 100644
--- a/src/main/java/com/android/tools/r8/references/ClassReference.java
+++ b/src/main/java/com/android/tools/r8/references/ClassReference.java
@@ -41,12 +41,18 @@
 
   @Override
   public boolean equals(Object o) {
-    return this == o;
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof ClassReference)) {
+      return false;
+    }
+    return descriptor.equals(((ClassReference) o).descriptor);
   }
 
   @Override
   public int hashCode() {
-    return System.identityHashCode(this);
+    return descriptor.hashCode();
   }
 
   @Override
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 c0c5120..d4d74fd 100644
--- a/src/main/java/com/android/tools/r8/references/FieldReference.java
+++ b/src/main/java/com/android/tools/r8/references/FieldReference.java
@@ -13,19 +13,15 @@
  * type of the field.
  */
 @Keep
-public class FieldReference {
+public final class FieldReference {
   private final ClassReference holderClass;
   private final String fieldName;
   private final TypeReference fieldType;
 
-  boolean isUnknown() {
-    return false;
-  }
-
   FieldReference(ClassReference holderClass, String fieldName, TypeReference fieldType) {
     assert holderClass != null;
     assert fieldName != null;
-    assert fieldType != null || isUnknown();
+    assert fieldType != null;
     this.holderClass = holderClass;
     this.fieldName = fieldName;
     this.fieldType = fieldType;
@@ -69,16 +65,4 @@
   public String toString() {
     return getHolderClass().toString() + getFieldName() + ":" + getFieldType().getDescriptor();
   }
-
-  public static final class UnknownFieldReference extends FieldReference {
-
-    public UnknownFieldReference(ClassReference holderClass, String fieldName) {
-      super(holderClass, fieldName, null);
-    }
-
-    @Override
-    boolean isUnknown() {
-      return true;
-    }
-  }
 }
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 7462a57..9ff0649 100644
--- a/src/main/java/com/android/tools/r8/references/MethodReference.java
+++ b/src/main/java/com/android/tools/r8/references/MethodReference.java
@@ -17,7 +17,7 @@
  * full list of formal parameters.
  */
 @Keep
-public class MethodReference {
+public final class MethodReference {
   private final ClassReference holderClass;
   private final String methodName;
   private final List<TypeReference> formalTypes;
@@ -30,17 +30,12 @@
       TypeReference returnType) {
     assert holderClass != null;
     assert methodName != null;
-    assert formalTypes != null || isUnknown();
     this.holderClass = holderClass;
     this.methodName = methodName;
     this.formalTypes = formalTypes;
     this.returnType = returnType;
   }
 
-  public boolean isUnknown() {
-    return false;
-  }
-
   public ClassReference getHolderClass() {
     return holderClass;
   }
@@ -90,16 +85,4 @@
   public String toString() {
     return getHolderClass().toString() + getMethodName() + getMethodDescriptor();
   }
-
-  public static final class UnknownMethodReference extends MethodReference {
-
-    @Override
-    public boolean isUnknown() {
-      return true;
-    }
-
-    public UnknownMethodReference(ClassReference holderClass, String methodName) {
-      super(holderClass, methodName, null, null);
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/references/PackageReference.java b/src/main/java/com/android/tools/r8/references/PackageReference.java
index 1c54921..b2474fe 100644
--- a/src/main/java/com/android/tools/r8/references/PackageReference.java
+++ b/src/main/java/com/android/tools/r8/references/PackageReference.java
@@ -33,7 +33,7 @@
     if (this == o) {
       return true;
     }
-    if (o == null || getClass() != o.getClass()) {
+    if (!(o instanceof PackageReference)) {
       return false;
     }
     PackageReference that = (PackageReference) o;
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index 444eee3..3a30ec4 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -6,21 +6,18 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.MapMaker;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.ConcurrentMap;
 
 /**
  * Reference provider/factory.
  *
  * <p>The reference class provides a single point for creating and managing reference objects that
- * represent types, methods and fields in a JVM/DEX application. The objects are interned/shared so
- * that allocation is reduced and equality is constant time. Internally, the objects are weakly
- * stored to avoid memory pressure.
+ * represent types, methods and fields in a JVM/DEX application. The objects may or may not be
+ * interned/shared.
  *
  * <p>No guarantees are made on identity and all references must be compared by {@code equals}.
  */
@@ -36,39 +33,10 @@
   public static PrimitiveReference LONG = PrimitiveReference.LONG;
   public static PrimitiveReference DOUBLE = PrimitiveReference.DOUBLE;
 
-  private static Reference instance;
-
-  // Weak map of classes. The values are weak and the descriptor is used as key.
-  private final ConcurrentMap<String, ClassReference> classes =
-      new MapMaker().weakValues().makeMap();
-
-  // Weak map of arrays. The values are weak and the descriptor is used as key.
-  private final ConcurrentMap<String, ArrayReference> arrays =
-      new MapMaker().weakValues().makeMap();
-
-  // Weak map of method references. Keys are strong and must not be identical to the value.
-  private final ConcurrentMap<MethodReference, MethodReference> methods =
-      new MapMaker().weakValues().makeMap();
-
-  // Weak map of field references. Keys are strong and must not be identical to the value.
-  private final ConcurrentMap<FieldReference, FieldReference> fields =
-      new MapMaker().weakValues().makeMap();
-
   private Reference() {
     // Intentionally hidden.
   }
 
-  private static Reference getInstance() {
-    if (instance == null) {
-      synchronized (Reference.class) {
-        if (instance == null) {
-          instance = new Reference();
-        }
-      }
-    }
-    return instance;
-  }
-
   public static TypeReference returnTypeFromDescriptor(String descriptor) {
     return descriptor.equals("V") ? null : typeFromDescriptor(descriptor);
   }
@@ -99,7 +67,7 @@
 
   /** Get a class reference from a JVM descriptor. */
   public static ClassReference classFromDescriptor(String descriptor) {
-    return getInstance().classes.computeIfAbsent(descriptor, ClassReference::fromDescriptor);
+    return ClassReference.fromDescriptor(descriptor);
   }
 
   /**
@@ -127,17 +95,12 @@
 
   /** Get an array reference from a JVM descriptor. */
   public static ArrayReference arrayFromDescriptor(String descriptor) {
-    return getInstance().arrays.computeIfAbsent(descriptor, ArrayReference::fromDescriptor);
+    return ArrayReference.fromDescriptor(descriptor);
   }
 
   /** Get an array reference from a base type and dimensions. */
   public static ArrayReference array(TypeReference baseType, int dimensions) {
-    String arrayDescriptor =
-        DescriptorUtils.toArrayDescriptor(dimensions, baseType.getDescriptor());
-    return getInstance()
-        .arrays
-        .computeIfAbsent(
-            arrayDescriptor, descriptor -> ArrayReference.fromBaseType(baseType, dimensions));
+    return ArrayReference.fromBaseType(baseType, dimensions);
   }
 
   /** Get a method reference from its full reference specification. */
@@ -146,15 +109,8 @@
       String methodName,
       List<TypeReference> formalTypes,
       TypeReference returnType) {
-    MethodReference key = new MethodReference(
+    return new MethodReference(
         holderClass, methodName, ImmutableList.copyOf(formalTypes), returnType);
-    return getInstance().methods.computeIfAbsent(key,
-        // Allocate a distinct value for the canonical reference so the key is not a strong pointer.
-        k -> new MethodReference(
-            k.getHolderClass(),
-            k.getMethodName(),
-            ImmutableList.copyOf(k.getFormalTypes()),
-            k.getReturnType()));
   }
 
   /** Get a method reference from a Java reflection method. */
@@ -208,10 +164,7 @@
   /** Get a field reference from its full reference specification. */
   public static FieldReference field(
       ClassReference holderClass, String fieldName, TypeReference fieldType) {
-    FieldReference key = new FieldReference(holderClass, fieldName, fieldType);
-    return getInstance().fields.computeIfAbsent(key,
-        // Allocate a distinct value for the canonical reference so the key is not a strong pointer.
-        k -> new FieldReference(k.getHolderClass(), k.getFieldName(), k.getFieldType()));
+    return new FieldReference(holderClass, fieldName, fieldType);
   }
 
   /** Get a field reference from a Java reflection field. */
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index e13c832..4ca10b6 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -92,9 +92,11 @@
     return Stream.of(
         new Element(
             this,
-            mapper == null ? obfuscatedReference : Reference.classFromTypeName(mapper.originalName),
-            mapper,
-            false));
+            RetracedClass.create(
+                mapper == null
+                    ? obfuscatedReference
+                    : Reference.classFromTypeName(mapper.originalName)),
+            mapper));
   }
 
   @Override
@@ -115,20 +117,19 @@
   public static class Element {
 
     private final RetraceClassResult classResult;
-    private final ClassReference classReference;
+    private final RetracedClass classReference;
     private final ClassNamingForNameMapper mapper;
 
     public Element(
         RetraceClassResult classResult,
-        ClassReference classReference,
-        ClassNamingForNameMapper mapper,
-        boolean isAmbiguous) {
+        RetracedClass classReference,
+        ClassNamingForNameMapper mapper) {
       this.classResult = classResult;
       this.classReference = classReference;
       this.mapper = mapper;
     }
 
-    public ClassReference getClassReference() {
+    public RetracedClass getRetracedClass() {
       return classReference;
     }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
index 5477eaa..265b3b2 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -7,9 +7,6 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.FieldReference;
-import com.android.tools.r8.references.FieldReference.UnknownFieldReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.List;
@@ -51,14 +48,14 @@
     return memberNamings.size() > 1;
   }
 
+  public RetracedField getUnknownReference() {
+    return RetracedField.createUnknown(classElement.getRetracedClass(), obfuscatedName);
+  }
+
   @Override
   public Stream<Element> stream() {
     if (!hasRetraceResult()) {
-      return Stream.of(
-          new Element(
-              this,
-              classElement,
-              new UnknownFieldReference(classElement.getClassReference(), obfuscatedName)));
+      return Stream.of(new Element(this, classElement, getUnknownReference()));
     }
     assert !memberNamings.isEmpty();
     return memberNamings.stream()
@@ -67,21 +64,24 @@
               assert memberNaming.isFieldNaming();
               FieldSignature fieldSignature =
                   memberNaming.getOriginalSignature().asFieldSignature();
-              ClassReference holder =
+              RetracedClass holder =
                   fieldSignature.isQualified()
-                      ? Reference.classFromDescriptor(
-                          DescriptorUtils.javaTypeToDescriptor(
-                              fieldSignature.toHolderFromQualified()))
-                      : classElement.getClassReference();
+                      ? RetracedClass.create(
+                          Reference.classFromDescriptor(
+                              DescriptorUtils.javaTypeToDescriptor(
+                                  fieldSignature.toHolderFromQualified())))
+                      : classElement.getRetracedClass();
               return new Element(
                   this,
                   classElement,
-                  Reference.field(
+                  RetracedField.create(
                       holder,
-                      fieldSignature.isQualified()
-                          ? fieldSignature.toUnqualifiedName()
-                          : fieldSignature.name,
-                      Reference.typeFromTypeName(fieldSignature.type)));
+                      Reference.field(
+                          holder.getClassReference(),
+                          fieldSignature.isQualified()
+                              ? fieldSignature.toUnqualifiedName()
+                              : fieldSignature.name,
+                          Reference.typeFromTypeName(fieldSignature.type))));
             });
   }
 
@@ -93,25 +93,29 @@
 
   public static class Element {
 
-    private final FieldReference fieldReference;
+    private final RetracedField fieldReference;
     private final RetraceFieldResult retraceFieldResult;
     private final RetraceClassResult.Element classElement;
 
     private Element(
         RetraceFieldResult retraceFieldResult,
         RetraceClassResult.Element classElement,
-        FieldReference fieldReference) {
+        RetracedField fieldReference) {
       this.classElement = classElement;
       this.fieldReference = fieldReference;
       this.retraceFieldResult = retraceFieldResult;
     }
 
-    public FieldReference getFieldReference() {
+    public boolean isUnknown() {
+      return fieldReference.isUnknown();
+    }
+
+    public RetracedField getField() {
       return fieldReference;
     }
 
     public RetraceFieldResult getRetraceFieldResult() {
-      return getRetraceFieldResult();
+      return retraceFieldResult;
     }
 
     public RetraceClassResult.Element getClassElement() {
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index 7977858..93c87e8 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -9,9 +9,6 @@
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.Range;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.MethodReference.UnknownMethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -41,8 +38,8 @@
     assert classElement != null;
   }
 
-  public UnknownMethodReference getUnknownReference() {
-    return new UnknownMethodReference(classElement.getClassReference(), obfuscatedName);
+  public RetracedMethod getUnknownReference() {
+    return RetracedMethod.createUnknown(classElement.getRetracedClass(), obfuscatedName);
   }
 
   private boolean hasRetraceResult() {
@@ -102,12 +99,13 @@
         .map(
             mappedRange -> {
               MethodSignature signature = mappedRange.signature;
-              ClassReference holder =
+              RetracedClass holder =
                   mappedRange.signature.isQualified()
-                      ? Reference.classFromDescriptor(
-                          DescriptorUtils.javaTypeToDescriptor(
-                              mappedRange.signature.toHolderFromQualified()))
-                      : classElement.getClassReference();
+                      ? RetracedClass.create(
+                          Reference.classFromDescriptor(
+                              DescriptorUtils.javaTypeToDescriptor(
+                                  mappedRange.signature.toHolderFromQualified())))
+                      : classElement.getRetracedClass();
               List<TypeReference> formalTypes = new ArrayList<>(signature.parameters.length);
               for (String parameter : signature.parameters) {
                 formalTypes.add(Reference.typeFromTypeName(parameter));
@@ -115,12 +113,14 @@
               TypeReference returnType =
                   Reference.returnTypeFromDescriptor(
                       DescriptorUtils.javaTypeToDescriptor(signature.type));
-              MethodReference retracedMethod =
-                  Reference.method(
+              RetracedMethod retracedMethod =
+                  RetracedMethod.create(
                       holder,
-                      signature.isQualified() ? signature.toUnqualifiedName() : signature.name,
-                      formalTypes,
-                      returnType);
+                      Reference.method(
+                          holder.getClassReference(),
+                          signature.isQualified() ? signature.toUnqualifiedName() : signature.name,
+                          formalTypes,
+                          returnType));
               return new Element(this, classElement, retracedMethod, mappedRange);
             });
   }
@@ -133,7 +133,7 @@
 
   public static class Element {
 
-    private final MethodReference methodReference;
+    private final RetracedMethod methodReference;
     private final RetraceMethodResult retraceMethodResult;
     private final RetraceClassResult.Element classElement;
     private final MappedRange mappedRange;
@@ -141,7 +141,7 @@
     private Element(
         RetraceMethodResult retraceMethodResult,
         RetraceClassResult.Element classElement,
-        MethodReference methodReference,
+        RetracedMethod methodReference,
         MappedRange mappedRange) {
       this.classElement = classElement;
       this.retraceMethodResult = retraceMethodResult;
@@ -149,7 +149,23 @@
       this.mappedRange = mappedRange;
     }
 
-    public MethodReference getMethodReference() {
+    public boolean isUnknown() {
+      return methodReference.isUnknown();
+    }
+
+    public boolean hasNoLineNumberRange() {
+      return mappedRange == null || mappedRange.minifiedRange == null;
+    }
+
+    public boolean containsMinifiedLineNumber(int linePosition) {
+      if (hasNoLineNumberRange()) {
+        return false;
+      }
+      return mappedRange.minifiedRange.from <= linePosition
+          && linePosition <= mappedRange.minifiedRange.to;
+    }
+
+    public RetracedMethod getMethod() {
       return methodReference;
     }
 
@@ -165,18 +181,6 @@
       return mappedRange != null ? mappedRange.getOriginalLineNumber(linePosition) : linePosition;
     }
 
-    public boolean containsMinifiedLineNumber(int linePosition) {
-      if (hasNoLineNumberRange()) {
-        return false;
-      }
-      return mappedRange.minifiedRange.from <= linePosition
-          && linePosition <= mappedRange.minifiedRange.to;
-    }
-
-    public boolean hasNoLineNumberRange() {
-      return mappedRange == null || mappedRange.minifiedRange == null;
-    }
-
     public int getFirstLineNumberOfOriginalRange() {
       if (hasNoLineNumberRange()) {
         return 0;
@@ -188,5 +192,6 @@
       return RetraceUtils.getSourceFile(
           classElement, methodReference.getHolderClass(), sourceFile, retraceMethodResult.retracer);
     }
+
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
index 40b3f41..8606324 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -7,11 +7,11 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceClassResult.Element;
 import com.android.tools.r8.retrace.RetraceRegularExpression.ClassNameGroup.ClassNameGroupHandler;
+import com.android.tools.r8.retrace.RetracedField.KnownRetracedField;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -101,7 +101,7 @@
       if (isAmbiguous) {
         retracedStrings.sort(new RetraceLineComparator());
       }
-      ClassReference previousContext = null;
+      RetracedClass previousContext = null;
       for (RetraceString retracedString : retracedStrings) {
         String finalString = retracedString.builder.build(string);
         if (!isAmbiguous) {
@@ -109,7 +109,7 @@
           continue;
         }
         assert retracedString.getClassContext() != null;
-        ClassReference currentContext = retracedString.getClassContext().getClassReference();
+        RetracedClass currentContext = retracedString.getClassContext().getRetracedClass();
         if (currentContext.equals(previousContext)) {
           int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(finalString);
           finalString =
@@ -131,9 +131,9 @@
           (line, t) -> {
             switch (t) {
               case CLASS:
-                return line.getClassContext().getClassReference().getTypeName();
+                return line.getClassContext().getRetracedClass().getTypeName();
               case METHOD:
-                return line.getMethodContext().getMethodReference().getMethodName();
+                return line.getMethodContext().getMethod().getMethodName();
               case SOURCE:
                 return line.getSource();
               case LINE:
@@ -241,7 +241,7 @@
 
   static class RetraceStringContext {
     private final Element classContext;
-    private final ClassReference qualifiedContext;
+    private final RetracedClass qualifiedContext;
     private final String methodName;
     private final RetraceMethodResult.Element methodContext;
     private final int minifiedLineNumber;
@@ -251,7 +251,7 @@
 
     private RetraceStringContext(
         Element classContext,
-        ClassReference qualifiedContext,
+        RetracedClass qualifiedContext,
         String methodName,
         RetraceMethodResult.Element methodContext,
         int minifiedLineNumber,
@@ -273,7 +273,7 @@
     }
 
     private RetraceStringContext withClassContext(
-        Element classContext, ClassReference qualifiedContext) {
+        Element classContext, RetracedClass qualifiedContext) {
       return new RetraceStringContext(
           classContext,
           qualifiedContext,
@@ -299,7 +299,7 @@
 
     private RetraceStringContext withMethodContext(
         RetraceMethodResult.Element methodContext,
-        ClassReference qualifiedContext,
+        RetracedClass qualifiedContext,
         boolean isAmbiguous) {
       return new RetraceStringContext(
           classContext,
@@ -312,7 +312,7 @@
           isAmbiguous);
     }
 
-    private RetraceStringContext withQualifiedContext(ClassReference qualifiedContext) {
+    private RetraceStringContext withQualifiedContext(RetracedClass qualifiedContext) {
       return new RetraceStringContext(
           classContext,
           qualifiedContext,
@@ -485,7 +485,7 @@
 
   abstract static class ClassNameGroup extends RegularExpressionGroup {
 
-    abstract String getClassName(ClassReference classReference);
+    abstract String getClassName(RetracedClass classReference);
 
     abstract ClassReference classFromMatch(String match);
 
@@ -525,13 +525,13 @@
               element -> {
                 final RetraceString newRetraceString =
                     retraceString.updateContext(
-                        context -> context.withClassContext(element, element.getClassReference()));
+                        context -> context.withClassContext(element, element.getRetracedClass()));
                 retraceStrings.add(newRetraceString);
                 if (qualifiedHandler == null) {
                   // If there is no qualified handler, commit right away.
                   newRetraceString.builder.appendRetracedString(
                       original,
-                      classNameGroup.getClassName(element.getClassReference()),
+                      classNameGroup.getClassName(element.getRetracedClass()),
                       startOfGroup,
                       matcher.end(captureGroup));
                 }
@@ -543,7 +543,7 @@
       void commitClassName(
           String original,
           RetraceString retraceString,
-          ClassReference qualifiedContext,
+          RetracedClass qualifiedContext,
           Matcher matcher) {
         if (matcher.start(captureGroup) == NO_MATCH) {
           return;
@@ -568,7 +568,7 @@
         assert !retraceClassResult.isAmbiguous();
         Box<RetraceStringContext> box = new Box<>();
         retraceClassResult.forEach(
-            element -> box.set(context.withClassContext(element, element.getClassReference())));
+            element -> box.set(context.withClassContext(element, element.getRetracedClass())));
         return box.get();
       }
 
@@ -597,7 +597,7 @@
     }
 
     @Override
-    String getClassName(ClassReference classReference) {
+    String getClassName(RetracedClass classReference) {
       return classReference.getTypeName();
     }
 
@@ -615,7 +615,7 @@
     }
 
     @Override
-    String getClassName(ClassReference classReference) {
+    String getClassName(RetracedClass classReference) {
       return classReference.getBinaryName();
     }
 
@@ -672,18 +672,15 @@
                   final RetraceString newRetraceString = retraceString.duplicate(newContext);
                   if (classNameGroupHandler != null) {
                     classNameGroupHandler.commitClassName(
-                        original,
-                        newRetraceString,
-                        element.getMethodReference().getHolderClass(),
-                        matcher);
+                        original, newRetraceString, element.getMethod().getHolderClass(), matcher);
                   }
                   retracedStrings.add(
                       newRetraceString.appendRetracedString(
                           original,
                           printVerbose
                               ? RetraceUtils.methodDescriptionFromMethodReference(
-                                  element.getMethodReference(), false, true)
-                              : element.getMethodReference().getMethodName(),
+                                  element.getMethod(), false, true)
+                              : element.getMethod().getMethodName(),
                           startOfGroup,
                           matcher.end(captureGroup)));
                 });
@@ -722,7 +719,7 @@
                   element,
                   retraceString.context.withMethodContext(
                       element,
-                      element.getMethodReference().getHolderClass(),
+                      element.getMethod().getHolderClass(),
                       element.getRetraceMethodResult().isAmbiguous())));
     }
   }
@@ -778,20 +775,16 @@
                 element -> {
                   if (classNameGroupHandler != null) {
                     classNameGroupHandler.commitClassName(
-                        original,
-                        retraceString,
-                        element.getFieldReference().getHolderClass(),
-                        matcher);
+                        original, retraceString, element.getField().getHolderClass(), matcher);
                   }
                   retracedStrings.add(
                       retraceString
                           .updateContext(
                               context ->
-                                  context.withQualifiedContext(
-                                      element.getFieldReference().getHolderClass()))
+                                  context.withQualifiedContext(element.getField().getHolderClass()))
                           .appendRetracedString(
                               original,
-                              getFieldString(element.getFieldReference()),
+                              getFieldString(element.getField()),
                               startOfGroup,
                               matcher.end(captureGroup)));
                 });
@@ -801,11 +794,13 @@
       };
     }
 
-    private String getFieldString(FieldReference fieldReference) {
-      if (!printVerbose) {
+    private String getFieldString(RetracedField fieldReference) {
+      if (!printVerbose || fieldReference.isUnknown()) {
         return fieldReference.getFieldName();
       }
-      return fieldReference.getFieldType().getTypeName() + " " + fieldReference.getFieldName();
+      assert fieldReference.isKnown();
+      KnownRetracedField knownRef = fieldReference.asKnown();
+      return knownRef.getFieldType().getTypeName() + " " + fieldReference.getFieldName();
     }
   }
 
@@ -917,8 +912,7 @@
               continue;
             }
             // If the method context is unknown, do nothing.
-            if (methodContext.getMethodReference().isUnknown()
-                || methodContext.hasNoLineNumberRange()) {
+            if (methodContext.isUnknown() || methodContext.hasNoLineNumberRange()) {
               retracedStrings.add(retraceString);
               continue;
             }
@@ -997,10 +991,10 @@
         for (RetraceString retraceString : strings) {
           retracedType.forEach(
               element -> {
-                TypeReference retracedReference = element.getTypeReference();
+                RetracedType retracedReference = element.getType();
                 retraceString.appendRetracedString(
                     original,
-                    retracedReference == null ? "void" : retracedReference.getTypeName(),
+                    retracedReference.isVoid() ? "void" : retracedReference.getTypeName(),
                     startOfGroup,
                     matcher.end(captureGroup));
               });
@@ -1039,8 +1033,8 @@
                       final RetraceTypeResult retraceResult =
                           retracer.retrace(Reference.returnTypeFromDescriptor(descriptor));
                       assert !retraceResult.isAmbiguous();
-                      final Box<TypeReference> elementBox = new Box<>();
-                      retraceResult.forEach(element -> elementBox.set(element.getTypeReference()));
+                      final Box<RetracedType> elementBox = new Box<>();
+                      retraceResult.forEach(element -> elementBox.set(element.getType()));
                       return elementBox.get().getTypeName();
                     })
                 .filter(Objects::nonNull)
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
index 4b21be1..e68e5c9 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
@@ -17,11 +17,11 @@
     this.synthesized = synthesized;
   }
 
-  public String getFilename() {
-    return filename;
-  }
-
   public boolean isSynthesized() {
     return synthesized;
   }
+
+  public String getFilename() {
+    return filename;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index fd28127..e2e3616 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -9,7 +9,6 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -235,7 +234,7 @@
                       new ExceptionLine(
                           initialWhiteSpace,
                           description,
-                          element.getClassReference().getTypeName(),
+                          element.getRetracedClass().getTypeName(),
                           message)));
       return exceptionLines;
     }
@@ -407,7 +406,7 @@
             .forEach(
                 classElement -> {
                   retraceClassAndMethods(
-                      retracer, verbose, lines, classElement.getClassReference().getTypeName());
+                      retracer, verbose, lines, classElement.getRetracedClass().getTypeName());
                 });
       } else {
         retraceClassAndMethods(retracer, verbose, lines, retraceClassLoaderName);
@@ -425,7 +424,7 @@
       }
       retraceResult.forEach(
           methodElement -> {
-            MethodReference methodReference = methodElement.getMethodReference();
+            RetracedMethod methodReference = methodElement.getMethod();
             lines.add(
                 new AtLine(
                     startingWhitespace,
@@ -568,7 +567,7 @@
                   exceptionLines.add(
                       new CircularReferenceLine(
                           startWhitespace,
-                          element.getClassReference().getTypeName(),
+                          element.getRetracedClass().getTypeName(),
                           endBracketAndWhitespace)));
       return exceptionLines;
     }
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 b70602d..4f7ec0a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.retrace;
 
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceTypeResult.Element;
 import java.util.function.Consumer;
@@ -24,15 +23,17 @@
   public Stream<Element> stream() {
     // Handle void and primitive types as single element results.
     if (obfuscatedType == null || obfuscatedType.isPrimitive()) {
-      return Stream.of(new Element(obfuscatedType));
+      return Stream.of(new Element(RetracedType.create(obfuscatedType)));
     }
     if (obfuscatedType.isArray()) {
       int dimensions = obfuscatedType.asArray().getDimensions();
       return retracer.retrace(obfuscatedType.asArray().getBaseType()).stream()
-          .map(base -> new Element(Reference.array(base.getTypeReference(), dimensions)));
+          .map(
+              baseElement ->
+                  new Element(RetracedType.create(baseElement.retracedType.toArray(dimensions))));
     }
     return retracer.retrace(obfuscatedType.asClass()).stream()
-        .map(clazz -> new Element(clazz.getClassReference()));
+        .map(classElement -> new Element(classElement.getRetracedClass().getRetracedType()));
   }
 
   public boolean isAmbiguous() {
@@ -47,14 +48,14 @@
 
   public static class Element {
 
-    private final TypeReference typeReference;
+    private final RetracedType retracedType;
 
-    public Element(TypeReference typeReference) {
-      this.typeReference = typeReference;
+    public Element(RetracedType retracedType) {
+      this.retracedType = retracedType;
     }
 
-    public TypeReference getTypeReference() {
-      return typeReference;
+    public RetracedType getType() {
+      return retracedType;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
index fe0bac2..335059e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -4,9 +4,8 @@
 
 package com.android.tools.r8.retrace;
 
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetracedMethod.KnownRetracedMethod;
 import com.android.tools.r8.utils.Box;
 import com.google.common.collect.Sets;
 import com.google.common.io.Files;
@@ -18,7 +17,7 @@
       Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source", "PG");
 
   public static String methodDescriptionFromMethodReference(
-      MethodReference methodReference, boolean appendHolder, boolean verbose) {
+      RetracedMethod methodReference, boolean appendHolder, boolean verbose) {
     StringBuilder sb = new StringBuilder();
     if (appendHolder) {
       sb.append(methodReference.getHolderClass().getTypeName());
@@ -27,15 +26,14 @@
     if (!verbose || methodReference.isUnknown()) {
       return sb.append(methodReference.getMethodName()).toString();
     }
-    sb.append(
-        methodReference.getReturnType() == null
-            ? "void"
-            : methodReference.getReturnType().getTypeName());
+    assert methodReference.isKnown();
+    KnownRetracedMethod knownRef = methodReference.asKnown();
+    sb.append(knownRef.isVoid() ? "void" : knownRef.getReturnType().getTypeName());
     sb.append(" ");
     sb.append(methodReference.getMethodName());
     sb.append("(");
     boolean seenFirstIndex = false;
-    for (TypeReference formalType : methodReference.getFormalTypes()) {
+    for (TypeReference formalType : knownRef.getFormalTypes()) {
       if (seenFirstIndex) {
         sb.append(",");
       }
@@ -63,17 +61,17 @@
 
   static RetraceSourceFileResult getSourceFile(
       RetraceClassResult.Element classElement,
-      ClassReference context,
+      RetracedClass context,
       String sourceFile,
       RetraceApi retracer) {
     // If no context is specified always retrace using the found class element.
     if (context == null) {
       return classElement.retraceSourceFile(sourceFile);
     }
-    if (context.equals(classElement.getClassReference())) {
+    if (context.equals(classElement.getRetracedClass())) {
       return classElement.retraceSourceFile(sourceFile);
     } else {
-      RetraceClassResult contextClassResult = retracer.retrace(context);
+      RetraceClassResult contextClassResult = retracer.retrace(context.getClassReference());
       assert !contextClassResult.isAmbiguous();
       if (contextClassResult.hasRetraceResult()) {
         Box<RetraceSourceFileResult> retraceSourceFile = new Box<>();
@@ -84,7 +82,7 @@
         return new RetraceSourceFileResult(
             synthesizeFileName(
                 context.getTypeName(),
-                classElement.getClassReference().getTypeName(),
+                classElement.getRetracedClass().getTypeName(),
                 sourceFile,
                 true),
             true);
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedClass.java b/src/main/java/com/android/tools/r8/retrace/RetracedClass.java
new file mode 100644
index 0000000..af064fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedClass.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+
+@Keep
+public final class RetracedClass {
+
+  private final ClassReference classReference;
+
+  private RetracedClass(ClassReference classReference) {
+    assert classReference != null;
+    this.classReference = classReference;
+  }
+
+  public static RetracedClass create(ClassReference classReference) {
+    return new RetracedClass(classReference);
+  }
+
+  public String getTypeName() {
+    return classReference.getTypeName();
+  }
+
+  public String getBinaryName() {
+    return classReference.getBinaryName();
+  }
+
+  public RetracedType getRetracedType() {
+    return RetracedType.create(classReference);
+  }
+
+  ClassReference getClassReference() {
+    return classReference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    return classReference.equals(((RetracedClass) o).classReference);
+  }
+
+  @Override
+  public int hashCode() {
+    return classReference.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedField.java b/src/main/java/com/android/tools/r8/retrace/RetracedField.java
new file mode 100644
index 0000000..b809cab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedField.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.TypeReference;
+import java.util.Objects;
+
+@Keep
+public abstract class RetracedField {
+
+  private RetracedField() {}
+
+  public boolean isUnknown() {
+    return true;
+  }
+
+  public final boolean isKnown() {
+    return !isUnknown();
+  }
+
+  public KnownRetracedField asKnown() {
+    return null;
+  }
+
+  public abstract RetracedClass getHolderClass();
+
+  public abstract String getFieldName();
+
+  public static final class KnownRetracedField extends RetracedField {
+
+    private final RetracedClass classReference;
+    private final FieldReference fieldReference;
+
+    private KnownRetracedField(RetracedClass classReference, FieldReference fieldReference) {
+      this.classReference = classReference;
+      this.fieldReference = fieldReference;
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return false;
+    }
+
+    @Override
+    public KnownRetracedField asKnown() {
+      return this;
+    }
+
+    @Override
+    public RetracedClass getHolderClass() {
+      return classReference;
+    }
+
+    @Override
+    public String getFieldName() {
+      return fieldReference.getFieldName();
+    }
+
+    public TypeReference getFieldType() {
+      return fieldReference.getFieldType();
+    }
+
+    public FieldReference getFieldReference() {
+      return fieldReference;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      KnownRetracedField that = (KnownRetracedField) o;
+      assert !fieldReference.equals(that.fieldReference)
+          || classReference.equals(that.classReference);
+      return fieldReference.equals(that.fieldReference);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(classReference, fieldReference);
+    }
+  }
+
+  public static final class UnknownRetracedField extends RetracedField {
+
+    private final RetracedClass classReference;
+    private final String name;
+
+    private UnknownRetracedField(RetracedClass classReference, String name) {
+      this.classReference = classReference;
+      this.name = name;
+    }
+
+    @Override
+    public RetracedClass getHolderClass() {
+      return classReference;
+    }
+
+    @Override
+    public String getFieldName() {
+      return name;
+    }
+  }
+
+  static RetracedField create(RetracedClass classReference, FieldReference fieldReference) {
+    return new KnownRetracedField(classReference, fieldReference);
+  }
+
+  static RetracedField createUnknown(RetracedClass classReference, String name) {
+    return new UnknownRetracedField(classReference, name);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
new file mode 100644
index 0000000..ee3b792
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
+import java.util.List;
+import java.util.Objects;
+
+@Keep
+public abstract class RetracedMethod {
+
+  private RetracedMethod() {}
+
+  public boolean isUnknown() {
+    return true;
+  }
+
+  public final boolean isKnown() {
+    return !isUnknown();
+  }
+
+  public KnownRetracedMethod asKnown() {
+    return null;
+  }
+
+  public abstract RetracedClass getHolderClass();
+
+  public abstract String getMethodName();
+
+  public static final class KnownRetracedMethod extends RetracedMethod {
+
+    private final MethodReference methodReference;
+    private final RetracedClass classReference;
+
+    private KnownRetracedMethod(RetracedClass classReference, MethodReference methodReference) {
+      assert methodReference != null;
+      this.classReference = classReference;
+      this.methodReference = methodReference;
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return false;
+    }
+
+    public boolean isVoid() {
+      return methodReference.getReturnType() == null;
+    }
+
+    @Override
+    public KnownRetracedMethod asKnown() {
+      return this;
+    }
+
+    @Override
+    public RetracedClass getHolderClass() {
+      return classReference;
+    }
+
+    @Override
+    public String getMethodName() {
+      return methodReference.getMethodName();
+    }
+
+    public TypeReference getReturnType() {
+      assert !isVoid();
+      return methodReference.getReturnType();
+    }
+
+    public List<TypeReference> getFormalTypes() {
+      return methodReference.getFormalTypes();
+    }
+
+    public MethodReference getClassReference() {
+      return methodReference;
+    }
+
+    public boolean equalsMethodReference(MethodReference reference) {
+      return methodReference.equals(reference);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      KnownRetracedMethod that = (KnownRetracedMethod) o;
+      assert !methodReference.equals(that.methodReference)
+          || classReference.equals(that.classReference);
+      return methodReference.equals(that.methodReference);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(methodReference, classReference);
+    }
+  }
+
+  public static final class UnknownRetracedMethod extends RetracedMethod {
+
+    private final RetracedClass classReference;
+    private final String name;
+
+    private UnknownRetracedMethod(RetracedClass classReference, String name) {
+      this.classReference = classReference;
+      this.name = name;
+    }
+
+    @Override
+    public RetracedClass getHolderClass() {
+      return classReference;
+    }
+
+    @Override
+    public String getMethodName() {
+      return name;
+    }
+  }
+
+  static RetracedMethod create(RetracedClass classReference, MethodReference methodReference) {
+    return new KnownRetracedMethod(classReference, methodReference);
+  }
+
+  static RetracedMethod createUnknown(RetracedClass classReference, String name) {
+    return new UnknownRetracedMethod(classReference, name);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedType.java b/src/main/java/com/android/tools/r8/retrace/RetracedType.java
new file mode 100644
index 0000000..016c8df
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedType.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import java.util.Objects;
+
+@Keep
+public final class RetracedType {
+
+  private final TypeReference typeReference;
+
+  private RetracedType(TypeReference typeReference) {
+    this.typeReference = typeReference;
+  }
+
+  static RetracedType create(TypeReference typeReference) {
+    return new RetracedType(typeReference);
+  }
+
+  public boolean isVoid() {
+    return typeReference == null;
+  }
+
+  public TypeReference toArray(int dimensions) {
+    return Reference.array(typeReference, dimensions);
+  }
+
+  public String getTypeName() {
+    assert !isVoid();
+    return typeReference.getTypeName();
+  }
+
+  public TypeReference getTypeReference() {
+    return typeReference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    return typeReference.equals(((RetracedType) o).typeReference);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(typeReference);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java b/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java
new file mode 100644
index 0000000..f259b01
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+
+class KeepRuleFormatter extends ResultFormatter {
+  final boolean allowObfuscation;
+
+  KeepRuleFormatter(
+      StringConsumer output, DiagnosticsHandler diagnosticsHandler, boolean allowObfuscation) {
+    super(output, diagnosticsHandler);
+    this.allowObfuscation = allowObfuscation;
+  }
+
+  @Override
+  protected void printTypeHeader(DexClass dexClass) {
+    append(allowObfuscation ? "-keep,allowobfuscation" : "-keep");
+    if (dexClass.isInterface()) {
+      append(" interface " + dexClass.type.toSourceString() + " {" + System.lineSeparator());
+    } else if (dexClass.accessFlags.isEnum()) {
+      append(" enum " + dexClass.type.toSourceString() + " {" + System.lineSeparator());
+    } else {
+      append(" class " + dexClass.type.toSourceString() + " {" + System.lineSeparator());
+    }
+  }
+
+  @Override
+  protected void printConstructorName(DexEncodedMethod encodedMethod) {
+    append("<init>");
+  }
+
+  @Override
+  protected void printField(DexClass dexClass, DexField field) {
+    append(
+        "  "
+            + field.type.toSourceString()
+            + " "
+            + field.name.toString()
+            + ";"
+            + System.lineSeparator());
+  }
+
+  @Override
+  protected void printMethod(DexEncodedMethod encodedMethod, String typeName) {
+    // Static initializers do not require keep rules - it is kept by keeping the class.
+    if (encodedMethod.accessFlags.isConstructor() && encodedMethod.accessFlags.isStatic()) {
+      return;
+    }
+    append("  ");
+    if (encodedMethod.isPublicMethod()) {
+      append("public ");
+    } else if (encodedMethod.isPrivateMethod()) {
+      append("private ");
+    } else if (encodedMethod.isProtectedMethod()) {
+      append("protected ");
+    }
+    if (encodedMethod.isStatic()) {
+      append("static ");
+    }
+    printNameAndReturn(encodedMethod);
+    printArguments(encodedMethod.method);
+    appendLine(";");
+  }
+
+  @Override
+  protected void printPackageNames(List<String> packageNames) {
+    if (!packageNames.isEmpty()) {
+      append("-keeppackagenames " + StringUtils.join(packageNames, ",") + System.lineSeparator());
+    }
+  }
+
+  @Override
+  protected void printTypeFooter() {
+    appendLine("}");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/PrintUsesFormatter.java b/src/main/java/com/android/tools/r8/tracereferences/PrintUsesFormatter.java
new file mode 100644
index 0000000..12b2e6b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/PrintUsesFormatter.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import java.util.List;
+
+class PrintUsesFormatter extends ResultFormatter {
+
+  PrintUsesFormatter(StringConsumer output, DiagnosticsHandler diagnosticsHandler) {
+    super(output, diagnosticsHandler);
+  }
+
+  @Override
+  protected void printConstructorName(DexEncodedMethod encodedMethod) {
+    if (encodedMethod.accessFlags.isStatic()) {
+      append("<clinit>");
+    } else {
+      String holderName = encodedMethod.holder().toSourceString();
+      String constructorName = holderName.substring(holderName.lastIndexOf('.') + 1);
+      append(constructorName);
+    }
+  }
+
+  @Override
+  protected void printMethod(DexEncodedMethod encodedMethod, String typeName) {
+    append(typeName + ": ");
+    printNameAndReturn(encodedMethod);
+    printArguments(encodedMethod.method);
+    appendLine("");
+  }
+
+  @Override
+  protected void printPackageNames(List<String> packageNames) {
+    // No need to print package names for text output.
+  }
+
+  @Override
+  protected void printTypeHeader(DexClass dexClass) {
+    appendLine(dexClass.type.toSourceString());
+  }
+
+  @Override
+  protected void printTypeFooter() {}
+
+  @Override
+  protected void printField(DexClass dexClass, DexField field) {
+    appendLine(
+        dexClass.type.toSourceString()
+            + ": "
+            + field.type.toSourceString()
+            + " "
+            + field.name.toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Result.java b/src/main/java/com/android/tools/r8/tracereferences/Result.java
new file mode 100644
index 0000000..6889f0c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/Result.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import com.android.tools.r8.graph.DexApplication;
+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 java.util.Map;
+import java.util.Set;
+
+class Result {
+  final DexApplication application;
+  final Set<DexType> types;
+  final Set<String> keepPackageNames;
+  final Map<DexType, Set<DexField>> fields;
+  final Map<DexType, Set<DexMethod>> methods;
+  final Set<DexReference> missingDefinition;
+
+  Result(
+      DexApplication application,
+      Set<DexType> types,
+      Set<String> keepPackageNames,
+      Map<DexType, Set<DexField>> fields,
+      Map<DexType, Set<DexMethod>> methods,
+      Set<DexReference> missingDefinition) {
+    this.application = application;
+    this.types = types;
+    this.keepPackageNames = keepPackageNames;
+    this.fields = fields;
+    this.methods = methods;
+    this.missingDefinition = missingDefinition;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/ResultFormatter.java b/src/main/java/com/android/tools/r8/tracereferences/ResultFormatter.java
new file mode 100644
index 0000000..2fd8934
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/ResultFormatter.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+abstract class ResultFormatter {
+
+  private final StringConsumer output;
+  private final DiagnosticsHandler diagnosticsHandler;
+
+  ResultFormatter(StringConsumer output, DiagnosticsHandler diagnosticsHandler) {
+    this.output = output;
+    this.diagnosticsHandler = diagnosticsHandler;
+  }
+
+  protected void append(String string) {
+    output.accept(string, diagnosticsHandler);
+  }
+
+  protected void appendLine(String string) {
+    output.accept(string + System.lineSeparator(), diagnosticsHandler);
+  }
+
+  protected void printArguments(DexMethod method) {
+    append("(");
+    for (int i = 0; i < method.getArity(); i++) {
+      if (i != 0) {
+        append(",");
+      }
+      append(method.proto.parameters.values[i].toSourceString());
+    }
+    append(")");
+  }
+
+  protected abstract void printConstructorName(DexEncodedMethod encodedMethod);
+
+  private void printError(String message) {
+    appendLine("# Error: " + message);
+  }
+
+  protected abstract void printField(DexClass dexClass, DexField field);
+
+  protected abstract void printMethod(DexEncodedMethod encodedMethod, String typeName);
+
+  protected abstract void printPackageNames(List<String> packageNames);
+
+  protected void printNameAndReturn(DexEncodedMethod encodedMethod) {
+    if (encodedMethod.accessFlags.isConstructor()) {
+      printConstructorName(encodedMethod);
+    } else {
+      DexMethod method = encodedMethod.method;
+      append(method.proto.returnType.toSourceString());
+      append(" ");
+      append(method.name.toSourceString());
+    }
+  }
+
+  protected abstract void printTypeHeader(DexClass dexClass);
+
+  protected abstract void printTypeFooter();
+
+  void format(Result result) {
+    int errors =
+        print(
+            result.application,
+            result.types,
+            result.keepPackageNames,
+            result.fields,
+            result.methods,
+            result.missingDefinition);
+    output.finished(diagnosticsHandler);
+    assert errors == result.missingDefinition.size();
+  }
+
+  private int print(
+      DexApplication application,
+      Set<DexType> types,
+      Set<String> keepPackageNames,
+      Map<DexType, Set<DexField>> fields,
+      Map<DexType, Set<DexMethod>> methods,
+      Set<DexReference> missingDefinition) {
+    int errors = 0;
+    List<DexType> sortedTypes = new ArrayList<>(types);
+    sortedTypes.sort(Comparator.comparing(DexType::toSourceString));
+    for (DexType type : sortedTypes) {
+      DexClass dexClass = application.definitionFor(type);
+      if (missingDefinition.contains(type)) {
+        assert dexClass == null;
+        printError("Could not find definition for type " + type.toSourceString());
+        errors++;
+        continue;
+      }
+      printTypeHeader(dexClass);
+      List<DexEncodedMethod> methodDefinitions = new ArrayList<>(methods.size());
+      for (DexMethod method : methods.get(type)) {
+        DexEncodedMethod encodedMethod = dexClass.lookupMethod(method);
+        if (missingDefinition.contains(method)) {
+          assert encodedMethod == null;
+          printError("Could not find definition for method " + method.toSourceString());
+          errors++;
+          continue;
+        }
+        methodDefinitions.add(encodedMethod);
+      }
+      methodDefinitions.sort(Comparator.comparing(x -> x.method.name.toSourceString()));
+      for (DexEncodedMethod encodedMethod : methodDefinitions) {
+        printMethod(encodedMethod, dexClass.type.toSourceString());
+      }
+      List<DexField> sortedFields = new ArrayList<>(fields.get(type));
+      sortedFields.sort(Comparator.comparing(DexField::toSourceString));
+      for (DexField field : sortedFields) {
+        if (missingDefinition.contains(field)) {
+          printError("Could not find definition for field " + field.toSourceString());
+          errors++;
+          continue;
+        }
+        printField(dexClass, field);
+      }
+      printTypeFooter();
+    }
+    ArrayList<String> packageNamesToKeep = new ArrayList<>(keepPackageNames);
+    Collections.sort(packageNamesToKeep);
+    printPackageNames(packageNamesToKeep);
+    return errors;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
new file mode 100644
index 0000000..4808947
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import static com.android.tools.r8.utils.ExceptionUtils.STATUS_ERROR;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import java.util.HashSet;
+import java.util.Set;
+
+@Keep
+public class TraceReferences {
+
+  public static void run(TraceReferencesCommand command) throws CompilationFailedException {
+    try {
+      runInternal(command);
+    } catch (TraceReferencesAbortException e) {
+      throw new CompilationFailedException();
+    } catch (Exception e) {
+      command.getDiagnosticsHandler().error(new ExceptionDiagnostic(e));
+      throw new CompilationFailedException();
+    }
+  }
+
+  private static void runInternal(TraceReferencesCommand command) throws Exception {
+    if (command.getLibrary().isEmpty()) {
+      throw new TraceReferencesException("No library specified");
+    }
+    if (command.getTarget().isEmpty()) {
+      throw new TraceReferencesException("No target specified");
+    }
+    if (command.getSource().isEmpty()) {
+      throw new TraceReferencesException("No source specified");
+    }
+    if (command.getOutput() == null) {
+      throw new TraceReferencesException("No output specified");
+    }
+    AndroidApp.Builder builder = AndroidApp.builder();
+    command.getLibrary().forEach(builder::addLibraryResourceProvider);
+    command.getTarget().forEach(builder::addLibraryResourceProvider);
+    command.getSource().forEach(builder::addProgramResourceProvider);
+    Set<String> tagetDescriptors = new HashSet<>();
+    command
+        .getTarget()
+        .forEach(provider -> tagetDescriptors.addAll(provider.getClassDescriptors()));
+    for (ProgramResourceProvider provider : command.getSource()) {
+      for (ProgramResource programResource : provider.getProgramResources()) {
+        tagetDescriptors.removeAll(programResource.getClassDescriptors());
+      }
+    }
+    Tracer tracer = new Tracer(tagetDescriptors, builder.build());
+    Result result = tracer.run();
+    ResultFormatter formatter;
+    switch (command.getOutputFormat()) {
+      case PRINTUSAGE:
+        formatter = new PrintUsesFormatter(command.getOutput(), command.getDiagnosticsHandler());
+        break;
+      case KEEP_RULES:
+        formatter =
+            new KeepRuleFormatter(command.getOutput(), command.getDiagnosticsHandler(), false);
+        break;
+      case KEEP_RULES_WITH_ALLOWOBFUSCATION:
+        formatter =
+            new KeepRuleFormatter(command.getOutput(), command.getDiagnosticsHandler(), true);
+        break;
+      default:
+        throw new TraceReferencesException("Unexpected format " + command.getOutputFormat().name());
+    }
+    formatter.format(result);
+  }
+
+  public static void main(String... args) {
+    try {
+      TraceReferencesCommand command = TraceReferencesCommand.parse(args, Origin.root()).build();
+      if (command.isPrintHelp()) {
+        System.out.println(TraceReferencesCommandParser.USAGE_MESSAGE);
+        return;
+      }
+      if (command.isPrintVersion()) {
+        System.out.println("referencetrace " + Version.getVersionString());
+        return;
+      }
+      run(command);
+    } catch (CompilationFailedException e) {
+      System.exit(STATUS_ERROR);
+    } catch (Throwable e) {
+      System.err.println("ReferenceTrace failed with an internal error.");
+      Throwable cause = e.getCause() == null ? e : e.getCause();
+      cause.printStackTrace();
+      System.exit(STATUS_ERROR);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesAbortException.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesAbortException.java
new file mode 100644
index 0000000..8afab19
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesAbortException.java
@@ -0,0 +1,6 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+public class TraceReferencesAbortException extends RuntimeException {}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
new file mode 100644
index 0000000..f6fcb69
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -0,0 +1,254 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.tracereferences.TraceReferencesCommandParser.OutputFormat;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@Keep
+public class TraceReferencesCommand {
+  private final boolean printHelp;
+  private final boolean printVersion;
+  private final DiagnosticsHandler diagnosticsHandler;
+  private final ImmutableList<ClassFileResourceProvider> library;
+  private final ImmutableList<ClassFileResourceProvider> traceTarget;
+  private final ImmutableList<ProgramResourceProvider> traceSource;
+  private final StringConsumer output;
+  private final OutputFormat outputFormat;
+
+  TraceReferencesCommand(
+      boolean printHelp,
+      boolean printVersion,
+      DiagnosticsHandler diagnosticsHandler,
+      ImmutableList<ClassFileResourceProvider> library,
+      ImmutableList<ClassFileResourceProvider> traceTarget,
+      ImmutableList<ProgramResourceProvider> traceSource,
+      StringConsumer output,
+      OutputFormat outputFormat) {
+    this.printHelp = printHelp;
+    this.printVersion = printVersion;
+    this.diagnosticsHandler = diagnosticsHandler;
+    this.library = library;
+    this.traceTarget = traceTarget;
+    this.traceSource = traceSource;
+    this.output = output;
+    this.outputFormat = outputFormat;
+  }
+
+  /**
+   * Utility method for obtaining a <code>ReferenceTraceCommand.Builder</code>.
+   *
+   * @param diagnosticsHandler The diagnostics handler for consuming messages.
+   */
+  public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
+    return new Builder(diagnosticsHandler);
+  }
+
+  /**
+   * Utility method for obtaining a <code>ReferenceTraceCommand.Builder</code> with a default
+   * diagnostics handler.
+   */
+  public static Builder builder() {
+    return new Builder(new DiagnosticsHandler() {});
+  }
+
+  public static Builder parse(String[] args, Origin origin) {
+    return TraceReferencesCommandParser.parse(args, origin);
+  }
+
+  public static Builder parse(String[] args, Origin origin, DiagnosticsHandler diagnosticsHandler) {
+    return TraceReferencesCommandParser.parse(args, origin, diagnosticsHandler);
+  }
+
+  public boolean isPrintHelp() {
+    return printHelp;
+  }
+
+  public boolean isPrintVersion() {
+    return printVersion;
+  }
+
+  public static class Builder {
+
+    private boolean printHelp = false;
+    private boolean printVersion = false;
+    private final DiagnosticsHandler diagnosticsHandler;
+    private final ImmutableList.Builder<ClassFileResourceProvider> libraryBuilder =
+        ImmutableList.builder();
+    private final ImmutableList.Builder<ClassFileResourceProvider> traceTargetBuilder =
+        ImmutableList.builder();
+    private final ImmutableList.Builder<ProgramResourceProvider> traceSourceBuilder =
+        ImmutableList.builder();
+    private StringConsumer output;
+    private OutputFormat outputFormat = TraceReferencesCommandParser.OutputFormat.PRINTUSAGE;
+
+    private Builder(DiagnosticsHandler diagnosticsHandler) {
+      this.diagnosticsHandler = diagnosticsHandler;
+    }
+
+    public Builder setPrintHelp(boolean printHelp) {
+      this.printHelp = printHelp;
+      return this;
+    }
+
+    /** True if the print-version flag is enabled. */
+    public boolean isPrintVersion() {
+      return printVersion;
+    }
+
+    /** Set the value of the print-version flag. */
+    public Builder setPrintVersion(boolean printVersion) {
+      this.printVersion = printVersion;
+      return this;
+    }
+
+    private void addLibraryOrTargetFile(
+        Path file, ImmutableList.Builder<ClassFileResourceProvider> builder) {
+      if (!Files.exists(file)) {
+        PathOrigin pathOrigin = new PathOrigin(file);
+        NoSuchFileException noSuchFileException = new NoSuchFileException(file.toString());
+        error(new ExceptionDiagnostic(noSuchFileException, pathOrigin));
+      }
+      if (isArchive(file)) {
+        try {
+          ArchiveClassFileProvider provider = new ArchiveClassFileProvider(file);
+          builder.add(provider);
+        } catch (IOException e) {
+          error(new ExceptionDiagnostic(e, new PathOrigin(file)));
+        }
+      } else {
+        error(new StringDiagnostic("Unsupported source file type", new PathOrigin(file)));
+      }
+    }
+
+    private void addSourceFile(Path file) {
+      if (!Files.exists(file)) {
+        PathOrigin pathOrigin = new PathOrigin(file);
+        NoSuchFileException noSuchFileException = new NoSuchFileException(file.toString());
+        error(new ExceptionDiagnostic(noSuchFileException, pathOrigin));
+      }
+      if (isArchive(file)) {
+        traceSourceBuilder.add(ArchiveResourceProvider.fromArchive(file, false));
+      } else {
+        error(new StringDiagnostic("Unsupported source file type", new PathOrigin(file)));
+      }
+    }
+
+    public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
+      libraryBuilder.add(provider);
+      return this;
+    }
+
+    public Builder addLibraryFiles(Path... files) {
+      addLibraryFiles(Arrays.asList(files));
+      return this;
+    }
+
+    public Builder addLibraryFiles(Collection<Path> files) {
+      for (Path file : files) {
+        addLibraryOrTargetFile(file, libraryBuilder);
+      }
+      return this;
+    }
+
+    public Builder addTargetFiles(Path... files) {
+      addTargetFiles(Arrays.asList(files));
+      return this;
+    }
+
+    public Builder addTargetFiles(Collection<Path> files) {
+      for (Path file : files) {
+        addLibraryOrTargetFile(file, traceTargetBuilder);
+      }
+      return this;
+    }
+
+    public Builder addSourceFiles(Path... files) {
+      addSourceFiles(Arrays.asList(files));
+      return this;
+    }
+
+    public Builder addSourceFiles(Collection<Path> files) {
+      for (Path file : files) {
+        addSourceFile(file);
+      }
+      return this;
+    }
+
+    Builder setOutputPath(Path output) {
+      this.output = new StringConsumer.FileConsumer(output);
+      return this;
+    }
+
+    Builder setOutputFormat(OutputFormat outputFormat) {
+      this.outputFormat = outputFormat;
+      return this;
+    }
+
+    public final TraceReferencesCommand build() {
+      ImmutableList<ClassFileResourceProvider> traceTarget = traceTargetBuilder.build();
+      ImmutableList<ProgramResourceProvider> traceSource = traceSourceBuilder.build();
+      return new TraceReferencesCommand(
+          printHelp,
+          printVersion,
+          diagnosticsHandler,
+          libraryBuilder.build(),
+          traceTarget,
+          traceSource,
+          output,
+          outputFormat);
+    }
+
+    void error(Diagnostic diagnostic) {
+      diagnosticsHandler.error(diagnostic);
+      // For now all errors are fatal.
+      throw new TraceReferencesAbortException();
+    }
+  }
+
+  DiagnosticsHandler getDiagnosticsHandler() {
+    return diagnosticsHandler;
+  }
+
+  List<ClassFileResourceProvider> getLibrary() {
+    return library;
+  }
+
+  List<ClassFileResourceProvider> getTarget() {
+    return traceTarget;
+  }
+
+  List<ProgramResourceProvider> getSource() {
+    return traceSource;
+  }
+
+  StringConsumer getOutput() {
+    return output;
+  }
+
+  OutputFormat getOutputFormat() {
+    return outputFormat;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
new file mode 100644
index 0000000..79de8e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
@@ -0,0 +1,166 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.FlagFile;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Set;
+
+class TraceReferencesCommandParser {
+
+  enum OutputFormat {
+    /** Format used with the -printusage flag */
+    PRINTUSAGE,
+    /** Keep rules keeping each of the traced references */
+    KEEP_RULES,
+    /**
+     * Keep rules with <code>allowobfuscation</code> modifier keeping each of the traced references
+     */
+    KEEP_RULES_WITH_ALLOWOBFUSCATION
+  }
+
+  private static final Set<String> OPTIONS_WITH_PARAMETER =
+      ImmutableSet.of("--lib", "--target", "--source", "--format", "--output");
+
+  static final String USAGE_MESSAGE =
+      String.join(
+          "\n",
+          Arrays.asList(
+              "Usage: referencetrace [options] [@<argfile>]",
+              " Each <argfile> is a file containing additional arguments (one per line)",
+              " and options are:",
+              "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
+              "  --target <file>         # Add <file> as a classpath resource.",
+              "  --source <file>         # Add <file> as a classpath resource.",
+              "  --output <file>         # Output result in <outfile>.",
+              "  --version               # Print the version of referencetrace.",
+              "  --help                  # Print this message."));
+  /**
+   * Parse the referencetrace command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @return referencetrace command builder with state set up according to parsed command line.
+   */
+  static TraceReferencesCommand.Builder parse(String[] args, Origin origin) {
+    return new TraceReferencesCommandParser().parse(args, origin, TraceReferencesCommand.builder());
+  }
+
+  /**
+   * Parse the referencetrace command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @param handler Custom defined diagnostics handler.
+   * @return referencetrace command builder with state set up according to parsed command line.
+   */
+  static TraceReferencesCommand.Builder parse(
+      String[] args, Origin origin, DiagnosticsHandler handler) {
+    return new TraceReferencesCommandParser()
+        .parse(args, origin, TraceReferencesCommand.builder(handler));
+  }
+
+  private TraceReferencesCommand.Builder parse(
+      String[] args, Origin origin, TraceReferencesCommand.Builder builder) {
+    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
+    for (int i = 0; i < expandedArgs.length; i++) {
+      String arg = expandedArgs[i].trim();
+      String nextArg = null;
+      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+        if (++i < expandedArgs.length) {
+          nextArg = expandedArgs[i];
+        } else {
+          builder.error(
+              new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
+          break;
+        }
+      }
+      if (arg.length() == 0) {
+        continue;
+      } else if (arg.equals("--help")) {
+        builder.setPrintHelp(true);
+      } else if (arg.equals("--version")) {
+        builder.setPrintVersion(true);
+      } else if (arg.equals("--lib")) {
+        addLibraryArgument(builder, origin, nextArg);
+      } else if (arg.equals("--target")) {
+        builder.addTargetFiles(Paths.get(nextArg));
+      } else if (arg.equals("--source")) {
+        builder.addSourceFiles(Paths.get(nextArg));
+      } else if (arg.equals("--format")) {
+        OutputFormat format = null;
+        if (nextArg.equals("printuses")) {
+          format = OutputFormat.PRINTUSAGE;
+        }
+        if (nextArg.equals("keep")) {
+          format = OutputFormat.KEEP_RULES;
+        }
+        if (nextArg.equals("keepallowobfuscation")) {
+          format = OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION;
+        }
+        if (format == null) {
+          builder.error(new StringDiagnostic("Unsupported format '" + nextArg + "'"));
+        }
+        builder.setOutputFormat(format);
+      } else if (arg.equals("--output")) {
+        builder.setOutputPath(Paths.get(nextArg));
+      } else if (arg.startsWith("@")) {
+        builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin));
+      } else {
+        builder.error(new StringDiagnostic("Unsupported argument '" + arg + "'"));
+      }
+    }
+    return builder;
+  }
+
+  /**
+   * This method must match the lookup in {@link
+   * com.android.tools.r8.JdkClassFileProvider#fromJdkHome}.
+   */
+  private static boolean isJdkHome(Path home) {
+    Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+    if (Files.exists(jrtFsJar)) {
+      return true;
+    }
+    // JDK has rt.jar in jre/lib/rt.jar.
+    Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar");
+    if (Files.exists(rtJar)) {
+      return true;
+    }
+    // JRE has rt.jar in lib/rt.jar.
+    rtJar = home.resolve("lib").resolve("rt.jar");
+    if (Files.exists(rtJar)) {
+      return true;
+    }
+    return false;
+  }
+
+  static void addLibraryArgument(
+      TraceReferencesCommand.Builder builder, Origin origin, String arg) {
+    Path path = Paths.get(arg);
+    if (isJdkHome(path)) {
+      try {
+        builder.addLibraryResourceProvider(JdkClassFileProvider.fromJdkHome(path));
+      } catch (IOException e) {
+        builder.error(new ExceptionDiagnostic(e, origin));
+      }
+    } else {
+      builder.addLibraryFiles(path);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesException.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesException.java
new file mode 100644
index 0000000..57fcc4e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesException.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+class TraceReferencesException extends Exception {
+  TraceReferencesException(String message) {
+    super(message);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
new file mode 100644
index 0000000..5ad1cfa
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -0,0 +1,291 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+class Tracer {
+
+  private final Set<String> descriptors;
+  private Set<DexType> types = Sets.newIdentityHashSet();
+  private Map<DexType, Set<DexMethod>> methods = Maps.newIdentityHashMap();
+  private Map<DexType, Set<DexField>> fields = Maps.newIdentityHashMap();
+  private Set<String> keepPackageNames = Sets.newHashSet();
+  private Set<DexReference> missingDefinitions = Sets.newHashSet();
+  private final DirectMappedDexApplication application;
+  private final AppInfoWithClassHierarchy appInfo;
+
+  Tracer(Set<String> descriptors, AndroidApp inputApp) throws Exception {
+    this.descriptors = descriptors;
+    InternalOptions options = new InternalOptions();
+    application =
+        new ApplicationReader(inputApp, options, new Timing("ReferenceTrace")).read().toDirect();
+    appInfo =
+        AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
+            application,
+            ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
+            MainDexClasses.createEmptyMainDexClasses());
+  }
+
+  Result run() {
+    UseCollector useCollector = new UseCollector(appInfo.dexItemFactory());
+    for (DexProgramClass clazz : application.classes()) {
+      useCollector.setContext(clazz);
+      useCollector.registerSuperType(clazz, clazz.superType);
+      for (DexType implementsType : clazz.interfaces.values) {
+        useCollector.registerSuperType(clazz, implementsType);
+      }
+      clazz.forEachProgramMethod(useCollector::registerMethod);
+      clazz.forEachField(useCollector::registerField);
+    }
+
+    return new Result(application, types, keepPackageNames, fields, methods, missingDefinitions);
+  }
+
+  private boolean isTargetType(DexType type) {
+    return descriptors.contains(type.toDescriptorString());
+  }
+
+  private void addType(DexType type) {
+    if (isTargetType(type) && types.add(type)) {
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz != null && clazz.accessFlags.isVisibilityDependingOnPackage()) {
+        keepPackageNames.add(clazz.type.getPackageName());
+      }
+      methods.put(type, Sets.newIdentityHashSet());
+      fields.put(type, Sets.newIdentityHashSet());
+    }
+  }
+
+  private void addField(DexField field) {
+    addType(field.type);
+    DexEncodedField baseField = appInfo.resolveField(field).getResolvedField();
+    if (baseField != null && baseField.holder() != field.holder) {
+      field = baseField.field;
+    }
+    addType(field.holder);
+    if (isTargetType(field.holder)) {
+      Set<DexField> typeFields = fields.get(field.holder);
+      assert typeFields != null;
+      if (baseField != null) {
+        if (baseField.accessFlags.isVisibilityDependingOnPackage()) {
+          keepPackageNames.add(baseField.holder().getPackageName());
+        }
+      } else {
+        missingDefinitions.add(field);
+      }
+      typeFields.add(field);
+    }
+  }
+
+  private void addMethod(DexMethod method) {
+    addType(method.holder);
+    for (DexType parameterType : method.proto.parameters.values) {
+      addType(parameterType);
+    }
+    addType(method.proto.returnType);
+    if (isTargetType(method.holder)) {
+      Set<DexMethod> typeMethods = methods.get(method.holder);
+      assert typeMethods != null;
+      DexClass holder = appInfo.definitionForHolder(method);
+      DexEncodedMethod definition = method.lookupOnClass(holder);
+      if (definition != null) {
+        if (definition.accessFlags.isVisibilityDependingOnPackage()) {
+          keepPackageNames.add(definition.holder().getPackageName());
+        }
+      } else {
+        missingDefinitions.add(method);
+      }
+      typeMethods.add(method);
+    }
+  }
+
+  class UseCollector extends UseRegistry {
+
+    private DexProgramClass context;
+
+    UseCollector(DexItemFactory factory) {
+      super(factory);
+    }
+
+    public void setContext(DexProgramClass context) {
+      this.context = context;
+    }
+
+    @Override
+    public void registerInitClass(DexType clazz) {
+      addType(clazz);
+    }
+
+    @Override
+    public void registerInvokeVirtual(DexMethod method) {
+      ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
+      DexEncodedMethod target =
+          resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
+      if (target != null && target.method != method) {
+        addType(method.holder);
+        addMethod(target.method);
+      } else {
+        addMethod(method);
+      }
+    }
+
+    @Override
+    public void registerInvokeDirect(DexMethod method) {
+      addMethod(method);
+    }
+
+    @Override
+    public void registerInvokeStatic(DexMethod method) {
+      DexEncodedMethod target = appInfo.unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
+      if (target != null && target.method != method) {
+        addType(method.holder);
+        addMethod(target.method);
+      } else {
+        addMethod(method);
+      }
+    }
+
+    @Override
+    public void registerInvokeInterface(DexMethod method) {
+      registerInvokeVirtual(method);
+    }
+
+    @Override
+    public void registerInvokeSuper(DexMethod method) {
+      DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method, context);
+      if (superTarget != null) {
+        addMethod(superTarget.method);
+      } else {
+        addMethod(method);
+      }
+    }
+
+    @Override
+    public void registerInstanceFieldWrite(DexField field) {
+      addField(field);
+    }
+
+    @Override
+    public void registerInstanceFieldRead(DexField field) {
+      addField(field);
+    }
+
+    @Override
+    public void registerNewInstance(DexType type) {
+      addType(type);
+    }
+
+    @Override
+    public void registerStaticFieldRead(DexField field) {
+      addField(field);
+    }
+
+    @Override
+    public void registerStaticFieldWrite(DexField field) {
+      addField(field);
+    }
+
+    @Override
+    public void registerTypeReference(DexType type) {
+      addType(type);
+    }
+
+    @Override
+    public void registerInstanceOf(DexType type) {
+      addType(type);
+    }
+
+    private void registerField(DexEncodedField field) {
+      registerTypeReference(field.field.type);
+    }
+
+    private void registerMethod(ProgramMethod method) {
+      DexEncodedMethod superTarget =
+          appInfo
+              .resolveMethodOn(method.getHolder(), method.getReference())
+              .lookupInvokeSpecialTarget(context, appInfo);
+      if (superTarget != null) {
+        addMethod(superTarget.method);
+      }
+      for (DexType type : method.getDefinition().parameters().values) {
+        registerTypeReference(type);
+      }
+      for (DexAnnotation annotation : method.getDefinition().annotations().annotations) {
+        if (annotation.annotation.type == appInfo.dexItemFactory().annotationThrows) {
+          DexValueArray dexValues = annotation.annotation.elements[0].value.asDexValueArray();
+          for (DexValue dexValType : dexValues.getValues()) {
+            registerTypeReference(dexValType.asDexValueType().value);
+          }
+        }
+      }
+      registerTypeReference(method.getDefinition().returnType());
+      method.registerCodeReferences(this);
+    }
+
+    private void registerSuperType(DexProgramClass clazz, DexType superType) {
+      registerTypeReference(superType);
+      // If clazz overrides any methods in superType, we should keep those as well.
+      clazz.forEachMethod(
+          method -> {
+            ResolutionResult resolutionResult =
+                appInfo.resolveMethodOn(superType, method.method, superType != clazz.superType);
+            DexEncodedMethod dexEncodedMethod = resolutionResult.getSingleTarget();
+            if (dexEncodedMethod != null) {
+              addMethod(dexEncodedMethod.method);
+            }
+          });
+    }
+
+    @Override
+    public void registerCallSite(DexCallSite callSite) {
+      super.registerCallSite(callSite);
+
+      // For Lambda's, in order to find the correct use, we need to register the method for the
+      // functional interface.
+      List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
+      if (directInterfaces != null) {
+        for (DexType directInterface : directInterfaces) {
+          DexProgramClass clazz = asProgramClassOrNull(appInfo.definitionFor(directInterface));
+          if (clazz != null) {
+            clazz.forEachProgramVirtualMethodMatching(
+                definition -> definition.getReference().name.equals(callSite.methodName),
+                this::registerMethod);
+          }
+        }
+      }
+    }
+  }
+}
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 cdd3ca3..a7481b7 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -186,6 +186,7 @@
     enableClassStaticizer = false;
     enableDevirtualization = false;
     enableLambdaMerging = false;
+    enableHorizontalClassMerging = false;
     enableStaticClassMerging = false;
     enableVerticalClassMerging = false;
     enableEnumUnboxing = false;
@@ -220,7 +221,8 @@
   public boolean enableFieldBitAccessAnalysis =
       System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
   public boolean enableStaticClassMerging = true;
-  public boolean enableHorizontalClassMerging = false;
+  public boolean enableHorizontalClassMerging =
+      System.getProperty("com.android.tools.r8.horizontalClassMerging") != null;
   public boolean enableVerticalClassMerging = true;
   public boolean enableArgumentRemoval = true;
   public boolean enableUnusedInterfaceRemoval = true;
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index 71400bd..f75d170 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -24,12 +24,17 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 import java.util.zip.CRC32;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
@@ -75,14 +80,26 @@
   }
 
   public static void zip(Path zipFile, Path inputDirectory) throws IOException {
+    List<Path> files =
+        Files.walk(inputDirectory)
+            .filter(path -> !Files.isDirectory(path))
+            .collect(Collectors.toList());
+    zip(zipFile, inputDirectory, files);
+  }
+
+  public static void zip(Path zipFile, Path basePath, Collection<Path> filesToZip)
+      throws IOException {
     try (ZipOutputStream stream =
         new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile)))) {
-      List<Path> files =
-          Files.walk(inputDirectory)
-              .filter(path -> !Files.isDirectory(path))
-              .collect(Collectors.toList());
-      for (Path path : files) {
-        ZipEntry zipEntry = new ZipEntry(inputDirectory.relativize(path).toString());
+      for (Path path : filesToZip) {
+        ZipEntry zipEntry =
+            new ZipEntry(
+                StreamSupport.stream(
+                        Spliterators.spliteratorUnknownSize(
+                            basePath.relativize(path).iterator(), Spliterator.ORDERED),
+                        false)
+                    .map(Path::toString)
+                    .collect(Collectors.joining("/")));
         stream.putNextEntry(zipEntry);
         Files.copy(path, stream);
         stream.closeEntry();
@@ -90,6 +107,10 @@
     }
   }
 
+  public static void zip(Path zipFile, Path basePath, Path... filesToZip) throws IOException {
+    zip(zipFile, basePath, Arrays.asList(filesToZip));
+  }
+
   public static List<File> unzip(String zipFile, File outDirectory) throws IOException {
     return unzip(zipFile, outDirectory, (entry) -> true);
   }
diff --git a/src/main/keep-applymapping.txt b/src/main/keep-applymapping.txt
index 6ce5283..c404d21 100644
--- a/src/main/keep-applymapping.txt
+++ b/src/main/keep-applymapping.txt
@@ -19,6 +19,3 @@
 -keep class com.android.tools.r8.joptsimple.OptionDescriptor {
   java.lang.String argumentDescription();
 }
-
-# We should support reserved names and compute the set based on the tests.
--keep class com.android.tools.r8.shaking.** { *; }
\ No newline at end of file
diff --git a/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
new file mode 100644
index 0000000..5c1453e
--- /dev/null
+++ b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.examples;
+
+public class NestHost {
+  /*private*/ int f;
+
+  public static class NestMember extends NestHost {}
+}
diff --git a/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
new file mode 100644
index 0000000..02fd5e7
--- /dev/null
+++ b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.examples;
+
+public class NonNestMember extends NestHost.NestMember {}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 6154959..f5c75aa 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1609,11 +1609,6 @@
     return builder;
   }
 
-  protected void runArtTest(CompilerUnderTest compilerUnderTest) throws Throwable {
-    // Use the default dex VM specified.
-    runArtTest(ToolHelper.getDexVm(), compilerUnderTest);
-  }
-
   private static class CompilationOptions implements Consumer<InternalOptions> {
 
     private final boolean disableInlining;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index bb8cb6e..b76e08f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -337,6 +337,11 @@
     return ClassFileTransformer.create(clazz);
   }
 
+  public static ClassFileTransformer transformer(Path path, ClassReference classReference)
+      throws IOException {
+    return ClassFileTransformer.create(Files.readAllBytes(path), classReference);
+  }
+
   public static ClassFileTransformer transformer(byte[] bytes, ClassReference classReference) {
     return ClassFileTransformer.create(bytes, classReference);
   }
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 06fd76e..20ea852 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -29,7 +29,9 @@
 
   public boolean canUseDefaultAndStaticInterfaceMethods() {
     assert isCfRuntime() || isDexRuntime();
-    return isCfRuntime() || getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+    return isCfRuntime()
+        || getApiLevel()
+            .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport());
   }
 
   // Convenience predicates.
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
new file mode 100644
index 0000000..3d13626
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.readsInstanceField;
+import static com.android.tools.r8.utils.codeinspector.Matchers.writesInstanceField;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.horizontalclassmerging.ClassMerger;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+
+public class ConstructorMergingTrivialOverlapTest extends HorizontalClassMergingTestBase {
+
+  public ConstructorMergingTrivialOverlapTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("7", "42", "13", "print a", "print b")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                ClassSubject aClassSubject = codeInspector.clazz(A.class);
+                assertThat(aClassSubject, isPresent());
+                FieldSubject classIdFieldSubject =
+                    aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
+                assertThat(classIdFieldSubject, isPresent());
+
+                MethodSubject firstInitSubject = aClassSubject.init("int");
+                assertThat(firstInitSubject, isPresent());
+                assertThat(
+                    firstInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+
+                ClassSubject synthesizedClass = getSynthesizedArgumentClassSubject(codeInspector);
+
+                MethodSubject otherInitSubject =
+                    aClassSubject.init("int", synthesizedClass.getFinalName());
+                assertThat(otherInitSubject, isPresent());
+                assertThat(
+                    otherInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+
+                MethodSubject printSubject = aClassSubject.method("void", "print");
+                assertThat(printSubject, isPresent());
+                assertThat(
+                    printSubject, readsInstanceField(classIdFieldSubject.getFieldReference()));
+
+                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+
+                // TODO(b/165517236): Explicitly check classes have been merged.
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println(7);
+    }
+
+    @NeverInline
+    public void print() {
+      System.out.println("print a");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      this(42);
+    }
+
+    public B(int x) {
+      System.out.println(x);
+    }
+
+    @NeverInline
+    public void print() {
+      System.out.println("print b");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      b = new B(13);
+      a.print();
+      b.print();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
similarity index 63%
rename from src/test/java/com/android/tools/r8/classmerging/horizontal/NoAnnotationsTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
index b7fc7a1..d17fa75 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
@@ -11,14 +11,16 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import org.junit.Test;
 
-public class NoAnnotationsTest extends HorizontalClassMergingTestBase {
-  public NoAnnotationsTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+public class NoClassesOrMembersWithAnnotationsTest extends HorizontalClassMergingTestBase {
+  public NoClassesOrMembersWithAnnotationsTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
     super(parameters, enableHorizontalClassMerging);
   }
 
@@ -27,7 +29,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addKeepClassRules(TypeAnnotation.class, MethodAnnotation.class)
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .addOptionsModification(
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
@@ -35,9 +36,12 @@
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("a", "b", "c", "foo")
+        .assertSuccessWithOutputLines(
+            "a", "b", "c", "foo", "null", "annotation 2", "annotation 1", "annotation 2")
         .inspect(
             codeInspector -> {
+              assertThat(codeInspector.clazz(TypeAnnotation.class), isPresent());
+              assertThat(codeInspector.clazz(MethodAnnotation.class), isPresent());
               assertThat(codeInspector.clazz(A.class), isPresent());
               assertThat(codeInspector.clazz(B.class), isPresent());
               assertThat(codeInspector.clazz(C.class), isPresent());
@@ -81,11 +85,37 @@
   }
 
   static class Main {
-    public static void main(String[] args) {
+    @NeverInline
+    public static void foo(TypeAnnotation annotation) {
+      System.out.println(annotation);
+    }
+
+    @NeverInline
+    public static void foo2(MethodAnnotation annotation) {
+      System.out.println(annotation.toString().replaceFirst(".*@.*", "annotation 2"));
+    }
+
+    public static void main(String[] args) throws NoSuchMethodException {
       A a = new A();
       B b = new B("b");
       C c = new C("c");
       c.foo();
+      foo(null);
+      foo2(
+          new MethodAnnotation() {
+            @Override
+            public Class<? extends Annotation> annotationType() {
+              return null;
+            }
+          });
+      System.out.println(
+          b.getClass().getAnnotations()[0].toString().replaceFirst(".*", "annotation 1"));
+      System.out.println(
+          c.getClass()
+              .getMethods()[0]
+              .getAnnotations()[0]
+              .toString()
+              .replaceFirst(".*", "annotation 2"));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoInterfacesTest.java
new file mode 100644
index 0000000..f286bfd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoInterfacesTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class NoInterfacesTest extends HorizontalClassMergingTestBase {
+  public NoInterfacesTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("bar", "foo y", "foo z")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(I.class), isPresent());
+              assertThat(codeInspector.clazz(X.class), isPresent());
+              assertThat(codeInspector.clazz(Y.class), isPresent());
+              assertThat(codeInspector.clazz(Z.class), isPresent());
+            });
+  }
+
+  public interface I {
+    void foo();
+  }
+
+  @NeverClassInline
+  public static class X {
+    @NeverInline
+    public static void bar() {
+      System.out.println("bar");
+    }
+  }
+
+  @NeverClassInline
+  public static class Y implements I {
+    @NeverInline
+    @Override
+    public void foo() {
+      System.out.println("foo y");
+    }
+  }
+
+  @NeverClassInline
+  public static class Z implements I {
+    @NeverInline
+    @Override
+    public void foo() {
+      System.out.println("foo z");
+    }
+  }
+
+  public static class Main {
+    @NeverInline
+    public static void foo(I i) {
+      i.foo();
+    }
+
+    public static void main(String[] args) {
+      X x = new X();
+      x.bar();
+      Y y = new Y();
+      Z z = new Z();
+      foo(y);
+      foo(z);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
new file mode 100644
index 0000000..e40ff51
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.ConstructorMergingOverlapTest.Main;
+import org.junit.Test;
+
+public class VerticallyMergedClassTest extends HorizontalClassMergingTestBase {
+  public VerticallyMergedClassTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("b", "a", "c")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), not(isPresent()));
+              assertThat(codeInspector.clazz(B.class), isPresent());
+              assertThat(codeInspector.clazz(C.class), isPresent());
+            });
+  }
+
+  public static class A {
+    public void print() {
+      System.out.println("a");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  @NeverClassInline
+  public static class B extends A {
+    public B() {
+      System.out.println("b");
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+    public C() {
+      System.out.println("c");
+    }
+  }
+
+  public static class Main {
+    @NeverInline
+    static void printA(A a) {
+      a.print();
+    }
+
+    public static void main(String[] args) {
+      printA(new B());
+      new C();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
new file mode 100644
index 0000000..9a7802f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarInnerClassesInInterfaces extends TestBase {
+
+  private List<String> EXPECTED_RESULT_WITHOUT_DESUGARING =
+      ImmutableList.of(
+          WithAnonymousInner.class.getName(), "true", WithLocalInner.class.getName(), "true");
+
+  private List<String> EXPECTED_RESULT_WITH_DESUGARING =
+      ImmutableList.of(
+          WithAnonymousInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
+          "true",
+          WithLocalInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
+          "true");
+
+  private List<String> EXPECTED_RESULT_WITH_DESUGARING_B168697955 =
+      ImmutableList.of(
+          WithAnonymousInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
+          "false",
+          WithLocalInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
+          "false");
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private final TestParameters parameters;
+
+  public DesugarInnerClassesInInterfaces(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testDesugar() throws Exception {
+    testForDesugaring(parameters)
+        .addInnerClasses(DesugarInnerClassesInInterfaces.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            DesugarTestConfiguration::isNotDesugared,
+            r -> r.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING))
+        .applyIf(
+            DesugarTestConfiguration::isDesugared,
+            r ->
+                r.assertSuccessWithOutputLines(
+                    parameters
+                            .getApiLevel()
+                            .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport())
+                        ? EXPECTED_RESULT_WITHOUT_DESUGARING
+                        : EXPECTED_RESULT_WITH_DESUGARING));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(DesugarInnerClassesInInterfaces.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules("-keep class * { *; }")
+            .addKeepAttributes("InnerClasses", "EnclosingMethod")
+            .compile()
+            .run(parameters.getRuntime(), TestClass.class);
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING);
+    } else {
+      // The static method which is moved to the companion class is inlined and causing
+      // this different output. The rule "-keep class * { *; }" does not keep the static
+      // method from being inlined after it has moved. Turning off inlining produces the
+      // expected result. The inlining cause the getEnclosingClass() to return null.
+      // See b/168697955.
+      result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING_B168697955);
+    }
+  }
+
+  @Test
+  public void testR8_B168697955() throws Exception {
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(DesugarInnerClassesInInterfaces.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules("-keep class * { *; }")
+            .addKeepAttributes("InnerClasses", "EnclosingMethod")
+            // With inlining turned off we get the expected result.
+            .addOptionsModification(options -> options.enableInlining = false)
+            .compile()
+            .run(parameters.getRuntime(), TestClass.class);
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING);
+    } else {
+      result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING);
+    }
+  }
+
+  interface WithAnonymousInner {
+    static Callable<Class<?>> staticOuter() {
+      return new Callable<Class<?>>() {
+        @Override
+        public Class<?> call() throws Exception {
+          return getClass().getEnclosingClass();
+        }
+      };
+    }
+
+    default Callable<Class<?>> defaultOuter() {
+      return new Callable<Class<?>>() {
+        @Override
+        public Class<?> call() throws Exception {
+          return getClass().getEnclosingClass();
+        }
+      };
+    }
+  }
+
+  interface WithLocalInner {
+    static Callable<Class<?>> staticOuter() {
+      class Local implements Callable<Class<?>> {
+        @Override
+        public Class<?> call() throws Exception {
+          return getClass().getEnclosingClass();
+        }
+      }
+      return new Local();
+    }
+
+    default Callable<Class<?>> defaultOuter() {
+      class Local implements Callable<Class<?>> {
+        @Override
+        public Class<?> call() throws Exception {
+          return getClass().getEnclosingClass();
+        }
+      }
+      return new Local();
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      System.out.println(new WithAnonymousInner() {}.defaultOuter().call().getName());
+      System.out.println(
+          new WithAnonymousInner() {}.defaultOuter()
+              .call()
+              .equals(WithAnonymousInner.staticOuter().call()));
+      System.out.println(new WithLocalInner() {}.defaultOuter().call().getName());
+      System.out.println(
+          new WithLocalInner() {}.defaultOuter()
+              .call()
+              .equals(WithLocalInner.staticOuter().call()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java b/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java
index acf084b..dfed133 100644
--- a/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java
@@ -40,8 +40,7 @@
   static class Main {
     public static void main(String[] args) {
       OuterImpl.register(args);
-      InnerClass inner = new InnerClass();
-      inner.foobar();
+      new InnerClass().foobar();
       System.out.println("42");
     }
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
index ed64145..3920cc8 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
@@ -5,9 +5,7 @@
 package com.android.tools.r8.resolution.packageprivate;
 
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -29,10 +27,7 @@
 import com.android.tools.r8.resolution.packageprivate.a.NonAbstractExtendingA;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassTransformer;
-import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -74,14 +69,9 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+    assertTrue(resolutionResult.isAccessibleFrom(context, appView).isFalse());
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
-    assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets = new HashSet<>();
-    lookupResult.forEach(
-        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
-    // TODO(b/148591377): The set should be empty.
-    ImmutableSet<String> expected = ImmutableSet.of(Abstract.class.getTypeName() + ".foo");
-    assertEquals(expected, targets);
+    assertTrue(lookupResult.isLookupResultFailure());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
index f0c3022..c688182 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -55,7 +55,7 @@
 
   static class A {
     private static String buildClassName(String className) {
-      return com.android.tools.r8.shaking.A.class.getPackage().getName() + "." + className;
+      return A.class.getPackage().getName() + "." + className;
     }
 
     public static void main(String[] args) {
@@ -179,7 +179,7 @@
         "Options with file names are not supported");
   }
 
-  class TestProvider implements ProgramResourceProvider, DataResourceProvider {
+  static class TestProvider implements ProgramResourceProvider, DataResourceProvider {
 
     @Override
     public Collection<ProgramResource> getProgramResources() throws ResourceException {
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index bee167f..6f6ad5a 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -35,6 +35,10 @@
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
 public class NonVirtualOverrideTest extends TestBase {
@@ -190,4 +194,899 @@
       assertThat(classSubject.method("void", "m4", ImmutableList.of()), not(isPresent()));
     }
   }
+
+  static class NonVirtualOverrideTestClass {
+
+    public static void main(String[] args) {
+      A a = new B();
+      a.m1();
+      a.m2();
+      a.m3();
+      a.m4();
+
+      a = new C();
+      a.m1();
+      a.m2();
+      a.m3();
+      a.m4();
+
+      B b = new B();
+      try {
+        b.m1();
+      } catch (IllegalAccessError exception) {
+        System.out.println("Caught IllegalAccessError when calling B.m1()");
+      }
+      try {
+        b.m3();
+      } catch (IncompatibleClassChangeError exception) {
+        System.out.println("Caught IncompatibleClassChangeError when calling B.m3()");
+      }
+
+      try {
+        b = new C();
+        b.m1();
+      } catch (IllegalAccessError exception) {
+        System.out.println("Caught IllegalAccessError when calling B.m1()");
+      }
+      try {
+        b = new C();
+        b.m3();
+      } catch (IncompatibleClassChangeError exception) {
+        System.out.println("Caught IncompatibleClassChangeError when calling B.m3()");
+      }
+
+      C c = new C();
+      c.m1();
+      c.m3();
+    }
+  }
+
+  static class A {
+
+    public void m1() {
+      System.out.println("In A.m1()");
+    }
+
+    public void m2() {
+      System.out.println("In A.m2()");
+    }
+
+    public void m3() {
+      System.out.println("In A.m3()");
+    }
+
+    public void m4() {
+      System.out.println("In A.m4()");
+    }
+  }
+
+  static class B extends A {
+
+    // Made private in the dump below. This method is targeted and can therefore not be removed.
+    @Override
+    public void m1() {
+      System.out.println("In B.m1()");
+    }
+
+    // Made private in the dump below. Ends up being dead code because the method is never called.
+    @Override
+    public void m2() {
+      System.out.println("In B.m2()");
+    }
+
+    // Made static in the dump below. This method is targeted and can therefore not be removed.
+    @Override
+    public void m3() {
+      System.out.println("In B.m3()");
+    }
+
+    // Made static in the dump below. Ends up being dead code because the method is never called.
+    @Override
+    public void m4() {
+      System.out.println("In B.m4()");
+    }
+  }
+
+  static class C extends B {
+
+    @Override
+    public void m1() {
+      System.out.println("In C.m1()");
+    }
+
+    @Override
+    public void m3() {
+      System.out.println("In C.m3()");
+    }
+  }
+
+  /* Below are dumps from the classes above with the changes to B as described */
+
+  static class NonVirtualOverrideTestClassDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_SUPER,
+          "com/android/tools/r8/shaking/NonVirtualOverrideTest$NonVirtualOverrideTestClass",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitSource("NonVirtualOverrideTest.java", null);
+
+      {
+        methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(7, label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(RETURN);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$NonVirtualOverrideTestClass;",
+            null,
+            label0,
+            label1,
+            0);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        Label label1 = new Label();
+        Label label2 = new Label();
+        methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/IllegalAccessError");
+        Label label3 = new Label();
+        Label label4 = new Label();
+        Label label5 = new Label();
+        methodVisitor.visitTryCatchBlock(
+            label3, label4, label5, "java/lang/IncompatibleClassChangeError");
+        Label label6 = new Label();
+        Label label7 = new Label();
+        Label label8 = new Label();
+        methodVisitor.visitTryCatchBlock(label6, label7, label8, "java/lang/IllegalAccessError");
+        Label label9 = new Label();
+        Label label10 = new Label();
+        Label label11 = new Label();
+        methodVisitor.visitTryCatchBlock(
+            label9, label10, label11, "java/lang/IncompatibleClassChangeError");
+        Label label12 = new Label();
+        methodVisitor.visitLabel(label12);
+        methodVisitor.visitLineNumber(10, label12);
+        methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/NonVirtualOverrideTest$B");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$B",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 1);
+        Label label13 = new Label();
+        methodVisitor.visitLabel(label13);
+        methodVisitor.visitLineNumber(11, label13);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+            "m1",
+            "()V",
+            false);
+        Label label14 = new Label();
+        methodVisitor.visitLabel(label14);
+        methodVisitor.visitLineNumber(12, label14);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+            "m2",
+            "()V",
+            false);
+        Label label15 = new Label();
+        methodVisitor.visitLabel(label15);
+        methodVisitor.visitLineNumber(13, label15);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+            "m3",
+            "()V",
+            false);
+        Label label16 = new Label();
+        methodVisitor.visitLabel(label16);
+        methodVisitor.visitLineNumber(14, label16);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+            "m4",
+            "()V",
+            false);
+        Label label17 = new Label();
+        methodVisitor.visitLabel(label17);
+        methodVisitor.visitLineNumber(16, label17);
+        methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/NonVirtualOverrideTest$C");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$C",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 1);
+        Label label18 = new Label();
+        methodVisitor.visitLabel(label18);
+        methodVisitor.visitLineNumber(17, label18);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+            "m1",
+            "()V",
+            false);
+        Label label19 = new Label();
+        methodVisitor.visitLabel(label19);
+        methodVisitor.visitLineNumber(18, label19);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+            "m2",
+            "()V",
+            false);
+        Label label20 = new Label();
+        methodVisitor.visitLabel(label20);
+        methodVisitor.visitLineNumber(19, label20);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+            "m3",
+            "()V",
+            false);
+        Label label21 = new Label();
+        methodVisitor.visitLabel(label21);
+        methodVisitor.visitLineNumber(20, label21);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+            "m4",
+            "()V",
+            false);
+        Label label22 = new Label();
+        methodVisitor.visitLabel(label22);
+        methodVisitor.visitLineNumber(22, label22);
+        methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/NonVirtualOverrideTest$B");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$B",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 2);
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(24, label0);
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$B",
+            "m1",
+            "()V",
+            false);
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(27, label1);
+        methodVisitor.visitJumpInsn(GOTO, label3);
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLineNumber(25, label2);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            3,
+            new Object[] {
+              "[Ljava/lang/String;",
+              "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+              "com/android/tools/r8/shaking/NonVirtualOverrideTest$B"
+            },
+            1,
+            new Object[] {"java/lang/IllegalAccessError"});
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        Label label23 = new Label();
+        methodVisitor.visitLabel(label23);
+        methodVisitor.visitLineNumber(26, label23);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("Caught IllegalAccessError when calling B.m1()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitLineNumber(29, label3);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$B",
+            "m3",
+            "()V",
+            false);
+        methodVisitor.visitLabel(label4);
+        methodVisitor.visitLineNumber(32, label4);
+        methodVisitor.visitJumpInsn(GOTO, label6);
+        methodVisitor.visitLabel(label5);
+        methodVisitor.visitLineNumber(30, label5);
+        methodVisitor.visitFrame(
+            Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IncompatibleClassChangeError"});
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        Label label24 = new Label();
+        methodVisitor.visitLabel(label24);
+        methodVisitor.visitLineNumber(31, label24);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("Caught IncompatibleClassChangeError when calling B.m3()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        methodVisitor.visitLabel(label6);
+        methodVisitor.visitLineNumber(35, label6);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/NonVirtualOverrideTest$C");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$C",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 2);
+        Label label25 = new Label();
+        methodVisitor.visitLabel(label25);
+        methodVisitor.visitLineNumber(36, label25);
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$B",
+            "m1",
+            "()V",
+            false);
+        methodVisitor.visitLabel(label7);
+        methodVisitor.visitLineNumber(39, label7);
+        methodVisitor.visitJumpInsn(GOTO, label9);
+        methodVisitor.visitLabel(label8);
+        methodVisitor.visitLineNumber(37, label8);
+        methodVisitor.visitFrame(
+            Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IllegalAccessError"});
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        Label label26 = new Label();
+        methodVisitor.visitLabel(label26);
+        methodVisitor.visitLineNumber(38, label26);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("Caught IllegalAccessError when calling B.m1()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        methodVisitor.visitLabel(label9);
+        methodVisitor.visitLineNumber(41, label9);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/NonVirtualOverrideTest$C");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$C",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 2);
+        Label label27 = new Label();
+        methodVisitor.visitLabel(label27);
+        methodVisitor.visitLineNumber(42, label27);
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$B",
+            "m3",
+            "()V",
+            false);
+        methodVisitor.visitLabel(label10);
+        methodVisitor.visitLineNumber(45, label10);
+        Label label28 = new Label();
+        methodVisitor.visitJumpInsn(GOTO, label28);
+        methodVisitor.visitLabel(label11);
+        methodVisitor.visitLineNumber(43, label11);
+        methodVisitor.visitFrame(
+            Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IncompatibleClassChangeError"});
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        Label label29 = new Label();
+        methodVisitor.visitLabel(label29);
+        methodVisitor.visitLineNumber(44, label29);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("Caught IncompatibleClassChangeError when calling B.m3()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        methodVisitor.visitLabel(label28);
+        methodVisitor.visitLineNumber(47, label28);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/NonVirtualOverrideTest$C");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$C",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        Label label30 = new Label();
+        methodVisitor.visitLabel(label30);
+        methodVisitor.visitLineNumber(48, label30);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$C",
+            "m1",
+            "()V",
+            false);
+        Label label31 = new Label();
+        methodVisitor.visitLabel(label31);
+        methodVisitor.visitLineNumber(49, label31);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$C",
+            "m3",
+            "()V",
+            false);
+        Label label32 = new Label();
+        methodVisitor.visitLabel(label32);
+        methodVisitor.visitLineNumber(50, label32);
+        methodVisitor.visitInsn(RETURN);
+        Label label33 = new Label();
+        methodVisitor.visitLabel(label33);
+        methodVisitor.visitLocalVariable(
+            "exception", "Ljava/lang/IllegalAccessError;", null, label23, label3, 3);
+        methodVisitor.visitLocalVariable(
+            "exception", "Ljava/lang/IncompatibleClassChangeError;", null, label24, label6, 3);
+        methodVisitor.visitLocalVariable(
+            "exception", "Ljava/lang/IllegalAccessError;", null, label26, label9, 3);
+        methodVisitor.visitLocalVariable(
+            "exception", "Ljava/lang/IncompatibleClassChangeError;", null, label29, label28, 3);
+        methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label12, label33, 0);
+        methodVisitor.visitLocalVariable(
+            "a",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$A;",
+            null,
+            label13,
+            label33,
+            1);
+        methodVisitor.visitLocalVariable(
+            "b",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$B;",
+            null,
+            label0,
+            label33,
+            2);
+        methodVisitor.visitLocalVariable(
+            "c",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$C;",
+            null,
+            label30,
+            label33,
+            3);
+        methodVisitor.visitMaxs(2, 4);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+
+  static class ADump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_SUPER,
+          "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitSource("NonVirtualOverrideTest.java", null);
+
+      {
+        methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(53, label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(RETURN);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$A;",
+            null,
+            label0,
+            label1,
+            0);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m1", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(56, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In A.m1()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(57, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$A;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m2", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(60, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In A.m2()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(61, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$A;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m3", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(64, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In A.m3()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(65, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$A;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m4", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(68, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In A.m4()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(69, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$A;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+
+  static class BDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_SUPER,
+          "com/android/tools/r8/shaking/NonVirtualOverrideTest$B",
+          null,
+          "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+          null);
+
+      classWriter.visitSource("NonVirtualOverrideTest.java", null);
+
+      {
+        methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(72, label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$A",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitInsn(RETURN);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$B;",
+            null,
+            label0,
+            label1,
+            0);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "m1", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(76, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In B.m1()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(77, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$B;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "m2", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(81, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In B.m2()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(82, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$B;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_STATIC, "m3", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(86, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In B.m3()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(87, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$B;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_STATIC, "m4", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(91, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In B.m4()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(92, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$B;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+
+  static class CDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_SUPER,
+          "com/android/tools/r8/shaking/NonVirtualOverrideTest$C",
+          null,
+          "com/android/tools/r8/shaking/NonVirtualOverrideTest$B",
+          null);
+
+      classWriter.visitSource("NonVirtualOverrideTest.java", null);
+
+      {
+        methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(95, label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/shaking/NonVirtualOverrideTest$B",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitInsn(RETURN);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$C;",
+            null,
+            label0,
+            label1,
+            0);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m1", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(98, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In C.m1()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(99, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$C;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m3", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(102, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("In C.m3()");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(103, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/NonVirtualOverrideTest$C;",
+            null,
+            label0,
+            label2,
+            0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTestDump.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTestDump.java
deleted file mode 100644
index e8f54b3..0000000
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTestDump.java
+++ /dev/null
@@ -1,726 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.shaking;
-
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-class NonVirtualOverrideTestClass {
-
-  public static void main(String[] args) {
-    A a = new B();
-    a.m1();
-    a.m2();
-    a.m3();
-    a.m4();
-
-    a = new C();
-    a.m1();
-    a.m2();
-    a.m3();
-    a.m4();
-
-    B b = new B();
-    try {
-      b.m1();
-    } catch (IllegalAccessError exception) {
-      System.out.println("Caught IllegalAccessError when calling B.m1()");
-    }
-    try {
-      b.m3();
-    } catch (IncompatibleClassChangeError exception) {
-      System.out.println("Caught IncompatibleClassChangeError when calling B.m3()");
-    }
-
-    try {
-      b = new C();
-      b.m1();
-    } catch (IllegalAccessError exception) {
-      System.out.println("Caught IllegalAccessError when calling B.m1()");
-    }
-    try {
-      b = new C();
-      b.m3();
-    } catch (IncompatibleClassChangeError exception) {
-      System.out.println("Caught IncompatibleClassChangeError when calling B.m3()");
-    }
-
-    C c = new C();
-    c.m1();
-    c.m3();
-  }
-}
-
-class A {
-
-  public void m1() {
-    System.out.println("In A.m1()");
-  }
-
-  public void m2() {
-    System.out.println("In A.m2()");
-  }
-
-  public void m3() {
-    System.out.println("In A.m3()");
-  }
-
-  public void m4() {
-    System.out.println("In A.m4()");
-  }
-}
-
-class B extends A {
-
-  // Made private in the dump below. This method is targeted and can therefore not be removed.
-  public void m1() {
-    System.out.println("In B.m1()");
-  }
-
-  // Made private in the dump below. Ends up being dead code because the method is never called.
-  public void m2() {
-    System.out.println("In B.m2()");
-  }
-
-  // Made static in the dump below. This method is targeted and can therefore not be removed.
-  public void m3() {
-    System.out.println("In B.m3()");
-  }
-
-  // Made static in the dump below. Ends up being dead code because the method is never called.
-  public void m4() {
-    System.out.println("In B.m4()");
-  }
-}
-
-class C extends B {
-
-  public void m1() {
-    System.out.println("In C.m1()");
-  }
-
-  public void m3() {
-    System.out.println("In C.m3()");
-  }
-}
-
-/* Below are dumps from the classes above with the changes to B as described */
-
-class NonVirtualOverrideTestClassDump implements Opcodes {
-
-  public static byte[] dump() {
-
-    ClassWriter classWriter = new ClassWriter(0);
-    MethodVisitor methodVisitor;
-
-    classWriter.visit(
-        V1_8,
-        ACC_SUPER,
-        "com/android/tools/r8/shaking/NonVirtualOverrideTestClass",
-        null,
-        "java/lang/Object",
-        null);
-
-    classWriter.visitSource("NonVirtualOverrideTestDump.java", null);
-
-    {
-      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(7, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-      methodVisitor.visitInsn(RETURN);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLocalVariable(
-          "this",
-          "Lcom/android/tools/r8/shaking/NonVirtualOverrideTestClass;",
-          null,
-          label0,
-          label1,
-          0);
-      methodVisitor.visitMaxs(1, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor =
-          classWriter.visitMethod(
-              ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      Label label1 = new Label();
-      Label label2 = new Label();
-      methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/IllegalAccessError");
-      Label label3 = new Label();
-      Label label4 = new Label();
-      Label label5 = new Label();
-      methodVisitor.visitTryCatchBlock(
-          label3, label4, label5, "java/lang/IncompatibleClassChangeError");
-      Label label6 = new Label();
-      Label label7 = new Label();
-      Label label8 = new Label();
-      methodVisitor.visitTryCatchBlock(label6, label7, label8, "java/lang/IllegalAccessError");
-      Label label9 = new Label();
-      Label label10 = new Label();
-      Label label11 = new Label();
-      methodVisitor.visitTryCatchBlock(
-          label9, label10, label11, "java/lang/IncompatibleClassChangeError");
-      Label label12 = new Label();
-      methodVisitor.visitLabel(label12);
-      methodVisitor.visitLineNumber(10, label12);
-      methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/B");
-      methodVisitor.visitInsn(DUP);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "com/android/tools/r8/shaking/B", "<init>", "()V", false);
-      methodVisitor.visitVarInsn(ASTORE, 1);
-      Label label13 = new Label();
-      methodVisitor.visitLabel(label13);
-      methodVisitor.visitLineNumber(11, label13);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/A", "m1", "()V", false);
-      Label label14 = new Label();
-      methodVisitor.visitLabel(label14);
-      methodVisitor.visitLineNumber(12, label14);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/A", "m2", "()V", false);
-      Label label15 = new Label();
-      methodVisitor.visitLabel(label15);
-      methodVisitor.visitLineNumber(13, label15);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/A", "m3", "()V", false);
-      Label label16 = new Label();
-      methodVisitor.visitLabel(label16);
-      methodVisitor.visitLineNumber(14, label16);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/A", "m4", "()V", false);
-      Label label17 = new Label();
-      methodVisitor.visitLabel(label17);
-      methodVisitor.visitLineNumber(16, label17);
-      methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/C");
-      methodVisitor.visitInsn(DUP);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "com/android/tools/r8/shaking/C", "<init>", "()V", false);
-      methodVisitor.visitVarInsn(ASTORE, 1);
-      Label label18 = new Label();
-      methodVisitor.visitLabel(label18);
-      methodVisitor.visitLineNumber(17, label18);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/A", "m1", "()V", false);
-      Label label19 = new Label();
-      methodVisitor.visitLabel(label19);
-      methodVisitor.visitLineNumber(18, label19);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/A", "m2", "()V", false);
-      Label label20 = new Label();
-      methodVisitor.visitLabel(label20);
-      methodVisitor.visitLineNumber(19, label20);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/A", "m3", "()V", false);
-      Label label21 = new Label();
-      methodVisitor.visitLabel(label21);
-      methodVisitor.visitLineNumber(20, label21);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/A", "m4", "()V", false);
-      Label label22 = new Label();
-      methodVisitor.visitLabel(label22);
-      methodVisitor.visitLineNumber(22, label22);
-      methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/B");
-      methodVisitor.visitInsn(DUP);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "com/android/tools/r8/shaking/B", "<init>", "()V", false);
-      methodVisitor.visitVarInsn(ASTORE, 2);
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(24, label0);
-      methodVisitor.visitVarInsn(ALOAD, 2);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/B", "m1", "()V", false);
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(27, label1);
-      methodVisitor.visitJumpInsn(GOTO, label3);
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(25, label2);
-      methodVisitor.visitFrame(
-          Opcodes.F_FULL,
-          3,
-          new Object[] {
-            "[Ljava/lang/String;",
-            "com/android/tools/r8/shaking/A",
-            "com/android/tools/r8/shaking/B"
-          },
-          1,
-          new Object[] {"java/lang/IllegalAccessError"});
-      methodVisitor.visitVarInsn(ASTORE, 3);
-      Label label23 = new Label();
-      methodVisitor.visitLabel(label23);
-      methodVisitor.visitLineNumber(26, label23);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("Caught IllegalAccessError when calling B.m1()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      methodVisitor.visitLabel(label3);
-      methodVisitor.visitLineNumber(29, label3);
-      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      methodVisitor.visitVarInsn(ALOAD, 2);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/B", "m3", "()V", false);
-      methodVisitor.visitLabel(label4);
-      methodVisitor.visitLineNumber(32, label4);
-      methodVisitor.visitJumpInsn(GOTO, label6);
-      methodVisitor.visitLabel(label5);
-      methodVisitor.visitLineNumber(30, label5);
-      methodVisitor.visitFrame(
-          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IncompatibleClassChangeError"});
-      methodVisitor.visitVarInsn(ASTORE, 3);
-      Label label24 = new Label();
-      methodVisitor.visitLabel(label24);
-      methodVisitor.visitLineNumber(31, label24);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("Caught IncompatibleClassChangeError when calling B.m3()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      methodVisitor.visitLabel(label6);
-      methodVisitor.visitLineNumber(35, label6);
-      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/C");
-      methodVisitor.visitInsn(DUP);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "com/android/tools/r8/shaking/C", "<init>", "()V", false);
-      methodVisitor.visitVarInsn(ASTORE, 2);
-      Label label25 = new Label();
-      methodVisitor.visitLabel(label25);
-      methodVisitor.visitLineNumber(36, label25);
-      methodVisitor.visitVarInsn(ALOAD, 2);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/B", "m1", "()V", false);
-      methodVisitor.visitLabel(label7);
-      methodVisitor.visitLineNumber(39, label7);
-      methodVisitor.visitJumpInsn(GOTO, label9);
-      methodVisitor.visitLabel(label8);
-      methodVisitor.visitLineNumber(37, label8);
-      methodVisitor.visitFrame(
-          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IllegalAccessError"});
-      methodVisitor.visitVarInsn(ASTORE, 3);
-      Label label26 = new Label();
-      methodVisitor.visitLabel(label26);
-      methodVisitor.visitLineNumber(38, label26);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("Caught IllegalAccessError when calling B.m1()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      methodVisitor.visitLabel(label9);
-      methodVisitor.visitLineNumber(41, label9);
-      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/C");
-      methodVisitor.visitInsn(DUP);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "com/android/tools/r8/shaking/C", "<init>", "()V", false);
-      methodVisitor.visitVarInsn(ASTORE, 2);
-      Label label27 = new Label();
-      methodVisitor.visitLabel(label27);
-      methodVisitor.visitLineNumber(42, label27);
-      methodVisitor.visitVarInsn(ALOAD, 2);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/B", "m3", "()V", false);
-      methodVisitor.visitLabel(label10);
-      methodVisitor.visitLineNumber(45, label10);
-      Label label28 = new Label();
-      methodVisitor.visitJumpInsn(GOTO, label28);
-      methodVisitor.visitLabel(label11);
-      methodVisitor.visitLineNumber(43, label11);
-      methodVisitor.visitFrame(
-          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IncompatibleClassChangeError"});
-      methodVisitor.visitVarInsn(ASTORE, 3);
-      Label label29 = new Label();
-      methodVisitor.visitLabel(label29);
-      methodVisitor.visitLineNumber(44, label29);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("Caught IncompatibleClassChangeError when calling B.m3()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      methodVisitor.visitLabel(label28);
-      methodVisitor.visitLineNumber(47, label28);
-      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/shaking/C");
-      methodVisitor.visitInsn(DUP);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "com/android/tools/r8/shaking/C", "<init>", "()V", false);
-      methodVisitor.visitVarInsn(ASTORE, 3);
-      Label label30 = new Label();
-      methodVisitor.visitLabel(label30);
-      methodVisitor.visitLineNumber(48, label30);
-      methodVisitor.visitVarInsn(ALOAD, 3);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/C", "m1", "()V", false);
-      Label label31 = new Label();
-      methodVisitor.visitLabel(label31);
-      methodVisitor.visitLineNumber(49, label31);
-      methodVisitor.visitVarInsn(ALOAD, 3);
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "com/android/tools/r8/shaking/C", "m3", "()V", false);
-      Label label32 = new Label();
-      methodVisitor.visitLabel(label32);
-      methodVisitor.visitLineNumber(50, label32);
-      methodVisitor.visitInsn(RETURN);
-      Label label33 = new Label();
-      methodVisitor.visitLabel(label33);
-      methodVisitor.visitLocalVariable(
-          "exception", "Ljava/lang/IllegalAccessError;", null, label23, label3, 3);
-      methodVisitor.visitLocalVariable(
-          "exception", "Ljava/lang/IncompatibleClassChangeError;", null, label24, label6, 3);
-      methodVisitor.visitLocalVariable(
-          "exception", "Ljava/lang/IllegalAccessError;", null, label26, label9, 3);
-      methodVisitor.visitLocalVariable(
-          "exception", "Ljava/lang/IncompatibleClassChangeError;", null, label29, label28, 3);
-      methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label12, label33, 0);
-      methodVisitor.visitLocalVariable(
-          "a", "Lcom/android/tools/r8/shaking/A;", null, label13, label33, 1);
-      methodVisitor.visitLocalVariable(
-          "b", "Lcom/android/tools/r8/shaking/B;", null, label0, label33, 2);
-      methodVisitor.visitLocalVariable(
-          "c", "Lcom/android/tools/r8/shaking/C;", null, label30, label33, 3);
-      methodVisitor.visitMaxs(2, 4);
-      methodVisitor.visitEnd();
-    }
-    classWriter.visitEnd();
-
-    return classWriter.toByteArray();
-  }
-}
-
-class ADump implements Opcodes {
-
-  public static byte[] dump() {
-
-    ClassWriter classWriter = new ClassWriter(0);
-    MethodVisitor methodVisitor;
-
-    classWriter.visit(
-        V1_8, ACC_SUPER, "com/android/tools/r8/shaking/A", null, "java/lang/Object", null);
-
-    classWriter.visitSource("NonVirtualOverrideTestDump.java", null);
-
-    {
-      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(53, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-      methodVisitor.visitInsn(RETURN);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/A;", null, label0, label1, 0);
-      methodVisitor.visitMaxs(1, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m1", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(56, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In A.m1()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(57, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/A;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m2", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(60, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In A.m2()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(61, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/A;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m3", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(64, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In A.m3()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(65, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/A;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m4", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(68, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In A.m4()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(69, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/A;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    classWriter.visitEnd();
-
-    return classWriter.toByteArray();
-  }
-}
-
-class BDump implements Opcodes {
-
-  public static byte[] dump() {
-
-    ClassWriter classWriter = new ClassWriter(0);
-    MethodVisitor methodVisitor;
-
-    classWriter.visit(
-        V1_8,
-        ACC_SUPER,
-        "com/android/tools/r8/shaking/B",
-        null,
-        "com/android/tools/r8/shaking/A",
-        null);
-
-    classWriter.visitSource("NonVirtualOverrideTestDump.java", null);
-
-    {
-      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(72, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "com/android/tools/r8/shaking/A", "<init>", "()V", false);
-      methodVisitor.visitInsn(RETURN);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/B;", null, label0, label1, 0);
-      methodVisitor.visitMaxs(1, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "m1", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(76, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In B.m1()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(77, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/B;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "m2", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(81, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In B.m2()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(82, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/B;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_STATIC, "m3", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(86, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In B.m3()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(87, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/B;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_STATIC, "m4", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(91, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In B.m4()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(92, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/B;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    classWriter.visitEnd();
-
-    return classWriter.toByteArray();
-  }
-}
-
-class CDump implements Opcodes {
-
-  public static byte[] dump() {
-
-    ClassWriter classWriter = new ClassWriter(0);
-    MethodVisitor methodVisitor;
-
-    classWriter.visit(
-        V1_8,
-        ACC_SUPER,
-        "com/android/tools/r8/shaking/C",
-        null,
-        "com/android/tools/r8/shaking/B",
-        null);
-
-    classWriter.visitSource("NonVirtualOverrideTestDump.java", null);
-
-    {
-      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(95, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "com/android/tools/r8/shaking/B", "<init>", "()V", false);
-      methodVisitor.visitInsn(RETURN);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/C;", null, label0, label1, 0);
-      methodVisitor.visitMaxs(1, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m1", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(98, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In C.m1()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(99, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/C;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m3", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(102, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn("In C.m3()");
-      methodVisitor.visitMethodInsn(
-          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(103, label1);
-      methodVisitor.visitInsn(RETURN);
-      Label label2 = new Label();
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLocalVariable(
-          "this", "Lcom/android/tools/r8/shaking/C;", null, label0, label2, 0);
-      methodVisitor.visitMaxs(2, 1);
-      methodVisitor.visitEnd();
-    }
-    classWriter.visitEnd();
-
-    return classWriter.toByteArray();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
new file mode 100644
index 0000000..29a7280
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.b169045091.testclasses.HelloGreeter;
+import com.android.tools.r8.shaking.b169045091.testclasses.WorldGreeterBase;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B169045091 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B169045091(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getWorldGreeterClassFileData())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getWorldGreeterClassFileData())
+        .addKeepMainRule(TestClass.class)
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private List<Class<?>> getProgramClasses() {
+    return ImmutableList.of(
+        TestClass.class, HelloGreeter.class, HelloGreeterBase.class, WorldGreeterBase.class);
+  }
+
+  private byte[] getWorldGreeterClassFileData() throws Exception {
+    return transformer(WorldGreeter.class)
+        .removeMethods(
+            (int access, String name, String descriptor, String signature, String[] exceptions) ->
+                name.equals("world"))
+        .transform();
+  }
+
+  @Test
+  public void testAccessibility() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(getProgramClasses())
+                .addClassProgramData(getWorldGreeterClassFileData())
+                .build(),
+            TestClass.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+
+    DexProgramClass context =
+        appView
+            .contextIndependentDefinitionFor(buildType(TestClass.class, dexItemFactory))
+            .asProgramClass();
+
+    // Test that HelloGreeter.greet() is accessible to TestClass.
+    DexMethod helloReference = buildNullaryVoidMethod(HelloGreeter.class, "hello", dexItemFactory);
+    assertTrue(
+        appInfo.resolveMethodOnClass(helloReference).isAccessibleFrom(context, appView).isTrue());
+
+    // Test that WorldGreeter.greet() is inaccessible to TestClass.
+    DexMethod worldReference = buildNullaryVoidMethod(WorldGreeter.class, "world", dexItemFactory);
+    assertTrue(
+        appInfo.resolveMethodOnClass(worldReference).isAccessibleFrom(context, appView).isFalse());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      // HelloGreeterBase.greet() is accessible to TestClass because they are in the same package.
+      new HelloGreeter().hello();
+
+      try {
+        // WorldGreeterBase.world() is inaccessible to TestClass.
+        new WorldGreeter().world();
+        throw new RuntimeException();
+      } catch (IllegalAccessError e) {
+        System.out.println(" world!");
+      }
+    }
+  }
+
+  @NoVerticalClassMerging
+  public static class HelloGreeterBase {
+    @NeverInline
+    protected void hello() {
+      System.out.print("Hello");
+    }
+  }
+
+  @NeverClassInline
+  public static class WorldGreeter extends WorldGreeterBase {
+
+    // Removed by a transformer.
+    @Override
+    public void world() {
+      super.world();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java b/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java
new file mode 100644
index 0000000..62923a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091;
+
+import static com.android.tools.r8.references.Reference.INT;
+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.ToolHelper;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.b169045091.B169045091.TestClass;
+import com.android.tools.r8.shaking.b169045091.examples.NestHost;
+import com.android.tools.r8.shaking.b169045091.examples.NestHost.NestMember;
+import com.android.tools.r8.shaking.b169045091.examples.NonNestMember;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+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 NestMemberAccessibilityTest extends TestBase {
+
+  private final Path TEST_DIRECTORY =
+      Paths.get(ToolHelper.EXAMPLES_JAVA11_BUILD_DIR)
+          .resolve(
+              DescriptorUtils.getBinaryNameFromJavaType(NestHost.class.getPackage().getName()));
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withNoneRuntime().build();
+  }
+
+  public NestMemberAccessibilityTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testAccessibility() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses()
+                .addProgramFiles(getProgramFiles())
+                .addClassProgramData(getNestHostClassFileData())
+                .build(),
+            TestClass.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+
+    DexProgramClass hostContext =
+        appView
+            .contextIndependentDefinitionFor(buildType(NestHost.class, dexItemFactory))
+            .asProgramClass();
+
+    DexProgramClass memberContext =
+        appView
+            .contextIndependentDefinitionFor(buildType(NestMember.class, dexItemFactory))
+            .asProgramClass();
+
+    DexProgramClass nonMemberContext =
+        appView
+            .contextIndependentDefinitionFor(buildType(NonNestMember.class, dexItemFactory))
+            .asProgramClass();
+
+    // Test that NestHost.f is accessible to NestHost and NestMember but not NonNestMember.
+    DexField hostFieldReference =
+        buildField(
+            Reference.field(Reference.classFromClass(NestHost.class), "f", INT), dexItemFactory);
+    assertTrue(
+        appInfo.resolveField(hostFieldReference).isAccessibleFrom(hostContext, appView).isTrue());
+    assertTrue(
+        appInfo.resolveField(hostFieldReference).isAccessibleFrom(memberContext, appView).isTrue());
+    assertTrue(
+        appInfo
+            .resolveField(hostFieldReference)
+            .isAccessibleFrom(nonMemberContext, appView)
+            .isFalse());
+
+    // Test that NestMember.f is accessible to NestMember but not NonNestMember.
+    DexField memberFieldReference =
+        buildField(
+            Reference.field(Reference.classFromClass(NestMember.class), "f", INT), dexItemFactory);
+    assertTrue(
+        appInfo
+            .resolveField(memberFieldReference)
+            .isAccessibleFrom(memberContext, appView)
+            .isTrue());
+    assertTrue(
+        appInfo
+            .resolveField(memberFieldReference)
+            .isAccessibleFrom(nonMemberContext, appView)
+            .isFalse());
+
+    // Test that NonNestMember.f is inaccessible to NonNestMember.
+    DexField nonMemberFieldReference =
+        buildField(
+            Reference.field(Reference.classFromClass(NonNestMember.class), "f", INT),
+            dexItemFactory);
+    assertTrue(
+        appInfo
+            .resolveField(nonMemberFieldReference)
+            .isAccessibleFrom(nonMemberContext, appView)
+            .isFalse());
+  }
+
+  private List<Path> getProgramFiles() {
+    return ImmutableList.of(
+        TEST_DIRECTORY.resolve("NestHost$NestMember.class"),
+        TEST_DIRECTORY.resolve("NonNestMember.class"));
+  }
+
+  private byte[] getNestHostClassFileData() throws Exception {
+    return transformer(
+            TEST_DIRECTORY.resolve("NestHost.class"), Reference.classFromClass(NestHost.class))
+        .setPrivate(NestHost.class.getDeclaredField("f"))
+        .transform();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NestHost.java b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
new file mode 100644
index 0000000..872e37b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.examples;
+
+/**
+ * Mirror of src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
+ */
+public class NestHost {
+
+  public int f;
+
+  public static class NestMember {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
new file mode 100644
index 0000000..26de699
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.examples;
+
+/**
+ * Mirror of
+ * src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java.
+ */
+public class NonNestMember {}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/HelloGreeter.java b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/HelloGreeter.java
new file mode 100644
index 0000000..edc59a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/HelloGreeter.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.shaking.b169045091.B169045091.HelloGreeterBase;
+
+@NeverClassInline
+public class HelloGreeter extends HelloGreeterBase {}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/WorldGreeterBase.java b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/WorldGreeterBase.java
new file mode 100644
index 0000000..9dc0b62
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/WorldGreeterBase.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b169045091.testclasses;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+
+@NoVerticalClassMerging
+public class WorldGreeterBase {
+  @NeverInline
+  protected void world() {
+    System.out.println(" world!");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
new file mode 100644
index 0000000..489d8b2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -0,0 +1,355 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsChecker;
+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.origin.Origin;
+import com.android.tools.r8.tracereferences.TraceReferencesCommandParser.OutputFormat;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import kotlin.text.Charsets;
+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 TraceReferencesCommandTest extends TestBase {
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public TraceReferencesCommandTest(TestParameters parameters) {}
+
+  @Test
+  public void emptyBuilder() throws Throwable {
+    verifyEmptyCommand(TraceReferencesCommand.builder().build());
+  }
+
+  private void verifyEmptyCommand(TraceReferencesCommand command) {
+    assertEquals(0, command.getLibrary().size());
+    assertEquals(0, command.getTarget().size());
+    assertEquals(0, command.getSource().size());
+    assertEquals(TraceReferencesCommandParser.OutputFormat.PRINTUSAGE, command.getOutputFormat());
+    assertNull(command.getOutput());
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void emptyRun() throws Throwable {
+    DiagnosticsChecker.checkErrorsContains(
+        "No library specified",
+        handler -> {
+          TraceReferences.run(TraceReferencesCommand.builder(handler).build());
+        });
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void emptyRunCommandLine() throws Throwable {
+    DiagnosticsChecker.checkErrorsContains(
+        "No library specified",
+        handler -> {
+          TraceReferences.run(
+              TraceReferencesCommand.parse(new String[] {""}, Origin.unknown(), handler).build());
+        });
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void onlyLibrarySpecified() throws Throwable {
+    DiagnosticsChecker.checkErrorsContains(
+        "No target specified",
+        handler -> {
+          TraceReferences.run(
+              TraceReferencesCommand.builder(handler)
+                  .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+                  .build());
+        });
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void onlyLibrarySpecifiedCommandLine() throws Throwable {
+    DiagnosticsChecker.checkErrorsContains(
+        "No target specified",
+        handler -> {
+          TraceReferences.run(
+              TraceReferencesCommand.parse(
+                      new String[] {
+                        "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString()
+                      },
+                      Origin.unknown(),
+                      handler)
+                  .build());
+        });
+  }
+
+  private String formatName(OutputFormat format) {
+    if (format == TraceReferencesCommandParser.OutputFormat.PRINTUSAGE) {
+      return "printuses";
+    }
+    if (format == TraceReferencesCommandParser.OutputFormat.KEEP_RULES) {
+      return "keep";
+    }
+    assertSame(format, TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION);
+    return "keepallowobfuscation";
+  }
+
+  public void runAndCheckOutput(
+      Path targetJar, Path sourceJar, OutputFormat format, String expected) throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path output = dir.resolve("output.txt");
+    TraceReferences.run(
+        TraceReferencesCommand.builder()
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .addTargetFiles(targetJar)
+            .addSourceFiles(sourceJar)
+            .setOutputPath(output)
+            .setOutputFormat(format)
+            .build());
+    assertEquals(expected, FileUtils.readTextFile(output, Charsets.UTF_8));
+
+    TraceReferences.run(
+        TraceReferencesCommand.parse(
+                new String[] {
+                  "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+                  "--target", targetJar.toString(),
+                  "--source", sourceJar.toString(),
+                  "--output", output.toString(),
+                  "--format", formatName(format)
+                },
+                Origin.unknown())
+            .build());
+    assertEquals(expected, FileUtils.readTextFile(output, Charsets.UTF_8));
+  }
+
+  public void runAndCheckOutput(
+      List<Class<?>> targetClasses,
+      List<Class<?>> sourceClasses,
+      OutputFormat format,
+      String expected)
+      throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar = dir.resolve("target.jar");
+    Path sourceJar = dir.resolve("source.jar");
+    ZipUtils.zip(
+        targetJar,
+        ToolHelper.getClassPathForTests(),
+        targetClasses.stream()
+            .map(ToolHelper::getClassFileForTestClass)
+            .collect(Collectors.toList()));
+    ZipUtils.zip(
+        sourceJar,
+        ToolHelper.getClassPathForTests(),
+        sourceClasses.stream()
+            .map(ToolHelper::getClassFileForTestClass)
+            .collect(Collectors.toList()));
+    runAndCheckOutput(targetJar, sourceJar, format, expected);
+  }
+
+  @Test
+  public void test_printUses() throws Throwable {
+    runAndCheckOutput(
+        ImmutableList.of(Target.class),
+        ImmutableList.of(Source.class),
+        TraceReferencesCommandParser.OutputFormat.PRINTUSAGE,
+        StringUtils.lines(
+            ImmutableList.of(
+                "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target",
+                "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: void"
+                    + " target(int)",
+                "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: int"
+                    + " field")));
+  }
+
+  @Test
+  public void test_keepRules() throws Throwable {
+    runAndCheckOutput(
+        ImmutableList.of(Target.class),
+        ImmutableList.of(Source.class),
+        TraceReferencesCommandParser.OutputFormat.KEEP_RULES,
+        StringUtils.lines(
+            ImmutableList.of(
+                "-keep class com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+                    + " {",
+                "  public static void target(int);",
+                "  int field;",
+                "}",
+                "-keeppackagenames com.android.tools.r8.tracereferences")));
+  }
+
+  @Test
+  public void test_keepRulesAllowObfuscation() throws Throwable {
+    runAndCheckOutput(
+        ImmutableList.of(Target.class),
+        ImmutableList.of(Source.class),
+        TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
+        StringUtils.lines(
+            ImmutableList.of(
+                "-keep,allowobfuscation class"
+                    + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target {",
+                "  public static void target(int);",
+                "  int field;",
+                "}",
+                "-keeppackagenames com.android.tools.r8.tracereferences")));
+  }
+
+  @Test
+  public void testNoReferences_printUses() throws Throwable {
+    runAndCheckOutput(
+        ImmutableList.of(OtherTarget.class),
+        ImmutableList.of(Source.class),
+        TraceReferencesCommandParser.OutputFormat.PRINTUSAGE,
+        StringUtils.lines(ImmutableList.of()));
+  }
+
+  @Test
+  public void testMissingReference_keepRules() throws Throwable {
+    runAndCheckOutput(
+        ImmutableList.of(OtherTarget.class),
+        ImmutableList.of(Source.class),
+        TraceReferencesCommandParser.OutputFormat.KEEP_RULES,
+        StringUtils.lines(ImmutableList.of()));
+  }
+
+  @Test
+  public void testNoReferences_keepRulesAllowObfuscation() throws Throwable {
+    runAndCheckOutput(
+        ImmutableList.of(OtherTarget.class),
+        ImmutableList.of(Source.class),
+        TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
+        StringUtils.lines(ImmutableList.of()));
+  }
+
+  public static void zip(Path zipFile, String path, byte[] data) throws IOException {
+    try (ZipOutputStream stream =
+        new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile)))) {
+      ZipEntry zipEntry = new ZipEntry(path);
+      stream.putNextEntry(zipEntry);
+      stream.write(data);
+      stream.closeEntry();
+    }
+  }
+
+  @Test
+  public void testMissingDefinition_printUses() throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar = dir.resolve("target.jar");
+    Path sourceJar = dir.resolve("source.jar");
+    zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
+    ZipUtils.zip(
+        sourceJar,
+        ToolHelper.getClassPathForTests(),
+        ToolHelper.getClassFileForTestClass(Source.class));
+    runAndCheckOutput(
+        targetJar,
+        sourceJar,
+        TraceReferencesCommandParser.OutputFormat.PRINTUSAGE,
+        StringUtils.lines(
+            ImmutableList.of(
+                "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target",
+                "# Error: Could not find definition for method void"
+                    + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+                    + ".target(int)",
+                "# Error: Could not find definition for field int"
+                    + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+                    + ".field")));
+  }
+
+  @Test
+  public void testMissingDefinition_keepRules() throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar = dir.resolve("target.jar");
+    Path sourceJar = dir.resolve("source.jar");
+    zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
+    ZipUtils.zip(
+        sourceJar,
+        ToolHelper.getClassPathForTests(),
+        ToolHelper.getClassFileForTestClass(Source.class));
+    runAndCheckOutput(
+        targetJar,
+        sourceJar,
+        TraceReferencesCommandParser.OutputFormat.KEEP_RULES,
+        StringUtils.lines(
+            ImmutableList.of(
+                "-keep class com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+                    + " {",
+                "# Error: Could not find definition for method void"
+                    + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+                    + ".target(int)",
+                "# Error: Could not find definition for field int"
+                    + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
+                    + ".field",
+                "}",
+                "-keeppackagenames com.android.tools.r8.tracereferences")));
+  }
+
+  @Test
+  public void testMissingDefinition_keepRulesAllowObfuscation() throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar = dir.resolve("target.jar");
+    Path sourceJar = dir.resolve("source.jar");
+    zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
+    ZipUtils.zip(
+        sourceJar,
+        ToolHelper.getClassPathForTests(),
+        ToolHelper.getClassFileForTestClass(Source.class));
+    runAndCheckOutput(
+        targetJar,
+        sourceJar,
+        TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
+        StringUtils.lines(
+            ImmutableList.of(
+                "-keep,allowobfuscation class"
+                    + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target {",
+                "# Error: Could not find definition for method void"
+                    + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target.target(int)",
+                "# Error: Could not find definition for field int"
+                    + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target.field",
+                "}",
+                "-keeppackagenames com.android.tools.r8.tracereferences")));
+  }
+
+  private byte[] getClassWithTargetRemoved() throws IOException {
+    return transformer(Target.class)
+        .removeMethods((access, name, descriptor, signature, exceptions) -> name.equals("target"))
+        .removeFields((access, name, descriptor, signature, value) -> name.equals("field"))
+        .transform();
+  }
+
+  static class Target {
+    public static int field;
+
+    public static void target(int i) {}
+  }
+
+  static class OtherTarget {
+    public static void target() {}
+  }
+
+  static class Source {
+    public static void source() {
+      Target.target(Target.field);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 84621b6..fa627ef 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -13,15 +13,18 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -364,6 +367,16 @@
     return setAccessFlags(method, MethodAccessFlags::setBridge);
   }
 
+  public ClassFileTransformer setPrivate(Field field) {
+    return setAccessFlags(
+        field,
+        accessFlags -> {
+          accessFlags.setPrivate();
+          accessFlags.unsetProtected();
+          accessFlags.unsetPublic();
+        });
+  }
+
   public ClassFileTransformer setPublic(Method method) {
     return setAccessFlags(
         method,
@@ -393,11 +406,34 @@
     return setAccessFlags(Reference.methodFromMethod(constructor), setter);
   }
 
+  public ClassFileTransformer setAccessFlags(Field field, Consumer<FieldAccessFlags> setter) {
+    return setAccessFlags(Reference.fieldFromField(field), setter);
+  }
+
   public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
     return setAccessFlags(Reference.methodFromMethod(method), setter);
   }
 
   private ClassFileTransformer setAccessFlags(
+      FieldReference fieldReference, Consumer<FieldAccessFlags> setter) {
+    return addClassTransformer(
+        new ClassTransformer() {
+
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            FieldAccessFlags accessFlags = FieldAccessFlags.fromCfAccessFlags(access);
+            if (name.equals(fieldReference.getFieldName())
+                && descriptor.equals(fieldReference.getFieldType().getDescriptor())) {
+              setter.accept(accessFlags);
+            }
+            return super.visitField(
+                accessFlags.getAsCfAccessFlags(), name, descriptor, signature, value);
+          }
+        });
+  }
+
+  private ClassFileTransformer setAccessFlags(
       MethodReference methodReference, Consumer<MethodAccessFlags> setter) {
     return addClassTransformer(
         new ClassTransformer() {
@@ -425,6 +461,11 @@
     boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
   }
 
+  @FunctionalInterface
+  public interface FieldPredicate {
+    boolean test(int access, String name, String descriptor, String signature, Object value);
+  }
+
   public ClassFileTransformer removeInnerClasses() {
     return addClassTransformer(
         new ClassTransformer() {
@@ -460,6 +501,19 @@
         });
   }
 
+  public ClassFileTransformer removeFields(FieldPredicate predicate) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            return predicate.test(access, name, descriptor, signature, value)
+                ? null
+                : super.visitField(access, name, descriptor, signature, value);
+          }
+        });
+  }
+
   /** Abstraction of the MethodVisitor.visitMethodInsn method with its sub visitor. */
   @FunctionalInterface
   public interface MethodInsnTransform {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 4f4c340..934c06f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.retrace.RetraceMethodResult;
 import com.android.tools.r8.retrace.RetraceMethodResult.Element;
+import com.android.tools.r8.retrace.RetracedMethod;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.Visibility;
 import com.google.common.collect.ImmutableList;
@@ -344,7 +345,7 @@
             .appendValue(subject.getOriginalName())
             .appendText(" was ");
         if (subject.isPresent()) {
-          AccessFlags accessFlags =
+          AccessFlags<?> accessFlags =
               subject.isMethodSubject()
                   ? subject.asMethodSubject().getMethod().accessFlags
                   : subject.asFieldSubject().getField().accessFlags;
@@ -463,7 +464,12 @@
                 returnValue.set(false);
                 return;
               }
-              sameMethod = element.getMethodReference().equals(currentInline.methodReference);
+              sameMethod =
+                  element.getMethod().isKnown()
+                      && element
+                          .getMethod()
+                          .asKnown()
+                          .equalsMethodReference(currentInline.methodReference);
               boolean samePosition =
                   element.getOriginalLineNumber(currentInline.minifiedPosition)
                       == currentInline.originalPosition;
@@ -495,7 +501,7 @@
         for (int i = 0; i < retraceElements.size(); i++) {
           Element retraceElement = retraceElements.get(i);
           StackTraceLine stackTraceLine = stackTrace.get(i);
-          MethodReference methodReference = retraceElement.getMethodReference();
+          RetracedMethod methodReference = retraceElement.getMethod();
           if (!stackTraceLine.methodName.equals(methodReference.getMethodName())
               || !stackTraceLine.className.equals(methodReference.getHolderClass().getTypeName())
               || stackTraceLine.lineNumber
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 8d6dd4b..b08651f 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -31,8 +31,8 @@
 import com.google.common.collect.ImmutableSet;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -629,9 +629,9 @@
     this.inspector = inspector;
 
     Set<GraphNode> targets = consumer.getTargets();
-    classes = new IdentityHashMap<>(targets.size());
-    methods = new IdentityHashMap<>(targets.size());
-    fields = new IdentityHashMap<>(targets.size());
+    classes = new HashMap<>(targets.size());
+    methods = new HashMap<>(targets.size());
+    fields = new HashMap<>(targets.size());
 
     for (GraphNode target : targets) {
       if (target instanceof ClassGraphNode) {
diff --git a/tools/create_art_tests.py b/tools/create_art_tests.py
index 248f893..978b086 100755
--- a/tools/create_art_tests.py
+++ b/tools/create_art_tests.py
@@ -24,24 +24,38 @@
 import static com.android.tools.r8.R8RunArtTestsTest.DexTool.$testGeneratingToolchainEnum;
 
 import com.android.tools.r8.R8RunArtTestsTest;
+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;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 /**
  * Auto-generated test for the art $name test using the $testGeneratingToolchain toolchain.
  *
  * DO NOT EDIT THIS FILE. EDIT THE HERE DOCUMENT TEMPLATE IN tools/create_art_tests.py INSTEAD!
  */
+@RunWith(Parameterized.class)
 public class $testClassName extends R8RunArtTestsTest {
 
-    public $testClassName() {
+    @Parameters(name = "{0}")
+    public static TestParametersCollection data() {
+      return TestBase.getTestParameters().withDexRuntimes().build();
+    }
+
+    private final TestParameters parameters;
+
+    public $testClassName(TestParameters parameters) {
       super("$name", $testGeneratingToolchainEnum);
+      this.parameters = parameters;
     }
 
     @Test
-    public void run$testClassName() throws Throwable {
-      // For testing with other Art VMs than the default pass the VM version as a argument to
-      // runArtTest, e.g. runArtTest(DexVm.ART_4_4_4_HOST, CompilerUnderTest.$compilerUnderTestEnum).
-      runArtTest(CompilerUnderTest.$compilerUnderTestEnum);
+    public void test() throws Throwable {
+      runArtTest(parameters.getRuntime().asDex().getVm(), CompilerUnderTest.$compilerUnderTestEnum);
     }
 }
 """)
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 7f27da8..a048c5c 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -7,14 +7,12 @@
 import datetime
 import os.path
 import re
-import shutil
 import subprocess
 import sys
 import urllib
 import xml
-import xml.etree.ElementTree as et
 import zipfile
-import archive_desugar_jdk_libs
+
 import utils
 
 R8_DEV_BRANCH = '2.2'
@@ -248,17 +246,18 @@
 
   return release_maven
 
-def git_message_dev(version):
+# ------------------------------------------------------ column 70 --v
+def git_message_dev(version, bugs):
   return """Update D8 R8 to %s
 
-This is a development snapshot, it's fine to use for studio canary build, but
-not for BETA or release, for those we would need a release version of R8
-binaries.
-This build IS suitable for preview release but IS NOT suitable for public release.
+This is a development snapshot, it's fine to use for studio canary
+build, but not for BETA or release, for those we would need a release
+version of R8 binaries. This build IS suitable for preview release
+but IS NOT suitable for public release.
 
 Built here: go/r8-releases/raw/%s
 Test: ./gradlew check
-Bug: """ % (version, version)
+Bug: %s""" % (version, version, '\nBug: '.join(bugs))
 
 
 def git_message_release(version, bugs):
@@ -267,7 +266,7 @@
 Built here: go/r8-releases/raw/%s/
 Test: ./gradlew check
 
-Bug: %s """ % (version, version, '\nBug: '.join(bugs))
+Bug: %s""" % (version, version, '\nBug: '.join(bugs))
 
 
 def prepare_studio(args):
@@ -281,7 +280,7 @@
       return 'DryRun: omitting studio release for %s' % options.version
 
     if 'dev' in options.version:
-      git_message = git_message_dev(options.version)
+      git_message = git_message_dev(options.version, options.bug)
       r8_checkout = utils.REPO_ROOT
       return release_studio_or_aosp(
         r8_checkout, args.studio, options, git_message)
diff --git a/tools/test.py b/tools/test.py
index fbc1985..27f15a8 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -163,6 +163,11 @@
   (options, args) = ParseOptions()
 
   if utils.is_bot():
+    if options.horizontal_class_merging:
+      # This flag is in preparation of running horizontal class merging
+      # but currently is the same as the default tests. Don't run to
+      # save resources on the bots.
+      return 0
     gradle.RunGradle(['--no-daemon', 'clean'])
 
   gradle_args = ['--stacktrace']
@@ -291,6 +296,9 @@
     return_code = gradle.RunGradle(gradle_args, throw_on_failure=False)
     return archive_and_return(return_code, options)
 
+  if options.horizontal_class_merging:
+    gradle_args.append('-PhorizontalClassMerging')
+
   # Now run tests on selected runtime(s).
   if options.runtimes:
     if options.dex_vm != 'default':