Merge commit 'a30e76e94a2d4da54ab602eac12224e945afe251' into dev-release
diff --git a/build.gradle b/build.gradle
index 99676ef..54f0898 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1975,6 +1975,10 @@
         include "com/android/tools/r8/internal/**"
     }
 
+    if (project.hasProperty('test_namespace')) {
+        include "com/android/tools/r8/" + project.getProperty('test_namespace') + "/**"
+    }
+
     if (project.hasProperty('tool')) {
         if (project.property('tool') == 'r8') {
             exclude "com/android/tools/r8/jctf/**"
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 586c4f1..18b86c7 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -36,6 +36,13 @@
           "                          # is the default handling of javac assertion code when",
           "                          # generating class file format.");
 
+  static final Iterable<String> THREAD_COUNT_USAGE_MESSAGE =
+      Arrays.asList(
+          "  " + THREAD_COUNT_FLAG + " <number of threads>",
+          "                          # Number of threads to use for compilation. If not specified",
+          "                          # the number will be based on heuristics taking the number",
+          "                          # of cores into account.");
+
   public static void parsePositiveIntArgument(
       Consumer<Diagnostic> errorConsumer,
       String flag,
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 93d9058..2ed9890 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -143,6 +143,7 @@
                   "  --main-dex-list-output <file>",
                   "                          # Output resulting main dex list in <file>."),
               ASSERTIONS_USAGE_MESSAGE,
+              THREAD_COUNT_USAGE_MESSAGE,
               Arrays.asList(
                   "  --version               # Print the version of d8.",
                   "  --help                  # Print this message.")));
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 17b7485..c295175 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -173,6 +174,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
+            ClassSignature.NO_CLASS_SIGNATURE,
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index 5d9392c..e816320 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -54,6 +54,7 @@
                   "                          # <file> is a desugared library configuration"
                       + " (json)."),
               ASSERTIONS_USAGE_MESSAGE,
+              THREAD_COUNT_USAGE_MESSAGE,
               Arrays.asList(
                   "  --version               # Print the version of l8.",
                   "  --help                  # Print this message.")));
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 92d9c12..9bee3f3 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,12 +71,12 @@
 import com.android.tools.r8.naming.SeedMapper;
 import com.android.tools.r8.naming.SourceFileRewriter;
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
-import com.android.tools.r8.optimize.BridgeHoisting;
 import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
 import com.android.tools.r8.optimize.VisibilityBridgeRemover;
+import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.repackaging.Repackaging;
 import com.android.tools.r8.repackaging.RepackagingLens;
@@ -703,7 +703,7 @@
                       timing)
                   .withEnumValueInfoMaps(enumValueInfoMapCollection));
           // Rerunning the enqueuer should not give rise to any method rewritings.
-          assert enqueuer.buildGraphLens(appView) == null;
+          assert enqueuer.buildGraphLens() == null;
           appView.withGeneratedMessageLiteBuilderShrinker(
               shrinker ->
                   shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
@@ -892,12 +892,14 @@
           || options.getProguardConfiguration().hasApplyMappingFile()) {
         assert appView.rootSet().verifyKeptItemsAreKept(appView);
       }
-      assert appView
-          .graphLens()
-          .verifyMappingToOriginalProgram(
-              appView,
-              new ApplicationReader(inputApp.withoutMainDexList(), options, timing)
-                  .read(executorService));
+
+      assert options.testing.disableMappingToOriginalProgramVerification
+          || appView
+              .graphLens()
+              .verifyMappingToOriginalProgram(
+                  appView,
+                  new ApplicationReader(inputApp.withoutMainDexList(), options, timing)
+                      .read(executorService));
 
       // Report synthetic rules (only for testing).
       // TODO(b/120959039): Move this to being reported through the graph consumer.
@@ -1019,7 +1021,7 @@
                 options.getProguardConfiguration().getDontWarnPatterns(),
                 executorService,
                 timing));
-    NestedGraphLens lens = enqueuer.buildGraphLens(appView);
+    NestedGraphLens lens = enqueuer.buildGraphLens();
     appView.rewriteWithLens(lens);
     if (InternalOptions.assertionsEnabled()) {
       // Register the dead proto types. These are needed to verify that no new missing types are
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 8c93579..b796705 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -99,6 +99,7 @@
                   "  --main-dex-list-output <file>  ",
                   "                          # Output the full main-dex list in <file>."),
               ASSERTIONS_USAGE_MESSAGE,
+              THREAD_COUNT_USAGE_MESSAGE,
               Arrays.asList(
                   "  --version               # Print the version of r8.",
                   "  --help                  # Print this message.")));
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index b0f836c..6073b28 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.PredicateUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -66,6 +67,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 public class ApplicationWriter {
@@ -76,6 +78,7 @@
   public final NamingLens namingLens;
   public final InternalOptions options;
   private final CodeToKeep desugaredLibraryCodeToKeep;
+  private final Predicate<DexType> isTypeMissing;
   public List<Marker> markers;
   public List<DexString> markerStrings;
 
@@ -179,6 +182,8 @@
     this.namingLens = namingLens;
     this.proguardMapSupplier = proguardMapSupplier;
     this.programConsumer = consumer;
+    this.isTypeMissing =
+        PredicateUtils.isNull(appView.appInfo()::definitionForWithoutExistenceAssert);
   }
 
   private List<VirtualFile> distribute(ExecutorService executorService)
@@ -454,12 +459,14 @@
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
       List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
-      if (enclosingMethod == null && innerClasses.isEmpty()) {
+      if (enclosingMethod == null
+          && innerClasses.isEmpty()
+          && clazz.getClassSignature().hasNoSignature()) {
         continue;
       }
 
       // EnclosingMember translates directly to an enclosing class/method if present.
-      List<DexAnnotation> annotations = new ArrayList<>(1 + innerClasses.size());
+      List<DexAnnotation> annotations = new ArrayList<>(2 + innerClasses.size());
       if (enclosingMethod != null) {
         if (enclosingMethod.getEnclosingMethod() != null) {
           annotations.add(
@@ -507,6 +514,13 @@
         }
       }
 
+      if (clazz.getClassSignature().hasSignature()) {
+        annotations.add(
+            DexAnnotation.createSignatureAnnotation(
+                clazz.getClassSignature().toRenamedString(namingLens, isTypeMissing),
+                options.itemFactory));
+      }
+
       if (!annotations.isEmpty()) {
         // Append the annotations to annotations array of the class.
         DexAnnotation[] copy =
@@ -520,6 +534,7 @@
       // Clear the attribute structures now that they are represented in annotations.
       clazz.clearEnclosingMethodAttribute();
       clazz.clearInnerClasses();
+      clazz.clearClassSignature();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 978a7e7..711eb47 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -51,6 +51,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -60,6 +62,7 @@
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.Reporter;
 import com.google.common.io.ByteStreams;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -757,7 +760,8 @@
       }
 
       AttributesAndAnnotations attrs =
-          new AttributesAndAnnotations(type, annotationsDirectory.clazz, options.itemFactory);
+          new AttributesAndAnnotations(
+              type, origin, annotationsDirectory.clazz, options.itemFactory, options.reporter);
 
       Long finalChecksum = checksum;
       ChecksumSupplier checksumSupplier =
@@ -776,6 +780,7 @@
               Collections.emptyList(),
               attrs.getEnclosingMethodAttribute(),
               attrs.getInnerClasses(),
+              attrs.classSignature,
               attrs.getAnnotations(),
               staticFields,
               instanceFields,
@@ -1327,6 +1332,7 @@
     private EnclosingMethodAttribute enclosingMethodAttribute = null;
     private List<InnerClassAttribute> innerClasses = null;
     private List<DexAnnotation> lazyAnnotations = null;
+    private ClassSignature classSignature = ClassSignature.NO_CLASS_SIGNATURE;
 
     public DexAnnotationSet getAnnotations() {
       if (lazyAnnotations != null) {
@@ -1346,8 +1352,16 @@
       return enclosingMethodAttribute;
     }
 
+    public ClassSignature getClassSignature() {
+      return classSignature;
+    }
+
     public AttributesAndAnnotations(
-        DexType type, DexAnnotationSet annotations, DexItemFactory factory) {
+        DexType type,
+        Origin origin,
+        DexAnnotationSet annotations,
+        DexItemFactory factory,
+        Reporter reporter) {
       this.originalAnnotations = annotations;
       DexType enclosingClass = null;
       DexMethod enclosingMethod = null;
@@ -1378,6 +1392,12 @@
           } else {
             memberClasses.addAll(members);
           }
+        } else if (DexAnnotation.isSignatureAnnotation(annotation, factory)) {
+          ensureAnnotations(i);
+          String signature = DexAnnotation.getSignature(annotation);
+          classSignature =
+              GenericSignature.parseClassSignature(
+                  type.getName(), signature, origin, factory, reporter);
         } else {
           copyAnnotation(annotation);
         }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 4bbcead..c169b25 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -85,11 +85,11 @@
 
   public boolean isMoreVisibleThan(
       AccessFlags other, String packageNameThis, String packageNameOther) {
-    int visibilityOrdinal = visibilityOrdinal();
-    if (visibilityOrdinal > other.visibilityOrdinal()) {
+    int visibilityOrdinal = getVisibilityOrdinal();
+    if (visibilityOrdinal > other.getVisibilityOrdinal()) {
       return true;
     }
-    if (visibilityOrdinal == other.visibilityOrdinal()
+    if (visibilityOrdinal == other.getVisibilityOrdinal()
         && isVisibilityDependingOnPackage()
         && !packageNameThis.equals(packageNameOther)) {
       return true;
@@ -98,14 +98,14 @@
   }
 
   public boolean isAtLeastAsVisibleAs(AccessFlags other) {
-    return visibilityOrdinal() >= other.visibilityOrdinal();
+    return getVisibilityOrdinal() >= other.getVisibilityOrdinal();
   }
 
   public boolean isSameVisibility(AccessFlags other) {
-    return visibilityOrdinal() == other.visibilityOrdinal();
+    return getVisibilityOrdinal() == other.getVisibilityOrdinal();
   }
 
-  private int visibilityOrdinal() {
+  public int getVisibilityOrdinal() {
     // public > protected > package > private
     if (isPublic()) {
       return 3;
@@ -121,7 +121,7 @@
   }
 
   public boolean isVisibilityDependingOnPackage() {
-    return visibilityOrdinal() == 1 || visibilityOrdinal() == 2;
+    return getVisibilityOrdinal() == 1 || getVisibilityOrdinal() == 2;
   }
 
   public boolean isPackagePrivate() {
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index ffe915a..e89f65b 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -331,11 +331,8 @@
       }
 
       private void warn(String message, DexType type, Origin origin) {
-        if (true) {
-          // TODO(b/169753370): Re-enable again after roll.
-          return;
-        }
-        if (!options.getProguardConfiguration().getDontWarnPatterns().matches(type)) {
+        if (options.getProguardConfiguration() == null
+            || !options.getProguardConfiguration().getDontWarnPatterns().matches(type)) {
           options.reporter.warning(new StringDiagnostic(message, origin));
         }
       }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 14fa13e..3929cfb 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.origin.Origin;
 import java.util.List;
 import java.util.function.Consumer;
@@ -26,6 +27,7 @@
           nestMembers,
           enclosingMember,
           innerClasses,
+          classSignature,
           annotations,
           staticFields,
           instanceFields,
@@ -45,6 +47,7 @@
             nestMembers,
             enclosingMember,
             innerClasses,
+            classSignature,
             annotations,
             staticFields,
             instanceFields,
@@ -65,6 +68,7 @@
           nestMembers,
           enclosingMember,
           innerClasses,
+          classSignature,
           annotations,
           staticFields,
           instanceFields,
@@ -84,6 +88,7 @@
             nestMembers,
             enclosingMember,
             innerClasses,
+            classSignature,
             annotations,
             staticFields,
             instanceFields,
@@ -106,6 +111,7 @@
         List<NestMemberClassAttribute> nestMembers,
         EnclosingMethodAttribute enclosingMember,
         List<InnerClassAttribute> innerClasses,
+        ClassSignature classSignature,
         DexAnnotationSet annotations,
         DexEncodedField[] staticFields,
         DexEncodedField[] instanceFields,
@@ -135,6 +141,7 @@
       List<NestMemberClassAttribute> nestMembers,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
+      ClassSignature classSignature,
       DexAnnotationSet annotations,
       DexEncodedField[] staticFields,
       DexEncodedField[] instanceFields,
@@ -154,6 +161,7 @@
         nestMembers,
         enclosingMember,
         innerClasses,
+        classSignature,
         annotations,
         staticFields,
         instanceFields,
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 dede4e0..d883e3e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -6,10 +6,12 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
@@ -24,6 +26,7 @@
 import java.util.ListIterator;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 public abstract class DexClass extends DexDefinition {
@@ -59,6 +62,9 @@
   private NestHostClassAttribute nestHost;
   private final List<NestMemberClassAttribute> nestMembers;
 
+  /** Generic signature information if the attribute is present in the input */
+  protected ClassSignature classSignature;
+
   public DexClass(
       DexString sourceFile,
       DexTypeList interfaces,
@@ -73,6 +79,7 @@
       List<NestMemberClassAttribute> nestMembers,
       EnclosingMethodAttribute enclosingMethod,
       List<InnerClassAttribute> innerClasses,
+      ClassSignature classSignature,
       DexAnnotationSet annotations,
       Origin origin,
       boolean skipNameValidationForTesting) {
@@ -92,6 +99,9 @@
     assert nestMembers != null;
     this.enclosingMethod = enclosingMethod;
     this.innerClasses = innerClasses;
+    assert classSignature != null;
+    this.classSignature = classSignature;
+    assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(classSignature, annotations);
     if (type == superType) {
       throw new CompilationError("Class " + type.toString() + " cannot extend itself");
     }
@@ -231,6 +241,15 @@
     }
   }
 
+  public TraversalContinuation traverseFields(Function<DexEncodedField, TraversalContinuation> fn) {
+    for (DexEncodedField field : fields()) {
+      if (fn.apply(field).shouldBreak()) {
+        return TraversalContinuation.BREAK;
+      }
+    }
+    return TraversalContinuation.CONTINUE;
+  }
+
   public List<DexEncodedField> staticFields() {
     assert staticFields != null;
     if (InternalOptions.assertionsEnabled()) {
@@ -749,6 +768,10 @@
     innerClasses.clear();
   }
 
+  public void clearClassSignature() {
+    classSignature = ClassSignature.NO_CLASS_SIGNATURE;
+  }
+
   public void removeInnerClasses(Predicate<InnerClassAttribute> predicate) {
     innerClasses.removeIf(predicate::test);
   }
@@ -774,6 +797,14 @@
     throw new Unreachable();
   }
 
+  public ClassSignature getClassSignature() {
+    return classSignature;
+  }
+
+  public void setClassSignature(ClassSignature classSignature) {
+    this.classSignature = classSignature;
+  }
+
   public boolean isLocalClass() {
     InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
     // The corresponding enclosing-method attribute might be not available, e.g., CF version 50.
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
index 288b336..c0a3125 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
@@ -19,6 +19,11 @@
     }
   }
 
+  @Override
+  public FieldAccessFlags getAccessFlags() {
+    return getDefinition().getAccessFlags();
+  }
+
   public boolean isProgramField() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
index 0bc5348..9dd5795 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
@@ -21,6 +21,8 @@
     this.definition = definition;
   }
 
+  public abstract AccessFlags<?> getAccessFlags();
+
   public DexType getContextType() {
     return getHolderType();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 87d1bb9..115bf84 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public MethodAccessFlags getAccessFlags() {
+    return getDefinition().getAccessFlags();
+  }
+
+  @Override
   public boolean isMethodTarget() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 118a04d..410f985 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
 import java.util.List;
@@ -26,6 +27,7 @@
       List<NestMemberClassAttribute> nestMembers,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
+      ClassSignature classSignature,
       DexAnnotationSet annotations,
       DexEncodedField[] staticFields,
       DexEncodedField[] instanceFields,
@@ -46,6 +48,7 @@
         nestMembers,
         enclosingMember,
         innerClasses,
+        classSignature,
         annotations,
         origin,
         skipNameValidationForTesting);
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index fcb79fe..43c065a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -37,6 +39,11 @@
   }
 
   @Override
+  public ProgramField lookupOnProgramClass(DexProgramClass clazz) {
+    return clazz != null ? clazz.lookupProgramField(this) : null;
+  }
+
+  @Override
   public <T> T apply(
       Function<DexType, T> classConsumer,
       Function<DexField, T> fieldConsumer,
@@ -161,4 +168,11 @@
   public DexField withHolder(DexType holder, DexItemFactory dexItemFactory) {
     return dexItemFactory.createField(holder, type, name);
   }
+
+  public FieldReference asFieldReference() {
+    return Reference.field(
+        Reference.classFromDescriptor(holder.toDescriptorString()),
+        name.toString(),
+        Reference.typeFromDescriptor(type.toDescriptorString()));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index cfccc2b..cdf97ab 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -62,6 +62,7 @@
 public class DexItemFactory {
 
   public static final String throwableDescriptorString = "Ljava/lang/Throwable;";
+  public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
 
   /** Set of types that may be synthesized during compilation. */
   private final Set<DexType> possibleCompilerSynthesizedTypes = Sets.newIdentityHashSet();
@@ -541,7 +542,7 @@
   public final DexType annotationMethodParameters =
       createStaticallyKnownType("Ldalvik/annotation/MethodParameters;");
   public final DexType annotationSignature =
-      createStaticallyKnownType("Ldalvik/annotation/Signature;");
+      createStaticallyKnownType(dalvikAnnotationSignatureString);
   public final DexType annotationSourceDebugExtension =
       createStaticallyKnownType("Ldalvik/annotation/SourceDebugExtension;");
   public final DexType annotationThrows = createStaticallyKnownType("Ldalvik/annotation/Throws;");
@@ -2261,6 +2262,7 @@
         });
   }
 
+  @Deprecated
   synchronized public void forAllTypes(Consumer<DexType> f) {
     new ArrayList<>(types.values()).forEach(f);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 22920eb..dcb4ed3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
 import java.util.Arrays;
@@ -27,6 +28,7 @@
       List<NestMemberClassAttribute> nestMembers,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
+      ClassSignature classSignature,
       DexAnnotationSet annotations,
       DexEncodedField[] staticFields,
       DexEncodedField[] instanceFields,
@@ -47,6 +49,7 @@
         nestMembers,
         enclosingMember,
         innerClasses,
+        classSignature,
         annotations,
         origin,
         skipNameValidationForTesting);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 2a429d6..a1767c3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -16,9 +16,9 @@
     this.name = name;
   }
 
-  public DexEncodedMember<?, ?> lookupOnClass(DexClass clazz) {
-    return clazz != null ? clazz.lookupMember(this) : null;
-  }
+  public abstract DexEncodedMember<?, ?> lookupOnClass(DexClass clazz);
+
+  public abstract ProgramMember<?, ?> lookupOnProgramClass(DexProgramClass clazz);
 
   public abstract boolean match(R entry);
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 128b353..212f5ca 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -75,6 +75,7 @@
     return clazz != null ? clazz.lookupMember(this) : null;
   }
 
+  @Override
   public ProgramMethod lookupOnProgramClass(DexProgramClass clazz) {
     return clazz != null ? clazz.lookupProgramMethod(this) : null;
   }
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 b5fe6b8..e61ff61 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.graph.GenericSignature.EMPTY_TYPE_ARGUMENTS;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 import static com.google.common.base.Predicates.alwaysTrue;
 
@@ -11,12 +12,15 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -62,6 +66,7 @@
       List<NestMemberClassAttribute> nestMembers,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
+      ClassSignature classSignature,
       DexAnnotationSet classAnnotations,
       DexEncodedField[] staticFields,
       DexEncodedField[] instanceFields,
@@ -81,6 +86,7 @@
         nestMembers,
         enclosingMember,
         innerClasses,
+        classSignature,
         classAnnotations,
         staticFields,
         instanceFields,
@@ -103,6 +109,7 @@
       List<NestMemberClassAttribute> nestMembers,
       EnclosingMethodAttribute enclosingMember,
       List<InnerClassAttribute> innerClasses,
+      ClassSignature classSignature,
       DexAnnotationSet classAnnotations,
       DexEncodedField[] staticFields,
       DexEncodedField[] instanceFields,
@@ -125,6 +132,7 @@
         nestMembers,
         enclosingMember,
         innerClasses,
+        classSignature,
         classAnnotations,
         origin,
         skipNameValidationForTesting);
@@ -182,6 +190,17 @@
     return toProgramMethodOrNull(getInitializer(types));
   }
 
+  /** Find member in this class matching {@param member}. */
+  @SuppressWarnings("unchecked")
+  public <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+      ProgramMember<D, R> lookupProgramMember(DexMember<D, R> member) {
+    ProgramMember<?, ?> definition =
+        member.isDexField()
+            ? lookupProgramField(member.asDexField())
+            : lookupProgramMethod(member.asDexMethod());
+    return (ProgramMember<D, R>) definition;
+  }
+
   public ProgramField lookupProgramField(DexField reference) {
     return toProgramFieldOrNull(lookupField(reference));
   }
@@ -204,8 +223,22 @@
     return null;
   }
 
+  public TraversalContinuation traverseProgramMembers(
+      Function<ProgramMember<?, ?>, TraversalContinuation> fn) {
+    TraversalContinuation continuation = traverseProgramFields(fn);
+    if (continuation.shouldContinue()) {
+      return traverseProgramMethods(fn);
+    }
+    return TraversalContinuation.BREAK;
+  }
+
+  public TraversalContinuation traverseProgramFields(
+      Function<? super ProgramField, TraversalContinuation> fn) {
+    return traverseFields(field -> fn.apply(new ProgramField(this, field)));
+  }
+
   public TraversalContinuation traverseProgramMethods(
-      Function<ProgramMethod, TraversalContinuation> fn) {
+      Function<? super ProgramMethod, TraversalContinuation> fn) {
     return getMethodCollection().traverse(method -> fn.apply(new ProgramMethod(this, method)));
   }
 
@@ -258,6 +291,7 @@
       for (InnerClassAttribute attribute : getInnerClasses()) {
         attribute.collectIndexedItems(indexedItems);
       }
+      // We are explicitly not adding items referenced in signatures.
       forEachProgramField(field -> field.collectIndexedItems(indexedItems));
       forEachProgramMethod(method -> method.collectIndexedItems(indexedItems, graphLens, rewriter));
     }
@@ -271,6 +305,7 @@
   void collectMixedSectionItems(MixedSectionCollection mixedItems) {
     assert getEnclosingMethodAttribute() == null;
     assert getInnerClasses().isEmpty();
+    assert !classSignature.hasSignature();
     if (hasClassOrMemberAnnotations()) {
       mixedItems.setAnnotationsDirectoryForClass(this, new DexAnnotationDirectory(this));
     }
@@ -280,6 +315,7 @@
   public void addDependencies(MixedSectionCollection collector) {
     assert getEnclosingMethodAttribute() == null;
     assert getInnerClasses().isEmpty();
+    assert !classSignature.hasSignature();
     // We only have a class data item if there are methods or fields.
     if (hasMethodsOrFields()) {
       collector.add(this);
@@ -449,7 +485,7 @@
       return;
     }
     addExtraInterfacesToInterfacesArray(extraInterfaces);
-    addExtraInterfacesToSignatureAnnotationIfPresent(extraInterfaces, factory);
+    addExtraInterfacesToSignatureIfPresent(extraInterfaces);
   }
 
   private void addExtraInterfacesToInterfacesArray(List<DexType> extraInterfaces) {
@@ -461,31 +497,22 @@
     interfaces = new DexTypeList(newInterfaces);
   }
 
-  private void addExtraInterfacesToSignatureAnnotationIfPresent(
-      List<DexType> extraInterfaces, DexItemFactory factory) {
-    // We need to introduce in the dalvik.annotation.Signature annotation the extra interfaces.
+  private void addExtraInterfacesToSignatureIfPresent(List<DexType> extraInterfaces) {
+    // We need to introduce the extra interfaces to the generic signature.
     // At this point we cheat and pretend the extraInterfaces simply don't use any generic types.
-    DexAnnotation[] annotations = annotations().annotations;
-    for (int i = 0; i < annotations.length; i++) {
-      DexAnnotation annotation = annotations[i];
-      if (DexAnnotation.isSignatureAnnotation(annotation, factory)) {
-        DexAnnotation[] rewrittenAnnotations = annotations.clone();
-        rewrittenAnnotations[i] = rewriteSignatureAnnotation(annotation, extraInterfaces, factory);
-        setAnnotations(new DexAnnotationSet(rewrittenAnnotations));
-        // There is at most one signature annotation, so we can return here.
-        return;
-      }
+    if (classSignature.hasNoSignature() || extraInterfaces.isEmpty()) {
+      return;
     }
-  }
-
-  private DexAnnotation rewriteSignatureAnnotation(
-      DexAnnotation annotation, List<DexType> extraInterfaces, DexItemFactory factory) {
-    String signature = DexAnnotation.getSignature(annotation);
-    StringBuilder newSignatureBuilder = new StringBuilder(signature);
+    ImmutableList.Builder<ClassTypeSignature> interfacesBuilder =
+        ImmutableList.<ClassTypeSignature>builder().addAll(classSignature.superInterfaceSignatures);
     for (DexType extraInterface : extraInterfaces) {
-      newSignatureBuilder.append(extraInterface.descriptor.toString());
+      interfacesBuilder.add(new ClassTypeSignature(extraInterface, EMPTY_TYPE_ARGUMENTS));
     }
-    return DexAnnotation.createSignatureAnnotation(newSignatureBuilder.toString(), factory);
+    classSignature =
+        new ClassSignature(
+            classSignature.formalTypeParameters,
+            classSignature.superClassSignature,
+            interfacesBuilder.build());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 602a6b7..b5d50f0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -500,4 +500,11 @@
     }
     return arrayDim;
   }
+
+  public DexString toArrayDescriptor(int dimensions, DexItemFactory dexItemFactory) {
+    byte[] newContent = new byte[content.length + dimensions];
+    Arrays.fill(newContent, 0, dimensions, (byte) '[');
+    System.arraycopy(content, 0, newContent, dimensions, content.length);
+    return dexItemFactory.createString(size + dimensions, newContent);
+  }
 }
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 f21e965..b1892bf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -52,6 +52,10 @@
     this.descriptor = descriptor;
   }
 
+  public DexString getDescriptor() {
+    return descriptor;
+  }
+
   @Override
   public int computeHashCode() {
     return descriptor.hashCode();
@@ -431,11 +435,7 @@
   }
 
   public DexType toArrayType(int dimensions, DexItemFactory dexItemFactory) {
-    byte[] content = new byte[descriptor.content.length + dimensions];
-    Arrays.fill(content, 0, dimensions, (byte) '[');
-    System.arraycopy(descriptor.content, 0, content, dimensions, descriptor.content.length);
-    DexString newDesc = dexItemFactory.createString(descriptor.size + dimensions, content);
-    return dexItemFactory.createType(newDesc);
+    return dexItemFactory.createType(descriptor.toArrayDescriptor(dimensions, dexItemFactory));
   }
 
   public DexType toArrayElementType(DexItemFactory dexItemFactory) {
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 2427913..5cd3f8f 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -5,8 +5,10 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.getClassBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
+import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -14,6 +16,7 @@
 import java.lang.reflect.GenericSignatureFormatError;
 import java.nio.CharBuffer;
 import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * Internal encoding of the generics signature attribute as defined by JVMS 7 $ 4.3.4.
@@ -217,6 +220,21 @@
         visitor.visitSuperInterface(superInterface);
       }
     }
+
+    public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+      if (hasNoSignature()) {
+        return null;
+      }
+      GenericSignaturePrinter genericSignaturePrinter =
+          new GenericSignaturePrinter(namingLens, isTypeMissing);
+      genericSignaturePrinter.visitClassSignature(this);
+      return genericSignaturePrinter.toString();
+    }
+
+    @Override
+    public String toString() {
+      return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue());
+    }
   }
 
   public abstract static class TypeSignature {
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
new file mode 100644
index 0000000..1b23c85
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -0,0 +1,148 @@
+// 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.graph;
+
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.List;
+import java.util.function.Predicate;
+
+public class GenericSignaturePrinter implements GenericSignatureVisitor {
+
+  private final NamingLens namingLens;
+  private final Predicate<DexType> isTypeMissing;
+
+  public GenericSignaturePrinter(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+    this.namingLens = namingLens;
+    this.isTypeMissing = isTypeMissing;
+  }
+
+  private final StringBuilder sb = new StringBuilder();
+
+  @Override
+  public void visitClassSignature(ClassSignature classSignature) {
+    classSignature.visit(this);
+  }
+
+  @Override
+  public void visitFormalTypeParameters(List<FormalTypeParameter> formalTypeParameters) {
+    if (formalTypeParameters.isEmpty()) {
+      return;
+    }
+    sb.append("<");
+    for (FormalTypeParameter formalTypeParameter : formalTypeParameters) {
+      sb.append(formalTypeParameter.name);
+      formalTypeParameter.visit(this);
+    }
+    sb.append(">");
+  }
+
+  @Override
+  public void visitClassBound(FieldTypeSignature fieldSignature) {
+    sb.append(":");
+    printFieldTypeSignature(fieldSignature, false);
+  }
+
+  @Override
+  public void visitInterfaceBound(FieldTypeSignature fieldSignature) {
+    sb.append(":");
+    printFieldTypeSignature(fieldSignature, false);
+  }
+
+  @Override
+  public void visitSuperClass(ClassTypeSignature classTypeSignature) {
+    printFieldTypeSignature(classTypeSignature, false);
+  }
+
+  @Override
+  public void visitSuperInterface(ClassTypeSignature classTypeSignature) {
+    printFieldTypeSignature(classTypeSignature, false);
+  }
+
+  @Override
+  public void visitTypeSignature(TypeSignature typeSignature) {
+    if (typeSignature.isBaseTypeSignature()) {
+      DexType type = typeSignature.asBaseTypeSignature().type;
+      sb.append(type.toDescriptorString());
+    } else {
+      printFieldTypeSignature(typeSignature.asFieldTypeSignature(), false);
+    }
+  }
+
+  @Override
+  public void visitSimpleClass(ClassTypeSignature classTypeSignature) {
+    printFieldTypeSignature(classTypeSignature, true);
+  }
+
+  @Override
+  public void visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+    if (typeArguments.isEmpty()) {
+      return;
+    }
+    sb.append("<");
+    for (FieldTypeSignature typeArgument : typeArguments) {
+      WildcardIndicator wildcardIndicator = typeArgument.getWildcardIndicator();
+      if (wildcardIndicator != WildcardIndicator.NONE) {
+        assert wildcardIndicator != WildcardIndicator.NOT_AN_ARGUMENT;
+        sb.append(wildcardIndicator == WildcardIndicator.POSITIVE ? "+" : "-");
+      }
+      visitTypeSignature(typeArgument);
+    }
+    sb.append(">");
+  }
+
+  private void printFieldTypeSignature(
+      FieldTypeSignature fieldTypeSignature, boolean printingInner) {
+    // For inner member classes we only print the inner name and the type-arguments.
+    if (fieldTypeSignature.isStar()) {
+      sb.append("*");
+    } else if (fieldTypeSignature.isTypeVariableSignature()) {
+      sb.append("T").append(fieldTypeSignature.asTypeVariableSignature().typeVariable).append(";");
+    } else if (fieldTypeSignature.isArrayTypeSignature()) {
+      sb.append("[");
+      fieldTypeSignature.asArrayTypeSignature().visit(this);
+    } else {
+      assert fieldTypeSignature.isClassTypeSignature();
+      ClassTypeSignature classTypeSignature = fieldTypeSignature.asClassTypeSignature();
+      if (classTypeSignature.isNoSignature()) {
+        return;
+      }
+      String renamedString = namingLens.lookupDescriptor(classTypeSignature.type).toString();
+      if (!printingInner) {
+        sb.append("L").append(DescriptorUtils.getBinaryNameFromDescriptor(renamedString));
+      } else {
+        assert classTypeSignature.enclosingTypeSignature != null;
+        DexType enclosingType = classTypeSignature.enclosingTypeSignature.type;
+        String outerDescriptor = namingLens.lookupDescriptor(enclosingType).toString();
+        String innerClassName = DescriptorUtils.getInnerClassName(outerDescriptor, renamedString);
+        if (innerClassName == null && isTypeMissing.test(classTypeSignature.type)) {
+          assert renamedString.equals(classTypeSignature.type.toDescriptorString());
+          innerClassName =
+              DescriptorUtils.getInnerClassName(enclosingType.toDescriptorString(), renamedString);
+        }
+        if (innerClassName == null) {
+          // We can no longer encode the inner name in the generic signature.
+          return;
+        }
+        sb.append(".").append(innerClassName);
+      }
+      classTypeSignature.visit(this);
+      if (!printingInner) {
+        sb.append(";");
+      }
+    }
+  }
+
+  @Override
+  public String toString() {
+    return sb.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
new file mode 100644
index 0000000..ddabc37
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -0,0 +1,241 @@
+// 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.graph;
+
+import static com.android.tools.r8.graph.GenericSignature.EMPTY_SUPER_INTERFACES;
+import static com.android.tools.r8.graph.GenericSignature.EMPTY_TYPE_ARGUMENTS;
+import static com.android.tools.r8.graph.GenericSignature.EMPTY_TYPE_PARAMS;
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
+import static com.android.tools.r8.graph.GenericSignature.StarFieldTypeSignature.STAR_FIELD_TYPE_SIGNATURE;
+
+import com.android.tools.r8.graph.GenericSignature.ArrayTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GenericSignatureTypeRewriter {
+
+  private final AppView<?> appView;
+  private final DexProgramClass context;
+
+  private final FieldTypeSignature objectTypeSignature;
+
+  public GenericSignatureTypeRewriter(AppView<?> appView, DexProgramClass context) {
+    this.appView = appView;
+    this.context = context;
+    objectTypeSignature =
+        new ClassTypeSignature(appView.dexItemFactory().objectType, EMPTY_TYPE_ARGUMENTS);
+  }
+
+  public ClassSignature rewrite(ClassSignature classSignature) {
+    if (classSignature.hasNoSignature() || appView.graphLens().isIdentityLens()) {
+      return classSignature;
+    }
+    return new ClassSignatureRewriter().run(classSignature);
+  }
+
+  private class ClassSignatureRewriter implements GenericSignatureVisitor {
+
+    private final List<FormalTypeParameter> rewrittenTypeParameters = new ArrayList<>();
+    private ClassTypeSignature rewrittenSuperClass;
+    private final List<ClassTypeSignature> rewrittenSuperInterfaces = new ArrayList<>();
+
+    @Override
+    public void visitClassSignature(ClassSignature classSignature) {
+      classSignature.visit(this);
+    }
+
+    @Override
+    public void visitFormalTypeParameters(List<FormalTypeParameter> formalTypeParameters) {
+      for (FormalTypeParameter formalTypeParameter : formalTypeParameters) {
+        rewrittenTypeParameters.add(new FormalTypeParameterRewriter().run(formalTypeParameter));
+      }
+    }
+
+    @Override
+    public void visitSuperClass(ClassTypeSignature classTypeSignature) {
+      rewrittenSuperClass = new ClassTypeSignatureRewriter(true).run(classTypeSignature);
+      if (rewrittenSuperClass == null) {
+        rewrittenSuperClass =
+            new ClassTypeSignature(appView.dexItemFactory().objectType, EMPTY_TYPE_ARGUMENTS);
+      }
+    }
+
+    @Override
+    public void visitSuperInterface(ClassTypeSignature classTypeSignature) {
+      ClassTypeSignature superInterface =
+          new ClassTypeSignatureRewriter(true).run(classTypeSignature);
+      if (superInterface != null) {
+        rewrittenSuperInterfaces.add(superInterface);
+      }
+    }
+
+    private ClassSignature run(ClassSignature classSignature) {
+      classSignature.visit(this);
+      if (rewrittenTypeParameters.isEmpty()
+          && rewrittenSuperInterfaces.isEmpty()
+          && rewrittenSuperClass.isNoSignature()
+          && rewrittenSuperClass.type == appView.dexItemFactory().objectType) {
+        return ClassSignature.NO_CLASS_SIGNATURE;
+      }
+      return new ClassSignature(
+          rewrittenTypeParameters.isEmpty() ? EMPTY_TYPE_PARAMS : rewrittenTypeParameters,
+          rewrittenSuperClass,
+          rewrittenSuperInterfaces.isEmpty() ? EMPTY_SUPER_INTERFACES : rewrittenSuperInterfaces);
+    }
+  }
+
+  private class FormalTypeParameterRewriter implements GenericSignatureVisitor {
+
+    private FieldTypeSignature rewrittenClassBound = NO_FIELD_TYPE_SIGNATURE;
+    private final List<FieldTypeSignature> rewrittenInterfaceBounds = new ArrayList<>();
+
+    @Override
+    public void visitClassBound(FieldTypeSignature fieldSignature) {
+      rewrittenClassBound = new TypeSignatureRewriter().run(fieldSignature);
+    }
+
+    @Override
+    public void visitInterfaceBound(FieldTypeSignature fieldSignature) {
+      FieldTypeSignature interfaceBound = new TypeSignatureRewriter().run(fieldSignature);
+      if (interfaceBound != null) {
+        rewrittenInterfaceBounds.add(interfaceBound);
+      }
+    }
+
+    private FormalTypeParameter run(FormalTypeParameter formalTypeParameter) {
+      formalTypeParameter.visit(this);
+      // Guard against the case where we have <T::...> that is, no class or interfaces bounds.
+      if (rewrittenInterfaceBounds.isEmpty()
+          && (rewrittenClassBound == null || !rewrittenClassBound.hasSignature())) {
+        rewrittenClassBound = objectTypeSignature;
+      }
+      return new FormalTypeParameter(
+          formalTypeParameter.name,
+          rewrittenClassBound == null ? NO_FIELD_TYPE_SIGNATURE : rewrittenClassBound,
+          rewrittenInterfaceBounds.isEmpty() ? EMPTY_TYPE_ARGUMENTS : rewrittenInterfaceBounds);
+    }
+  }
+
+  private class TypeSignatureRewriter implements GenericSignatureVisitor {
+
+    private TypeSignature run(TypeSignature typeSignature) {
+      if (typeSignature.isBaseTypeSignature()) {
+        return typeSignature;
+      }
+      assert typeSignature.isFieldTypeSignature();
+      return run(typeSignature.asFieldTypeSignature());
+    }
+
+    private FieldTypeSignature run(FieldTypeSignature fieldTypeSignature) {
+      if (fieldTypeSignature.isStar()) {
+        return fieldTypeSignature;
+      }
+      if (fieldTypeSignature.isTypeVariableSignature()) {
+        return fieldTypeSignature;
+      }
+      if (fieldTypeSignature.isArrayTypeSignature()) {
+        ArrayTypeSignature arrayTypeSignature = fieldTypeSignature.asArrayTypeSignature();
+        TypeSignature rewrittenElement = run(arrayTypeSignature.elementSignature);
+        if (rewrittenElement == null) {
+          return new ArrayTypeSignature(objectTypeSignature);
+        }
+        return rewrittenElement.toArrayTypeSignature();
+      }
+      assert fieldTypeSignature.isClassTypeSignature();
+      ClassTypeSignature classTypeSignature = fieldTypeSignature.asClassTypeSignature();
+      if (classTypeSignature.isNoSignature()) {
+        return classTypeSignature;
+      }
+      return new ClassTypeSignatureRewriter(false).run(classTypeSignature);
+    }
+  }
+
+  private class ClassTypeSignatureRewriter implements GenericSignatureVisitor {
+
+    private final AppInfoWithLiveness appInfoWithLiveness;
+    private final boolean isSuperClassOrInterface;
+
+    // These fields are updated when iterating the modeled structure.
+    private DexType currentType;
+
+    // The following references are used to have a head and tail pointer to the classTypeSignature
+    // link we are building. The topClassSignature will have a reference to the top-most package
+    // and class-name. The parentClassSignature is a pointer pointing to the tail always and will
+    // be linked and updated when calling ClassTypeSignature.link.
+    private ClassTypeSignature topClassSignature;
+    private ClassTypeSignature parentClassSignature;
+
+    private ClassTypeSignatureRewriter(boolean isSuperClassOrInterface) {
+      appInfoWithLiveness =
+          appView.appInfo().hasLiveness() ? appView.appInfo().withLiveness() : null;
+      this.isSuperClassOrInterface = isSuperClassOrInterface;
+    }
+
+    @Override
+    public void visitSimpleClass(ClassTypeSignature classTypeSignature) {
+      currentType = getTarget(classTypeSignature.type);
+      if (currentType == null) {
+        return;
+      }
+      classTypeSignature.visit(this);
+    }
+
+    @Override
+    public void visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+      ClassTypeSignature newClassTypeSignature;
+      if (typeArguments.isEmpty()) {
+        newClassTypeSignature = new ClassTypeSignature(currentType, EMPTY_TYPE_ARGUMENTS);
+      } else {
+        List<FieldTypeSignature> rewrittenTypeArguments = new ArrayList<>(typeArguments.size());
+        for (FieldTypeSignature typeArgument : typeArguments) {
+          if (typeArgument.isStar()) {
+            rewrittenTypeArguments.add(typeArgument);
+            continue;
+          }
+          FieldTypeSignature rewritten = new TypeSignatureRewriter().run(typeArgument);
+          if (rewritten != null) {
+            rewrittenTypeArguments.add(rewritten.asArgument(typeArgument.getWildcardIndicator()));
+          } else {
+            rewrittenTypeArguments.add(STAR_FIELD_TYPE_SIGNATURE);
+          }
+        }
+        newClassTypeSignature = new ClassTypeSignature(currentType, rewrittenTypeArguments);
+      }
+      if (topClassSignature == null) {
+        topClassSignature = newClassTypeSignature;
+        parentClassSignature = newClassTypeSignature;
+      } else {
+        ClassTypeSignature.link(parentClassSignature, newClassTypeSignature);
+        parentClassSignature = newClassTypeSignature;
+      }
+    }
+
+    private ClassTypeSignature run(ClassTypeSignature classTypeSignature) {
+      currentType = getTarget(classTypeSignature.type);
+      if (currentType == null) {
+        return null;
+      }
+      classTypeSignature.visit(this);
+      return topClassSignature;
+    }
+
+    private DexType getTarget(DexType type) {
+      if (appInfoWithLiveness != null && appInfoWithLiveness.wasPruned(type)) {
+        return null;
+      }
+      DexType rewrittenType = appView.graphLens().lookupType(type);
+      if (isSuperClassOrInterface && context.type == rewrittenType) {
+        return null;
+      }
+      return rewrittenType;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureUtils.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureUtils.java
new file mode 100644
index 0000000..7c0dcd2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureUtils.java
@@ -0,0 +1,34 @@
+// 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.graph;
+
+import static com.android.tools.r8.graph.GenericSignature.ClassSignature.NO_CLASS_SIGNATURE;
+import static com.android.tools.r8.graph.GenericSignature.MethodTypeSignature.NO_METHOD_TYPE_SIGNATURE;
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
+
+import com.android.tools.r8.graph.GenericSignature.DexDefinitionSignature;
+
+public class GenericSignatureUtils {
+
+  public static boolean verifyNoDuplicateGenericDefinitions(
+      DexDefinitionSignature<?> signature, DexAnnotationSet annotations) {
+    assert signature != null;
+    if (signature == NO_METHOD_TYPE_SIGNATURE
+        || signature == NO_FIELD_TYPE_SIGNATURE
+        || signature == NO_CLASS_SIGNATURE
+        || annotations == null) {
+      return true;
+    }
+    // The check is on the string descriptor to allow for not passing in a factory.
+    for (DexAnnotation annotation : annotations.annotations) {
+      assert !annotation
+          .getAnnotationType()
+          .descriptor
+          .toString()
+          .equals(DexItemFactory.dalvikAnnotationSignatureString);
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index b276437..7681f24 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -331,9 +331,7 @@
   }
 
   /** Lookup a rebound or non-rebound method reference using the current graph lens. */
-  public MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
-    return internalLookupMethod(method, context, type, result -> result);
-  }
+  public abstract MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type);
 
   protected abstract MethodLookupResult internalLookupMethod(
       DexMethod reference, DexMethod context, Type type, LookupMethodContinuation continuation);
@@ -610,6 +608,10 @@
       this.previousLens = previousLens;
     }
 
+    public final DexItemFactory dexItemFactory() {
+      return dexItemFactory;
+    }
+
     public final GraphLens getPrevious() {
       return previousLens;
     }
@@ -622,6 +624,21 @@
     }
 
     @Override
+    public MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
+      if (method.getHolderType().isArrayType()) {
+        assert lookupType(method.getReturnType()) == method.getReturnType();
+        assert method.getParameters().stream()
+            .allMatch(parameterType -> lookupType(parameterType) == parameterType);
+        return MethodLookupResult.builder(this)
+            .setReference(method.withHolder(lookupType(method.getHolderType()), dexItemFactory))
+            .setType(type)
+            .build();
+      }
+      assert method.getHolderType().isClassType();
+      return internalLookupMethod(method, context, type, result -> result);
+    }
+
+    @Override
     public final DexType lookupType(DexType type) {
       if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) {
         return type;
@@ -744,6 +761,11 @@
     }
 
     @Override
+    public MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
+      return MethodLookupResult.builder(this).setReference(method).setType(type).build();
+    }
+
+    @Override
     public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
         DexMethod method) {
       return RewrittenPrototypeDescription.none();
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index cd3baa0..f3195c9 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -91,17 +91,17 @@
     }
   }
 
-  public DexType getLiveContext(AppInfoWithLiveness appInfo) {
+  public DexType getLiveContext(AppView<? extends AppInfoWithLiveness> appView) {
     DexType context = getOuter();
     if (context == null) {
-      DexClass inner = appInfo.definitionFor(getInner());
+      DexClass inner = appView.definitionFor(getInner());
       if (inner != null && inner.getEnclosingMethodAttribute() != null) {
         EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethodAttribute();
         if (enclosingMethodAttribute.getEnclosingClass() != null) {
           context = enclosingMethodAttribute.getEnclosingClass();
         } else {
           DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
-          if (!appInfo.liveMethods.contains(enclosingMethod)) {
+          if (!appView.appInfo().liveMethods.contains(enclosingMethod)) {
             // EnclosingMethodAttribute will be pruned as it references the pruned method.
             // Hence, the current InnerClassAttribute will be removed too. No live context.
             return null;
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index ea58f8d..4382eaa 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -206,6 +207,7 @@
     private final List<NestMemberClassAttribute> nestMembers = new ArrayList<>();
     private EnclosingMethodAttribute enclosingMember = null;
     private final List<InnerClassAttribute> innerClasses = new ArrayList<>();
+    private ClassSignature classSignature = ClassSignature.NO_CLASS_SIGNATURE;
     private List<DexAnnotation> annotations = null;
     private List<DexAnnotationElement> defaultAnnotations = null;
     private final List<DexEncodedField> staticFields = new ArrayList<>();
@@ -329,9 +331,9 @@
       assert superName != null || name.equals(Constants.JAVA_LANG_OBJECT_NAME);
       superType = superName == null ? null : application.getTypeFromName(superName);
       this.interfaces = application.getTypeListFromNames(interfaces);
-      if (signature != null && !signature.isEmpty()) {
-        addAnnotation(DexAnnotation.createSignatureAnnotation(signature, application.getFactory()));
-      }
+      classSignature =
+          GenericSignature.parseClassSignature(
+              name, signature, origin, application.getFactory(), application.options.reporter);
     }
 
     @Override
@@ -410,6 +412,7 @@
               nestMembers,
               enclosingMember,
               innerClasses,
+              classSignature,
               createAnnotationSet(annotations, application.options),
               staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
               instanceFields.toArray(DexEncodedField.EMPTY_ARRAY),
@@ -567,8 +570,8 @@
       this.desc = desc;
       this.value = value;
       if (signature != null && !signature.isEmpty()) {
-        addAnnotation(DexAnnotation.createSignatureAnnotation(
-            signature, parent.application.getFactory()));
+        addAnnotation(
+            DexAnnotation.createSignatureAnnotation(signature, parent.application.getFactory()));
       }
     }
 
@@ -648,7 +651,6 @@
     private void addAnnotation(DexAnnotation annotation) {
       getAnnotations().add(annotation);
     }
-
     private List<DexAnnotation> getAnnotations() {
       if (annotations == null) {
         annotations = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
index 4b20148..8aa36c0 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
@@ -41,11 +42,20 @@
     return new ConcurrentBuilder();
   }
 
+  public static IdentityBuilder identityBuilder() {
+    return new IdentityBuilder();
+  }
+
   // TODO(b/132593519): We should not need sorted maps with the new member rebinding analysis.
   public static SortedBuilder sortedBuilder() {
     return new SortedBuilder();
   }
 
+  public Modifier modifier() {
+    return new Modifier(
+        directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes);
+  }
+
   public void forEachMethodReference(Consumer<DexMethod> method) {
     Set<DexMethod> seen = Sets.newIdentityHashSet();
     directInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
@@ -75,6 +85,10 @@
     virtualInvokes.forEach(consumer);
   }
 
+  public void forEachVirtualInvokeContext(DexMethod method, Consumer<ProgramMethod> consumer) {
+    virtualInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer);
+  }
+
   public MethodAccessInfoCollection rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLens lens) {
     return new MethodAccessInfoCollection(
@@ -107,11 +121,16 @@
     private final T virtualInvokes;
 
     private Builder(Supplier<T> factory) {
-      directInvokes = factory.get();
-      interfaceInvokes = factory.get();
-      staticInvokes = factory.get();
-      superInvokes = factory.get();
-      virtualInvokes = factory.get();
+      this(factory.get(), factory.get(), factory.get(), factory.get(), factory.get());
+    }
+
+    private Builder(
+        T directInvokes, T interfaceInvokes, T staticInvokes, T superInvokes, T virtualInvokes) {
+      this.directInvokes = directInvokes;
+      this.interfaceInvokes = interfaceInvokes;
+      this.staticInvokes = staticInvokes;
+      this.superInvokes = superInvokes;
+      this.virtualInvokes = virtualInvokes;
     }
 
     public T getDirectInvokes() {
@@ -138,23 +157,45 @@
       return registerInvokeMethodInContext(invokedMethod, context, directInvokes);
     }
 
+    public void registerInvokeDirectInContexts(DexMethod invokedMethod, ProgramMethodSet contexts) {
+      contexts.forEach(context -> registerInvokeDirectInContext(invokedMethod, context));
+    }
+
     public boolean registerInvokeInterfaceInContext(
         DexMethod invokedMethod, ProgramMethod context) {
       return registerInvokeMethodInContext(invokedMethod, context, interfaceInvokes);
     }
 
+    public void registerInvokeInterfaceInContexts(
+        DexMethod invokedMethod, ProgramMethodSet contexts) {
+      contexts.forEach(context -> registerInvokeInterfaceInContext(invokedMethod, context));
+    }
+
     public boolean registerInvokeStaticInContext(DexMethod invokedMethod, ProgramMethod context) {
       return registerInvokeMethodInContext(invokedMethod, context, staticInvokes);
     }
 
+    public void registerInvokeStaticInContexts(DexMethod invokedMethod, ProgramMethodSet contexts) {
+      contexts.forEach(context -> registerInvokeStaticInContext(invokedMethod, context));
+    }
+
     public boolean registerInvokeSuperInContext(DexMethod invokedMethod, ProgramMethod context) {
       return registerInvokeMethodInContext(invokedMethod, context, superInvokes);
     }
 
+    public void registerInvokeSuperInContexts(DexMethod invokedMethod, ProgramMethodSet contexts) {
+      contexts.forEach(context -> registerInvokeSuperInContext(invokedMethod, context));
+    }
+
     public boolean registerInvokeVirtualInContext(DexMethod invokedMethod, ProgramMethod context) {
       return registerInvokeMethodInContext(invokedMethod, context, virtualInvokes);
     }
 
+    public void registerInvokeVirtualInContexts(
+        DexMethod invokedMethod, ProgramMethodSet contexts) {
+      contexts.forEach(context -> registerInvokeVirtualInContext(invokedMethod, context));
+    }
+
     private static boolean registerInvokeMethodInContext(
         DexMethod invokedMethod, ProgramMethod context, Map<DexMethod, ProgramMethodSet> invokes) {
       return invokes
@@ -176,10 +217,38 @@
     }
   }
 
+  public static class IdentityBuilder
+      extends Builder<IdentityHashMap<DexMethod, ProgramMethodSet>> {
+
+    private IdentityBuilder() {
+      super(IdentityHashMap::new);
+    }
+  }
+
   public static class SortedBuilder extends Builder<TreeMap<DexMethod, ProgramMethodSet>> {
 
     private SortedBuilder() {
       super(() -> new TreeMap<>(DexMethod::slowCompareTo));
     }
   }
+
+  public static class Modifier extends Builder<Map<DexMethod, ProgramMethodSet>> {
+
+    private Modifier(
+        Map<DexMethod, ProgramMethodSet> directInvokes,
+        Map<DexMethod, ProgramMethodSet> interfaceInvokes,
+        Map<DexMethod, ProgramMethodSet> staticInvokes,
+        Map<DexMethod, ProgramMethodSet> superInvokes,
+        Map<DexMethod, ProgramMethodSet> virtualInvokes) {
+      super(directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes);
+    }
+
+    public void addAll(MethodAccessInfoCollection collection) {
+      collection.forEachDirectInvoke(this::registerInvokeDirectInContexts);
+      collection.forEachInterfaceInvoke(this::registerInvokeInterfaceInContexts);
+      collection.forEachStaticInvoke(this::registerInvokeStaticInContexts);
+      collection.forEachSuperInvoke(this::registerInvokeSuperInContexts);
+      collection.forEachVirtualInvoke(this::registerInvokeVirtualInContexts);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index a74f0c4..d877896 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -252,6 +252,9 @@
 
   @Override
   void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    // The code assumes that when replacement.apply(method) is called, the map is up-to-date with
+    // the previously replaced methods. We therefore cannot postpone the map updates to the end of
+    // the method.
     ArrayList<DexEncodedMethod> initialValues = new ArrayList<>(methodMap.values());
     for (DexEncodedMethod method : initialValues) {
       DexEncodedMethod newMethod = replacement.apply(method);
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 883b0a6..82a133c 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -8,7 +8,33 @@
 
   DexProgramClass getContextClass();
 
+  AccessFlags<?> getAccessFlags();
+
   DexType getContextType();
 
   DexDefinition getDefinition();
+
+  default boolean isProgramClass() {
+    return false;
+  }
+
+  default DexProgramClass asProgramClass() {
+    return null;
+  }
+
+  default boolean isProgramField() {
+    return false;
+  }
+
+  default ProgramField asProgramField() {
+    return null;
+  }
+
+  default boolean isProgramMethod() {
+    return false;
+  }
+
+  default ProgramMethod asProgramMethod() {
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index a10b066..5f5a62b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -113,6 +113,11 @@
       methodMap = methodMap.remap(remapMethods, Function.identity(), Function.identity());
     }
 
+    public Builder mapField(DexField from, DexField to) {
+      fieldMap.put(from, to);
+      return this;
+    }
+
     /** Unidirectional mapping from one method to another. */
     public Builder recordExtraOriginalSignature(DexMethod from, DexMethod to) {
       methodMap.setRepresentative(from, to);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
index db92416..ec8d495 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collections;
@@ -75,6 +76,7 @@
               Collections.emptyList(),
               null,
               Collections.emptyList(),
+              ClassSignature.NO_CLASS_SIGNATURE,
               DexAnnotationSet.empty(),
               DexEncodedField.EMPTY_ARRAY,
               DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index b57adbf..e33137c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -389,8 +389,7 @@
       DexField field = encodedField.field;
       DexField newField = fixupFieldReference(field);
       if (newField != encodedField.field) {
-        // TODO(b/165498187): track mapped fields
-        /* lensBuilder.map(field, newField); */
+        lensBuilder.mapField(field, newField);
         setter.setField(i, encodedField.toTypeSubstitutedField(newField));
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 5280f61..2d4fb70 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -111,6 +112,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
+            ClassSignature.NO_CLASS_SIGNATURE,
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 7936ffd..f2ea71d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -230,6 +231,7 @@
         Collections.emptyList(),
         null,
         Collections.emptyList(),
+        ClassSignature.NO_CLASS_SIGNATURE,
         DexAnnotationSet.empty(),
         DexEncodedField.EMPTY_ARRAY, // No static fields.
         new DexEncodedField[] {wrapperField},
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 e58eeba..6d37c32 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -712,6 +713,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
+            ClassSignature.NO_CLASS_SIGNATURE,
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
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 20c3ae4..67d389f 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -213,6 +214,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
+            ClassSignature.NO_CLASS_SIGNATURE,
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
@@ -299,6 +301,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
+            ClassSignature.NO_CLASS_SIGNATURE,
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index ecad620..cc6fec7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -160,6 +161,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
+            ClassSignature.NO_CLASS_SIGNATURE,
             DexAnnotationSet.empty(),
             synthesizeStaticFields(),
             synthesizeInstanceFields(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 89dc96c..01b87d2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
@@ -185,6 +186,7 @@
         Collections.emptyList(),
         null,
         Collections.emptyList(),
+        ClassSignature.NO_CLASS_SIGNATURE,
         DexAnnotationSet.empty(),
         DexEncodedField.EMPTY_ARRAY,
         DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index 613fac6..7b0d6b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.IRCode;
@@ -151,6 +152,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
+            ClassSignature.NO_CLASS_SIGNATURE,
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 655ee2f..56d0a08 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -1391,7 +1392,7 @@
         Collections.emptyList(),
         null,
         Collections.emptyList(),
-        // TODO: Build dex annotations structure.
+        ClassSignature.NO_CLASS_SIGNATURE,
         DexAnnotationSet.empty(),
         DexEncodedField.EMPTY_ARRAY, // Static fields.
         DexEncodedField.EMPTY_ARRAY, // Instance fields.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index b1fcc81..7a81edc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodCollection;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -252,6 +253,7 @@
                       Collections.emptyList(),
                       null,
                       Collections.emptyList(),
+                      ClassSignature.NO_CLASS_SIGNATURE,
                       DexAnnotationSet.empty(),
                       DexEncodedField.EMPTY_ARRAY, // Static fields.
                       DexEncodedField.EMPTY_ARRAY, // Instance fields.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index fbe8514..c766569 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -59,6 +59,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -358,7 +359,7 @@
     ProgramMethodSet dependencies = enumUnboxingCandidatesInfo.allMethodDependencies();
     enumUnboxingCandidatesInfo.clear();
     // Update keep info on any of the enum methods of the removed classes.
-    updateKeepInfo(enumClassesToUnbox);
+    updateKeepInfo(enumsToUnbox);
     DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
     UnboxedEnumMemberRelocator relocator =
         UnboxedEnumMemberRelocator.builder(appView)
@@ -414,26 +415,9 @@
         });
   }
 
-  private void updateKeepInfo(Set<DexProgramClass> enumsToUnbox) {
-    appView
-        .appInfo()
-        .getKeepInfo()
-        .mutate(
-            keepInfo -> {
-              for (DexProgramClass enumToUnbox : enumsToUnbox) {
-                assert !keepInfo.getClassInfo(enumToUnbox).isPinned();
-                enumToUnbox.forEachProgramMethod(
-                    method -> {
-                      keepInfo.unsafeAllowMinificationOfMethod(method);
-                      keepInfo.unsafeUnpinMethod(method);
-                    });
-                enumToUnbox.forEachProgramField(
-                    field -> {
-                      keepInfo.unsafeAllowMinificationOfField(field);
-                      keepInfo.unsafeUnpinField(field);
-                    });
-              }
-            });
+  private void updateKeepInfo(Set<DexType> enumsToUnbox) {
+    KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
+    keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(enumsToUnbox));
   }
 
   public EnumInstanceFieldDataMap finishAnalysis() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index 9bc6826..d99bf3e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
@@ -14,9 +13,9 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -44,40 +43,10 @@
         originalMethodSignatures,
         previousLens,
         dexItemFactory);
-    assert noDuplicateEntries(fieldMap, originalFieldSignatures);
-    assert noDuplicateEntries(methodMap, originalMethodSignatures);
     this.prototypeChangesPerMethod = prototypeChangesPerMethod;
     this.unboxedEnums = unboxedEnums;
   }
 
-  private <T extends DexMember<?, ?>> boolean noDuplicateEntries(
-      Map<T, T> map, BiMap<T, T> originalSignatures) {
-    if (map.size() == originalSignatures.size()) {
-      return true;
-    }
-    IdentityHashMap<T, T> methodMapReverse = new IdentityHashMap<>();
-    IdentityHashMap<T, Set<T>> duplicate = new IdentityHashMap<>();
-    map.forEach(
-        (k, v) -> {
-          if (methodMapReverse.containsKey(v)) {
-            Set<T> dexMethods = duplicate.computeIfAbsent(v, ignored -> Sets.newIdentityHashSet());
-            dexMethods.add(methodMapReverse.get(v));
-            dexMethods.add(k);
-          } else {
-            methodMapReverse.put(v, k);
-          }
-        });
-    assert !duplicate.isEmpty();
-    StringBuilder sb = new StringBuilder();
-    sb.append("Enum unboxing has created duplicate members: \n");
-    duplicate.forEach(
-        (target, origins) -> {
-          sb.append(origins).append(" -> ").append(target).append("\n");
-        });
-    assert false : sb.toString();
-    return false;
-  }
-
   @Override
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
       RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
@@ -99,26 +68,45 @@
     return type;
   }
 
-  public static Builder builder() {
+  public static Builder enumUnboxingLensBuilder() {
     return new Builder();
   }
 
-  static class Builder extends NestedGraphLens.Builder {
+  static class Builder {
+
+    protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
+    protected final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
+    protected final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
 
     private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
         new IdentityHashMap<>();
 
-    public void move(DexMethod from, boolean fromStatic, DexMethod to, boolean toStatic) {
-      move(from, fromStatic, to, toStatic, 0);
+    public void map(DexType from, DexType to) {
+      if (from == to) {
+        return;
+      }
+      typeMap.put(from, to);
+    }
+
+    public void move(DexField from, DexField to) {
+      if (from == to) {
+        return;
+      }
+      originalFieldSignatures.put(to, from);
+    }
+
+    public void move(DexMethod from, DexMethod to, boolean fromStatic, boolean toStatic) {
+      move(from, to, fromStatic, toStatic, 0);
     }
 
     public void move(
         DexMethod from,
-        boolean fromStatic,
         DexMethod to,
+        boolean fromStatic,
         boolean toStatic,
         int numberOfExtraNullParameters) {
-      super.move(from, to);
+      assert from != to;
+      originalMethodSignatures.put(to, from);
       int offsetDiff = 0;
       int toOffset = BooleanUtils.intValue(!toStatic);
       RewrittenPrototypeDescription.ArgumentInfoCollection.Builder builder =
@@ -153,13 +141,15 @@
 
     public EnumUnboxingLens build(
         DexItemFactory dexItemFactory, GraphLens previousLens, Set<DexType> unboxedEnums) {
-      if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
+      if (typeMap.isEmpty()
+          && originalFieldSignatures.isEmpty()
+          && originalMethodSignatures.isEmpty()) {
         return null;
       }
       return new EnumUnboxingLens(
           typeMap,
-          methodMap,
-          fieldMap,
+          originalMethodSignatures.inverse(),
+          originalFieldSignatures.inverse(),
           originalFieldSignatures,
           originalMethodSignatures,
           previousLens,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 634d74e..df641af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -28,7 +28,7 @@
 class EnumUnboxingTreeFixer {
 
   private final Map<DexType, List<DexEncodedMethod>> unboxedEnumsMethods = new IdentityHashMap<>();
-  private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
+  private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.enumUnboxingLensBuilder();
   private final AppView<?> appView;
   private final DexItemFactory factory;
   private final Set<DexType> enumsToUnbox;
@@ -108,7 +108,7 @@
     DexProto proto = encodedMethod.isStatic() ? method.proto : factory.prependHolderToProto(method);
     DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
     assert appView.definitionFor(encodedMethod.holder()).lookupMethod(newMethod) == null;
-    lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true);
+    lensBuilder.move(method, newMethod, encodedMethod.isStatic(), true);
     encodedMethod.accessFlags.promoteToPublic();
     encodedMethod.accessFlags.promoteToStatic();
     encodedMethod.clearAnnotations();
@@ -131,7 +131,7 @@
     int numberOfExtraNullParameters = newMethod.getArity() - encodedMethod.method.getArity();
     boolean isStatic = encodedMethod.isStatic();
     lensBuilder.move(
-        encodedMethod.method, isStatic, newMethod, isStatic, numberOfExtraNullParameters);
+        encodedMethod.method, newMethod, isStatic, isStatic, numberOfExtraNullParameters);
     DexEncodedMethod newEncodedMethod = encodedMethod.toTypeSubstitutedMethod(newMethod);
     assert !encodedMethod.isLibraryMethodOverride().isTrue()
         : "Enum unboxing is changing the signature of a library override in a non unboxed class.";
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
index 16c9118..b199c47 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.ProgramPackage;
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -121,6 +122,7 @@
               Collections.emptyList(),
               null,
               Collections.emptyList(),
+              ClassSignature.NO_CLASS_SIGNATURE,
               DexAnnotationSet.empty(),
               DexEncodedField.EMPTY_ARRAY,
               DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
index 5ddc8dd..92f0972 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -50,7 +51,8 @@
         Collections.emptyList(),
         buildEnclosingMethodAttribute(),
         buildInnerClasses(),
-        buildAnnotations(),
+        buildClassSignature(),
+        DexAnnotationSet.empty(),
         buildStaticFields(appView, feedback),
         buildInstanceFields(),
         buildDirectMethods(),
@@ -68,7 +70,7 @@
 
   protected abstract List<InnerClassAttribute> buildInnerClasses();
 
-  protected abstract DexAnnotationSet buildAnnotations();
+  protected abstract ClassSignature buildClassSignature();
 
   protected abstract DexEncodedMethod[] buildVirtualMethods();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index 5b0b0b4..db7f56c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -23,6 +22,8 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -33,6 +34,7 @@
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
@@ -84,14 +86,11 @@
   }
 
   @Override
-  protected DexAnnotationSet buildAnnotations() {
+  protected ClassSignature buildClassSignature() {
     // Kotlin-style lambdas supported by the merged may only contain optional signature and
     // kotlin metadata annotations. We remove the latter, but keep the signature if present.
-    String signature = id.signature;
-    return signature == null
-        ? DexAnnotationSet.empty()
-        : new DexAnnotationSet(
-            new DexAnnotation[]{DexAnnotation.createSignatureAnnotation(signature, factory)});
+    return GenericSignature.parseClassSignature(
+        origin, id.signature, new SynthesizedOrigin(origin, getClass()), factory, options.reporter);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 049748f..5a90f80 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -91,9 +91,6 @@
 
   public static boolean hasValidAnnotations(Kotlin kotlin, DexClass lambda) {
     for (DexAnnotation annotation : lambda.annotations().annotations) {
-      if (DexAnnotation.isSignatureAnnotation(annotation, kotlin.factory)) {
-        continue;
-      }
       if (annotation.annotation.type == kotlin.factory.kotlinMetadataType) {
         continue;
       }
@@ -104,13 +101,7 @@
 
   String validateAnnotations(AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
       throws LambdaStructureError {
-    String signature = null;
     for (DexAnnotation annotation : lambda.liveAnnotations(appView).annotations) {
-      if (DexAnnotation.isSignatureAnnotation(annotation, kotlin.factory)) {
-        signature = DexAnnotation.getSignature(annotation);
-        continue;
-      }
-
       if (annotation.annotation.type == appView.dexItemFactory().kotlinMetadataType) {
         // Ignore kotlin metadata on lambda classes. Metadata on synthetic
         // classes exists but is not used in the current Kotlin version (1.2.21)
@@ -123,7 +114,7 @@
           "unexpected annotation: " + annotation.annotation.type.toSourceString());
     }
     assert hasValidAnnotations(kotlin, lambda);
-    return signature;
+    return lambda.getClassSignature().toString();
   }
 
   void validateStaticFields(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index e28734b..06e3679 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -46,11 +46,13 @@
 import com.android.tools.r8.utils.AsmUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.PredicateUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Optional;
+import java.util.function.Predicate;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassTooLargeException;
@@ -79,6 +81,7 @@
   private final NamingLens namingLens;
   private final InternalOptions options;
   private final Marker marker;
+  private final Predicate<DexType> isTypeMissing;
 
   public final ProguardMapSupplier proguardMapSupplier;
 
@@ -96,6 +99,8 @@
     assert marker != null;
     this.marker = marker;
     this.proguardMapSupplier = proguardMapSupplier;
+    this.isTypeMissing =
+        PredicateUtils.isNull(appView.appInfo()::definitionForWithoutExistenceAssert);
   }
 
   public void write(ClassFileConsumer consumer) {
@@ -179,7 +184,7 @@
     }
     String desc = namingLens.lookupDescriptor(clazz.type).toString();
     String name = namingLens.lookupInternalName(clazz.type);
-    String signature = getSignature(clazz.annotations());
+    String signature = clazz.getClassSignature().toRenamedString(namingLens, isTypeMissing);
     String superName =
         clazz.type == options.itemFactory.objectType
             ? null
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 6f5a765..40713b8 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -32,8 +32,6 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
 import java.util.function.Predicate;
 
 class ClassNameMinifier {
@@ -114,14 +112,11 @@
     }
   }
 
-  ClassRenaming computeRenaming(Timing timing, ExecutorService executorService)
-      throws ExecutionException {
-    return computeRenaming(timing, executorService, Collections.emptyMap());
+  ClassRenaming computeRenaming(Timing timing) {
+    return computeRenaming(timing, Collections.emptyMap());
   }
 
-  ClassRenaming computeRenaming(
-      Timing timing, ExecutorService executorService, Map<DexType, DexString> syntheticClasses)
-      throws ExecutionException {
+  ClassRenaming computeRenaming(Timing timing, Map<DexType, DexString> syntheticClasses) {
     // Externally defined synthetic classes populate an initial renaming.
     renaming.putAll(syntheticClasses);
 
@@ -159,10 +154,6 @@
     }
     timing.end();
 
-    timing.begin("rename-arrays");
-    appView.dexItemFactory().forAllTypes(this::renameArrayTypeIfNeeded);
-    timing.end();
-
     return new ClassRenaming(Collections.unmodifiableMap(renaming), getPackageRenaming());
   }
 
@@ -253,7 +244,7 @@
     if (attribute == null) {
       return null;
     }
-    return attribute.getLiveContext(appView.appInfo());
+    return attribute.getLiveContext(appView);
   }
 
   private DexString computeName(DexType type) {
@@ -359,23 +350,6 @@
     return state;
   }
 
-  private void renameArrayTypeIfNeeded(DexType type) {
-    if (type.isArrayType()) {
-      DexType base = type.toBaseType(appView.dexItemFactory());
-      DexString value = renaming.get(base);
-      if (value != null) {
-        int dimensions = type.descriptor.numberOfLeadingSquareBrackets();
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < dimensions; i++) {
-          builder.append('[');
-        }
-        builder.append(value.toString());
-        DexString descriptor = appView.dexItemFactory().createString(builder.toString());
-        renaming.put(type, descriptor);
-      }
-    }
-  }
-
   protected class Namespace implements InternalNamingState {
 
     private final String packageName;
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 42a9287..b7bfcdb 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
+import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
@@ -27,7 +28,7 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-class MinifiedRenaming extends NamingLens {
+class MinifiedRenaming extends NonIdentityNamingLens {
 
   final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Map<String, String> packageRenaming;
@@ -38,6 +39,7 @@
       ClassRenaming classRenaming,
       MethodRenaming methodRenaming,
       FieldRenaming fieldRenaming) {
+    super(appView.dexItemFactory());
     this.appView = appView;
     this.packageRenaming = classRenaming.packageRenaming;
     renaming.putAll(classRenaming.classRenaming);
@@ -51,7 +53,7 @@
   }
 
   @Override
-  public DexString lookupDescriptor(DexType type) {
+  protected DexString internalLookupClassDescriptor(DexType type) {
     return renaming.getOrDefault(type, type.descriptor);
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 3d27387..b886f2b 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -58,7 +58,7 @@
             new MinificationPackageNamingStrategy(appView),
             // Use deterministic class order to make sure renaming is deterministic.
             appView.appInfo().classesWithDeterministicOrder());
-    ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing, executorService);
+    ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
     timing.end();
 
     assert new MinifiedRenaming(
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index 77b5b12..9147b84 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -45,6 +45,13 @@
 
   public abstract DexString lookupDescriptor(DexType type);
 
+  public DexString lookupClassDescriptor(DexType type) {
+    assert type.isClassType();
+    return internalLookupClassDescriptor(type);
+  }
+
+  protected abstract DexString internalLookupClassDescriptor(DexType type);
+
   public abstract DexString lookupInnerName(InnerClassAttribute attribute, InternalOptions options);
 
   public abstract DexString lookupName(DexMethod method);
@@ -116,7 +123,7 @@
       return type.replaceBaseType(newBaseType, dexItemFactory);
     }
     assert type.isClassType();
-    return dexItemFactory.createType(lookupDescriptor(type));
+    return dexItemFactory.createType(lookupClassDescriptor(type));
   }
 
   public boolean hasPrefixRewritingLogic() {
@@ -179,7 +186,34 @@
     return true;
   }
 
-  private static class IdentityLens extends NamingLens {
+  public abstract static class NonIdentityNamingLens extends NamingLens {
+
+    private final DexItemFactory dexItemFactory;
+
+    protected NonIdentityNamingLens(DexItemFactory dexItemFactory) {
+      this.dexItemFactory = dexItemFactory;
+    }
+
+    protected DexItemFactory dexItemFactory() {
+      return dexItemFactory;
+    }
+
+    @Override
+    public final DexString lookupDescriptor(DexType type) {
+      if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) {
+        return type.getDescriptor();
+      }
+      if (type.isArrayType()) {
+        DexType baseType = type.toBaseType(dexItemFactory);
+        DexString desc = lookupDescriptor(baseType);
+        return desc.toArrayDescriptor(type.getNumberOfLeadingSquareBrackets(), dexItemFactory);
+      }
+      assert type.isClassType();
+      return lookupClassDescriptor(type);
+    }
+  }
+
+  private static final class IdentityLens extends NamingLens {
 
     private IdentityLens() {
       // Intentionally left empty.
@@ -191,6 +225,11 @@
     }
 
     @Override
+    protected DexString internalLookupClassDescriptor(DexType type) {
+      return type.descriptor;
+    }
+
+    @Override
     public DexString lookupInnerName(InnerClassAttribute attribute, InternalOptions options) {
       return attribute.getInnerName();
     }
diff --git a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
index a1451fc..e987def 100644
--- a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collections;
 import java.util.HashMap;
@@ -21,7 +22,7 @@
 import java.util.stream.Stream;
 
 // Naming lens for rewriting type prefixes.
-public class PrefixRewritingNamingLens extends NamingLens {
+public class PrefixRewritingNamingLens extends NonIdentityNamingLens {
 
   final NamingLens namingLens;
   final InternalOptions options;
@@ -40,6 +41,7 @@
   }
 
   public PrefixRewritingNamingLens(NamingLens namingLens, AppView<?> appView) {
+    super(appView.dexItemFactory());
     this.appView = appView;
     this.namingLens = namingLens;
     this.options = appView.options();
@@ -68,7 +70,7 @@
   }
 
   @Override
-  public DexString lookupDescriptor(DexType type) {
+  protected DexString internalLookupClassDescriptor(DexType type) {
     DexString renaming = getRenaming(type);
     return renaming != null ? renaming : namingLens.lookupDescriptor(type);
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index a29f8ef..fab1147 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -143,7 +143,7 @@
             new MinificationPackageNamingStrategy(appView),
             mappedClasses);
     ClassRenaming classRenaming =
-        classNameMinifier.computeRenaming(timing, executorService, syntheticCompanionClasses);
+        classNameMinifier.computeRenaming(timing, syntheticCompanionClasses);
     timing.end();
 
     ApplyMappingMemberNamingStrategy nameStrategy =
@@ -578,9 +578,9 @@
     }
 
     @Override
-    public DexString lookupDescriptor(DexType type) {
+    protected DexString internalLookupClassDescriptor(DexType type) {
       checkForUseOfNotMappedReference(type);
-      return super.lookupDescriptor(type);
+      return super.internalLookupClassDescriptor(type);
     }
 
     private void checkForUseOfNotMappedReference(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index 92b4402..e1a49d8 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -27,7 +29,7 @@
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
-// TODO(b/129925954): Reimplement this by using the internal encoding and transformation logic.
+// TODO(b/169516860): We should generalize this to handle rewriting of attributes in general.
 public class GenericSignatureRewriter {
 
   private final AppView<?> appView;
@@ -56,17 +58,27 @@
     ThreadUtils.processItems(
         classes,
         clazz -> {
+          GenericSignatureTypeRewriter genericSignatureTypeRewriter =
+              new GenericSignatureTypeRewriter(appView, clazz);
           GenericSignatureCollector genericSignatureCollector =
               new GenericSignatureCollector(clazz);
           GenericSignatureParser<DexType> genericSignatureParser =
               new GenericSignatureParser<>(genericSignatureCollector);
-          clazz.setAnnotations(
-              rewriteGenericSignatures(
-                  clazz.annotations(),
-                  genericSignatureParser::parseClassSignature,
-                  genericSignatureCollector::getRenamedSignature,
-                  (signature, e) ->
-                      options.warningInvalidSignature(clazz, clazz.getOrigin(), signature, e)));
+          ClassSignature classSignature = clazz.getClassSignature();
+          if (classSignature.hasSignature()) {
+            // TODO(b/129925954): We still have to rewrite to capture the lastWrittenType.
+            //  The design is utterly broken.
+            DexAnnotation classSignatureAnnotation =
+                DexAnnotation.createSignatureAnnotation(
+                    classSignature.toString(), options.itemFactory);
+            rewriteGenericSignatures(
+                new DexAnnotationSet(new DexAnnotation[] {classSignatureAnnotation}),
+                genericSignatureParser::parseClassSignature,
+                genericSignatureCollector::getRenamedSignature,
+                (signature, e) ->
+                    options.warningInvalidSignature(clazz, clazz.getOrigin(), signature, e));
+          }
+          clazz.setClassSignature(genericSignatureTypeRewriter.rewrite(classSignature));
           clazz.forEachField(
               field ->
                   field.setAnnotations(
@@ -91,6 +103,7 @@
         executorService);
   }
 
+  // TODO(b/129925954): Remove this when using modeled signatures for methods and fields.
   private DexAnnotationSet rewriteGenericSignatures(
       DexAnnotationSet annotations,
       Consumer<String> parser,
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 4edec01..55c58c6cd 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -11,16 +11,20 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 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.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Timing;
 import java.util.LinkedHashSet;
@@ -32,6 +36,7 @@
 
   private final DexApplication application;
   private final AppView<AppInfoWithLiveness> appView;
+  private final KeepInfoCollection keepInfo;
   private final SubtypingInfo subtypingInfo;
   private final MethodPoolCollection methodPoolCollection;
 
@@ -43,6 +48,7 @@
       SubtypingInfo subtypingInfo) {
     this.application = application;
     this.appView = appView;
+    this.keepInfo = appView.appInfo().getKeepInfo();
     this.subtypingInfo = subtypingInfo;
     this.methodPoolCollection =
         // We will add private instance methods when we promote them.
@@ -82,6 +88,12 @@
     return lensBuilder.build(appView);
   }
 
+  private void doPublicize(ProgramDefinition definition) {
+    definition.getAccessFlags().promoteToPublic();
+    keepInfo.mutate(
+        keepInfo -> keepInfo.unsetRequireAllowAccessModificationForRepackaging(definition));
+  }
+
   private void publicizeType(DexType type) {
     DexProgramClass clazz = asProgramClassOrNull(application.definitionFor(type));
     if (clazz != null) {
@@ -91,30 +103,31 @@
   }
 
   private void publicizeClass(DexProgramClass clazz) {
-    clazz.accessFlags.promoteToPublic();
+    doPublicize(clazz);
 
     // Publicize fields.
-    clazz.forEachField(
+    clazz.forEachProgramField(
         field -> {
-          if (field.isPublic()) {
+          DexEncodedField definition = field.getDefinition();
+          if (definition.isPublic()) {
             return;
           }
-          if (!appView.appInfo().isAccessModificationAllowed(field.field)) {
+          if (!appView.appInfo().isAccessModificationAllowed(field.getReference())) {
             // TODO(b/131130038): Also do not publicize package-private and protected fields that
             //  are kept.
-            if (field.isPrivate()) {
+            if (definition.isPrivate()) {
               return;
             }
           }
-          field.accessFlags.promoteToPublic();
+          doPublicize(field);
         });
 
     // Publicize methods.
     Set<DexEncodedMethod> privateInstanceMethods = new LinkedHashSet<>();
-    clazz.forEachMethod(
+    clazz.forEachProgramMethod(
         method -> {
-          if (publicizeMethod(clazz, method)) {
-            privateInstanceMethods.add(method);
+          if (publicizeMethod(method)) {
+            privateInstanceMethods.add(method.getDefinition());
           }
         });
     if (!privateInstanceMethods.isEmpty()) {
@@ -131,58 +144,58 @@
     }
   }
 
-  private boolean publicizeMethod(DexProgramClass holder, DexEncodedMethod method) {
-    MethodAccessFlags accessFlags = method.accessFlags;
+  private boolean publicizeMethod(ProgramMethod method) {
+    MethodAccessFlags accessFlags = method.getAccessFlags();
     if (accessFlags.isPublic()) {
       return false;
     }
     // If this method is mentioned in keep rules, do not transform (rule applications changed).
-    if (!appView.appInfo().isAccessModificationAllowed(method.method)) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (!appView.appInfo().isAccessModificationAllowed(method.getReference())) {
       // TODO(b/131130038): Also do not publicize package-private and protected methods that are
       //  kept.
-      if (method.isPrivate()) {
+      if (definition.isPrivate()) {
         return false;
       }
     }
 
-    if (!accessFlags.isPrivate() || appView.dexItemFactory().isConstructor(method.method)) {
+    if (!accessFlags.isPrivate() || appView.dexItemFactory().isConstructor(method.getReference())) {
       // TODO(b/150589374): This should check for dispatch targets or just abandon in
       //  package-private.
-      accessFlags.promoteToPublic();
+      doPublicize(method);
       return false;
     }
 
-    if (!accessFlags.isStatic()) {
-
-      // We can't publicize private instance methods in interfaces or methods that are copied from
-      // interfaces to lambda-desugared classes because this will be added as a new default method.
-      // TODO(b/111118390): It might be possible to transform it into static methods, though.
-      if (holder.isInterface() || accessFlags.isSynthetic()) {
-        return false;
-      }
-
-      boolean wasSeen = methodPoolCollection.markIfNotSeen(holder, method.method);
-      if (wasSeen) {
-        // We can't do anything further because even renaming is not allowed due to the keep rule.
-        if (!appView.appInfo().isMinificationAllowed(method.method)) {
-          return false;
-        }
-        // TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
-        return false;
-      }
-      lensBuilder.add(method.method);
-      accessFlags.promoteToFinal();
-      accessFlags.promoteToPublic();
-      // The method just became public and is therefore not a library override.
-      method.setLibraryMethodOverride(OptionalBool.FALSE);
-      return true;
+    if (accessFlags.isStatic()) {
+      // For private static methods we can just relax the access to public, since
+      // even though JLS prevents from declaring static method in derived class if
+      // an instance method with same signature exists in superclass, JVM actually
+      // does not take into account access of the static methods.
+      doPublicize(method);
+      return false;
     }
 
-    // For private static methods we can just relax the access to public, since
-    // even though JLS prevents from declaring static method in derived class if
-    // an instance method with same signature exists in superclass, JVM actually
-    // does not take into account access of the static methods.
-    accessFlags.promoteToPublic();
-    return false;
+    // We can't publicize private instance methods in interfaces or methods that are copied from
+    // interfaces to lambda-desugared classes because this will be added as a new default method.
+    // TODO(b/111118390): It might be possible to transform it into static methods, though.
+    if (method.getHolder().isInterface() || accessFlags.isSynthetic()) {
+      return false;
+    }
+
+    boolean wasSeen = methodPoolCollection.markIfNotSeen(method.getHolder(), method.getReference());
+    if (wasSeen) {
+      // We can't do anything further because even renaming is not allowed due to the keep rule.
+      if (!appView.appInfo().isMinificationAllowed(method.getReference())) {
+        return false;
+      }
+      // TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
+      return false;
+    }
+    lensBuilder.add(method.getReference());
+    accessFlags.promoteToFinal();
+    doPublicize(method);
+    // The method just became public and is therefore not a library override.
+    definition.setLibraryMethodOverride(OptionalBool.FALSE);
+    return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
new file mode 100644
index 0000000..5a9e20c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
@@ -0,0 +1,29 @@
+// 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.optimize;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+
+public class MemberRebindingUtils {
+
+  public static boolean isNonReboundMethodReference(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      DexMethod method,
+      ProgramMethod context) {
+    DexClass clazz = appView.definitionForHolder(method, context);
+    if (clazz == null) {
+      return false;
+    }
+    SingleResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOn(clazz, method).asSingleResolution();
+    return resolutionResult != null
+        && resolutionResult.getResolvedHolder().getType() != method.getHolderType();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
similarity index 82%
rename from src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
rename to src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index 4dc644c..0d2c4e8 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -1,7 +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.optimize;
+package com.android.tools.r8.optimize.bridgehoisting;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
@@ -31,9 +31,6 @@
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
-import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -73,11 +70,13 @@
 
   private final AppView<AppInfoWithLiveness> appView;
 
-  // A lens that keeps track of the changes for construction of the Proguard map.
-  private final BridgeHoistingLens.Builder lensBuilder = new BridgeHoistingLens.Builder();
+  // Structure that keeps track of the changes for construction of the Proguard map and
+  // AppInfoWithLiveness maintenance.
+  private final BridgeHoistingResult result;
 
   public BridgeHoisting(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
+    this.result = new BridgeHoistingResult(appView);
   }
 
   public void run() {
@@ -85,9 +84,30 @@
     BottomUpClassHierarchyTraversal.forProgramClasses(appView, subtypingInfo)
         .excludeInterfaces()
         .visit(appView.appInfo().classes(), clazz -> processClass(clazz, subtypingInfo));
-    if (!lensBuilder.isEmpty()) {
-      BridgeHoistingLens lens = lensBuilder.build(appView);
+    if (!result.isEmpty()) {
+      BridgeHoistingLens lens = result.buildLens();
       appView.rewriteWithLens(lens);
+
+      // Update method access info collection.
+      MethodAccessInfoCollection.Modifier methodAccessInfoCollectionModifier =
+          appView.appInfo().getMethodAccessInfoCollection().modifier();
+
+      // The bridge hoisting lens does not specify any code rewritings. Therefore references to the
+      // bridge methods are left as-is in the code, but they are rewritten to the hoisted bridges
+      // during the rewriting of AppInfoWithLiveness. Therefore, this conservatively records that
+      // there may be an invoke-virtual instruction that targets each of the removed bridges.
+      methodAccessInfoCollectionModifier.addAll(result.getBridgeMethodAccessInfoCollection());
+
+      // Additionally, we record the invokes from the newly synthesized bridge methods.
+      result.forEachHoistedBridge(
+          (bridge, bridgeInfo) -> {
+            if (bridgeInfo.isVirtualBridgeInfo()) {
+              DexMethod reference = bridgeInfo.asVirtualBridgeInfo().getInvokedMethod();
+              methodAccessInfoCollectionModifier.registerInvokeVirtualInContext(reference, bridge);
+            } else {
+              assert false;
+            }
+          });
     }
   }
 
@@ -120,40 +140,6 @@
     return candidates;
   }
 
-  /**
-   * Returns true if the bridge method is referencing a method in the superclass of {@param holder}.
-   * If this is not the case, we cannot hoist the bridge method, as that would lead to a type error:
-   * <code>
-   *   class A {
-   *     void bridge() {
-   *       v0 <- Argument
-   *       invoke-virtual {v0}, void B.m() // <- not valid
-   *       Return
-   *     }
-   *   }
-   *   class B extends A {
-   *     void m() {
-   *       ...
-   *     }
-   *   }
-   * </code>
-   */
-  private boolean bridgeIsTargetingMethodInSuperclass(
-      DexProgramClass holder, BridgeInfo bridgeInfo) {
-    if (bridgeInfo.isVirtualBridgeInfo()) {
-      VirtualBridgeInfo virtualBridgeInfo = bridgeInfo.asVirtualBridgeInfo();
-      DexMethod invokedMethod = virtualBridgeInfo.getInvokedMethod();
-      assert !appView.appInfo().isStrictSubtypeOf(invokedMethod.holder, holder.type);
-      if (invokedMethod.holder == holder.type) {
-        return false;
-      }
-      assert appView.appInfo().isStrictSubtypeOf(holder.type, invokedMethod.holder);
-      return true;
-    }
-    assert false;
-    return false;
-  }
-
   private void hoistBridgeIfPossible(
       DexMethod method, DexProgramClass clazz, Set<DexProgramClass> subclasses) {
     // If the method is defined on the parent class, we cannot hoist the bridge.
@@ -251,7 +237,7 @@
       newMethod.getAccessFlags().demoteFromFinal();
     }
     clazz.addVirtualMethod(newMethod);
-    lensBuilder.move(representative.getReference(), newMethodReference);
+    result.move(representative.getReference(), newMethodReference);
 
     // Remove all of the bridges in the eligible subclasses.
     for (DexProgramClass subclass : eligibleSubclasses) {
@@ -366,47 +352,4 @@
             code.getDebugInfo())
         : code;
   }
-
-  static class BridgeHoistingLens extends NestedGraphLens {
-
-    public BridgeHoistingLens(
-        AppView<?> appView, BiMap<DexMethod, DexMethod> originalMethodSignatures) {
-      super(
-          ImmutableMap.of(),
-          ImmutableMap.of(),
-          ImmutableMap.of(),
-          null,
-          originalMethodSignatures,
-          appView.graphLens(),
-          appView.dexItemFactory());
-    }
-
-    @Override
-    public boolean hasCodeRewritings() {
-      return getPrevious().hasCodeRewritings();
-    }
-
-    @Override
-    public boolean isLegitimateToHaveEmptyMappings() {
-      return true;
-    }
-
-    static class Builder {
-
-      private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
-
-      public boolean isEmpty() {
-        return originalMethodSignatures.isEmpty();
-      }
-
-      public void move(DexMethod from, DexMethod to) {
-        originalMethodSignatures.forcePut(to, originalMethodSignatures.getOrDefault(from, from));
-      }
-
-      public BridgeHoistingLens build(AppView<?> appView) {
-        assert !isEmpty();
-        return new BridgeHoistingLens(appView, originalMethodSignatures);
-      }
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
new file mode 100644
index 0000000..9fe3e29
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
@@ -0,0 +1,91 @@
+// 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.optimize.bridgehoisting;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
+import java.util.Set;
+
+class BridgeHoistingLens extends NonIdentityGraphLens {
+
+  // Mapping from non-hoisted bridge methods to hoisted bridge methods.
+  private final BidirectionalManyToOneMap<DexMethod, DexMethod> bridgeToHoistedBridgeMap;
+
+  public BridgeHoistingLens(
+      AppView<?> appView,
+      BidirectionalManyToOneMap<DexMethod, DexMethod> bridgeToHoistedBridgeMap) {
+    super(appView.dexItemFactory(), appView.graphLens());
+    this.bridgeToHoistedBridgeMap = bridgeToHoistedBridgeMap;
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    return getPrevious().getOriginalMethodSignature(internalGetPreviousMethodSignature(method));
+  }
+
+  @Override
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied);
+    return bridgeToHoistedBridgeMap.getOrDefault(renamedMethod, renamedMethod);
+  }
+
+  @Override
+  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    Set<DexMethod> bridges = bridgeToHoistedBridgeMap.getKeys(method);
+    return bridges.isEmpty() ? method : bridges.iterator().next();
+  }
+
+  @Override
+  public DexType getOriginalType(DexType type) {
+    return getPrevious().getOriginalType(type);
+  }
+
+  @Override
+  public DexField getOriginalFieldSignature(DexField field) {
+    return getPrevious().getOriginalFieldSignature(field);
+  }
+
+  @Override
+  public DexField getRenamedFieldSignature(DexField originalField) {
+    return getPrevious().getRenamedFieldSignature(originalField);
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return getPrevious().isContextFreeForMethods();
+  }
+
+  @Override
+  public boolean hasCodeRewritings() {
+    return getPrevious().hasCodeRewritings();
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    return previous;
+  }
+
+  @Override
+  protected MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    return previous;
+  }
+
+  @Override
+  protected DexType internalDescribeLookupClassType(DexType previous) {
+    return previous;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
new file mode 100644
index 0000000..09a9b27
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
@@ -0,0 +1,75 @@
+// 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.optimize.bridgehoisting;
+
+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.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+class BridgeHoistingResult {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  // Mapping from non-hoisted bridge methods to hoisted bridge methods.
+  private final BidirectionalManyToOneMap<DexMethod, DexMethod> bridgeToHoistedBridgeMap =
+      new BidirectionalManyToOneMap<>();
+
+  // Mapping from non-hoisted bridge methods to the set of contexts in which they are accessed.
+  private final MethodAccessInfoCollection.IdentityBuilder bridgeMethodAccessInfoCollectionBuilder =
+      MethodAccessInfoCollection.identityBuilder();
+
+  BridgeHoistingResult(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  public void forEachHoistedBridge(BiConsumer<ProgramMethod, BridgeInfo> consumer) {
+    bridgeToHoistedBridgeMap.forEach(
+        (bridges, hoistedBridge) -> {
+          DexProgramClass clazz = appView.definitionForProgramType(hoistedBridge.getHolderType());
+          ProgramMethod method = hoistedBridge.lookupOnProgramClass(clazz);
+          if (method != null) {
+            consumer.accept(method, method.getDefinition().getOptimizationInfo().getBridgeInfo());
+          }
+        });
+  }
+
+  public MethodAccessInfoCollection getBridgeMethodAccessInfoCollection() {
+    return bridgeMethodAccessInfoCollectionBuilder.build();
+  }
+
+  public boolean isEmpty() {
+    return bridgeToHoistedBridgeMap.isEmpty();
+  }
+
+  public void move(DexMethod from, DexMethod to) {
+    Set<DexMethod> keys = bridgeToHoistedBridgeMap.getKeys(from);
+    if (keys.isEmpty()) {
+      bridgeToHoistedBridgeMap.put(from, to);
+    } else {
+      for (DexMethod original : keys) {
+        bridgeToHoistedBridgeMap.put(original, to);
+      }
+    }
+
+    MethodAccessInfoCollection methodAccessInfoCollection =
+        appView.appInfo().getMethodAccessInfoCollection();
+    methodAccessInfoCollection.forEachVirtualInvokeContext(
+        from,
+        context ->
+            bridgeMethodAccessInfoCollectionBuilder.registerInvokeVirtualInContext(from, context));
+  }
+
+  public BridgeHoistingLens buildLens() {
+    assert !isEmpty();
+    return new BridgeHoistingLens(appView, bridgeToHoistedBridgeMap);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
index 50a6e69..09ca5bd 100644
--- a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
 import com.android.tools.r8.references.PackageReference;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -95,23 +96,18 @@
     return new RelocatorNamingLens(typeMappings, packingMappings.build(), appView.dexItemFactory());
   }
 
-  Map<DexType, DexString> getTypeMappings() {
-    return typeMappings;
-  }
-
-  private static class RelocatorNamingLens extends NamingLens {
+  private static class RelocatorNamingLens extends NonIdentityNamingLens {
 
     private final Map<DexType, DexString> typeMappings;
     private final Map<String, String> packageMappings;
-    private final DexItemFactory factory;
 
     private RelocatorNamingLens(
         Map<DexType, DexString> typeMappings,
         Map<String, String> packageMappings,
         DexItemFactory factory) {
+      super(factory);
       this.typeMappings = typeMappings;
       this.packageMappings = packageMappings;
-      this.factory = factory;
     }
 
     @Override
@@ -120,20 +116,7 @@
     }
 
     @Override
-    public DexString lookupDescriptor(DexType type) {
-      if (type.isPrimitiveType() || type.isVoidType()) {
-        return type.descriptor;
-      }
-      if (type.isArrayType()) {
-        DexType baseType = type.toBaseType(factory);
-        if (baseType == null || baseType.isPrimitiveType()) {
-          return type.descriptor;
-        }
-        String baseDescriptor = typeMappings.getOrDefault(baseType, baseType.descriptor).toString();
-        return factory.createString(
-            DescriptorUtils.toArrayDescriptor(
-                type.getNumberOfLeadingSquareBrackets(), baseDescriptor));
-      }
+    protected DexString internalLookupClassDescriptor(DexType type) {
       return typeMappings.getOrDefault(type, type.descriptor);
     }
 
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 95ee828..79b2e1e 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -96,7 +96,7 @@
           repackagingConfiguration.getNewPackageDescriptor(pkg, seenPackageDescriptors);
       if (pkg.getPackageDescriptor().equals(newPackageDescriptor)) {
         for (DexProgramClass alreadyRepackagedClass : pkg) {
-          if (!appView.appInfo().isRepackagingAllowed(alreadyRepackagedClass.getType())) {
+          if (!appView.appInfo().isRepackagingAllowed(alreadyRepackagedClass)) {
             mappings.put(alreadyRepackagedClass.getType(), alreadyRepackagedClass.getType());
           }
         }
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index fbaf1b8..b4760c1 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -54,7 +54,7 @@
     // package descriptor.
     boolean hasPinnedItem = false;
     for (DexProgramClass clazz : pkg) {
-      boolean isPinned = !appView.appInfo().isRepackagingAllowed(clazz.getType());
+      boolean isPinned = !appView.appInfo().isRepackagingAllowed(clazz);
       Node classNode = createNode(clazz);
       if (isPinned) {
         pinnedNodes.add(classNode);
@@ -136,6 +136,14 @@
     // Trace the type references in the method signature.
     definition.getProto().forEachType(registry::registerTypeReference);
 
+    // Check if this overrides a package-private method.
+    DexProgramClass superClass =
+        appView.programDefinitionFor(method.getHolder().getSuperType(), method.getHolder());
+    if (superClass != null) {
+      registry.registerMemberAccess(
+          appView.appInfo().resolveMethodOn(superClass, method.getReference()));
+    }
+
     // Trace the references in the method and method parameter annotations.
     RepackagingAnnotationTracer annotationTracer =
         new RepackagingAnnotationTracer(appView, registry);
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
index f519f01..8820838 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
@@ -69,10 +69,11 @@
             fixupType(clazz.superType),
             fixupTypeList(clazz.interfaces),
             clazz.getSourceFile(),
-            fixupNestHost(clazz.getNestHost()),
+            fixupNestHost(clazz.getNestHostClassAttribute()),
             fixupNestMemberAttributes(clazz.getNestMembersClassAttributes()),
             fixupEnclosingMethodAttribute(clazz.getEnclosingMethodAttribute()),
             fixupInnerClassAttributes(clazz.getInnerClasses()),
+            clazz.getClassSignature(),
             clazz.annotations(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
@@ -196,8 +197,10 @@
         .createMethod(fixupType(method.holder), fixupProto(method.proto), method.name);
   }
 
-  private NestHostClassAttribute fixupNestHost(DexType type) {
-    return type != null ? new NestHostClassAttribute(fixupType(type)) : null;
+  private NestHostClassAttribute fixupNestHost(NestHostClassAttribute nestHostClassAttribute) {
+    return nestHostClassAttribute != null
+        ? new NestHostClassAttribute(fixupType(nestHostClassAttribute.getNestHost()))
+        : null;
   }
 
   private List<NestMemberClassAttribute> fixupNestMemberAttributes(
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index b27182d..57361d3 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MemberResolutionResult;
 import com.android.tools.r8.graph.ProgramDefinition;
@@ -32,6 +33,7 @@
   private final AppInfoWithLiveness appInfo;
   private final RepackagingConstraintGraph constraintGraph;
   private final ProgramDefinition context;
+  private final InitClassLens initClassLens;
   private final RepackagingConstraintGraph.Node node;
 
   public RepackagingUseRegistry(
@@ -42,6 +44,7 @@
     this.appInfo = appView.appInfo();
     this.constraintGraph = constraintGraph;
     this.context = context;
+    this.initClassLens = appView.initClassLens();
     this.node = constraintGraph.getNode(context.getDefinition());
   }
 
@@ -145,7 +148,7 @@
 
   @Override
   public void registerInitClass(DexType type) {
-    registerTypeAccess(type);
+    registerFieldAccess(initClassLens.getInitClassField(type));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index 64cf59a..9b6694f 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.utils.StringDiagnostic;
 import java.io.IOException;
 import java.util.List;
 import java.util.function.Consumer;
@@ -136,11 +135,6 @@
       if (this.retracedStackTraceConsumer == null) {
         throw new RuntimeException("RetracedStackConsumer not specified");
       }
-      if (isVerbose && regularExpression != null) {
-        this.diagnosticsHandler.warning(
-            new StringDiagnostic(
-                "Retrace does not support verbose output when a regular expression is specified"));
-      }
       return new RetraceCommand(
           isVerbose,
           regularExpression,
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index 0f7d547..31619e7 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -19,6 +19,7 @@
 
   private Retracer(ClassNameMapper classNameMapper) {
     this.classNameMapper = classNameMapper;
+    assert classNameMapper != null;
   }
 
   public static RetraceApi create(ClassNameMapper classNameMapper) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 1a8d708..4fa3212 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -83,6 +83,9 @@
         assert !DexAnnotation.isMemberClassesAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingMethodAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingClassAnnotation(annotation, dexItemFactory);
+        // TODO(b/129925954): Signature is being represented as a class attribute.
+        assert !holder.isDexClass()
+            || !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
         if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) {
           return true;
         }
@@ -184,15 +187,6 @@
     }
   }
 
-  private static boolean hasSignatureAnnotation(DexProgramClass clazz, DexItemFactory itemFactory) {
-    for (DexAnnotation annotation : clazz.annotations().annotations) {
-      if (DexAnnotation.isSignatureAnnotation(annotation, itemFactory)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   public void run() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       stripAttributes(clazz);
@@ -291,6 +285,9 @@
           hasInnerClassesFromSet(clazz, classesToRetainInnerClassAttributeFor);
     }
     if (keptAnyway || keepForThisInnerClass || keepForThisEnclosingClass) {
+      if (!keep.signature) {
+        clazz.clearClassSignature();
+      }
       if (!keep.enclosingMethod) {
         clazz.clearEnclosingMethodAttribute();
       }
@@ -325,6 +322,7 @@
       // reflection. (Note that clearing these attributes can enable more vertical class merging.)
       clazz.clearEnclosingMethodAttribute();
       clazz.clearInnerClasses();
+      clazz.clearClassSignature();
     }
   }
 
@@ -368,7 +366,7 @@
         Map<DexType, DexProgramClass> enclosingClasses = new IdentityHashMap<>();
         Set<DexProgramClass> genericClasses = Sets.newIdentityHashSet();
         for (DexProgramClass clazz : appView.appInfo().classes()) {
-          if (hasSignatureAnnotation(clazz, appView.dexItemFactory())) {
+          if (clazz.getClassSignature().hasSignature()) {
             genericClasses.add(clazz);
           }
           for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
@@ -387,7 +385,7 @@
               if (appView.appInfo().isNonProgramTypeOrLiveProgramType(inner)) {
                 result.add(inner);
               }
-              DexType context = innerClassAttribute.getLiveContext(appView.appInfo());
+              DexType context = innerClassAttribute.getLiveContext(appView);
               if (context != null && appView.appInfo().isNonProgramTypeOrLiveProgramType(context)) {
                 result.add(context);
               }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index acb52c5..b0ee416 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
@@ -477,18 +478,18 @@
               DexMethod method = reference.asDexMethod();
               DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(method.holder));
               if (clazz != null) {
-                DexEncodedMethod definition = clazz.lookupMethod(method);
+                ProgramMethod definition = clazz.lookupProgramMethod(method);
                 if (definition != null) {
-                  collection.pinMethod(clazz, definition);
+                  collection.pinMethod(definition);
                 }
               }
             } else {
               DexField field = reference.asDexField();
               DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(field.holder));
               if (clazz != null) {
-                DexEncodedField definition = clazz.lookupField(field);
+                ProgramField definition = clazz.lookupProgramField(field);
                 if (definition != null) {
-                  collection.pinField(clazz, definition);
+                  collection.pinField(definition);
                 }
               }
             }
@@ -912,9 +913,22 @@
     return keepInfo.getInfo(reference, this).isAccessModificationAllowed(options());
   }
 
-  public boolean isRepackagingAllowed(DexType type) {
-    return options().isRepackagingEnabled()
-        && keepInfo.getClassInfo(type, this).isRepackagingAllowed(options());
+  public boolean isRepackagingAllowed(DexProgramClass clazz) {
+    if (!options().isRepackagingEnabled()) {
+      return false;
+    }
+    if (!keepInfo.getInfo(clazz).isRepackagingAllowed(options())) {
+      return false;
+    }
+    return clazz
+        .traverseProgramMethods(
+            member -> {
+              if (keepInfo.getInfo(member).isRepackagingAllowed(options())) {
+                return TraversalContinuation.CONTINUE;
+              }
+              return TraversalContinuation.BREAK;
+            })
+        .shouldContinue();
   }
 
   public boolean isPinned(DexReference reference) {
@@ -951,6 +965,7 @@
     if (!removedClasses.isEmpty()) {
       // Rebuild the hierarchy.
       objectAllocationInfoCollection.mutate(mutator -> {}, this);
+      keepInfo.mutate(keepInfo -> keepInfo.removeKeepInfoForPrunedItems(removedClasses));
     }
     return new AppInfoWithLiveness(this, application, removedClasses, additionalPinnedItems);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
index 64b1ef0..fc5c0b2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -47,7 +47,7 @@
     // Use an existing static field if there is one.
     DexEncodedField encodedClinitField = null;
     for (DexEncodedField staticField : clazz.staticFields()) {
-      // We need to field to be accessible from the contexts in which it is accessed.
+      // We need the field to be accessible from the contexts in which it is accessed.
       if (!isMinimumRequiredVisibility(staticField, minimumRequiredVisibility)) {
         continue;
       }
@@ -56,8 +56,18 @@
       if (staticField.field.type.isWideType()) {
         continue;
       }
-      encodedClinitField = staticField;
-      break;
+      if (encodedClinitField == null) {
+        encodedClinitField = staticField;
+      } else {
+        // Prefer the field that is most visible.
+        if (staticField.getAccessFlags().getVisibilityOrdinal()
+            > encodedClinitField.getAccessFlags().getVisibilityOrdinal()) {
+          encodedClinitField = staticField;
+        }
+      }
+      if (encodedClinitField.isPublic()) {
+        break;
+      }
     }
     if (encodedClinitField == null) {
       FieldAccessFlags accessFlags =
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index a210ef7..cd02bbb 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -54,6 +54,7 @@
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -679,7 +680,7 @@
 
   private void enqueueRootField(
       ProgramField field, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
-    keepFieldWithRules(field.getHolder(), field.getDefinition(), rules);
+    keepFieldWithRules(field, rules);
     workList.enqueueMarkFieldKeptAction(
         field, graphReporter.reportKeepField(precondition, rules, field.getDefinition()));
   }
@@ -697,7 +698,7 @@
 
   private void enqueueRootMethod(
       ProgramMethod method, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
-    keepMethodWithRules(method.getHolder(), method.getDefinition(), rules);
+    keepMethodWithRules(method, rules);
     workList.enqueueMarkMethodKeptAction(
         method, graphReporter.reportKeepMethod(precondition, rules, method.getDefinition()));
   }
@@ -2605,13 +2606,13 @@
   }
 
   private void markEnumValuesAsReachable(DexProgramClass clazz, KeepReason reason) {
-    DexEncodedMethod valuesMethod = clazz.lookupMethod(generatedEnumValuesMethod(clazz));
+    ProgramMethod valuesMethod = clazz.lookupProgramMethod(generatedEnumValuesMethod(clazz));
     if (valuesMethod != null) {
       // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
       // marking for not renaming it is in the root set.
-      workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason);
-      keepInfo.keepMethod(clazz, valuesMethod);
-      shouldNotBeMinified(valuesMethod.toReference());
+      workList.enqueueMarkMethodKeptAction(valuesMethod, reason);
+      keepInfo.keepMethod(valuesMethod);
+      shouldNotBeMinified(valuesMethod.getReference());
     }
   }
 
@@ -2734,25 +2735,26 @@
     return appInfoWithLiveness;
   }
 
-  public NestedGraphLens buildGraphLens(AppView<?> appView) {
+  public NestedGraphLens buildGraphLens() {
     return lambdaRewriter != null ? lambdaRewriter.fixup() : null;
   }
 
   private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinClass(clazz, info -> applyKeepRules(rules, info));
+    keepInfo.joinClass(clazz, info -> applyKeepRules(clazz, rules, info));
   }
 
-  private void keepMethodWithRules(
-      DexProgramClass holder, DexEncodedMethod method, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinMethod(holder, method, info -> applyKeepRules(rules, info));
+  private void keepMethodWithRules(ProgramMethod method, Set<ProguardKeepRuleBase> rules) {
+    keepInfo.joinMethod(method, info -> applyKeepRules(method, rules, info));
   }
 
-  private void keepFieldWithRules(
-      DexProgramClass holder, DexEncodedField field, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinField(holder, field, info -> applyKeepRules(rules, info));
+  private void keepFieldWithRules(ProgramField field, Set<ProguardKeepRuleBase> rules) {
+    keepInfo.joinField(field, info -> applyKeepRules(field, rules, info));
   }
 
-  private void applyKeepRules(Set<ProguardKeepRuleBase> rules, KeepInfo.Joiner<?, ?, ?> joiner) {
+  private void applyKeepRules(
+      ProgramDefinition definition,
+      Set<ProguardKeepRuleBase> rules,
+      KeepInfo.Joiner<?, ?, ?> joiner) {
     for (ProguardKeepRuleBase rule : rules) {
       ProguardKeepRuleModifiers modifiers =
           (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
@@ -2760,6 +2762,9 @@
       if (!modifiers.allowsShrinking) {
         // TODO(b/159589281): Evaluate this interpretation.
         joiner.pin();
+        if (!definition.getAccessFlags().isPublic()) {
+          joiner.requireAccessModificationForRepackaging();
+        }
       }
       if (!modifiers.allowsObfuscation) {
         joiner.disallowMinification();
@@ -3676,8 +3681,9 @@
             clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
       }
       if (!keepInfo.getFieldInfo(encodedField, clazz).isPinned()) {
-        keepInfo.pinField(clazz, encodedField);
-        markFieldAsKept(new ProgramField(clazz, encodedField), KeepReason.reflectiveUseIn(method));
+        ProgramField programField = new ProgramField(clazz, encodedField);
+        keepInfo.pinField(programField);
+        markFieldAsKept(programField, KeepReason.reflectiveUseIn(method));
       }
     } else {
       assert identifierItem.isDexMethod();
@@ -3869,10 +3875,11 @@
 
         // Also pin all of its virtual methods to ensure that the devirtualizer does not perform
         // illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
-        for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
-          keepInfo.pinMethod(clazz, virtualMethod);
-          markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
-        }
+        clazz.forEachProgramVirtualMethod(
+            virtualMethod -> {
+              keepInfo.pinMethod(virtualMethod);
+              markVirtualMethodAsReachable(virtualMethod.getReference(), true, null, reason);
+            });
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index 1e32804..40b2c9e 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -24,7 +24,8 @@
     super(builder);
   }
 
-  private Builder builder() {
+  @Override
+  Builder builder() {
     return new Builder(this);
   }
 
@@ -33,18 +34,11 @@
     return new Joiner(this);
   }
 
-  /**
-   * True if a class may be repackaged.
-   *
-   * <p>This method requires knowledge of the global configuration as that can override the concrete
-   * value on a given item.
-   */
+  @Override
   public boolean isRepackagingAllowed(GlobalKeepInfoConfiguration configuration) {
-    return configuration.isRepackagingEnabled() && internalIsRepackagingAllowed();
-  }
-
-  boolean internalIsRepackagingAllowed() {
-    return internalIsMinificationAllowed();
+    return configuration.isRepackagingEnabled()
+        && internalIsMinificationAllowed()
+        && !internalIsAccessModificationRequiredForRepackaging();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
index 6f22db4..bdfb7cc 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.shaking;
 
 /** Immutable keep requirements for a field. */
-public final class KeepFieldInfo extends KeepInfo<KeepFieldInfo.Builder, KeepFieldInfo> {
+public final class KeepFieldInfo extends KeepMemberInfo<KeepFieldInfo.Builder, KeepFieldInfo> {
 
   // Requires all aspects of a field to be kept.
   private static final KeepFieldInfo TOP = new Builder().makeTop().build();
@@ -26,6 +26,7 @@
 
   // This builder is not private as there are known instances where it is safe to modify keep info
   // in a non-upwards direction.
+  @Override
   Builder builder() {
     return new Builder(this);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index a1f8424..4cb677f 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -6,23 +6,34 @@
 import com.android.tools.r8.shaking.KeepInfo.Builder;
 
 /** Keep information that can be associated with any item, i.e., class, method or field. */
-public abstract class KeepInfo<B extends Builder, K extends KeepInfo> {
+public abstract class KeepInfo<B extends Builder<B, K>, K extends KeepInfo<B, K>> {
 
   private final boolean pinned;
   private final boolean allowMinification;
   private final boolean allowAccessModification;
+  private final boolean requireAccessModificationForRepackaging;
 
-  private KeepInfo(boolean pinned, boolean allowMinification, boolean allowAccessModification) {
+  private KeepInfo(
+      boolean pinned,
+      boolean allowMinification,
+      boolean allowAccessModification,
+      boolean requireAccessModificationForRepackaging) {
     this.pinned = pinned;
     this.allowMinification = allowMinification;
     this.allowAccessModification = allowAccessModification;
+    this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
   }
 
   KeepInfo(B builder) {
     this(
-        builder.isPinned(), builder.isMinificationAllowed(), builder.isAccessModificationAllowed());
+        builder.isPinned(),
+        builder.isMinificationAllowed(),
+        builder.isAccessModificationAllowed(),
+        builder.isAccessModificationRequiredForRepackaging());
   }
 
+  abstract B builder();
+
   /** True if an item must be present in the output. */
   public boolean isPinned() {
     return pinned;
@@ -43,6 +54,18 @@
   }
 
   /**
+   * True if an item may be repackaged.
+   *
+   * <p>This method requires knowledge of the global configuration as that can override the concrete
+   * value on a given item.
+   */
+  public abstract boolean isRepackagingAllowed(GlobalKeepInfoConfiguration configuration);
+
+  boolean internalIsAccessModificationRequiredForRepackaging() {
+    return requireAccessModificationForRepackaging;
+  }
+
+  /**
    * True if an item may have its access flags modified.
    *
    * <p>This method requires knowledge of the global access modification as that will override the
@@ -71,7 +94,7 @@
   }
 
   /** Builder to construct an arbitrary keep info object. */
-  public abstract static class Builder<B extends Builder, K extends KeepInfo> {
+  public abstract static class Builder<B extends Builder<B, K>, K extends KeepInfo<B, K>> {
 
     abstract B self();
 
@@ -87,6 +110,7 @@
     private boolean pinned;
     private boolean allowMinification;
     private boolean allowAccessModification;
+    private boolean requireAccessModificationForRepackaging;
 
     Builder() {
       // Default initialized. Use should be followed by makeTop/makeBottom.
@@ -97,11 +121,14 @@
       pinned = original.isPinned();
       allowMinification = original.internalIsMinificationAllowed();
       allowAccessModification = original.internalIsAccessModificationAllowed();
+      requireAccessModificationForRepackaging =
+          original.internalIsAccessModificationRequiredForRepackaging();
     }
 
     B makeTop() {
       pin();
       disallowMinification();
+      requireAccessModificationForRepackaging();
       disallowAccessModification();
       return self();
     }
@@ -109,6 +136,7 @@
     B makeBottom() {
       unpin();
       allowMinification();
+      unsetRequireAccessModificationForRepackaging();
       allowAccessModification();
       return self();
     }
@@ -131,6 +159,8 @@
     private boolean internalIsEqualTo(K other) {
       return isPinned() == other.isPinned()
           && isMinificationAllowed() == other.internalIsMinificationAllowed()
+          && isAccessModificationRequiredForRepackaging()
+              == other.internalIsAccessModificationRequiredForRepackaging()
           && isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
           && isEqualTo(other);
     }
@@ -143,6 +173,10 @@
       return allowMinification;
     }
 
+    public boolean isAccessModificationRequiredForRepackaging() {
+      return requireAccessModificationForRepackaging;
+    }
+
     public boolean isAccessModificationAllowed() {
       return allowAccessModification;
     }
@@ -173,6 +207,20 @@
       return setAllowMinification(false);
     }
 
+    public B setRequireAccessModificationForRepackaging(
+        boolean requireAccessModificationForRepackaging) {
+      this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
+      return self();
+    }
+
+    public B requireAccessModificationForRepackaging() {
+      return setRequireAccessModificationForRepackaging(true);
+    }
+
+    public B unsetRequireAccessModificationForRepackaging() {
+      return setRequireAccessModificationForRepackaging(false);
+    }
+
     public B setAllowAccessModification(boolean allowAccessModification) {
       this.allowAccessModification = allowAccessModification;
       return self();
@@ -189,7 +237,7 @@
 
   /** Joiner to construct monotonically increasing keep info object. */
   public abstract static class Joiner<
-      J extends Joiner, B extends Builder, K extends KeepInfo<B, K>> {
+      J extends Joiner<J, B, K>, B extends Builder<B, K>, K extends KeepInfo<B, K>> {
 
     abstract J self();
 
@@ -223,6 +271,11 @@
       return self();
     }
 
+    public J requireAccessModificationForRepackaging() {
+      builder.requireAccessModificationForRepackaging();
+      return self();
+    }
+
     public K join() {
       K joined = builder.build();
       K original = builder.original;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index d2cbf66..0881bef 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -17,6 +16,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
@@ -27,6 +27,7 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Consumer;
 
 // Non-mutable collection of keep information pertaining to a program.
@@ -104,7 +105,7 @@
     return definition == null ? KeepFieldInfo.bottom() : getFieldInfo(definition, holder);
   }
 
-  public final KeepInfo getInfo(DexReference reference, DexDefinitionSupplier definitions) {
+  public final KeepInfo<?, ?> getInfo(DexReference reference, DexDefinitionSupplier definitions) {
     if (reference.isDexType()) {
       return getClassInfo(reference.asDexType(), definitions);
     }
@@ -117,6 +118,19 @@
     throw new Unreachable();
   }
 
+  public final KeepInfo<?, ?> getInfo(ProgramDefinition definition) {
+    if (definition.isProgramClass()) {
+      return getClassInfo(definition.asProgramClass());
+    }
+    if (definition.isProgramMethod()) {
+      return getMethodInfo(definition.asProgramMethod());
+    }
+    if (definition.isProgramField()) {
+      return getFieldInfo(definition.asProgramField());
+    }
+    throw new Unreachable();
+  }
+
   public final boolean isPinned(DexReference reference, DexDefinitionSupplier definitions) {
     return getInfo(reference, definitions).isPinned();
   }
@@ -197,6 +211,12 @@
       this.ruleInstances = ruleInstances;
     }
 
+    public void removeKeepInfoForPrunedItems(Set<DexType> removedClasses) {
+      keepClassInfo.keySet().removeIf(removedClasses::contains);
+      keepFieldInfo.keySet().removeIf(field -> removedClasses.contains(field.getHolderType()));
+      keepMethodInfo.keySet().removeIf(method -> removedClasses.contains(method.getHolderType()));
+    }
+
     @Override
     public KeepInfoCollection rewrite(NonIdentityGraphLens lens, InternalOptions options) {
       Map<DexType, KeepClassInfo> newClassInfo = new IdentityHashMap<>(keepClassInfo.size());
@@ -204,7 +224,8 @@
           (type, info) -> {
             DexType newType = lens.lookupType(type);
             assert newType == type || !info.isPinned() || info.isMinificationAllowed(options);
-            newClassInfo.put(newType, info);
+            KeepClassInfo previous = newClassInfo.put(newType, info);
+            assert previous == null;
           });
       Map<DexMethod, KeepMethodInfo> newMethodInfo = new IdentityHashMap<>(keepMethodInfo.size());
       keepMethodInfo.forEach(
@@ -222,7 +243,9 @@
                     .allMatch(x -> x);
             assert !info.isPinned()
                 || newMethod.getReturnType() == lens.lookupType(method.getReturnType());
-            newMethodInfo.put(newMethod, info);
+            KeepMethodInfo previous = newMethodInfo.put(newMethod, info);
+            // TODO(b/169927809): Avoid collisions.
+            // assert previous == null;
           });
       Map<DexField, KeepFieldInfo> newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size());
       keepFieldInfo.forEach(
@@ -231,7 +254,8 @@
             assert newField.name == field.name
                 || !info.isPinned()
                 || info.isMinificationAllowed(options);
-            newFieldInfo.put(newField, info);
+            KeepFieldInfo previous = newFieldInfo.put(newField, info);
+            assert previous == null;
           });
       Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> newRuleInstances =
           new IdentityHashMap<>(ruleInstances.size());
@@ -315,17 +339,17 @@
       } else if (reference.isDexMethod()) {
         DexMethod method = reference.asDexMethod();
         DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(method.holder));
-        DexEncodedMethod definition = method.lookupOnClass(clazz);
+        ProgramMethod definition = method.lookupOnProgramClass(clazz);
         if (definition != null) {
-          joinMethod(clazz, definition, fn::accept);
+          joinMethod(definition, fn::accept);
         }
       } else {
         assert reference.isDexField();
         DexField field = reference.asDexField();
         DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(field.holder));
-        DexEncodedField definition = field.lookupOnClass(clazz);
+        ProgramField definition = field.lookupOnProgramClass(clazz);
         if (definition != null) {
-          joinField(clazz, definition, fn::accept);
+          joinField(definition, fn::accept);
         }
       }
     }
@@ -338,9 +362,8 @@
       joinClass(clazz, KeepInfo.Joiner::pin);
     }
 
-    public void joinMethod(
-        DexProgramClass holder, DexEncodedMethod method, Consumer<KeepMethodInfo.Joiner> fn) {
-      KeepMethodInfo info = getMethodInfo(method, holder);
+    public void joinMethod(ProgramMethod method, Consumer<KeepMethodInfo.Joiner> fn) {
+      KeepMethodInfo info = getMethodInfo(method);
       if (info == KeepMethodInfo.top()) {
         return;
       }
@@ -348,24 +371,16 @@
       fn.accept(joiner);
       KeepMethodInfo joined = joiner.join();
       if (!info.equals(joined)) {
-        keepMethodInfo.put(method.method, joined);
+        keepMethodInfo.put(method.getReference(), joined);
       }
     }
 
-    public void joinMethod(ProgramMethod programMethod, Consumer<KeepMethodInfo.Joiner> fn) {
-      joinMethod(programMethod.getHolder(), programMethod.getDefinition(), fn);
+    public void keepMethod(ProgramMethod method) {
+      joinMethod(method, KeepInfo.Joiner::top);
     }
 
-    public void keepMethod(ProgramMethod programMethod) {
-      keepMethod(programMethod.getHolder(), programMethod.getDefinition());
-    }
-
-    public void keepMethod(DexProgramClass holder, DexEncodedMethod method) {
-      joinMethod(holder, method, KeepInfo.Joiner::top);
-    }
-
-    public void pinMethod(DexProgramClass holder, DexEncodedMethod method) {
-      joinMethod(holder, method, KeepInfo.Joiner::pin);
+    public void pinMethod(ProgramMethod method) {
+      joinMethod(method, KeepInfo.Joiner::pin);
     }
 
     public void unsafeAllowMinificationOfMethod(ProgramMethod method) {
@@ -396,9 +411,31 @@
       }
     }
 
-    public void joinField(
-        DexProgramClass holder, DexEncodedField field, Consumer<KeepFieldInfo.Joiner> fn) {
-      KeepFieldInfo info = getFieldInfo(field, holder);
+    public void unsetRequireAllowAccessModificationForRepackaging(ProgramDefinition definition) {
+      if (definition.isProgramClass()) {
+        DexProgramClass clazz = definition.asProgramClass();
+        KeepClassInfo info = getClassInfo(clazz);
+        keepClassInfo.put(
+            clazz.getType(), info.builder().unsetRequireAccessModificationForRepackaging().build());
+      } else if (definition.isProgramMethod()) {
+        ProgramMethod method = definition.asProgramMethod();
+        KeepMethodInfo info = getMethodInfo(method);
+        keepMethodInfo.put(
+            method.getReference(),
+            info.builder().unsetRequireAccessModificationForRepackaging().build());
+      } else if (definition.isProgramField()) {
+        ProgramField field = definition.asProgramField();
+        KeepFieldInfo info = getFieldInfo(field);
+        keepFieldInfo.put(
+            field.getReference(),
+            info.builder().unsetRequireAccessModificationForRepackaging().build());
+      } else {
+        throw new Unreachable();
+      }
+    }
+
+    public void joinField(ProgramField field, Consumer<KeepFieldInfo.Joiner> fn) {
+      KeepFieldInfo info = getFieldInfo(field);
       if (info.isTop()) {
         return;
       }
@@ -406,29 +443,16 @@
       fn.accept(joiner);
       KeepFieldInfo joined = joiner.join();
       if (!info.equals(joined)) {
-        keepFieldInfo.put(field.field, joined);
+        keepFieldInfo.put(field.getReference(), joined);
       }
     }
 
-    public void keepField(ProgramField programField) {
-      keepField(programField.getHolder(), programField.getDefinition());
+    public void keepField(ProgramField field) {
+      joinField(field, KeepInfo.Joiner::top);
     }
 
-    public void keepField(DexProgramClass holder, DexEncodedField field) {
-      joinField(holder, field, KeepInfo.Joiner::top);
-    }
-
-    public void pinField(DexProgramClass holder, DexEncodedField field) {
-      joinField(holder, field, KeepInfo.Joiner::pin);
-    }
-
-    public void keepMember(DexProgramClass holder, DexEncodedMember<?, ?> member) {
-      if (member.isDexEncodedMethod()) {
-        keepMethod(holder, member.asDexEncodedMethod());
-      } else {
-        assert member.isDexEncodedField();
-        keepField(holder, member.asDexEncodedField());
-      }
+    public void pinField(ProgramField field) {
+      joinField(field, KeepInfo.Joiner::pin);
     }
 
     public void unsafeAllowMinificationOfField(ProgramField field) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
new file mode 100644
index 0000000..770e84c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
@@ -0,0 +1,21 @@
+// 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;
+
+import com.android.tools.r8.shaking.KeepInfo.Builder;
+
+/** Immutable keep requirements for a member. */
+public abstract class KeepMemberInfo<B extends Builder<B, K>, K extends KeepInfo<B, K>>
+    extends KeepInfo<B, K> {
+
+  KeepMemberInfo(B builder) {
+    super(builder);
+  }
+
+  @Override
+  public boolean isRepackagingAllowed(GlobalKeepInfoConfiguration configuration) {
+    return configuration.isRepackagingEnabled()
+        && !internalIsAccessModificationRequiredForRepackaging();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index 98eae1b..ab243b6 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.shaking;
 
 /** Immutable keep requirements for a method. */
-public final class KeepMethodInfo extends KeepInfo<KeepMethodInfo.Builder, KeepMethodInfo> {
+public final class KeepMethodInfo extends KeepMemberInfo<KeepMethodInfo.Builder, KeepMethodInfo> {
 
   // Requires all aspects of a method to be kept.
   private static final KeepMethodInfo TOP = new Builder().makeTop().build();
@@ -26,6 +26,7 @@
 
   // This builder is not private as there are known instances where it is safe to modify keep info
   // in a non-upwards direction.
+  @Override
   Builder builder() {
     return new Builder(this);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 951e2e5..18becf3 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -36,6 +36,7 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final TreePrunerConfiguration configuration;
   private final UnusedItemsPrinter unusedItemsPrinter;
+  private final Set<DexType> missingTypes;
   private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
   private final Set<DexMethod> methodsToKeepForConfigurationDebugging = Sets.newIdentityHashSet();
 
@@ -47,6 +48,7 @@
     InternalOptions options = appView.options();
     this.appView = appView;
     this.configuration = configuration;
+    this.missingTypes = appView.appInfo().getMissingTypes();
     this.unusedItemsPrinter =
         options.hasUsageInformationConsumer()
             ? new UnusedItemsPrinter(
@@ -174,7 +176,7 @@
     if (reachableStaticFields != null) {
       clazz.setStaticFields(reachableStaticFields);
     }
-    clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
+    clazz.removeInnerClasses(this::isAttributeReferencingMissingOrPrunedType);
     clazz.removeEnclosingMethodAttribute(this::isAttributeReferencingPrunedItem);
     rewriteNestAttributes(clazz);
     unusedItemsPrinter.visited();
@@ -195,6 +197,10 @@
     }
   }
 
+  private boolean isTypeMissing(DexType type) {
+    return missingTypes.contains(type);
+  }
+
   private boolean isTypeLive(DexType type) {
     return appView.appInfo().isNonProgramTypeOrLiveProgramType(type);
   }
@@ -238,13 +244,12 @@
             && !appInfo.liveMethods.contains(attr.getEnclosingMethod()));
   }
 
-  private boolean isAttributeReferencingPrunedType(InnerClassAttribute attr) {
-    AppInfoWithLiveness appInfo = appView.appInfo();
-    if (!isTypeLive(attr.getInner())) {
+  private boolean isAttributeReferencingMissingOrPrunedType(InnerClassAttribute attr) {
+    if (isTypeMissing(attr.getInner()) || !isTypeLive(attr.getInner())) {
       return true;
     }
-    DexType context = attr.getLiveContext(appInfo);
-    return context == null || !isTypeLive(context);
+    DexType context = attr.getLiveContext(appView);
+    return context == null || isTypeMissing(context) || !isTypeLive(context);
   }
 
   private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> int firstUnreachableIndex(
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 0815577..3f8077f 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -639,10 +639,19 @@
       Log.debug(getClass(), "Merged %d classes.", mergedClasses.size());
     }
     timing.end();
+
+    if (mergedClasses.isEmpty()) {
+      return null;
+    }
+
     timing.begin("fixup");
     VerticalClassMergerGraphLens lens = new TreeFixer().fixupTypeReferences();
+    KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
+    keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.keySet()));
     timing.end();
-    assert lens == null || verifyGraphLens(lens);
+
+    assert lens != null;
+    assert verifyGraphLens(lens);
     return lens;
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 1091d09..80c47f2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestHostClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
@@ -71,7 +72,6 @@
     List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
     EnclosingMethodAttribute enclosingMembers = null;
     List<InnerClassAttribute> innerClasses = Collections.emptyList();
-    DexAnnotationSet classAnnotations = DexAnnotationSet.empty();
     DexEncodedField[] staticFields = DexEncodedField.EMPTY_ARRAY;
     DexEncodedField[] instanceFields = DexEncodedField.EMPTY_ARRAY;
     DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
@@ -106,7 +106,8 @@
         nestMembers,
         enclosingMembers,
         innerClasses,
-        classAnnotations,
+        ClassSignature.NO_CLASS_SIGNATURE,
+        DexAnnotationSet.empty(),
         staticFields,
         instanceFields,
         directMethods,
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Formatter.java b/src/main/java/com/android/tools/r8/tracereferences/Formatter.java
new file mode 100644
index 0000000..b545e65
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/Formatter.java
@@ -0,0 +1,176 @@
+// 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.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.PackageReference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedClass;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedField;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedMethod;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+abstract class Formatter {
+
+  private final StringBuilder output;
+
+  Formatter() {
+    output = new StringBuilder();
+  }
+
+  String get() {
+    return output.toString();
+  }
+
+  protected void append(String string) {
+    output.append(string);
+  }
+
+  protected void appendLine(String string) {
+    output.append(StringUtils.lines(string));
+  }
+
+  protected void appendLine() {
+    appendLine("");
+  }
+
+  protected void printArguments(MethodReference method) {
+    StringUtils.append(
+        output,
+        ListUtils.map(method.getFormalTypes(), TypeReference::getTypeName),
+        ",",
+        BraceType.PARENS);
+  }
+
+  protected abstract void printConstructorName(MethodReference method);
+
+  private void printError(String message) {
+    append("# Error: " + message);
+  }
+
+  protected abstract void printField(TracedField field);
+
+  protected abstract void printMethod(TracedMethod method);
+
+  private void printFieldError(FieldReference field) {
+    appendLine(
+        field.getFieldType().getTypeName()
+            + " "
+            + field.getHolderClass().getTypeName()
+            + "."
+            + field.getFieldName());
+  }
+
+  private void printMethodError(MethodReference method) {
+    printReturn(method);
+    append(" ");
+    append(method.getHolderClass().getTypeName());
+    append(".");
+    append(method.getMethodName());
+    printArguments(method);
+    appendLine();
+  }
+
+  protected abstract void printPackageNames(List<String> packageNames);
+
+  protected void printReturn(MethodReference method) {
+    append(method.getReturnType() != null ? method.getReturnType().getTypeName() : "void");
+  }
+
+  protected void printNameAndReturn(MethodReference method) {
+    if (method.getMethodName().equals("<init>")) {
+      printConstructorName(method);
+    } else {
+      printReturn(method);
+      append(" ");
+      append(method.getMethodName());
+    }
+  }
+
+  protected abstract void printTypeHeader(TracedClass clazz);
+
+  protected abstract void printTypeFooter();
+
+  void format(TraceReferencesResult result) {
+    int errors =
+        print(
+            result.types,
+            result.keepPackageNames,
+            result.fields,
+            result.methods,
+            result.missingDefinition);
+    assert errors == result.missingDefinition.size();
+  }
+
+  private int print(
+      Set<TracedClass> types,
+      Set<PackageReference> keepPackageNames,
+      Map<ClassReference, Set<TracedField>> fields,
+      Map<ClassReference, Set<TracedMethod>> methods,
+      Set<Object> missingDefinition) {
+    int errors = 0;
+    List<TracedClass> sortedTypes = new ArrayList<>(types);
+    sortedTypes.sort(Comparator.comparing(tracedClass -> tracedClass.getReference().getTypeName()));
+    for (TracedClass type : sortedTypes) {
+      if (missingDefinition.contains(type)) {
+        printError("Could not find definition for type " + type.getReference().getTypeName());
+        errors++;
+        continue;
+      }
+      printTypeHeader(type);
+      Set<TracedMethod> methodsForClass = methods.get(type.getReference());
+      if (methodsForClass != null) {
+        List<TracedMethod> sortedMethods = new ArrayList<>(methods.get(type.getReference()).size());
+        for (TracedMethod method : methods.get(type.getReference())) {
+          if (method.isMissingDefinition()) {
+            printError("Could not find definition for method ");
+            printMethodError(method.getReference());
+            errors++;
+            continue;
+          }
+          assert method.getAccessFlags() != null;
+          sortedMethods.add(method);
+        }
+        sortedMethods.sort(
+            Comparator.comparing(tracedMethod -> tracedMethod.getReference().toString()));
+        for (TracedMethod method : sortedMethods) {
+          printMethod(method);
+        }
+      }
+      Set<TracedField> fieldsForClass = fields.get(type.getReference());
+      if (fieldsForClass != null) {
+        List<TracedField> sortedFields = new ArrayList<>(fieldsForClass);
+        sortedFields.sort(
+            Comparator.comparing(tracedField -> tracedField.getReference().toString()));
+        for (TracedField field : sortedFields) {
+          if (field.isMissingDefinition()) {
+            printError("Could not find definition for field ");
+            printFieldError(field.getReference());
+            errors++;
+            continue;
+          }
+          printField(field);
+        }
+      }
+      printTypeFooter();
+    }
+    List<String> packageNamesToKeep =
+        keepPackageNames.stream()
+            .map(PackageReference::getPackageName)
+            .sorted()
+            .collect(Collectors.toList());
+    printPackageNames(packageNamesToKeep);
+    return errors;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java b/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java
index f259b01..85352f2 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java
@@ -3,70 +3,66 @@
 // 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.references.MethodReference;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedClass;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedField;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedMethod;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 
-class KeepRuleFormatter extends ResultFormatter {
+class KeepRuleFormatter extends Formatter {
   final boolean allowObfuscation;
 
-  KeepRuleFormatter(
-      StringConsumer output, DiagnosticsHandler diagnosticsHandler, boolean allowObfuscation) {
-    super(output, diagnosticsHandler);
+  KeepRuleFormatter(boolean allowObfuscation) {
     this.allowObfuscation = allowObfuscation;
   }
 
   @Override
-  protected void printTypeHeader(DexClass dexClass) {
+  protected void printTypeHeader(TracedClass tracedClass) {
     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());
+    if (tracedClass.getAccessFlags().isInterface()) {
+      appendLine(" interface " + tracedClass.getReference().getTypeName() + " {");
+    } else if (tracedClass.getAccessFlags().isEnum()) {
+      appendLine(" enum " + tracedClass.getReference().getTypeName() + " {");
     } else {
-      append(" class " + dexClass.type.toSourceString() + " {" + System.lineSeparator());
+      appendLine(" class " + tracedClass.getReference().getTypeName() + " {");
     }
   }
 
   @Override
-  protected void printConstructorName(DexEncodedMethod encodedMethod) {
+  protected void printConstructorName(MethodReference method) {
     append("<init>");
   }
 
   @Override
-  protected void printField(DexClass dexClass, DexField field) {
+  protected void printField(TracedField field) {
     append(
         "  "
-            + field.type.toSourceString()
+            + field.getReference().getFieldType().getTypeName()
             + " "
-            + field.name.toString()
+            + field.getReference().getFieldName()
             + ";"
             + 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()) {
+  protected void printMethod(TracedMethod tracedMethod) {
+    if (tracedMethod.getReference().getMethodName().equals("<clinit>")) {
       return;
     }
     append("  ");
-    if (encodedMethod.isPublicMethod()) {
+    if (tracedMethod.getAccessFlags().isPublic()) {
       append("public ");
-    } else if (encodedMethod.isPrivateMethod()) {
+    } else if (tracedMethod.getAccessFlags().isPrivate()) {
       append("private ");
-    } else if (encodedMethod.isProtectedMethod()) {
+    } else if (tracedMethod.getAccessFlags().isProtected()) {
       append("protected ");
     }
-    if (encodedMethod.isStatic()) {
+    if (tracedMethod.getAccessFlags().isStatic()) {
       append("static ");
     }
-    printNameAndReturn(encodedMethod);
-    printArguments(encodedMethod.method);
+    printNameAndReturn(tracedMethod.getReference());
+    printArguments(tracedMethod.getReference());
     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
index 12b2e6b..a29fdb5 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/PrintUsesFormatter.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/PrintUsesFormatter.java
@@ -3,35 +3,30 @@
 // 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.references.MethodReference;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedClass;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedField;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedMethod;
 import java.util.List;
 
-class PrintUsesFormatter extends ResultFormatter {
-
-  PrintUsesFormatter(StringConsumer output, DiagnosticsHandler diagnosticsHandler) {
-    super(output, diagnosticsHandler);
-  }
+class PrintUsesFormatter extends Formatter {
 
   @Override
-  protected void printConstructorName(DexEncodedMethod encodedMethod) {
-    if (encodedMethod.accessFlags.isStatic()) {
+  protected void printConstructorName(MethodReference method) {
+    if (method.getMethodName().equals("<clinit>")) {
       append("<clinit>");
     } else {
-      String holderName = encodedMethod.holder().toSourceString();
+      String holderName = method.getHolderClass().getTypeName();
       String constructorName = holderName.substring(holderName.lastIndexOf('.') + 1);
       append(constructorName);
     }
   }
 
   @Override
-  protected void printMethod(DexEncodedMethod encodedMethod, String typeName) {
-    append(typeName + ": ");
-    printNameAndReturn(encodedMethod);
-    printArguments(encodedMethod.method);
+  protected void printMethod(TracedMethod method) {
+    append(method.getReference().getHolderClass().getTypeName() + ": ");
+    printNameAndReturn(method.getReference());
+    printArguments(method.getReference());
     appendLine("");
   }
 
@@ -41,20 +36,20 @@
   }
 
   @Override
-  protected void printTypeHeader(DexClass dexClass) {
-    appendLine(dexClass.type.toSourceString());
+  protected void printTypeHeader(TracedClass type) {
+    appendLine(type.getReference().getTypeName());
   }
 
   @Override
   protected void printTypeFooter() {}
 
   @Override
-  protected void printField(DexClass dexClass, DexField field) {
+  protected void printField(TracedField field) {
     appendLine(
-        dexClass.type.toSourceString()
+        field.getReference().getHolderClass().getTypeName()
             + ": "
-            + field.type.toSourceString()
+            + field.getReference().getFieldType().getTypeName()
             + " "
-            + field.name.toString());
+            + field.getReference().getFieldName());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Result.java b/src/main/java/com/android/tools/r8/tracereferences/Result.java
deleted file mode 100644
index 6889f0c..0000000
--- a/src/main/java/com/android/tools/r8/tracereferences/Result.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.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
deleted file mode 100644
index 2fd8934..0000000
--- a/src/main/java/com/android/tools/r8/tracereferences/ResultFormatter.java
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.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
index d0ec93e..acc2cb6 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -32,7 +32,7 @@
       throw new CompilationFailedException();
     } catch (Exception e) {
       command.getDiagnosticsHandler().error(new ExceptionDiagnostic(e));
-      throw new CompilationFailedException();
+      throw new CompilationFailedException(e);
     }
   }
 
@@ -46,8 +46,8 @@
     if (command.getSource().isEmpty()) {
       throw new TraceReferencesException("No source specified");
     }
-    if (command.getOutput() == null) {
-      throw new TraceReferencesException("No output specified");
+    if (command.getConsumer() == null) {
+      throw new TraceReferencesException("No consumer specified");
     }
     AndroidApp.Builder builder = AndroidApp.builder();
     command.getLibrary().forEach(builder::addLibraryResourceProvider);
@@ -80,25 +80,8 @@
         }
       }
     }
-    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);
+    Tracer tracer = new Tracer(tagetDescriptors, builder.build(), command.getDiagnosticsHandler());
+    tracer.run(command.getConsumer());
   }
 
   public static void run(String... args) throws CompilationFailedException {
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
index f6fcb69..ae0ee57 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -11,10 +11,8 @@
 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;
@@ -35,8 +33,7 @@
   private final ImmutableList<ClassFileResourceProvider> library;
   private final ImmutableList<ClassFileResourceProvider> traceTarget;
   private final ImmutableList<ProgramResourceProvider> traceSource;
-  private final StringConsumer output;
-  private final OutputFormat outputFormat;
+  private final TraceReferencesConsumer consumer;
 
   TraceReferencesCommand(
       boolean printHelp,
@@ -45,16 +42,14 @@
       ImmutableList<ClassFileResourceProvider> library,
       ImmutableList<ClassFileResourceProvider> traceTarget,
       ImmutableList<ProgramResourceProvider> traceSource,
-      StringConsumer output,
-      OutputFormat outputFormat) {
+      TraceReferencesConsumer consumer) {
     this.printHelp = printHelp;
     this.printVersion = printVersion;
     this.diagnosticsHandler = diagnosticsHandler;
     this.library = library;
     this.traceTarget = traceTarget;
     this.traceSource = traceSource;
-    this.output = output;
-    this.outputFormat = outputFormat;
+    this.consumer = consumer;
   }
 
   /**
@@ -101,8 +96,7 @@
         ImmutableList.builder();
     private final ImmutableList.Builder<ProgramResourceProvider> traceSourceBuilder =
         ImmutableList.builder();
-    private StringConsumer output;
-    private OutputFormat outputFormat = TraceReferencesCommandParser.OutputFormat.PRINTUSAGE;
+    private TraceReferencesConsumer consumer;
 
     private Builder(DiagnosticsHandler diagnosticsHandler) {
       this.diagnosticsHandler = diagnosticsHandler;
@@ -197,13 +191,8 @@
       return this;
     }
 
-    Builder setOutputPath(Path output) {
-      this.output = new StringConsumer.FileConsumer(output);
-      return this;
-    }
-
-    Builder setOutputFormat(OutputFormat outputFormat) {
-      this.outputFormat = outputFormat;
+    Builder setConsumer(TraceReferencesConsumer consumer) {
+      this.consumer = consumer;
       return this;
     }
 
@@ -217,8 +206,7 @@
           libraryBuilder.build(),
           traceTarget,
           traceSource,
-          output,
-          outputFormat);
+          consumer);
     }
 
     void error(Diagnostic diagnostic) {
@@ -244,11 +232,7 @@
     return traceSource;
   }
 
-  StringConsumer getOutput() {
-    return output;
-  }
-
-  OutputFormat getOutputFormat() {
-    return outputFormat;
+  TraceReferencesConsumer getConsumer() {
+    return consumer;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
index 79de8e2..476d0d4 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
@@ -6,11 +6,13 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.tracereferences.TraceReferencesFormattingConsumer.OutputFormat;
 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.io.PrintStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -19,17 +21,6 @@
 
 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");
 
@@ -78,6 +69,8 @@
   private TraceReferencesCommand.Builder parse(
       String[] args, Origin origin, TraceReferencesCommand.Builder builder) {
     String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
+    Path output = null;
+    OutputFormat format = TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE;
     for (int i = 0; i < expandedArgs.length; i++) {
       String arg = expandedArgs[i].trim();
       String nextArg = null;
@@ -103,28 +96,42 @@
       } 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;
+          format = TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE;
         }
         if (nextArg.equals("keep")) {
-          format = OutputFormat.KEEP_RULES;
+          format = TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES;
         }
         if (nextArg.equals("keepallowobfuscation")) {
-          format = OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION;
+          format = TraceReferencesFormattingConsumer.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));
+        output = 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 + "'"));
       }
     }
+    final Path finalOutput = output;
+    builder.setConsumer(
+        new TraceReferencesFormattingConsumer(format) {
+          @Override
+          public void finished() {
+            PrintStream out = System.out;
+            if (finalOutput != null) {
+              try {
+                out = new PrintStream(Files.newOutputStream(finalOutput));
+              } catch (IOException e) {
+                builder.error(new ExceptionDiagnostic(e));
+              }
+            }
+            out.print(get());
+          }
+        });
     return builder;
   }
 
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java
new file mode 100644
index 0000000..38b8086
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java
@@ -0,0 +1,90 @@
+// 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.Keep;
+import com.android.tools.r8.KeepForSubclassing;
+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.PackageReference;
+
+/** Consumer interface for recording references */
+@KeepForSubclassing
+public interface TraceReferencesConsumer {
+
+  /**
+   * Interface for asking for the access flags for a traced reference when the definition is present
+   */
+  @Keep
+  interface AccessFlags {
+    boolean isStatic();
+
+    boolean isPublic();
+
+    boolean isProtected();
+
+    boolean isPrivate();
+  }
+
+  /**
+   * Interface for asking for additional class information for a traced class when the definition is
+   * found.
+   */
+  @Keep
+  interface ClassAccessFlags extends AccessFlags {
+    boolean isInterface();
+
+    boolean isEnum();
+  }
+
+  @Keep
+  interface FieldAccessFlags extends AccessFlags {}
+
+  @Keep
+  interface MethodAccessFlags extends AccessFlags {}
+
+  /** Interface implemented by all references reported */
+  @Keep
+  interface TracedReference<T, F> {
+    /** Returns if the reference does not have a definition in the program traced. */
+    boolean isMissingDefinition();
+
+    /** Returns the reference traced. */
+    T getReference();
+
+    /**
+     * Returns the access flags for the reference traced. If the definition is not found (<code>
+     * isMissingDefinition()</code> returns <code>true</code>) the access flags are not known and
+     * this returns <code>null</code>.
+     */
+    F getAccessFlags();
+  }
+
+  @Keep
+  interface TracedClass extends TracedReference<ClassReference, ClassAccessFlags> {}
+
+  @Keep
+  interface TracedField extends TracedReference<FieldReference, FieldAccessFlags> {}
+
+  @Keep
+  interface TracedMethod extends TracedReference<MethodReference, MethodAccessFlags> {}
+
+  /** Class has been traced. */
+  void acceptType(TracedClass tracedClazz);
+
+  /** Field has been traced. */
+  void acceptField(TracedField tracedField);
+
+  /** Method has been traced. */
+  void acceptMethod(TracedMethod tracedMethod);
+
+  /** Package which is required for package privatge access has been traced. */
+  void acceptPackage(PackageReference pkg);
+
+  /**
+   * Tracing has finished. There will be no more calls to any of the <code>acceptXXX</code> methods.
+   */
+  void finished();
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java
new file mode 100644
index 0000000..67167c4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java
@@ -0,0 +1,79 @@
+// 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.errors.Unreachable;
+import com.android.tools.r8.references.PackageReference;
+
+class TraceReferencesFormattingConsumer implements TraceReferencesConsumer {
+
+  public 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 final OutputFormat format;
+  private final TraceReferencesResult.Builder builder = TraceReferencesResult.builder();
+  private boolean finishedCalled = false;
+
+  public TraceReferencesFormattingConsumer(OutputFormat format) {
+    this.format = format;
+  }
+
+  @Override
+  public void acceptType(TracedClass type) {
+    assert !finishedCalled;
+    builder.acceptType(type);
+  }
+
+  @Override
+  public void acceptField(TracedField field) {
+    assert !finishedCalled;
+    builder.acceptField(field);
+  }
+
+  @Override
+  public void acceptMethod(TracedMethod method) {
+    assert !finishedCalled;
+    builder.acceptMethod(method);
+  }
+
+  @Override
+  public void acceptPackage(PackageReference pkg) {
+    assert !finishedCalled;
+    builder.acceptPackage(pkg);
+  }
+
+  @Override
+  public void finished() {
+    assert !finishedCalled;
+    finishedCalled = true;
+  }
+
+  public String get() {
+    TraceReferencesResult result = builder.build();
+    Formatter formatter;
+    switch (format) {
+      case PRINTUSAGE:
+        formatter = new PrintUsesFormatter();
+        break;
+      case KEEP_RULES:
+        formatter = new KeepRuleFormatter(false);
+        break;
+      case KEEP_RULES_WITH_ALLOWOBFUSCATION:
+        formatter = new KeepRuleFormatter(true);
+        break;
+      default:
+        throw new Unreachable();
+    }
+    formatter.format(result);
+    return formatter.get();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java
new file mode 100644
index 0000000..f8dab44
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java
@@ -0,0 +1,94 @@
+// 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.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.PackageReference;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedClass;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedField;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedMethod;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+class TraceReferencesResult {
+
+  final Set<TracedClass> types;
+  final Map<ClassReference, Set<TracedField>> fields;
+  final Map<ClassReference, Set<TracedMethod>> methods;
+  final Set<PackageReference> keepPackageNames;
+  final Set<Object> missingDefinition;
+
+  TraceReferencesResult(
+      Set<TracedClass> types,
+      Map<ClassReference, Set<TracedField>> fields,
+      Map<ClassReference, Set<TracedMethod>> methods,
+      Set<PackageReference> keepPackageNames,
+      Set<Object> missingDefinition) {
+    this.types = types;
+    this.fields = fields;
+    this.methods = methods;
+    this.keepPackageNames = keepPackageNames;
+    this.missingDefinition = missingDefinition;
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  static class Builder implements TraceReferencesConsumer {
+    private final Set<TracedClass> types = new HashSet<>();
+    private final Map<ClassReference, Set<TracedField>> fields = new HashMap<>();
+    private final Map<ClassReference, Set<TracedMethod>> methods = new HashMap<>();
+    private final Set<Object> missingDefinition = new HashSet<>();
+    private final Set<PackageReference> keepPackageNames = new HashSet<>();
+
+    @Override
+    public void acceptType(TracedClass tracedClass) {
+      types.add(tracedClass);
+      if (tracedClass.isMissingDefinition()) {
+        this.missingDefinition.add(tracedClass.getReference());
+      }
+    }
+
+    @Override
+    public void acceptField(TracedField tracedField) {
+      FieldReference field = tracedField.getReference();
+      fields.computeIfAbsent(field.getHolderClass(), k -> new HashSet<>()).add(tracedField);
+      if (tracedField.isMissingDefinition()) {
+        this.missingDefinition.add(field);
+      }
+    }
+
+    @Override
+    public void acceptMethod(TracedMethod tracedMethod) {
+      MethodReference method = tracedMethod.getReference();
+      methods.computeIfAbsent(method.getHolderClass(), k -> new HashSet<>()).add(tracedMethod);
+      if (tracedMethod.isMissingDefinition()) {
+        this.missingDefinition.add(method);
+      }
+    }
+
+    @Override
+    public void acceptPackage(PackageReference pkg) {
+      keepPackageNames.add(pkg);
+    }
+
+    @Override
+    public void finished() {}
+
+    TraceReferencesResult build() {
+      missingDefinition.forEach(
+          missingDefinition -> {
+            assert missingDefinition instanceof ClassReference
+                || missingDefinition instanceof FieldReference
+                || missingDefinition instanceof MethodReference;
+          });
+      return new TraceReferencesResult(types, fields, methods, keepPackageNames, missingDefinition);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 5ad1cfa..d30158b 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -17,7 +18,6 @@
 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;
@@ -26,29 +26,209 @@
 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.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.shaking.MainDexClasses;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.AccessFlags;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.ClassAccessFlags;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.FieldAccessFlags;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.MethodAccessFlags;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedClass;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedField;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedMethod;
+import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedReference;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
+import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 class Tracer {
 
+  static class AccessFlagsImpl<T extends com.android.tools.r8.graph.AccessFlags<T>>
+      implements AccessFlags {
+    T accessFlags;
+
+    AccessFlagsImpl(T accessFlags) {
+      this.accessFlags = accessFlags;
+    }
+
+    @Override
+    public boolean isStatic() {
+      return accessFlags.isStatic();
+    }
+
+    @Override
+    public boolean isPublic() {
+      return accessFlags.isPublic();
+    }
+
+    @Override
+    public boolean isProtected() {
+      return accessFlags.isProtected();
+    }
+
+    @Override
+    public boolean isPrivate() {
+      return accessFlags.isPrivate();
+    }
+  }
+
+  static class ClassAccessFlagsImpl
+      extends AccessFlagsImpl<com.android.tools.r8.graph.ClassAccessFlags>
+      implements ClassAccessFlags {
+    ClassAccessFlagsImpl(com.android.tools.r8.graph.ClassAccessFlags accessFlags) {
+      super(accessFlags);
+    }
+
+    @Override
+    public boolean isInterface() {
+      return accessFlags.isInterface();
+    }
+
+    @Override
+    public boolean isEnum() {
+      return accessFlags.isEnum();
+    }
+  }
+
+  static class FieldAccessFlagsImpl
+      extends AccessFlagsImpl<com.android.tools.r8.graph.FieldAccessFlags>
+      implements FieldAccessFlags {
+    FieldAccessFlagsImpl(com.android.tools.r8.graph.FieldAccessFlags accessFlags) {
+      super(accessFlags);
+    }
+  }
+
+  static class MethodAccessFlagsImpl
+      extends AccessFlagsImpl<com.android.tools.r8.graph.MethodAccessFlags>
+      implements MethodAccessFlags {
+    MethodAccessFlagsImpl(com.android.tools.r8.graph.MethodAccessFlags accessFlags) {
+      super(accessFlags);
+    }
+  }
+
+  abstract static class TracedReferenceBase<T, F> implements TracedReference<T, F> {
+    private final T reference;
+    private final F accessFlags;
+    private final boolean missingDefinition;
+
+    private TracedReferenceBase(T reference, F accessFlags, boolean missingDefinition) {
+      assert accessFlags != null || missingDefinition;
+      this.reference = reference;
+      this.accessFlags = accessFlags;
+      this.missingDefinition = missingDefinition;
+    }
+
+    @Override
+    public T getReference() {
+      return reference;
+    }
+
+    @Override
+    public boolean isMissingDefinition() {
+      return missingDefinition;
+    }
+
+    @Override
+    public F getAccessFlags() {
+      return accessFlags;
+    }
+
+    @Override
+    public int hashCode() {
+      // Equality is only based on the reference.
+      return reference.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      // Equality is only based on the reference.
+      if (!(other instanceof TracedReferenceBase)) {
+        return false;
+      }
+      return reference.equals(((TracedReferenceBase<?, ?>) other).reference);
+    }
+
+    public abstract String getKindName();
+  }
+
+  static class TracedClassImpl extends TracedReferenceBase<ClassReference, ClassAccessFlags>
+      implements TracedClass {
+    private TracedClassImpl(DexType reference, DexClass definition) {
+      super(
+          Reference.classFromDescriptor(reference.toDescriptorString()),
+          definition != null ? new ClassAccessFlagsImpl(definition.getAccessFlags()) : null,
+          definition == null);
+    }
+
+    @Override
+    public String getKindName() {
+      return "type";
+    }
+
+    @Override
+    public String toString() {
+      return getReference().getTypeName();
+    }
+  }
+
+  static class TracedFieldImpl extends TracedReferenceBase<FieldReference, FieldAccessFlags>
+      implements TracedField {
+    private TracedFieldImpl(DexField reference, DexEncodedField definition) {
+      super(
+          Reference.field(
+              Reference.classFromDescriptor(reference.holder.toDescriptorString()),
+              reference.name.toString(),
+              Reference.typeFromDescriptor(reference.type.toDescriptorString())),
+          definition != null ? new FieldAccessFlagsImpl(definition.getAccessFlags()) : null,
+          definition == null);
+    }
+
+    @Override
+    public String getKindName() {
+      return "field";
+    }
+
+    @Override
+    public String toString() {
+      return getReference().toString();
+    }
+  }
+
+  static class TracedMethodImpl extends TracedReferenceBase<MethodReference, MethodAccessFlags>
+      implements TracedMethod {
+    private TracedMethodImpl(DexMethod reference, DexEncodedMethod definition) {
+      super(
+          reference.asMethodReference(),
+          definition != null ? new MethodAccessFlagsImpl(definition.getAccessFlags()) : null,
+          definition == null);
+    }
+
+    @Override
+    public String getKindName() {
+      return "method";
+    }
+
+    @Override
+    public String toString() {
+      return getReference().toString();
+    }
+  }
+
   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 DiagnosticsHandler diagnostics;
   private final DirectMappedDexApplication application;
   private final AppInfoWithClassHierarchy appInfo;
 
-  Tracer(Set<String> descriptors, AndroidApp inputApp) throws Exception {
+  Tracer(Set<String> descriptors, AndroidApp inputApp, DiagnosticsHandler diagnostics)
+      throws Exception {
     this.descriptors = descriptors;
+    this.diagnostics = diagnostics;
     InternalOptions options = new InternalOptions();
     application =
         new ApplicationReader(inputApp, options, new Timing("ReferenceTrace")).read().toDirect();
@@ -59,8 +239,8 @@
             MainDexClasses.createEmptyMainDexClasses());
   }
 
-  Result run() {
-    UseCollector useCollector = new UseCollector(appInfo.dexItemFactory());
+  void run(TraceReferencesConsumer consumer) {
+    UseCollector useCollector = new UseCollector(appInfo.dexItemFactory(), consumer, diagnostics);
     for (DexProgramClass clazz : application.classes()) {
       useCollector.setContext(clazz);
       useCollector.registerSuperType(clazz, clazz.superType);
@@ -70,74 +250,80 @@
       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);
-    }
+    consumer.finished();
   }
 
   class UseCollector extends UseRegistry {
 
+    private final TraceReferencesConsumer consumer;
     private DexProgramClass context;
+    private final DiagnosticsHandler diagnostics;
+    private Set<TracedReference<?, ?>> missingDefinitionReported = new HashSet<>();
 
-    UseCollector(DexItemFactory factory) {
+    UseCollector(
+        DexItemFactory factory, TraceReferencesConsumer consumer, DiagnosticsHandler diagnostics) {
       super(factory);
+      this.consumer = consumer;
+      this.diagnostics = diagnostics;
+    }
+
+    private boolean isTargetType(DexType type) {
+      return descriptors.contains(type.toDescriptorString());
+    }
+
+    private void addType(DexType type) {
+      if (isTargetType(type)) {
+        DexClass clazz = appInfo.definitionFor(type);
+        TracedClassImpl tracedClass = new TracedClassImpl(type, clazz);
+        consumer.acceptType(tracedClass);
+        checkDiagnostics(tracedClass);
+        if (clazz != null && clazz.accessFlags.isVisibilityDependingOnPackage()) {
+          consumer.acceptPackage(Reference.packageFromString(clazz.type.getPackageName()));
+        }
+      }
+    }
+
+    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)) {
+        TracedFieldImpl tracedField = new TracedFieldImpl(field, baseField);
+        consumer.acceptField(tracedField);
+        checkDiagnostics(tracedField);
+        if (baseField != null && baseField.accessFlags.isVisibilityDependingOnPackage()) {
+          consumer.acceptPackage(Reference.packageFromString(baseField.holder().getPackageName()));
+        }
+      }
+    }
+
+    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)) {
+        DexClass holder = appInfo.definitionForHolder(method);
+        DexEncodedMethod definition = method.lookupOnClass(holder);
+        TracedMethodImpl tracedMethod = new TracedMethodImpl(method, definition);
+        consumer.acceptMethod(tracedMethod);
+        checkDiagnostics(tracedMethod);
+        if (definition != null && definition.accessFlags.isVisibilityDependingOnPackage()) {
+          consumer.acceptPackage(Reference.packageFromString(definition.holder().getPackageName()));
+        }
+      }
+    }
+
+    private void checkDiagnostics(TracedReferenceBase<?, ?> tracedReference) {
+      if (tracedReference.isMissingDefinition() && missingDefinitionReported.add(tracedReference)) {
+        diagnostics.warning(
+            new StringDiagnostic(
+                "Missing definition of " + tracedReference.getKindName() + " " + tracedReference));
+      }
     }
 
     public void setContext(DexProgramClass context) {
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 95b0fde..a4cef91 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -552,6 +552,27 @@
         binaryName.replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR));
   }
 
+  /**
+   * Computes the inner name from the outer- and inner descriptors. If outer is not a prefix of the
+   * inner descriptor null is returned. Do not use this method if the relationship between inner and
+   * outer is not reflected in the name.
+   *
+   * @param outerDescriptor the outer descriptor, such as Lfoo/bar/Baz;
+   * @param innerDescriptor the inner descriptor, such as Lfoo/bar/Baz$Qux;
+   * @return the inner name or null, i.e. Qux in the example above
+   */
+  public static String getInnerClassName(String outerDescriptor, String innerDescriptor) {
+    if (innerDescriptor.length() <= outerDescriptor.length()) {
+      return null;
+    }
+    String prefix =
+        outerDescriptor.substring(0, outerDescriptor.length() - 1) + INNER_CLASS_SEPARATOR;
+    if (innerDescriptor.startsWith(prefix)) {
+      return innerDescriptor.substring(prefix.length(), innerDescriptor.length() - 1);
+    }
+    return null;
+  }
+
   public static class ModuleAndDescriptor {
     private final String module;
     private final String descriptor;
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 af11559..091325a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1280,6 +1280,7 @@
     public int verificationSizeLimitInBytesOverride = -1;
     public boolean forceIRForCfToCfDesugar =
         System.getProperty("com.android.tools.r8.forceIRForCfToCfDesugar") != null;
+    public boolean disableMappingToOriginalProgramVerification = false;
 
     // Flag to allow processing of resources in D8. A data resource consumer still needs to be
     // specified.
diff --git a/src/main/java/com/android/tools/r8/utils/PredicateUtils.java b/src/main/java/com/android/tools/r8/utils/PredicateUtils.java
index 880da5e..f219259 100644
--- a/src/main/java/com/android/tools/r8/utils/PredicateUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/PredicateUtils.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class PredicateUtils {
@@ -20,4 +21,8 @@
   public static <T> Predicate<T> not(Predicate<T> predicate) {
     return t -> !predicate.test(t);
   }
+
+  public static <T, R> Predicate<T> isNull(Function<T, R> func) {
+    return t -> func.apply(t) == null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
index cae7430..3c3d37c 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
@@ -4,17 +4,23 @@
 
 package com.android.tools.r8.utils.collections;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
 
 public class BidirectionalManyToOneMap<K, V> {
 
   private final Map<K, V> backing = new IdentityHashMap<>();
   private final Map<V, Set<K>> inverse = new IdentityHashMap<>();
 
+  public void forEach(BiConsumer<Set<K>, V> consumer) {
+    inverse.forEach((value, keys) -> consumer.accept(keys, value));
+  }
+
   public V getOrDefault(K key, V value) {
     return backing.getOrDefault(key, value);
   }
@@ -39,8 +45,28 @@
     return inverse.getOrDefault(value, Collections.emptySet());
   }
 
+  public boolean isEmpty() {
+    return backing.isEmpty();
+  }
+
+  public void remove(K key) {
+    V value = backing.remove(key);
+    if (value != null) {
+      Set<K> keys = inverse.get(value);
+      keys.remove(key);
+      if (keys.isEmpty()) {
+        inverse.remove(value);
+      }
+    }
+  }
+
   public void put(K key, V value) {
+    remove(key);
     backing.put(key, value);
     inverse.computeIfAbsent(value, ignore -> new LinkedHashSet<>()).add(key);
   }
+
+  public Collection<V> values() {
+    return backing.values();
+  }
 }
diff --git a/src/test/examples/naming101/c.java b/src/test/examples/naming101/c.java
index 7477024..21744bc 100644
--- a/src/test/examples/naming101/c.java
+++ b/src/test/examples/naming101/c.java
@@ -4,5 +4,5 @@
 package naming101;
 
 public class c {
-  static int i = 1;
+  public static int i = 1;
 }
diff --git a/src/test/examples/naming101/d.java b/src/test/examples/naming101/d.java
index 3bb8131..6da1daf 100644
--- a/src/test/examples/naming101/d.java
+++ b/src/test/examples/naming101/d.java
@@ -4,7 +4,7 @@
 package naming101;
 
 public class d {
-  static int c() {
+  public static int c() {
     return c.i;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index ea8ff8b..1fcd0e7 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -4,12 +4,10 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 
 public class D8TestCompileResult extends TestCompileResult<D8TestCompileResult, D8TestRunResult> {
-  D8TestCompileResult(
-      TestState state, AndroidApp app, AndroidApiLevel minApiLevel, OutputMode outputMode) {
+  D8TestCompileResult(TestState state, AndroidApp app, int minApiLevel, OutputMode outputMode) {
     super(state, app, minApiLevel, outputMode);
   }
 
diff --git a/src/test/java/com/android/tools/r8/DXTestBuilder.java b/src/test/java/com/android/tools/r8/DXTestBuilder.java
index 49055a1..30917a7 100644
--- a/src/test/java/com/android/tools/r8/DXTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/DXTestBuilder.java
@@ -51,7 +51,7 @@
       Path outJar = dxOutputFolder.resolve("output.jar");
 
       List<String> args = new ArrayList<>();
-      args.add("--min-sdk-version=" + minApiLevel.getLevel());
+      args.add("--min-sdk-version=" + minApiLevel);
       args.add("--output=" + outJar.toString());
       args.addAll(injars.stream().map(Path::toString).collect(Collectors.toList()));
       ProcessResult result =
diff --git a/src/test/java/com/android/tools/r8/DXTestCompileResult.java b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
index 9cb6482..4a0c42f 100644
--- a/src/test/java/com/android/tools/r8/DXTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
@@ -5,12 +5,11 @@
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 
 public class DXTestCompileResult extends TestCompileResult<DXTestCompileResult, DXTestRunResult> {
 
-  DXTestCompileResult(TestState state, AndroidApp app, AndroidApiLevel minApiLevel) {
+  DXTestCompileResult(TestState state, AndroidApp app, int minApiLevel) {
     super(state, app, minApiLevel, OutputMode.DexIndexed);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
index 73fe9b4..ea6b2f7 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
@@ -25,7 +24,7 @@
       Path outputJar,
       ProcessResult processResult,
       String proguardMap,
-      AndroidApiLevel minApiLevel,
+      int minApiLevel,
       OutputMode outputMode) {
     super(state, AndroidApp.builder().addProgramFiles(outputJar).build(), minApiLevel, outputMode);
     assert processResult.exitCode == 0;
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
index 0f62c75..634cfe8 100644
--- a/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListRunResult.java
@@ -8,15 +8,18 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.ListUtils;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class GenerateMainDexListRunResult
     extends SingleTestRunResult<GenerateMainDexListRunResult> {
 
+  final TestState state;
   List<String> mainDexList;
 
-  public GenerateMainDexListRunResult(List<String> mainDexList) {
+  public GenerateMainDexListRunResult(List<String> mainDexList, TestState state) {
     super(null, null, null);
     this.mainDexList = mainDexList;
+    this.state = state;
   }
 
   public List<ClassReference> getMainDexList() {
@@ -29,6 +32,12 @@
         });
   }
 
+  public GenerateMainDexListRunResult inspectDiagnosticMessages(
+      Consumer<TestDiagnosticMessages> consumer) {
+    consumer.accept(state.getDiagnosticsMessages());
+    return self();
+  }
+
   @Override
   protected GenerateMainDexListRunResult self() {
     return this;
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
index 6b5829b..0700a5f 100644
--- a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
@@ -12,6 +12,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 public class GenerateMainDexListTestBuilder
     extends TestBaseBuilder<
@@ -26,7 +27,8 @@
   }
 
   public static GenerateMainDexListTestBuilder create(TestState state) {
-    return new GenerateMainDexListTestBuilder(state, GenerateMainDexListCommand.builder());
+    return new GenerateMainDexListTestBuilder(
+        state, GenerateMainDexListCommand.builder(state.getDiagnosticsHandler()));
   }
 
   @Override
@@ -67,7 +69,7 @@
   }
 
   public GenerateMainDexListRunResult run() throws CompilationFailedException {
-    return new GenerateMainDexListRunResult(GenerateMainDexList.run(builder.build()));
+    return new GenerateMainDexListRunResult(GenerateMainDexList.run(builder.build()), getState());
   }
 
   public GenerateMainDexListTestBuilder addMainDexRules(Collection<String> rules) {
@@ -78,4 +80,13 @@
   public GenerateMainDexListTestBuilder addMainDexRules(String... rules) {
     return addMainDexRules(Arrays.asList(rules));
   }
+
+  public GenerateMainDexListTestBuilder addDataResources(List<DataEntryResource> resources) {
+    resources.forEach(builder.getAppBuilder()::addDataResource);
+    return self();
+  }
+
+  public GenerateMainDexListTestBuilder addDataEntryResources(DataEntryResource... resources) {
+    return addDataResources(Arrays.asList(resources));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
index b51c598..c352417 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
@@ -18,8 +17,7 @@
   private final Path outputJar;
   private final String proguardMap;
 
-  ProguardTestCompileResult(
-      TestState state, Path outputJar, AndroidApiLevel minApiLevel, String proguardMap) {
+  ProguardTestCompileResult(TestState state, Path outputJar, int minApiLevel, String proguardMap) {
     super(
         state,
         AndroidApp.builder().addProgramFiles(outputJar).build(),
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 5e2f078..c03cd49 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -101,7 +101,6 @@
   @Override
   @Test
   public void lambdaDesugaring() throws Throwable {
-    expectThrowsWithHorizontalClassMerging();
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
@@ -121,7 +120,6 @@
 
   @Test
   public void testMultipleInterfacesLambdaOutValue() throws Throwable {
-    expectThrowsWithHorizontalClassMerging();
     // We can only remove trivial check casts for the lambda objects if we keep track all the
     // multiple interfaces we additionally specified for the lambdas
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -143,9 +141,6 @@
   @Test
   @IgnoreIfVmOlderThan(Version.V7_0_0)
   public void lambdaDesugaringWithDefaultMethods() throws Throwable {
-    // This should be fixed by horizontal class merging field mapping.
-    expectThrowsWithHorizontalClassMerging();
-
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.N)
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 1ad8f28..de56e20 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -452,10 +452,18 @@
   }
 
   public T enableMemberValuePropagationAnnotations() {
-    if (!enableMemberValuePropagationAnnotations) {
-      enableMemberValuePropagationAnnotations = true;
-      addInternalKeepRules(
-          "-neverpropagatevalue class * { @com.android.tools.r8.NeverPropagateValue *; }");
+    return enableMemberValuePropagationAnnotations(true);
+  }
+
+  public T enableMemberValuePropagationAnnotations(boolean enable) {
+    if (enable) {
+      if (!enableMemberValuePropagationAnnotations) {
+        enableMemberValuePropagationAnnotations = true;
+        addInternalKeepRules(
+            "-neverpropagatevalue class * { @com.android.tools.r8.NeverPropagateValue *; }");
+      }
+    } else {
+      assert !enableMemberValuePropagationAnnotations;
     }
     return self();
   }
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 0deb4f5..85c6b96 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -40,7 +39,7 @@
       List<ProguardConfigurationRule> syntheticProguardRules,
       String proguardMap,
       CollectingGraphConsumer graphConsumer,
-      AndroidApiLevel minApiLevel,
+      int minApiLevel,
       List<Path> features) {
     super(state, app, minApiLevel, outputMode);
     this.proguardConfiguration = proguardConfiguration;
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 7dfe4b5..c888c1d 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -45,19 +45,18 @@
 import org.hamcrest.Matcher;
 
 public abstract class TestCompileResult<
-        CR extends TestCompileResult<CR, RR>, RR extends TestRunResult>
+        CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>>
     extends TestBaseResult<CR, RR> {
 
   public final AndroidApp app;
-  public final AndroidApiLevel minApiLevel;
+  public final int minApiLevel;
   private final OutputMode outputMode;
   final List<Path> additionalRunClassPath = new ArrayList<>();
   final List<String> vmArguments = new ArrayList<>();
   private boolean withArt6Plus64BitsLib = false;
   private boolean withArtFrameworks = true;
 
-  TestCompileResult(
-      TestState state, AndroidApp app, AndroidApiLevel minApiLevel, OutputMode outputMode) {
+  TestCompileResult(TestState state, AndroidApp app, int minApiLevel, OutputMode outputMode) {
     super(state);
     this.app = app;
     this.minApiLevel = minApiLevel;
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 590f10a..3e5ba5f 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -53,7 +53,7 @@
   private final List<Path> additionalRunClassPath = new ArrayList<>();
   private ProgramConsumer programConsumer;
   private StringConsumer mainDexListConsumer;
-  protected AndroidApiLevel minApiLevel = ToolHelper.getMinApiLevelForDexVm();
+  protected int minApiLevel = ToolHelper.getMinApiLevelForDexVm().getLevel();
   private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
   private ByteArrayOutputStream stdout = null;
   private PrintStream oldStdout = null;
@@ -110,7 +110,7 @@
     if (backend.isDex() || !isTestShrinkerBuilder()) {
       assert !builder.isMinApiLevelSet()
           : "Don't set the API level directly through BaseCompilerCommand.Builder in tests";
-      builder.setMinApiLevel(minApiLevel.getLevel());
+      builder.setMinApiLevel(minApiLevel);
     }
     if (useDefaultRuntimeLibrary) {
       if (backend == Backend.DEX) {
@@ -238,7 +238,7 @@
   }
 
   public T setMinApi(int minApiLevel) {
-    this.minApiLevel = AndroidApiLevel.getAndroidApiLevel(minApiLevel);
+    this.minApiLevel = minApiLevel;
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 6d3590d..477aab1 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -174,6 +174,10 @@
     return addKeepRules("-keep class " + pkg.getName() + ".*");
   }
 
+  public T addKeepPackageNamesRule(Package pkg) {
+    return addKeepRules("-keeppackagenames " + pkg.getName());
+  }
+
   public T addKeepMainRule(Class<?> mainClass) {
     return addKeepMainRule(mainClass.getTypeName());
   }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index aed9abc..32fa0b8 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -39,7 +39,6 @@
 
   @Test
   public void test() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(RemoveVisibilityBridgeMethodsTest.class)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index 4baa16c..b3ff5a3 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.bootstrap;
 
+import static com.android.tools.r8.graph.GenericSignatureIdentityTest.testParseSignaturesInJar;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.google.common.io.ByteStreams.toByteArray;
 import static org.hamcrest.CoreMatchers.anyOf;
@@ -208,6 +209,11 @@
   }
 
   @Test
+  public void testSignatures() throws Exception {
+    testParseSignaturesInJar(r8R8Release.getFirst());
+  }
+
+  @Test
   public void test() throws Exception {
     expectThrowsWithHorizontalClassMerging();
     Path helloJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
index 316d547..9783627 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
@@ -50,7 +50,7 @@
                 MethodSubject firstInitSubject = aClassSubject.init("int");
                 assertThat(firstInitSubject, isPresent());
                 assertThat(
-                    firstInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+                    firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
                 ClassSubject synthesizedClass = getSynthesizedArgumentClassSubject(codeInspector);
 
@@ -58,12 +58,11 @@
                     aClassSubject.init("int", synthesizedClass.getFinalName());
                 assertThat(otherInitSubject, isPresent());
                 assertThat(
-                    otherInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+                    otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
                 MethodSubject printSubject = aClassSubject.method("void", "print");
                 assertThat(printSubject, isPresent());
-                assertThat(
-                    printSubject, readsInstanceField(classIdFieldSubject.getFieldReference()));
+                assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
index b7d3a2d..dd1b6ca 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
@@ -56,18 +56,17 @@
                 MethodSubject firstInitSubject = aClassSubject.init("int");
                 assertThat(firstInitSubject, isPresent());
                 assertThat(
-                    firstInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+                    firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
                 MethodSubject otherInitSubject =
                     aClassSubject.init(changedClassSubject.getFinalName(), "int");
                 assertThat(otherInitSubject, isPresent());
                 assertThat(
-                    otherInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+                    otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
                 MethodSubject printSubject = aClassSubject.method("void", "print");
                 assertThat(printSubject, isPresent());
-                assertThat(
-                    printSubject, readsInstanceField(classIdFieldSubject.getFieldReference()));
+                assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
 
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
index 3d13626..fea20ed 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
@@ -50,7 +50,7 @@
                 MethodSubject firstInitSubject = aClassSubject.init("int");
                 assertThat(firstInitSubject, isPresent());
                 assertThat(
-                    firstInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+                    firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
                 ClassSubject synthesizedClass = getSynthesizedArgumentClassSubject(codeInspector);
 
@@ -58,12 +58,11 @@
                     aClassSubject.init("int", synthesizedClass.getFinalName());
                 assertThat(otherInitSubject, isPresent());
                 assertThat(
-                    otherInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+                    otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
                 MethodSubject printSubject = aClassSubject.method("void", "print");
                 assertThat(printSubject, isPresent());
-                assertThat(
-                    printSubject, readsInstanceField(classIdFieldSubject.getFieldReference()));
+                assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldTypeMergedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldTypeMergedTest.java
new file mode 100644
index 0000000..616c88a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldTypeMergedTest.java
@@ -0,0 +1,112 @@
+// 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.isFieldOfArrayType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFieldOfType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import org.junit.Test;
+
+public class FieldTypeMergedTest extends HorizontalClassMergingTestBase {
+  public FieldTypeMergedTest(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("a", "b", "bar", "bar", "bar")
+        .inspect(
+            codeInspector -> {
+              ClassSubject aClassSubject = codeInspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+
+              ClassSubject cClassSubject = codeInspector.clazz(C.class);
+              assertThat(codeInspector.clazz(C.class), isPresent());
+
+              FieldSubject fieldSubject = cClassSubject.uniqueFieldWithName("fieldB");
+              assertThat(fieldSubject, isPresent());
+              if (enableHorizontalClassMerging) {
+                assertThat(
+                    fieldSubject, isFieldOfType(aClassSubject.getDexProgramClass().getType()));
+              }
+
+              fieldSubject = cClassSubject.uniqueFieldWithName("fieldArrayB");
+              assertThat(fieldSubject, isPresent());
+              assertTrue(fieldSubject.getDexField().type.isArrayType());
+              if (enableHorizontalClassMerging) {
+                assertThat(
+                    fieldSubject,
+                    isFieldOfArrayType(
+                        codeInspector, aClassSubject.getDexProgramClass().getType()));
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("a");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      System.out.println("b");
+    }
+
+    @NeverInline
+    public void bar() {
+      System.out.println("bar");
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+    B fieldB;
+    B[] fieldArrayB;
+
+    public C(B b) {
+      fieldB = b;
+      fieldArrayB = new B[] {b, b};
+    }
+
+    @NeverInline
+    public void foo() {
+      fieldB.bar();
+      for (B b : fieldArrayB) {
+        b.bar();
+      }
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A();
+      B b = new B();
+      new C(b).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
index b2e1d59..605ff5e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
@@ -13,9 +13,6 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.classmerging.horizontal.ConstructorMergingTest.A;
-import com.android.tools.r8.classmerging.horizontal.ConstructorMergingTest.B;
-import com.android.tools.r8.classmerging.horizontal.ConstructorMergingTest.Main;
 import com.android.tools.r8.horizontalclassmerging.ClassMerger;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -53,17 +50,16 @@
                 MethodSubject firstInitSubject = aClassSubject.init("int");
                 assertThat(firstInitSubject, isPresent());
                 assertThat(
-                    firstInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+                    firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
                 MethodSubject otherInitSubject = aClassSubject.init("long", "int");
                 assertThat(otherInitSubject, isPresent());
                 assertThat(
-                    otherInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+                    otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
                 MethodSubject printSubject = aClassSubject.method("void", "print");
                 assertThat(printSubject, isPresent());
-                assertThat(
-                    printSubject, readsInstanceField(classIdFieldSubject.getFieldReference()));
+                assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonMapTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonMapTest.java
new file mode 100644
index 0000000..fa5c6d3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonMapTest.java
@@ -0,0 +1,71 @@
+// 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.desugaredlibrary.gson;
+
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.gson.TestClasses.TestClass;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Assume;
+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 GsonMapTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final Path GSON_CONFIGURATION =
+      Paths.get("src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg");
+  private static final Path GSON_2_8_1_JAR = Paths.get("third_party/iosched_2019/gson-2.8.1.jar");
+
+  @Parameters(name = "shrinkDesugaredLibrary: {0}, runtime: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public GsonMapTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testGsonMap() throws Exception {
+    Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassesAndInnerClasses(TestClasses.class)
+            .addProgramFiles(GSON_2_8_1_JAR)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRuleFiles(GSON_CONFIGURATION)
+            .allowUnusedProguardConfigurationRules()
+            .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
+            .allowDiagnosticMessages()
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibrary,
+                parameters.getApiLevel(),
+                keepRuleConsumer.get(),
+                shrinkDesugaredLibrary)
+            .run(parameters.getRuntime(), TestClass.class);
+    // TODO(b/167649682): Should be always true.
+    // .assertSuccessWithOutputLines("true", "true", "true", "true", "true");
+    if (shrinkDesugaredLibrary) {
+      runResult.assertSuccessWithOutputLines("true", "true", "false", "false", "false");
+    } else {
+      runResult.assertSuccessWithOutputLines("true", "true", "false", "true", "false");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/TestClasses.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/TestClasses.java
new file mode 100644
index 0000000..7b57814
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/TestClasses.java
@@ -0,0 +1,221 @@
+// 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.desugaredlibrary.gson;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class TestClasses {
+
+  // Program class extending ConcurrentHashMap.
+  static class NullableConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
+    NullableConcurrentHashMap() {
+      super();
+    }
+
+    @SuppressWarnings("NullableProblems")
+    @Override
+    public V put(K key, V value) {
+      if (key == null || value == null) {
+        return null;
+      }
+      return super.put(key, value);
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+      for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
+        put(entry.getKey(), entry.getValue());
+      }
+    }
+  }
+
+  // Program class extending the library class HashMap, implementing Map.
+  static class NullableHashMap<K, V> extends HashMap<K, V> {
+    NullableHashMap() {
+      super();
+    }
+
+    @SuppressWarnings("NullableProblems")
+    @Override
+    public V put(K key, V value) {
+      if (key == null || value == null) {
+        return null;
+      }
+      return super.put(key, value);
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+      for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
+        put(entry.getKey(), entry.getValue());
+      }
+    }
+  }
+
+  // Program class implementing Map.
+  static class NullableMap<K, V> implements Map<K, V> {
+    private Map<K, V> map = new HashMap<>();
+
+    NullableMap() {
+      super();
+    }
+
+    @Override
+    public int size() {
+      return map.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return map.isEmpty();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+      return map.containsKey(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+      return map.containsValue(value);
+    }
+
+    @Override
+    public V get(Object key) {
+      return map.get(key);
+    }
+
+    @Nullable
+    @Override
+    public V put(K key, V value) {
+      return map.put(key, value);
+    }
+
+    @Override
+    public V remove(Object key) {
+      return map.remove(key);
+    }
+
+    @Override
+    public void putAll(@NotNull Map<? extends K, ? extends V> m) {
+      for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
+        put(entry.getKey(), entry.getValue());
+      }
+    }
+
+    @Override
+    public void clear() {
+      map.clear();
+    }
+
+    @NotNull
+    @Override
+    public Set<K> keySet() {
+      return map.keySet();
+    }
+
+    @NotNull
+    @Override
+    public Collection<V> values() {
+      return map.values();
+    }
+
+    @NotNull
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+      return map.entrySet();
+    }
+  }
+
+  static class Data {
+    final int id;
+    final String name;
+
+    Data(int id, String name) {
+      this.id = id;
+      this.name = name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (!(o instanceof Data)) return false;
+      Data data = (Data) o;
+      return id == data.id && name.equals(data.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(id, name);
+    }
+
+    @Override
+    public String toString() {
+      return "Data{" + "id=" + id + ", name='" + name + '\'' + '}';
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Gson gson = new Gson();
+      HashMap<Integer, Data> hashMap = new HashMap<>();
+      NullableHashMap<Integer, Data> nullableHashMap = new NullableHashMap<>();
+      NullableMap<Integer, Data> nullableMap = new NullableMap<>();
+      ConcurrentHashMap<Integer, Data> concurrentHashMap = new ConcurrentHashMap<>();
+      NullableConcurrentHashMap<Integer, Data> nullableConcurrentHashMap =
+          new NullableConcurrentHashMap<>();
+
+      fillMap(hashMap);
+      fillMap(nullableHashMap);
+      fillMap(nullableMap);
+      fillMap(concurrentHashMap);
+      fillMap(nullableConcurrentHashMap);
+
+      String hashMapJson = gson.toJson(hashMap);
+      String nullableHashMapJson = gson.toJson(nullableHashMap);
+      String nullableMapJson = gson.toJson(nullableMap);
+      String concurrentHashMapJson = gson.toJson(concurrentHashMap);
+      String nullableConcurrentHashMapJson = gson.toJson(nullableConcurrentHashMap);
+
+      Type hashMapType = new TypeToken<HashMap<Integer, Data>>() {}.getType();
+      HashMap<Integer, Data> hashMapDeserialized = gson.fromJson(hashMapJson, hashMapType);
+      Type nullableHashMapType = new TypeToken<HashMap<Integer, Data>>() {}.getType();
+      HashMap<Integer, Data> nullableHashMapDeserialized =
+          gson.fromJson(nullableHashMapJson, nullableHashMapType);
+      Type nullableMapType = new TypeToken<HashMap<Integer, Data>>() {}.getType();
+      HashMap<Integer, Data> nullableMapDeserialized =
+          gson.fromJson(nullableMapJson, nullableMapType);
+      Type concurrentHashMapType = new TypeToken<ConcurrentHashMap<Integer, Data>>() {}.getType();
+      ConcurrentHashMap<Integer, Data> concurrentHashMapDeserialized =
+          gson.fromJson(concurrentHashMapJson, concurrentHashMapType);
+      Type nullableConcurrentHashMapType =
+          new TypeToken<NullableConcurrentHashMap<Integer, Data>>() {}.getType();
+      NullableConcurrentHashMap<Integer, Data> nullableConcurrentHashMapDeserialized =
+          gson.fromJson(nullableConcurrentHashMapJson, nullableConcurrentHashMapType);
+
+      System.out.println(hashMap.equals(hashMapDeserialized));
+      System.out.println(nullableHashMap.equals(nullableHashMapDeserialized));
+      System.out.println(nullableMap.equals(nullableMapDeserialized));
+      System.out.println(concurrentHashMap.equals(concurrentHashMapDeserialized));
+      System.out.println(nullableConcurrentHashMap.equals(nullableConcurrentHashMapDeserialized));
+    }
+
+    public static void fillMap(Map<Integer, Data> map) {
+      map.put(1, new Data(1, "a"));
+      map.put(2, new Data(2, "b"));
+      map.put(3, new Data(3, "c"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg
new file mode 100644
index 0000000..51f38a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg
@@ -0,0 +1,32 @@
+# 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.
+
+# Gson uses generic type information stored in a class file when working with fields. R8
+# removes such information by default, so configure it to keep all of it.
+-keepattributes Signature
+-keepattributes EnclosingMethod
+-keepattributes InnerClasses
+
+# For using GSON @Expose annotation
+-keepattributes AnnotationDefault,RuntimeVisibleAnnotations
+
+# Gson specific classes
+-dontwarn sun.misc.Unsafe
+
+#-keep class com.google.gson.stream.** { *; }
+# Application classes that will be serialized/deserialized over Gson
+-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.GsonMapTest.Data { <fields>; }
+-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.GsonMapTest.NullableConcurrentHashMap { <fields>; }
+
+# Prevent R8 from stripping interface information from TypeAdapter, TypeAdapterFactory,
+# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
+-keep class * extends com.google.gson.TypeAdapter
+-keep class * implements com.google.gson.TypeAdapterFactory
+-keep class * implements com.google.gson.JsonSerializer
+-keep class * implements com.google.gson.JsonDeserializer
+
+# Prevent R8 from leaving Data object members always null
+-keepclassmembers,allowobfuscation class * {
+  @com.google.gson.annotations.SerializedName <fields>;
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index e1d892d..762916e 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -47,7 +47,6 @@
 
   @Test
   public void testR8CompiledWithR8() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index b567056..b134ae5 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
@@ -112,6 +113,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
+            ClassSignature.NO_CLASS_SIGNATURE,
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java
new file mode 100644
index 0000000..b94f484
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java
@@ -0,0 +1,91 @@
+// 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.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Reporter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+
+@RunWith(Parameterized.class)
+public class GenericSignatureIdentityTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public GenericSignatureIdentityTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testAllClassSignature() throws Exception {
+    testParseSignaturesInJar(ToolHelper.R8_WITH_DEPS_JAR);
+  }
+
+  public static void testParseSignaturesInJar(Path jar) throws Exception {
+    GenericSignatureReader genericSignatureReader = new GenericSignatureReader();
+    ZipInputStream inputStream = new ZipInputStream(Files.newInputStream(jar));
+    ZipEntry next = inputStream.getNextEntry();
+    while (next != null) {
+      if (next.getName().endsWith(".class")) {
+        ClassReader classReader = new ClassReader(inputStream);
+        classReader.accept(genericSignatureReader, 0);
+      }
+      next = inputStream.getNextEntry();
+    }
+  }
+
+  private static class GenericSignatureReader extends ClassVisitor {
+
+    public final Set<String> signatures = new HashSet<>();
+    private final DexItemFactory factory = new DexItemFactory();
+
+    private GenericSignatureReader() {
+      super(ASM7);
+    }
+
+    @Override
+    public void visit(
+        int version,
+        int access,
+        String name,
+        String signature,
+        String superName,
+        String[] interfaces) {
+      if (signature == null) {
+        return;
+      }
+      TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+      ClassSignature classSignature =
+          GenericSignature.parseClassSignature(
+              name, signature, Origin.unknown(), factory, new Reporter(testDiagnosticMessages));
+      assertEquals(signature, classSignature.toString());
+      testDiagnosticMessages.assertNoMessages();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
index 366e98b..ddff057 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -43,23 +43,28 @@
 @RunWith(Parameterized.class)
 public class GenericSignatureTest extends TestBase {
 
+  private final TestParameters parameters;
+
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
-  public GenericSignatureTest(TestParameters parameters) {}
+  public GenericSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
 
   @Test
   public void test() throws Exception {
     AndroidApp app =
-        testForD8()
+        testForD8(parameters.getBackend())
             .debug()
             .addProgramClassesAndInnerClasses(
                 GenericSignatureTestClassA.class,
                 GenericSignatureTestClassB.class,
                 GenericSignatureTestClassCY.class,
                 GenericSignatureTestClassCYY.class)
+            .setMinApi(parameters.getApiLevel())
             .compile()
             .app;
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(app);
@@ -103,13 +108,7 @@
     // class <T:GenericSignatureTestClassA<T>.Y>CYY<T extends A<T>.Y> extends CY<T>
     DexClass clazz = cyy.getDexProgramClass();
     assertNotNull(clazz);
-    classSignature =
-        GenericSignature.parseClassSignature(
-            clazz.getType().getName(),
-            getGenericSignature(clazz, appView),
-            clazz.origin,
-            appView.dexItemFactory(),
-            appView.options().reporter);
+    classSignature = clazz.classSignature;
     assertNotNull(classSignature);
 
     assertEquals(1, classSignature.formalTypeParameters.size());
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
new file mode 100644
index 0000000..fade8ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
@@ -0,0 +1,177 @@
+// 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.graph.genericsignature;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.graph.GenericSignature.ClassSignature.NO_CLASS_SIGNATURE;
+import static com.google.common.base.Predicates.alwaysFalse;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignaturePrinter;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Reporter;
+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 ClassSignatureTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ClassSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testSuperClass() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz;");
+  }
+
+  @Test
+  public void testSuperClassWithInnerClasses() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz.Foo.Bar;");
+  }
+
+  @Test
+  public void testSuperClassWithInterfaces1() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz;Lfoo/bar/qux;");
+  }
+
+  @Test
+  public void testSuperClassWithInterfaces2() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz;Lfoo/bar/qux;Lfoo/bar/quux;");
+  }
+
+  @Test
+  public void testSuperClassWithInterfacesInnerClassesSeparatedByDollar() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz$Foo$Bar;Lfoo/bar/qux;Lfoo/bar/quux$Foo$Bar;");
+  }
+
+  @Test
+  public void testSuperClassWithInterfacesInnerClassesSeparatedByPeriod() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz.Foo.Bar;Lfoo/bar/qux;Lfoo/bar/quux.Foo.Bar;");
+  }
+
+  @Test
+  public void testInnerClassesWithSeparatorInName1() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz<*>.Foo$$$<*>.Bar;");
+  }
+
+  @Test
+  public void testInnerClassesWithSeparatorInName2() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz<*>.Foo$Bar$$<*>.Qux;");
+  }
+
+  @Test
+  public void testSuperClassWithInnerClassesGenericArguments2() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz<LFoo;>.Foo<LFoo;>.Bar<LFoo;>;");
+  }
+
+  @Test
+  public void testSuperClassWithInterfacesInnerClassesGenericArguments2() {
+    testParsingAndPrintingEqual(
+        "Lfoo/bar/baz<LFoo;>.Foo<LFoo;>.Bar<LFoo;>;Lfoo/bar/qux<LFoo;>;Lfoo/bar/quux<LFoo;>.Foo<LFoo;>.Bar<LFoo;>;");
+  }
+
+  @Test
+  public void testSuperClassWithInterfacesInnerClassesGenericArguments3() {
+    testParsingAndPrintingEqual(
+        "Lfoo/bar/baz<LFoo;[[I[[[LBar<LFoo;>;>.Foo<LFoo;>.Bar<LFoo;>;Lfoo/bar/qux<LFoo;>;Lfoo/bar/quux<LFoo;>.Foo<LFoo;>.Bar<LFoo;>;");
+  }
+
+  @Test
+  public void testWildCards() {
+    testParsingAndPrintingEqual("Lfoo/Bar<*>;");
+  }
+
+  @Test
+  public void testWildCardsPositiveNegative() {
+    testParsingAndPrintingEqual("Lfoo/Bar<*+[I-LFoo<+LBar;>;>;");
+  }
+
+  @Test
+  public void testSuperClassError() {
+    TestDiagnosticMessages testDiagnosticMessages = testParsingAndPrintingError("Lfoo/bar/baz");
+    testDiagnosticMessages.assertAllWarningsMatch(
+        diagnosticMessage(containsString("Invalid signature 'Lfoo/bar/baz'")));
+  }
+
+  @Test
+  public void testReferenceToTypeVariable() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz<TT;>;");
+  }
+
+  @Test
+  public void testFormalTypeParameters() {
+    testParsingAndPrintingEqual("<T:Ljava/lang/Object;>Lfoo/bar/baz<TT;>;");
+  }
+
+  @Test
+  public void testFormalTypeWithEmpty() {
+    testParsingAndPrintingEqual("<T:>Lfoo/bar/baz<TT;>;");
+  }
+
+  @Test
+  public void testFormalTypeParametersWithInterfaces() {
+    testParsingAndPrintingEqual("<T:Ljava/lang/Object;:LI;>Lfoo/bar/baz<TT;>;");
+  }
+
+  @Test
+  public void testFormalTypeParametersArguments() {
+    testParsingAndPrintingEqual(
+        "<T:Ljava/lang/Object;:LI;R:LFoo<TT;[Lfoo/bar<TT;>.Baz<TT;[I>;>;>Lfoo/bar/baz<TT;>;");
+  }
+
+  @Test
+  public void testFormalTypeParameters2() {}
+
+  @Test
+  public void testFormalTypeParametersEmptyError() {
+    // TODO(b/169716723): This should throw an error
+    assertThrows(AssertionError.class, () -> testParsingAndPrintingError("<>Lfoo/bar/baz<TT;>;"));
+  }
+
+  private void testParsingAndPrintingEqual(String signature) {
+    ClassSignature parsed =
+        GenericSignature.parseClassSignature(
+            "A", signature, Origin.unknown(), new DexItemFactory(), new Reporter());
+    GenericSignaturePrinter genericSignaturePrinter =
+        new GenericSignaturePrinter(NamingLens.getIdentityLens(), alwaysFalse());
+    genericSignaturePrinter.visitClassSignature(parsed);
+    String outSignature = genericSignaturePrinter.toString();
+    assertEquals(signature, outSignature);
+  }
+
+  private TestDiagnosticMessages testParsingAndPrintingError(String signature) {
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+    ClassSignature parsed =
+        GenericSignature.parseClassSignature(
+            "A",
+            signature,
+            Origin.unknown(),
+            new DexItemFactory(),
+            new Reporter(testDiagnosticMessages));
+    assertEquals(NO_CLASS_SIGNATURE, parsed);
+    return testDiagnosticMessages;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
index 0465bc5..99b186f 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -35,6 +36,7 @@
           Collections.emptyList(),
           null,
           Collections.emptyList(),
+          ClassSignature.NO_CLASS_SIGNATURE,
           DexAnnotationSet.empty(),
           DexEncodedField.EMPTY_ARRAY,
           DexEncodedField.EMPTY_ARRAY,
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 0028070..dec2ff2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -41,7 +41,6 @@
 
   @Test
   public void testJstyleRunnable() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     final String folder = "lambdas_jstyle_runnable";
     final String mainClassName = "lambdas_jstyle_runnable.MainKt";
     final String implementer1ClassName = "lambdas_jstyle_runnable.Implementer1Kt";
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index e03e056..fd09ef3 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -830,6 +831,7 @@
               Collections.emptyList(),
               null,
               Collections.emptyList(),
+              ClassSignature.NO_CLASS_SIGNATURE,
               DexAnnotationSet.empty(),
               DexEncodedField.EMPTY_ARRAY,
               DexEncodedField.EMPTY_ARRAY,
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index b2ec374..e3e965c 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -322,6 +322,9 @@
                 ProguardKeepAttributes.SIGNATURE)
             .addKeepAllClassesRuleWithAllowObfuscation()
             .allowDiagnosticMessages()
+            .addOptionsModification(
+                internalOptions ->
+                    internalOptions.testing.disableMappingToOriginalProgramVerification = true)
             .compile();
 
     compileResult.assertNoInfoMessages();
diff --git a/src/test/java/com/android/tools/r8/naming/OverloadedReservedFieldNamingTest.java b/src/test/java/com/android/tools/r8/naming/OverloadedReservedFieldNamingTest.java
index 11c9154..2e211ad 100644
--- a/src/test/java/com/android/tools/r8/naming/OverloadedReservedFieldNamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/OverloadedReservedFieldNamingTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -57,7 +58,8 @@
     ClassSubject classSubject = inspector.clazz(A.class);
     assertThat(classSubject, isPresent());
 
-    FieldSubject fieldSubject = classSubject.uniqueFieldWithName("a");
+    FieldSubject fieldSubject =
+        classSubject.asFoundClassSubject().uniqueFieldWithName("a", Reference.BOOL);
     assertThat(fieldSubject, isPresentAndNotRenamed());
 
     FieldSubject helloFieldSubject = classSubject.uniqueFieldWithName("hello");
diff --git a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
index caa1ae9..66ec3bb 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
@@ -4,27 +4,33 @@
 package com.android.tools.r8.naming;
 
 import static com.android.tools.r8.naming.ClassNameMinifier.getParentPackagePrefix;
-import static com.android.tools.r8.utils.DescriptorUtils.getPackageNameFromDescriptor;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.base.CharMatcher;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
+import java.io.File;
+import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,36 +38,16 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class PackageNamingTest extends NamingTestBase {
+public class PackageNamingTest extends TestBase {
 
-  public PackageNamingTest(
-      String test,
-      List<String> keepRulesFiles,
-      BiConsumer<DexItemFactory, NamingLens> inspection) {
-    super(test, keepRulesFiles, inspection);
-  }
-
-  @Test
-  public void packageNamingTest() throws Exception {
-    NamingLens naming = runMinifier(ListUtils.map(keepRulesFiles, Paths::get));
-    inspection.accept(naming);
-  }
-
-  private static final List<String> CLASSES_IN_NAMING044 = ImmutableList.of(
-      "Lnaming044/A;", "Lnaming044/B;", "Lnaming044/sub/SubA;", "Lnaming044/sub/SubB;");
-
-  private static final List<String> CLASSES_IN_NAMING101 = ImmutableList.of(
-      "Lnaming101/c;", "Lnaming101/d;",
-      "Lnaming101/a/a;", "Lnaming101/a/c;",
-      "Lnaming101/a/b/c",
-      "Lnaming101/b/a'", "Lnaming101/b/b;"
-  );
+  private final Path input;
+  private final Path keepRulesFile;
+  private final ThrowingConsumer<CodeInspector, RuntimeException> inspection;
 
   @Parameters(name = "test: {0} keep: {1}")
   public static Collection<Object[]> data() {
     List<String> tests = Arrays.asList("naming044", "naming101");
-
-    Map<String, BiConsumer<DexItemFactory, NamingLens>> inspections = new HashMap<>();
+    Map<String, ThrowingConsumer<CodeInspector, RuntimeException>> inspections = new HashMap<>();
     inspections.put("naming044:keep-rules-001.txt", PackageNamingTest::test044_rule001);
     inspections.put("naming044:keep-rules-002.txt", PackageNamingTest::test044_rule002);
     inspections.put("naming044:keep-rules-003.txt", PackageNamingTest::test044_rule003);
@@ -75,322 +61,361 @@
     inspections.put("naming101:keep-rules-102.txt", PackageNamingTest::test101_rule102);
     inspections.put("naming101:keep-rules-104.txt", PackageNamingTest::test101_rule104);
     inspections.put("naming101:keep-rules-106.txt", PackageNamingTest::test101_rule106);
-
     return createTests(tests, inspections);
   }
 
-  private static int countPackageDepth(String descriptor) {
-    return CharMatcher.is('/').countIn(descriptor);
+  public PackageNamingTest(
+      String test,
+      Path keepRulesFile,
+      ThrowingConsumer<CodeInspector, RuntimeException> inspection) {
+    this.input = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar");
+    this.keepRulesFile = keepRulesFile;
+    this.inspection = inspection;
   }
 
-  private static void verifyUniqueNaming(
-      DexItemFactory dexItemFactory,
-      NamingLens naming,
-      List<String> klasses) {
+  @Test
+  public void packageNamingTest() throws Exception {
+    testForR8(Backend.DEX)
+        .addProgramFiles(input)
+        .addKeepRuleFiles(keepRulesFile)
+        .setMinApi(21)
+        .compile()
+        .inspect(inspection);
+  }
+
+  private static final List<String> CLASSES_IN_NAMING044 =
+      ImmutableList.of("naming044.A", "naming044.B", "naming044.sub.SubA", "naming044.sub.SubB");
+
+  private static final List<String> CLASSES_IN_NAMING101 =
+      ImmutableList.of(
+          "naming101.c",
+          "naming101.d",
+          "naming101.a.a",
+          "naming101.a.c",
+          "naming101.a.b.c",
+          "naming101.b.a'",
+          "naming101.b.b");
+
+  private static List<Object[]> createTests(
+      List<String> tests,
+      Map<String, ThrowingConsumer<CodeInspector, RuntimeException>> inspections) {
+    List<Object[]> testCases = new ArrayList<>();
+    Set<String> usedInspections = new HashSet<>();
+    for (String test : tests) {
+      File[] keepFiles =
+          new File(ToolHelper.EXAMPLES_DIR + test)
+              .listFiles(file -> file.isFile() && file.getName().endsWith(".txt"));
+      for (File keepFile : keepFiles) {
+        String keepName = keepFile.getName();
+        ThrowingConsumer<CodeInspector, RuntimeException> inspection =
+            getTestOptionalParameter(inspections, usedInspections, test, keepName);
+        if (inspection != null) {
+          testCases.add(new Object[] {test, keepFile.toPath(), inspection});
+        }
+      }
+    }
+    assert usedInspections.size() == inspections.size();
+    return testCases;
+  }
+
+  private static <T> T getTestOptionalParameter(
+      Map<String, T> specifications, Set<String> usedSpecifications, String test, String keepName) {
+    T parameter = specifications.get(test);
+    if (parameter == null) {
+      parameter = specifications.get(test + ":" + keepName);
+      if (parameter != null) {
+        usedSpecifications.add(test + ":" + keepName);
+      }
+    } else {
+      usedSpecifications.add(test);
+    }
+    return parameter;
+  }
+
+  private static int countPackageDepth(ClassSubject subject) {
+    return CharMatcher.is('.').countIn(subject.getFinalName());
+  }
+
+  private static void verifyUniqueNaming(CodeInspector inspector, List<String> klasses) {
     Set<String> renamedNames = Sets.newHashSet();
     for (String klass : klasses) {
-      DexType k = dexItemFactory.createType(klass);
-      String rename = naming.lookupDescriptor(k).toSourceString();
-      assertFalse(renamedNames.contains(rename));
-      renamedNames.add(rename);
+      String finalName = inspector.clazz(klass).getFinalName();
+      assertFalse(renamedNames.contains(finalName));
+      renamedNames.add(finalName);
     }
   }
 
   // repackageclasses ''
-  private static void test044_rule001(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+  private static void test044_rule001(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING044);
 
     // All classes are moved to the top-level package, hence no package separator.
-    DexType b = dexItemFactory.createType("Lnaming044/B;");
-    assertFalse(naming.lookupDescriptor(b).toSourceString().contains("/"));
+    ClassSubject bClassSubject = inspector.clazz("naming044.B");
+    assertFalse(bClassSubject.getFinalName().contains("."));
 
     // Even classes in a sub-package are moved to the same top-level package.
-    DexType sub = dexItemFactory.createType("Lnaming044/sub/SubB;");
-    assertFalse(naming.lookupDescriptor(sub).toSourceString().contains("/"));
+    assertFalse(inspector.clazz("naming044.sub.SubB").getFinalName().contains("."));
 
-    // method naming044.B.m would be renamed.
-    DexMethod m = dexItemFactory.createMethod(
-        b, dexItemFactory.createProto(dexItemFactory.intType), "m");
-    assertNotEquals("m", naming.lookupName(m).toSourceString());
+    // Method naming044.B.m would be renamed.
+    MethodSubject mMethodSubject = bClassSubject.uniqueMethodWithName("m");
+    assertNotEquals("m", mMethodSubject.getMethod().getName().toSourceString());
   }
 
   // repackageclasses 'p44.x'
-  private static void test044_rule002(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+  private static void test044_rule002(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING044);
 
     // All classes are moved to a single package, so they all have the same package prefix.
-    DexType a = dexItemFactory.createType("Lnaming044/A;");
-    assertTrue(naming.lookupDescriptor(a).toSourceString().startsWith("Lp44/x/"));
+    assertThat(inspector.clazz("naming044.A").getFinalName(), startsWith("p44.x."));
 
-    DexType b = dexItemFactory.createType("Lnaming044/B;");
-    assertTrue(naming.lookupDescriptor(b).toSourceString().startsWith("Lp44/x/"));
+    ClassSubject bClassSubject = inspector.clazz("naming044.B");
+    assertThat(bClassSubject.getFinalName(), startsWith("p44.x."));
 
-    DexType sub = dexItemFactory.createType("Lnaming044/sub/SubB;");
-    assertTrue(naming.lookupDescriptor(sub).toSourceString().startsWith("Lp44/x/"));
+    ClassSubject subBClassSubject = inspector.clazz("naming044.sub.SubB");
+    assertThat(subBClassSubject.getFinalName(), startsWith("p44.x."));
+
     // Even classes in a sub-package are moved to the same package.
     assertEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(sub).toSourceString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(b).toSourceString()));
+        subBClassSubject.getDexProgramClass().getType().getPackageName(),
+        bClassSubject.getDexProgramClass().getType().getPackageName());
   }
 
   // flattenpackagehierarchy ''
-  private static void test044_rule003(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+  private static void test044_rule003(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING044);
 
     // All packages are moved to the top-level package, hence only one package separator.
-    DexType b = dexItemFactory.createType("Lnaming044/B;");
-    assertEquals(1, countPackageDepth(naming.lookupDescriptor(b).toSourceString()));
+    ClassSubject b = inspector.clazz("naming044.B");
+    ClassSubject bClassSubject = inspector.clazz("naming044.B");
+    assertEquals(1, countPackageDepth(bClassSubject));
 
     // Classes in a sub-package are moved to the top-level as well, but in a different one.
-    DexType sub = dexItemFactory.createType("Lnaming044/sub/SubB;");
-    assertEquals(1, countPackageDepth(naming.lookupDescriptor(sub).toSourceString()));
+    ClassSubject sub = inspector.clazz("naming044.sub.SubB");
+    assertEquals(1, countPackageDepth(sub));
     assertNotEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(sub).toSourceString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(b).toSourceString()));
+        sub.getDexProgramClass().getType().getPackageName(),
+        b.getDexProgramClass().getType().getPackageName());
 
     // method naming044.B.m would be renamed.
-    DexMethod m = dexItemFactory.createMethod(
-        b, dexItemFactory.createProto(dexItemFactory.intType), "m");
-    assertNotEquals("m", naming.lookupName(m).toSourceString());
+    MethodSubject mMethodSubject = b.uniqueMethodWithName("m");
+    assertNotEquals("m", mMethodSubject.getMethod().getName().toSourceString());
   }
 
   // flattenpackagehierarchy 'p44.y'
-  private static void test044_rule004(DexItemFactory dexItemFactory, NamingLens naming) {
+  private static void test044_rule004(CodeInspector inspector) {
     // All packages are moved to a single package.
-    DexType a = dexItemFactory.createType("Lnaming044/A;");
-    assertTrue(naming.lookupDescriptor(a).toSourceString().startsWith("Lp44/y/"));
+    ClassSubject a = inspector.clazz("naming044.A");
+    assertTrue(a.getFinalName().startsWith("p44.y."));
     // naming004.A -> Lp44/y/a/a;
-    assertEquals(3, countPackageDepth(naming.lookupDescriptor(a).toSourceString()));
+    assertEquals(3, countPackageDepth(a));
 
-    DexType b = dexItemFactory.createType("Lnaming044/B;");
-    assertTrue(naming.lookupDescriptor(b).toSourceString().startsWith("Lp44/y/"));
+    ClassSubject b = inspector.clazz("naming044.B");
+    assertTrue(b.getFinalName().startsWith("p44.y."));
     // naming004.B -> Lp44/y/a/b;
-    assertEquals(3, countPackageDepth(naming.lookupDescriptor(b).toSourceString()));
+    assertEquals(3, countPackageDepth(b));
 
-    DexType sub = dexItemFactory.createType("Lnaming044/sub/SubB;");
-    assertTrue(naming.lookupDescriptor(sub).toSourceString().startsWith("Lp44/y/"));
+    ClassSubject sub = inspector.clazz("naming044.sub.SubB");
+    assertTrue(sub.getFinalName().startsWith("p44.y."));
     // naming004.sub.SubB -> Lp44/y/b/b;
-    assertEquals(3, countPackageDepth(naming.lookupDescriptor(sub).toSourceString()));
+    assertEquals(3, countPackageDepth(sub));
 
     // Classes in a sub-package should be in a different package.
     assertNotEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(sub).toSourceString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(b).toSourceString()));
+        sub.getDexProgramClass().getType().getPackageName(),
+        b.getDexProgramClass().getType().getPackageName());
   }
 
-  private static void test044_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+  private static void test044_rule005(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING044);
 
     // All packages are renamed somehow. Need to check package hierarchy is consistent.
-    DexType a = dexItemFactory.createType("Lnaming044/A;");
-    assertEquals(1, countPackageDepth(naming.lookupDescriptor(a).toSourceString()));
-    DexType b = dexItemFactory.createType("Lnaming044/B;");
-    assertEquals(1, countPackageDepth(naming.lookupDescriptor(b).toSourceString()));
+    ClassSubject a = inspector.clazz("naming044.A");
+    assertEquals(1, countPackageDepth(a));
+    ClassSubject b = inspector.clazz("naming044.B");
+    assertEquals(1, countPackageDepth(b));
     assertEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(a).toSourceString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(b).toSourceString()));
+        a.getDexProgramClass().getType().getPackageName(),
+        b.getDexProgramClass().getType().getPackageName());
 
-    DexType sub_a = dexItemFactory.createType("Lnaming044/sub/SubA;");
-    assertEquals(2, countPackageDepth(naming.lookupDescriptor(sub_a).toSourceString()));
-    DexType sub_b = dexItemFactory.createType("Lnaming044/sub/SubB;");
-    assertEquals(2, countPackageDepth(naming.lookupDescriptor(sub_b).toSourceString()));
+    ClassSubject sub_a = inspector.clazz("naming044.sub.SubA");
+    assertEquals(2, countPackageDepth(sub_a));
+    ClassSubject sub_b = inspector.clazz("naming044.sub.SubB");
+    assertEquals(2, countPackageDepth(sub_b));
     assertEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(sub_a).toSourceString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(sub_b).toSourceString()));
+        sub_a.getDexProgramClass().getType().getPackageName(),
+        sub_b.getDexProgramClass().getType().getPackageName());
 
-    // Lnaming044/B -> La/c --prefix--> La
-    // Lnaming044/sub/SubB -> La/b/b --prefix--> La/b --prefix--> La
+    // Lnaming044.B -> La/c --prefix--> La
+    // Lnaming044.sub.SubB -> La/b/b --prefix--> La/b --prefix--> La
     assertEquals(
-        getParentPackagePrefix(naming.lookupDescriptor(b).toSourceString()),
-        getParentPackagePrefix(
-            getParentPackagePrefix(naming.lookupDescriptor(sub_b).toSourceString())));
+        getParentPackagePrefix(b.getFinalName()),
+        getParentPackagePrefix(getParentPackagePrefix(sub_b.getFinalName())));
   }
 
-  private static void test101_rule001(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+  private static void test101_rule001(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING101);
 
     // All classes are moved to the top-level package, hence no package separator.
-    DexType c = dexItemFactory.createType("Lnaming101/c;");
-    assertFalse(naming.lookupDescriptor(c).toSourceString().contains("/"));
+    ClassSubject c = inspector.clazz("naming101.c");
+    assertFalse(c.getFinalName().contains("."));
 
-    DexType abc = dexItemFactory.createType("Lnaming101/a/b/c;");
-    assertFalse(naming.lookupDescriptor(abc).toSourceString().contains("/"));
-    assertNotEquals(
-        naming.lookupDescriptor(abc).toSourceString(),
-        naming.lookupDescriptor(c).toSourceString());
+    ClassSubject abc = inspector.clazz("naming101.a.b.c");
+    assertFalse(abc.getFinalName().contains("."));
+    assertNotEquals(abc.getFinalName(), c.getFinalName());
   }
 
-  private static void test101_rule002(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+  private static void test101_rule002(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING101);
 
     // Check naming101.*.a is kept due to **.a
-    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
-    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
-    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+    ClassSubject aa = inspector.clazz("naming101.a.a");
+    assertEquals("naming101.a.a", aa.getFinalName());
+    ClassSubject ba = inspector.clazz("naming101.b.a");
+    assertEquals("naming101.b.a", ba.getFinalName());
 
     // Repackaged to naming101.a, but naming101.a.a exists to make a name conflict.
     // Thus, everything else should not be renamed to 'a',
     // except for naming101.b.a, which is also kept due to **.a
-    List<String> klasses = ImmutableList.of(
-        "Lnaming101/c;",
-        "Lnaming101/d;",
-        "Lnaming101/a/c;",
-        "Lnaming101/a/b/c;",
-        "Lnaming101/b/b;");
+    List<String> klasses =
+        ImmutableList.of(
+            "naming101.c", "naming101.d", "naming101.a.c", "naming101.a.b.c", "naming101.b.b");
     for (String klass : klasses) {
-      DexType k = dexItemFactory.createType(klass);
-      String renamedName = naming.lookupDescriptor(k).toSourceString();
-      assertEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
-      assertNotEquals("Lnaming101/a/a;", renamedName);
+      ClassSubject k = inspector.clazz(klass);
+      assertEquals("naming101.a", k.getDexProgramClass().getType().getPackageName());
+      assertNotEquals("naming101.a.a", k.getFinalName());
     }
   }
 
-  private static void test101_rule003(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+  private static void test101_rule003(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING101);
 
     // All packages are moved to the top-level package, hence only one package separator.
-    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals(1, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
+    ClassSubject aa = inspector.clazz("naming101.a.a");
+    assertEquals(1, countPackageDepth(aa));
 
-    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
-    assertEquals(1, countPackageDepth(naming.lookupDescriptor(ba).toSourceString()));
+    ClassSubject ba = inspector.clazz("naming101.b.a");
+    assertEquals(1, countPackageDepth(ba));
 
     assertNotEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(aa).toSourceString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(ba).toSourceString()));
+        aa.getDexProgramClass().getType().getPackageName(),
+        ba.getDexProgramClass().getType().getPackageName());
   }
 
-  private static void test101_rule004(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+  private static void test101_rule004(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING101);
 
     // Check naming101.*.a is kept due to **.a
-    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
-    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
-    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+    ClassSubject aa = inspector.clazz("naming101.a.a");
+    assertEquals("naming101.a.a", aa.getFinalName());
+    ClassSubject ba = inspector.clazz("naming101.b.a");
+    assertEquals("naming101.b.a", ba.getFinalName());
 
     // Flattened to naming101, hence all other classes will be in naming101.* package.
     // Due to naming101.a.a, prefix naming101.a is already used. So, any other classes,
     // except for naming101.a.c, should not have naming101.a as package.
-    List<String> klasses = ImmutableList.of(
-        "Lnaming101/c;",
-        "Lnaming101/d;",
-        "Lnaming101/a/b/c;",
-        "Lnaming101/b/a;",
-        "Lnaming101/b/b;");
+    List<String> klasses =
+        ImmutableList.of(
+            "naming101.c", "naming101.d", "naming101.a.b.c", "naming101.b.a", "naming101.b.b");
     for (String klass : klasses) {
-      DexType k = dexItemFactory.createType(klass);
-      String renamedName = naming.lookupDescriptor(k).toSourceString();
-      assertNotEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+      ClassSubject k = inspector.clazz(klass);
+      assertNotEquals("naming101.a", k.getDexProgramClass().getType().getPackageName());
     }
   }
 
-  private static void test101_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+  private static void test101_rule005(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING101);
 
     // All packages are renamed somehow. Need to check package hierarchy is consistent.
-    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals(2, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
-    DexType abc = dexItemFactory.createType("Lnaming101/a/b/c;");
-    assertEquals(3, countPackageDepth(naming.lookupDescriptor(abc).toSourceString()));
+    ClassSubject aa = inspector.clazz("naming101.a.a");
+    assertEquals(2, countPackageDepth(aa));
+    ClassSubject abc = inspector.clazz("naming101.a.b.c");
+    assertEquals(3, countPackageDepth(abc));
 
-    // Lnaming101/a/a; -> La/a/a; --prefix--> La/a
-    // Lnaming101/a/b/c; -> La/a/a/a; --prefix--> La/a/a --prefix--> La/a
+    // naming101.a/a; -> La/a/a; --prefix--> La/a
+    // naming101.a/b/c; -> La/a/a/a; --prefix--> La/a/a --prefix--> La/a
     assertEquals(
-        getParentPackagePrefix(naming.lookupDescriptor(aa).toSourceString()),
-        getParentPackagePrefix(
-            getParentPackagePrefix(naming.lookupDescriptor(abc).toSourceString())));
+        getParentPackagePrefix(aa.getFinalName()),
+        getParentPackagePrefix(getParentPackagePrefix(abc.getFinalName())));
   }
 
-  private static void test101_rule102(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+  private static void test101_rule102(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING101);
 
     // Check naming101.*.a is kept due to **.a
-    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
-    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
-    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+    ClassSubject aa = inspector.clazz("naming101.a.a");
+    assertEquals("naming101.a.a", aa.getFinalName());
+    ClassSubject ba = inspector.clazz("naming101.b.a");
+    assertEquals("naming101.b.a", ba.getFinalName());
     // Due to package-private access, classes in the same package should remain in the same package.
-    DexType ac = dexItemFactory.createType("Lnaming101/a/c;");
+    ClassSubject ac = inspector.clazz("naming101.a.c");
     assertEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(aa).toString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(ac).toString()));
-    DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
+        aa.getDexProgramClass().getType().getPackageName(),
+        ac.getDexProgramClass().getType().getPackageName());
+    ClassSubject bb = inspector.clazz("naming101.b.b");
     assertEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(ba).toString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(bb).toString()));
+        ba.getDexProgramClass().getType().getPackageName(),
+        bb.getDexProgramClass().getType().getPackageName());
 
     // All other classes can be repackaged to naming101.a, but naming101.a.a exists to make a name
     // conflict. Thus, those should not be renamed to 'a'.
-    List<String> klasses = ImmutableList.of(
-        "Lnaming101/c;",
-        "Lnaming101/d;",
-        "Lnaming101/a/b/c;");
+    List<String> klasses = ImmutableList.of("naming101.c", "naming101.d", "naming101.a.b.c");
     for (String klass : klasses) {
-      DexType k = dexItemFactory.createType(klass);
-      String renamedName = naming.lookupDescriptor(k).toSourceString();
-      assertEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
-      assertNotEquals("Lnaming101/a/a;", renamedName);
+      ClassSubject k = inspector.clazz(klass);
+      assertEquals("naming101.a", k.getDexProgramClass().getType().getPackageName());
+      assertNotEquals("naming101.a.a", k.getFinalName());
     }
   }
 
-  private static void test101_rule104(DexItemFactory dexItemFactory, NamingLens naming) {
-    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+  private static void test101_rule104(CodeInspector inspector) {
+    verifyUniqueNaming(inspector, CLASSES_IN_NAMING101);
 
     // Check naming101.*.a is kept due to **.a
-    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
-    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
-    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+    ClassSubject aa = inspector.clazz("naming101.a.a");
+    assertEquals("naming101.a.a", aa.getFinalName());
+    ClassSubject ba = inspector.clazz("naming101.b.a");
+    assertEquals("naming101.b.a", ba.getFinalName());
     // Due to package-private access, classes in the same package should remain in the same package.
-    DexType ac = dexItemFactory.createType("Lnaming101/a/c;");
+    ClassSubject ac = inspector.clazz("naming101.a.c");
     assertEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(aa).toString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(ac).toString()));
-    DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
+        aa.getDexProgramClass().getType().getPackageName(),
+        ac.getDexProgramClass().getType().getPackageName());
+    ClassSubject bb = inspector.clazz("naming101.b.b");
     assertEquals(
-        getPackageNameFromDescriptor(naming.lookupDescriptor(ba).toString()),
-        getPackageNameFromDescriptor(naming.lookupDescriptor(bb).toString()));
+        ba.getDexProgramClass().getType().getPackageName(),
+        bb.getDexProgramClass().getType().getPackageName());
 
     // All other packages are flattened to naming101, hence all other classes will be in
     // naming101.* package. Due to naming101.a.a, prefix naming101.a is already used. So,
     // remaining classes should not have naming101.a as package.
-    List<String> klasses = ImmutableList.of(
-        "Lnaming101/c;",
-        "Lnaming101/d;",
-        "Lnaming101/a/b/c;");
+    List<String> klasses = ImmutableList.of("naming101.c", "naming101.d", "naming101.a.b.c");
     for (String klass : klasses) {
-      DexType k = dexItemFactory.createType(klass);
-      String renamedName = naming.lookupDescriptor(k).toSourceString();
-      assertNotEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+      ClassSubject k = inspector.clazz(klass);
+      assertNotEquals("naming101.a", k.getDexProgramClass().getType().getPackageName());
     }
   }
 
-  private static void test101_rule106(DexItemFactory dexItemFactory, NamingLens naming) {
+  private static void test101_rule106(CodeInspector inspector) {
     // Classes that end with either c or d will be kept and renamed.
     List<String> klasses =
         CLASSES_IN_NAMING101.stream()
-            .filter(className -> className.endsWith("c;") || className.endsWith("d;"))
+            .filter(className -> className.endsWith("c") || className.endsWith("d"))
             .collect(Collectors.toList());
-    verifyUniqueNaming(dexItemFactory, naming, klasses);
+    verifyUniqueNaming(inspector, klasses);
 
     // Check naming101.c is kept as-is.
-    DexType topC = dexItemFactory.createType("Lnaming101/c;");
-    assertEquals("Lnaming101/c;", naming.lookupDescriptor(topC).toSourceString());
+    ClassSubject topC = inspector.clazz("naming101.c");
+    assertEquals("naming101.c", topC.getFinalName());
 
     // naming101.d accesses to a package-private, static field in naming101.c.
     // Therefore, it should remain in the same package.
-    DexType topD = dexItemFactory.createType("Lnaming101/d;");
-    String renamedTopD = naming.lookupDescriptor(topD).toSourceString();
-    assertEquals("naming101", getPackageNameFromDescriptor(renamedTopD));
+    ClassSubject topD = inspector.clazz("naming101.d");
+    assertEquals("naming101", topD.getDexProgramClass().getType().getPackageName());
 
     // Due to the keep rule for naming101.c, package prefix naming101 is reserved.
     // That is, every renamed class should have naming101 as parent package prefix.
     for (String klass : klasses) {
-      DexType t = dexItemFactory.createType(klass);
-      String rename = naming.lookupDescriptor(t).toSourceString();
-      assertTrue(rename.startsWith("Lnaming101/"));
+      ClassSubject t = inspector.clazz(klass);
+      assertTrue(t.getFinalName().startsWith("naming101."));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java b/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
index 01a95ba..7511e28 100644
--- a/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
@@ -58,7 +58,7 @@
             L.class,
             ImplL.class)
         .addKeepMainRule(Main.class)
-        .addKeepClassRules(InterfaceToKeep.class)
+        .addKeepClassRules(InterfaceToKeep.class, ImplI.class, K.class)
         .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*")
         .setMinApi(parameters.getApiLevel())
         .noMinification()
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
new file mode 100644
index 0000000..47a1dcd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
@@ -0,0 +1,87 @@
+// 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.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.repackage.RepackageInnerAndOuterClassTest.Outer.Inner;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageInnerAndOuterClassTest extends RepackageTestBase {
+
+  public RepackageInnerAndOuterClassTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    test(
+        testForR8(parameters.getBackend())
+            // TODO(b/169750465): It should not be necessary to explicitly remove the inner class
+            //  attributes from TestClass to allow repackaging of Inner and Outer.
+            .addProgramClassFileData(transformer(TestClass.class).removeInnerClasses().transform()),
+        true);
+  }
+
+  @Test
+  public void testR8Compat() throws Exception {
+    test(testForR8Compat(parameters.getBackend()).addProgramClasses(TestClass.class), false);
+  }
+
+  private void test(R8TestBuilder<?> testBuilder, boolean eligibleForRepackaging) throws Exception {
+    testBuilder
+        .addProgramClasses(Outer.class, Inner.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .enableNoStaticClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> inspect(inspector, eligibleForRepackaging))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector, boolean eligibleForRepackaging) {
+    assertThat(Outer.class, isRepackagedIf(inspector, eligibleForRepackaging));
+    assertThat(Inner.class, isRepackagedIf(inspector, eligibleForRepackaging));
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      Outer.greet();
+      Inner.greet();
+    }
+  }
+
+  @NoStaticClassMerging
+  public static class Outer {
+
+    @NeverInline
+    public static void greet() {
+      System.out.print("Hello");
+    }
+
+    @NoStaticClassMerging
+    public static class Inner {
+
+      @NeverInline
+      public static void greet() {
+        System.out.println(" world!");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
index 2f6574b..b9d3c40 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -169,16 +169,18 @@
 
     // 3.A, 3.B, 3.C) Accessing a kept method is OK.
     markShouldAlwaysBeEligible.accept(AccessPublicKeptMethodOnReachableClass.class);
-    markShouldAlwaysBeEligible.accept(AccessPackagePrivateKeptMethodOnReachableClassDirect.class);
-    markShouldAlwaysBeEligible.accept(AccessPackagePrivateKeptMethodOnReachableClassIndirect.class);
+    markEligibleWithAllowAccessModification.accept(
+        AccessPackagePrivateKeptMethodOnReachableClassDirect.class);
+    markEligibleWithAllowAccessModification.accept(
+        AccessPackagePrivateKeptMethodOnReachableClassIndirect.class);
 
     // 4) -keepclassmembers,allowobfuscation class ReachableClassWithKeptMethod { <methods>; }
 
     // 4.A, 4.B, 4.C) Accessing a kept method is OK.
     markShouldAlwaysBeEligible.accept(AccessPublicKeptMethodAllowRenamingOnReachableClass.class);
-    markShouldAlwaysBeEligible.accept(
+    markEligibleWithAllowAccessModification.accept(
         AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.class);
-    markShouldAlwaysBeEligible.accept(
+    markEligibleWithAllowAccessModification.accept(
         AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.class);
 
     // 5) No keep rule.
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
index 8eb3d77..3e2f695 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
@@ -8,7 +8,7 @@
 import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
 import static org.junit.Assert.assertFalse;
 
-import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.StringUtils;
@@ -62,6 +62,10 @@
     return isRepackagedAsExpected(inspector, null, eligibleForRepackaging);
   }
 
+  protected Matcher<Class<?>> isNotRepackaged(CodeInspector inspector) {
+    return isRepackagedAsExpected(inspector, null, false);
+  }
+
   /**
    * Checks that the class of interest is repackaged as expected.
    *
@@ -135,7 +139,7 @@
     };
   }
 
-  protected void configureRepackaging(R8FullTestBuilder testBuilder) {
+  protected void configureRepackaging(R8TestBuilder<?> testBuilder) {
     testBuilder
         .addKeepRules(
             "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + getRepackagePackage() + "\"")
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithArrayMethodTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithArrayMethodTest.java
new file mode 100644
index 0000000..0216990
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithArrayMethodTest.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithArrayMethodTest extends RepackageTestBase {
+
+  public RepackageWithArrayMethodTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {}
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      A[] array = new A[1];
+      array[0] = new A();
+      array.clone()[0].greet();
+    }
+  }
+
+  public static class A {
+
+    @NeverInline
+    public void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithBridgeHoistingTest.java
new file mode 100644
index 0000000..3db9028
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithBridgeHoistingTest.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.repackage;
+
+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.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithBridgeHoistingTest extends RepackageTestBase {
+
+  public RepackageWithBridgeHoistingTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            TestClass.class, GreeterInterface.class, GreeterBase.class, Greeting.class)
+        .addProgramClassFileData(
+            transformer(PrintGreeter.class)
+                .setBridge(PrintGreeter.class.getDeclaredMethod("greetBridge", Greeting.class))
+                .transform(),
+            transformer(PrintlnGreeter.class)
+                .setBridge(PrintlnGreeter.class.getDeclaredMethod("greetBridge", Greeting.class))
+                .transform())
+        .addKeepMainRule(TestClass.class)
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject greeterBase = inspector.clazz(GreeterBase.class);
+    assertThat(greeterBase, isPresent());
+    assertThat(greeterBase.uniqueMethodWithName("greetBridge"), isPresent());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      new PrintGreeter().greetBridge(new Greeting("Hello"));
+      new PrintGreeter().greetBridge(new Greeting(" world!"));
+
+      // Pass in an unknown greeter to prevent devirtualization in println().
+      println(System.currentTimeMillis() >= 0 ? new PrintlnGreeter() : new PrintGreeter());
+    }
+
+    @NeverInline
+    static void println(GreeterInterface greeter) {
+      greeter.greet(new Greeting(""));
+    }
+  }
+
+  @NoVerticalClassMerging
+  public interface GreeterInterface {
+
+    void greet(Greeting greeting);
+  }
+
+  public abstract static class GreeterBase implements GreeterInterface {
+
+    // Will become the holder for greetBridge() after bridge hoisting. The bridge implementation
+    // will be "invoke-virtual GreeterBase.greet()". This signature is a new non-rebound method
+    // signature introduced by bridge hoisting.
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class PrintGreeter extends GreeterBase {
+
+    // Will be hoisted to GreeterBase.greetBridge().
+    @NeverInline
+    public /*bridge*/ void greetBridge(Greeting greeting) {
+      greet(greeting);
+    }
+
+    @NeverInline
+    public void greet(Greeting greeting) {
+      System.out.print(greeting);
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class PrintlnGreeter extends GreeterBase {
+
+    // Will be hoisted to GreeterBase.greetBridge().
+    @NeverInline
+    public /*bridge*/ void greetBridge(Greeting greeting) {
+      greet(greeting);
+    }
+
+    @NeverInline
+    public void greet(Greeting greeting) {
+      System.out.println(greeting);
+    }
+  }
+
+  public static class Greeting {
+
+    private final String greeting;
+
+    public Greeting(String greeting) {
+      this.greeting = greeting;
+    }
+
+    @Override
+    public String toString() {
+      return greeting;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
index 9bc8115..e548bcf 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
@@ -4,23 +4,43 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
 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.NeverPropagateValue;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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 RepackageWithInitClassTest extends RepackageTestBase {
 
+  private final boolean enableMemberValuePropagationAnnotations;
+
+  @Parameters(name = "{2}, kind: {1}, @NeverPropagateValue: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
   public RepackageWithInitClassTest(
-      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+      boolean enableMemberValuePropagationAnnotations,
+      String flattenPackageHierarchyOrRepackageClasses,
+      TestParameters parameters) {
     super(flattenPackageHierarchyOrRepackageClasses, parameters);
+    this.enableMemberValuePropagationAnnotations = enableMemberValuePropagationAnnotations;
   }
 
   @Test
@@ -30,6 +50,7 @@
         .addClassObfuscationDictionary("a")
         .addKeepMainRule(TestClass.class)
         .apply(this::configureRepackaging)
+        .enableMemberValuePropagationAnnotations(enableMemberValuePropagationAnnotations)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -41,28 +62,45 @@
     ClassSubject repackagedClassSubject = inspector.clazz(StaticMemberValuePropagation.class);
     assertThat(repackagedClassSubject, isPresent());
 
-    // Verify that a $r8$clinit field was synthesized.
     String clinitFieldName = inspector.getFactory().objectMembers.clinitField.name.toSourceString();
-    assertThat(repackagedClassSubject.uniqueFieldWithName(clinitFieldName), isPresent());
-    assertThat(repackagedClassSubject.uniqueFieldWithName("GREETING"), not(isPresent()));
+    if (enableMemberValuePropagationAnnotations) {
+      // No $r8$clinit field should have been synthesized since we can use the HELLO field.
+      assertThat(repackagedClassSubject.uniqueFieldWithName(clinitFieldName), not(isPresent()));
+      assertThat(repackagedClassSubject.uniqueFieldWithName("HELLO"), isPresent());
 
-    // Verify that the class was repackaged.
-    assertThat(StaticMemberValuePropagation.class, isRepackaged(inspector));
+      // Verify that the WORLD field has been removed.
+      assertThat(repackagedClassSubject.uniqueFieldWithName("WORLD"), not(isPresent()));
+
+      // Verify that the class was not repackaged.
+      assertThat(StaticMemberValuePropagation.class, isNotRepackaged(inspector));
+    } else {
+      // Verify that a $r8$clinit field was synthesized.
+      assertThat(repackagedClassSubject.uniqueFieldWithName(clinitFieldName), isPresent());
+
+      // Verify that both fields have been removed.
+      assertThat(repackagedClassSubject.uniqueFieldWithName("HELLO"), not(isPresent()));
+      assertThat(repackagedClassSubject.uniqueFieldWithName("WORLD"), not(isPresent()));
+
+      // Verify that the class was repackaged.
+      assertThat(StaticMemberValuePropagation.class, isRepackaged(inspector));
+    }
   }
 
   static class TestClass {
 
     public static void main(String[] args) {
-      System.out.println(StaticMemberValuePropagation.GREETING);
+      System.out.println(StaticMemberValuePropagation.WORLD);
     }
   }
 
   public static class StaticMemberValuePropagation {
 
-    public static String GREETING = " world!";
+    @NeverPropagateValue static String HELLO = "Hello";
+
+    public static String WORLD = " world!";
 
     static {
-      System.out.print("Hello");
+      System.out.print(HELLO);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithKeepPackagePrivateTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithKeepPackagePrivateTest.java
new file mode 100644
index 0000000..8020e78
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithKeepPackagePrivateTest.java
@@ -0,0 +1,104 @@
+// 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.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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 RepackageWithKeepPackagePrivateTest extends RepackageTestBase {
+
+  private final boolean allowAccessModification;
+
+  @Parameters(name = "{2}, kind: {1}, access modification: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public RepackageWithKeepPackagePrivateTest(
+      boolean allowAccessModification,
+      String flattenPackageHierarchyOrRepackageClasses,
+      TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+    this.allowAccessModification = allowAccessModification;
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addKeepRules(getKeepRules())
+            .addKeepPackageNamesRule(getClass().getPackage())
+            .allowAccessModification(allowAccessModification)
+            .apply(this::configureRepackaging)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::inspect);
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addClasspathClasses(A.class, B.class)
+        .addKeepAllClassesRule()
+        .addApplyMapping(compileResult.getProguardMap())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private List<String> getKeepRules() {
+    String modifiers = "allowobfuscation";
+    if (allowAccessModification) {
+      modifiers += ",allowaccessmodification";
+    }
+    return ImmutableList.of(
+        "-keep," + modifiers + " class " + A.class.getTypeName() + " { void greet(); }",
+        "-keep," + modifiers + " class " + B.class.getTypeName() + " { void greet(); }");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(A.class, isRepackagedIf(inspector, allowAccessModification));
+    assertThat(B.class, isRepackagedIf(inspector, allowAccessModification));
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      A.greet();
+      B.greet();
+    }
+  }
+
+  static class A {
+
+    public static void greet() {
+      System.out.print("Hello");
+    }
+  }
+
+  public static class B {
+
+    static void greet() {
+      System.out.println(" world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithOverridesOfPackagePrivateMethodsTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithOverridesOfPackagePrivateMethodsTest.java
new file mode 100644
index 0000000..951a2b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithOverridesOfPackagePrivateMethodsTest.java
@@ -0,0 +1,96 @@
+// 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.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithOverridesOfPackagePrivateMethodsTest extends RepackageTestBase {
+
+  public RepackageWithOverridesOfPackagePrivateMethodsTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(HelloGreeter.class, isNotRepackaged(inspector));
+    assertThat(WorldGreeter.class, isNotRepackaged(inspector));
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      greet(new HelloGreeter());
+      greet(new WorldGreeter());
+    }
+
+    @NeverInline
+    static void greet(HelloGreeterBase greeter) {
+      greeter.greet();
+    }
+
+    @NeverInline
+    static void greet(WorldGreeterBase greeter) {
+      greeter.greet();
+    }
+  }
+
+  @NoVerticalClassMerging
+  public abstract static class HelloGreeterBase {
+
+    abstract void greet();
+  }
+
+  @NeverClassInline
+  public static class HelloGreeter extends HelloGreeterBase {
+
+    @NeverInline
+    @Override
+    void greet() {
+      System.out.print("Hello");
+    }
+  }
+
+  @NoVerticalClassMerging
+  public abstract static class WorldGreeterBase {
+
+    abstract void greet();
+  }
+
+  @NeverClassInline
+  public static class WorldGreeter extends WorldGreeterBase {
+
+    @NeverInline
+    @Override
+    public void greet() {
+      System.out.println(" world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithOverridesOfProtectedMethodsTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithOverridesOfProtectedMethodsTest.java
new file mode 100644
index 0000000..7747cbd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithOverridesOfProtectedMethodsTest.java
@@ -0,0 +1,96 @@
+// 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.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithOverridesOfProtectedMethodsTest extends RepackageTestBase {
+
+  public RepackageWithOverridesOfProtectedMethodsTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(HelloGreeter.class, isRepackaged(inspector));
+    assertThat(WorldGreeter.class, isRepackaged(inspector));
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      greet(new HelloGreeter());
+      greet(new WorldGreeter());
+    }
+
+    @NeverInline
+    static void greet(HelloGreeterBase greeter) {
+      greeter.greet();
+    }
+
+    @NeverInline
+    static void greet(WorldGreeterBase greeter) {
+      greeter.greet();
+    }
+  }
+
+  @NoVerticalClassMerging
+  public abstract static class HelloGreeterBase {
+
+    protected abstract void greet();
+  }
+
+  @NeverClassInline
+  public static class HelloGreeter extends HelloGreeterBase {
+
+    @NeverInline
+    @Override
+    protected void greet() {
+      System.out.print("Hello");
+    }
+  }
+
+  @NoVerticalClassMerging
+  public abstract static class WorldGreeterBase {
+
+    protected abstract void greet();
+  }
+
+  @NeverClassInline
+  public static class WorldGreeter extends WorldGreeterBase {
+
+    @NeverInline
+    @Override
+    public void greet() {
+      System.out.println(" world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithServiceLoaderTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithServiceLoaderTest.java
new file mode 100644
index 0000000..13ebc95
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithServiceLoaderTest.java
@@ -0,0 +1,126 @@
+// 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.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DataResourceConsumerForTesting;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.util.List;
+import java.util.ServiceLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithServiceLoaderTest extends RepackageTestBase {
+
+  public RepackageWithServiceLoaderTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    Box<DataResourceConsumerForTesting> dataResourceConsumer = new Box<>();
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addDataEntryResources(
+                DataEntryResource.fromBytes(
+                    StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+                    "META-INF/services/" + Service.class.getTypeName(),
+                    Origin.unknown()))
+            .addKeepMainRule(TestClass.class)
+            .addOptionsModification(
+                options -> {
+                  dataResourceConsumer.set(
+                      new DataResourceConsumerForTesting(options.dataResourceConsumer));
+                  options.dataResourceConsumer = dataResourceConsumer.get();
+                })
+            .apply(this::configureRepackaging)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::inspect)
+            .run(parameters.getRuntime(), TestClass.class)
+            .assertSuccessWithOutputLines("Hello world!");
+
+    CodeInspector inspector = runResult.inspector();
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    assertTrue(
+        testClassSubject
+            .mainMethod()
+            .streamInstructions()
+            .filter(InstructionSubject::isInvokeStatic)
+            .map(InstructionSubject::getMethod)
+            .anyMatch(
+                method ->
+                    method
+                        .toSourceString()
+                        .equals(
+                            "java.util.ServiceLoader"
+                                + " java.util.ServiceLoader.load(java.lang.Class)")));
+
+    ClassSubject serviceClassSubject = inspector.clazz(Service.class);
+    assertThat(serviceClassSubject, isPresent());
+
+    ClassSubject serviceImplClassSubject = inspector.clazz(ServiceImpl.class);
+    assertThat(serviceImplClassSubject, isPresent());
+
+    inspectResource(
+        dataResourceConsumer.get().get("META-INF/services/" + serviceClassSubject.getFinalName()),
+        serviceImplClassSubject);
+  }
+
+  private void inspectResource(List<String> contents, ClassSubject serviceImplClassSubject) {
+    assertNotNull(contents);
+    assertEquals(1, contents.size());
+    assertEquals(serviceImplClassSubject.getFinalName(), contents.get(0));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(Service.class, isRepackaged(inspector));
+    assertThat(ServiceImpl.class, isRepackaged(inspector));
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      for (Service service : ServiceLoader.load(getServiceClass())) {
+        service.greet();
+      }
+    }
+
+    static Class<? extends Service> getServiceClass() {
+      return System.currentTimeMillis() > 0 ? Service.class : ServiceImpl.class;
+    }
+  }
+
+  public interface Service {
+
+    void greet();
+  }
+
+  public static class ServiceImpl implements Service {
+
+    @Override
+    public void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java
index 170116b..ee99ea1 100644
--- a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java
@@ -4,13 +4,19 @@
 
 package com.android.tools.r8.rewrite.serviceloaders;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DataResourceConsumerForTesting;
@@ -49,8 +55,10 @@
                 AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
                 Origin.unknown()))
         .addOptionsModification(o -> o.dataResourceConsumer = dataResourceConsumer)
+        .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
+        .inspectDiagnosticMessages(this::inspectDiagnosticMessages)
         .addRunClasspathClasses(Service.class, ServiceImpl.class)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithEmptyOutput();
@@ -59,6 +67,41 @@
         dataResourceConsumer.get(AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName()));
   }
 
+  @Test
+  public void testMainDex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForMainDexListGenerator()
+        .addProgramClasses(TestClass.class)
+        .addLibraryFiles(ToolHelper.getFirstSupportedAndroidJar(parameters.getApiLevel()))
+        .addMainDexRules(keepMainProguardConfiguration(TestClass.class))
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+                AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
+                Origin.unknown()))
+        .run()
+        .inspectDiagnosticMessages(this::inspectDiagnosticMessages);
+  }
+
+  private void inspectDiagnosticMessages(TestDiagnosticMessages inspector) {
+    inspector.assertWarningsCount(2);
+    inspector.assertAllWarningsMatch(
+        diagnosticMessage(
+            anyOf(
+                containsString(
+                    "Unexpected reference to missing service class: "
+                        + AppServices.SERVICE_DIRECTORY_NAME
+                        + Service.class.getTypeName()
+                        + "."),
+                containsString(
+                    "Unexpected reference to missing service implementation class in "
+                        + AppServices.SERVICE_DIRECTORY_NAME
+                        + Service.class.getTypeName()
+                        + ": "
+                        + ServiceImpl.class.getTypeName()
+                        + "."))));
+  }
+
   private void inspectResource(List<String> contents) {
     assertNotNull(contents);
     assertEquals(1, contents.size());
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java
index 5976b7c..9039c50 100644
--- a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.rewrite.serviceloaders;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -70,8 +72,22 @@
                       new DataResourceConsumerForTesting(options.dataResourceConsumer));
                   options.dataResourceConsumer = dataResourceConsumer.get();
                 })
+            .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
-            .compile();
+            .compile()
+            .inspectDiagnosticMessages(
+                inspector -> {
+                  inspector.assertWarningsCount(1);
+                  inspector.assertAllWarningsMatch(
+                      diagnosticMessage(
+                          containsString(
+                              "Unexpected reference to missing service implementation class in "
+                                  + AppServices.SERVICE_DIRECTORY_NAME
+                                  + Service.class.getTypeName()
+                                  + ": "
+                                  + ServiceImpl.class.getTypeName()
+                                  + ".")));
+                });
 
     CodeInspector inspector = compileResult.inspector();
     ClassSubject serviceClassSubject = inspector.clazz(Service.class);
@@ -154,6 +170,7 @@
 
   public static class ServiceImpl implements Service {
 
+    @Override
     @NeverInline
     public void greet() {
       System.out.println("Hello world!");
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java
index 896cc71..b3f120c 100644
--- a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java
@@ -4,12 +4,16 @@
 
 package com.android.tools.r8.rewrite.serviceloaders;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.rewrite.serviceloaders.MissingServiceImplementationClassTest.Service;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.ServiceLoader;
 import org.junit.Test;
@@ -44,8 +48,20 @@
                 AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
                 Origin.unknown()))
         .addFeatureSplit(FeatureClass.class)
+        .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
-        .compile();
+        .compile()
+        .inspectDiagnosticMessages(
+            inspector -> {
+              inspector.assertWarningsCount(1);
+              inspector.assertAllWarningsMatch(
+                  diagnosticMessage(
+                      containsString(
+                          "Unexpected reference to missing service implementation class in "
+                              + AppServices.SERVICE_DIRECTORY_NAME
+                              + Service.class.getTypeName()
+                              + ": MissingClass.")));
+            });
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 6bd40a0..184fdc9 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -100,7 +100,6 @@
 
   @Test
   public void testWithMembersPresentAnnotation() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     testForR8(Backend.CF)
         .addProgramFiles(R8_JAR)
         .addKeepRules("-keepclasseswithmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
diff --git a/src/test/java/com/android/tools/r8/shaking/innerclassattributes/MissingOuterClassTest.java b/src/test/java/com/android/tools/r8/shaking/innerclassattributes/MissingOuterClassTest.java
new file mode 100644
index 0000000..c16510e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/innerclassattributes/MissingOuterClassTest.java
@@ -0,0 +1,97 @@
+// 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.innerclassattributes;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.shaking.innerclassattributes.MissingOuterClassTest.Outer.Inner;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 MissingOuterClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MissingOuterClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepAllClassesRule()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .compile()
+        .inspect(
+            inspector -> {
+              // Verify that the inner class attributes referring to MissingOuterClassTest have been
+              // removed.
+              ClassSubject outerClassSubject = inspector.clazz(Outer.class);
+              assertThat(outerClassSubject, isPresent());
+
+              ClassSubject innerClassSubject = inspector.clazz(Inner.class);
+              assertThat(innerClassSubject, isPresent());
+
+              List<InnerClassAttribute> outerInnerClassAttributes =
+                  outerClassSubject.getDexProgramClass().getInnerClasses();
+              assertEquals(1, outerInnerClassAttributes.size());
+              inspectInnerClassAttribute(
+                  outerClassSubject,
+                  outerInnerClassAttributes.get(0),
+                  outerClassSubject,
+                  innerClassSubject);
+
+              List<InnerClassAttribute> innerInnerClassAttributes =
+                  innerClassSubject.getDexProgramClass().getInnerClasses();
+              assertEquals(1, innerInnerClassAttributes.size());
+              inspectInnerClassAttribute(
+                  innerClassSubject,
+                  innerInnerClassAttributes.get(0),
+                  outerClassSubject,
+                  innerClassSubject);
+            });
+  }
+
+  private void inspectInnerClassAttribute(
+      ClassSubject hostClassSubject,
+      InnerClassAttribute innerClassAttribute,
+      ClassSubject outerClassSubject,
+      ClassSubject innerClassSubject) {
+    assertEquals(innerClassSubject.getDexProgramClass().getType(), innerClassAttribute.getInner());
+    assertEquals(outerClassSubject.getDexProgramClass().getType(), innerClassAttribute.getOuter());
+    if (parameters.isCfRuntime() || hostClassSubject == innerClassSubject) {
+      assertEquals(
+          DescriptorUtils.getInnerClassName(
+              outerClassSubject.getFinalDescriptor(), innerClassSubject.getFinalDescriptor()),
+          innerClassAttribute.getInnerName().toSourceString());
+    } else {
+      assertEquals(DexItemFactory.unknownTypeName, innerClassAttribute.getInnerName());
+    }
+  }
+
+  static class Outer {
+
+    static class Inner {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
index 489d8b2..88ec578 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -14,7 +14,7 @@
 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.tracereferences.TraceReferencesFormattingConsumer.OutputFormat;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -26,6 +26,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -53,8 +54,7 @@
     assertEquals(0, command.getLibrary().size());
     assertEquals(0, command.getTarget().size());
     assertEquals(0, command.getSource().size());
-    assertEquals(TraceReferencesCommandParser.OutputFormat.PRINTUSAGE, command.getOutputFormat());
-    assertNull(command.getOutput());
+    assertNull(command.getConsumer());
   }
 
   @Test(expected = CompilationFailedException.class)
@@ -105,29 +105,48 @@
   }
 
   private String formatName(OutputFormat format) {
-    if (format == TraceReferencesCommandParser.OutputFormat.PRINTUSAGE) {
+    if (format == TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE) {
       return "printuses";
     }
-    if (format == TraceReferencesCommandParser.OutputFormat.KEEP_RULES) {
+    if (format == TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES) {
       return "keep";
     }
-    assertSame(format, TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION);
+    assertSame(
+        format, TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION);
     return "keepallowobfuscation";
   }
 
   public void runAndCheckOutput(
       Path targetJar, Path sourceJar, OutputFormat format, String expected) throws Throwable {
+    runAndCheckOutput(targetJar, sourceJar, format, expected, null);
+  }
+
+  public void runAndCheckOutput(
+      Path targetJar,
+      Path sourceJar,
+      OutputFormat format,
+      String expected,
+      Consumer<DiagnosticsChecker> diagnosticsCheckerConsumer)
+      throws Throwable {
     Path dir = temp.newFolder().toPath();
     Path output = dir.resolve("output.txt");
+    DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
+    TraceReferencesFormattingConsumer consumer = new TraceReferencesFormattingConsumer(format);
     TraceReferences.run(
-        TraceReferencesCommand.builder()
+        TraceReferencesCommand.builder(diagnosticsChecker)
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addTargetFiles(targetJar)
             .addSourceFiles(sourceJar)
-            .setOutputPath(output)
-            .setOutputFormat(format)
+            .setConsumer(consumer)
             .build());
-    assertEquals(expected, FileUtils.readTextFile(output, Charsets.UTF_8));
+    assertEquals(expected, consumer.get());
+    if (diagnosticsCheckerConsumer != null) {
+      diagnosticsCheckerConsumer.accept(diagnosticsChecker);
+    } else {
+      assertEquals(0, diagnosticsChecker.errors.size());
+      assertEquals(0, diagnosticsChecker.warnings.size());
+      assertEquals(0, diagnosticsChecker.infos.size());
+    }
 
     TraceReferences.run(
         TraceReferencesCommand.parse(
@@ -172,7 +191,7 @@
     runAndCheckOutput(
         ImmutableList.of(Target.class),
         ImmutableList.of(Source.class),
-        TraceReferencesCommandParser.OutputFormat.PRINTUSAGE,
+        TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE,
         StringUtils.lines(
             ImmutableList.of(
                 "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target",
@@ -187,7 +206,7 @@
     runAndCheckOutput(
         ImmutableList.of(Target.class),
         ImmutableList.of(Source.class),
-        TraceReferencesCommandParser.OutputFormat.KEEP_RULES,
+        TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES,
         StringUtils.lines(
             ImmutableList.of(
                 "-keep class com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
@@ -203,7 +222,7 @@
     runAndCheckOutput(
         ImmutableList.of(Target.class),
         ImmutableList.of(Source.class),
-        TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
+        TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
         StringUtils.lines(
             ImmutableList.of(
                 "-keep,allowobfuscation class"
@@ -219,7 +238,7 @@
     runAndCheckOutput(
         ImmutableList.of(OtherTarget.class),
         ImmutableList.of(Source.class),
-        TraceReferencesCommandParser.OutputFormat.PRINTUSAGE,
+        TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE,
         StringUtils.lines(ImmutableList.of()));
   }
 
@@ -228,7 +247,7 @@
     runAndCheckOutput(
         ImmutableList.of(OtherTarget.class),
         ImmutableList.of(Source.class),
-        TraceReferencesCommandParser.OutputFormat.KEEP_RULES,
+        TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES,
         StringUtils.lines(ImmutableList.of()));
   }
 
@@ -237,7 +256,7 @@
     runAndCheckOutput(
         ImmutableList.of(OtherTarget.class),
         ImmutableList.of(Source.class),
-        TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
+        TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
         StringUtils.lines(ImmutableList.of()));
   }
 
@@ -264,7 +283,7 @@
     runAndCheckOutput(
         targetJar,
         sourceJar,
-        TraceReferencesCommandParser.OutputFormat.PRINTUSAGE,
+        TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE,
         StringUtils.lines(
             ImmutableList.of(
                 "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target",
@@ -273,7 +292,12 @@
                     + ".target(int)",
                 "# Error: Could not find definition for field int"
                     + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
-                    + ".field")));
+                    + ".field")),
+        diagnosticsChecker -> {
+          assertEquals(0, diagnosticsChecker.errors.size());
+          assertEquals(2, diagnosticsChecker.warnings.size());
+          assertEquals(0, diagnosticsChecker.infos.size());
+        });
   }
 
   @Test
@@ -289,7 +313,7 @@
     runAndCheckOutput(
         targetJar,
         sourceJar,
-        TraceReferencesCommandParser.OutputFormat.KEEP_RULES,
+        TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES,
         StringUtils.lines(
             ImmutableList.of(
                 "-keep class com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
@@ -301,7 +325,12 @@
                     + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target"
                     + ".field",
                 "}",
-                "-keeppackagenames com.android.tools.r8.tracereferences")));
+                "-keeppackagenames com.android.tools.r8.tracereferences")),
+        diagnosticsChecker -> {
+          assertEquals(0, diagnosticsChecker.errors.size());
+          assertEquals(2, diagnosticsChecker.warnings.size());
+          assertEquals(0, diagnosticsChecker.infos.size());
+        });
   }
 
   @Test
@@ -317,7 +346,7 @@
     runAndCheckOutput(
         targetJar,
         sourceJar,
-        TraceReferencesCommandParser.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
+        TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION,
         StringUtils.lines(
             ImmutableList.of(
                 "-keep,allowobfuscation class"
@@ -327,7 +356,12 @@
                 "# Error: Could not find definition for field int"
                     + " com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target.field",
                 "}",
-                "-keeppackagenames com.android.tools.r8.tracereferences")));
+                "-keeppackagenames com.android.tools.r8.tracereferences")),
+        diagnosticsChecker -> {
+          assertEquals(0, diagnosticsChecker.errors.size());
+          assertEquals(2, diagnosticsChecker.warnings.size());
+          assertEquals(0, diagnosticsChecker.infos.size());
+        });
   }
 
   private byte[] getClassWithTargetRemoved() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 6a5b949..efaf58b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -245,12 +245,10 @@
   }
 
   public String getOriginalSignatureAttribute(
-      DexAnnotationSet annotations, BiConsumer<GenericSignatureParser, String> parse) {
-    String finalSignature = getFinalSignatureAttribute(annotations);
+      String finalSignature, BiConsumer<GenericSignatureParser, String> parse) {
     if (finalSignature == null || mapping == null) {
       return finalSignature;
     }
-
     GenericSignatureGenerator rewriter = new GenericSignatureGenerator();
     GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
     parse.accept(parser, finalSignature);
@@ -486,6 +484,6 @@
   }
 
   public RetraceApi retrace() {
-    return Retracer.create(mapping);
+    return Retracer.create(mapping == null ? ClassNameMapper.builder().build() : mapping);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
index e63a9fd..2fd8d37 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -14,7 +14,7 @@
 
   public abstract DexEncodedField getField();
 
-  public DexField getFieldReference() {
+  public DexField getDexField() {
     return getField().field;
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index ab1d03b..3192f8b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -28,14 +28,21 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
 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.RetraceApi;
+import com.android.tools.r8.retrace.RetraceTypeResult;
+import com.android.tools.r8.retrace.RetracedField;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.Sets;
 import java.io.File;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 import org.junit.rules.TemporaryFolder;
@@ -206,14 +213,58 @@
 
   @Override
   public FieldSubject uniqueFieldWithName(String name) {
-    FieldSubject fieldSubject = null;
+    return uniqueFieldWithName(name, null);
+  }
+
+  // TODO(b/169882658): This should be removed when we have identity mappings for ambiguous cases.
+  public FieldSubject uniqueFieldWithName(String name, TypeReference originalType) {
+    RetraceApi retracer = codeInspector.retrace();
+    Set<FoundFieldSubject> candidates = Sets.newIdentityHashSet();
+    Set<FoundFieldSubject> sameTypeCandidates = Sets.newIdentityHashSet();
     for (FoundFieldSubject candidate : allFields()) {
-      if (candidate.getOriginalName(false).equals(name)) {
-        assert fieldSubject == null;
-        fieldSubject = candidate;
+      FieldReference fieldReference = candidate.getDexField().asFieldReference();
+      // TODO(b/169882658): This if should be removed completely.
+      if (candidate.getFinalName().equals(name)) {
+        candidates.add(candidate);
+        if (isNullOrEqual(originalType, fieldReference.getFieldType())) {
+          sameTypeCandidates.add(candidate);
+        }
       }
+      retracer
+          .retrace(fieldReference)
+          .forEach(
+              element -> {
+                RetracedField field = element.getField();
+                if (!element.isUnknown() && field.getFieldName().equals(name)) {
+                  candidates.add(candidate);
+                  // TODO(b/169953605): There should not be a need for mapping the final type.
+                  TypeReference fieldOriginalType = originalType;
+                  if (fieldOriginalType == null) {
+                    RetraceTypeResult retraceTypeResult =
+                        retracer.retrace(fieldReference.getFieldType());
+                    assert !retraceTypeResult.isAmbiguous();
+                    fieldOriginalType =
+                        retraceTypeResult.stream().iterator().next().getType().getTypeReference();
+                  }
+                  if (isNullOrEqual(fieldOriginalType, field.asKnown().getFieldType())) {
+                    sameTypeCandidates.add(candidate);
+                  }
+                }
+              });
     }
-    return fieldSubject != null ? fieldSubject : new AbsentFieldSubject();
+    assert candidates.size() >= sameTypeCandidates.size();
+    // If we have any merged types we cannot rely on sameTypeCandidates, so we look in all
+    // candidates first.
+    if (candidates.size() == 1) {
+      return candidates.iterator().next();
+    }
+    return sameTypeCandidates.size() == 1
+        ? sameTypeCandidates.iterator().next()
+        : new AbsentFieldSubject();
+  }
+
+  private boolean isNullOrEqual(TypeReference original, TypeReference rewritten) {
+    return original == null || original.equals(rewritten);
   }
 
   @Override
@@ -359,12 +410,12 @@
   @Override
   public String getOriginalSignatureAttribute() {
     return codeInspector.getOriginalSignatureAttribute(
-        dexClass.annotations(), GenericSignatureParser::parseClassSignature);
+        dexClass.getClassSignature().toString(), GenericSignatureParser::parseClassSignature);
   }
 
   @Override
   public String getFinalSignatureAttribute() {
-    return codeInspector.getFinalSignatureAttribute(dexClass.annotations());
+    return dexClass.getClassSignature().toString();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index 411dad2..9ef7938 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -134,7 +134,8 @@
   @Override
   public String getOriginalSignatureAttribute() {
     return codeInspector.getOriginalSignatureAttribute(
-        dexField.annotations(), GenericSignatureParser::parseFieldSignature);
+        codeInspector.getFinalSignatureAttribute(dexField.annotations()),
+        GenericSignatureParser::parseFieldSignature);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 536aa5e3..4e49582 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -186,7 +186,8 @@
   @Override
   public String getOriginalSignatureAttribute() {
     return codeInspector.getOriginalSignatureAttribute(
-        dexMethod.annotations(), GenericSignatureParser::parseMethodSignature);
+        codeInspector.getFinalSignatureAttribute(dexMethod.annotations()),
+        GenericSignatureParser::parseMethodSignature);
   }
 
   public DexMethod getOriginalDexMethod(DexItemFactory dexItemFactory) {
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 934c06f..017a637 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
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import com.android.tools.r8.references.MethodReference;
@@ -436,6 +437,52 @@
     };
   }
 
+  public static Matcher<FieldSubject> isFieldOfType(DexType type) {
+    return new TypeSafeMatcher<FieldSubject>() {
+      @Override
+      protected boolean matchesSafely(FieldSubject fieldSubject) {
+        return fieldSubject.getDexField().type == type;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is field of type");
+      }
+
+      @Override
+      protected void describeMismatchSafely(FieldSubject item, Description mismatchDescription) {
+        mismatchDescription
+            .appendText(item.getOriginalSignature().toString())
+            .appendText(" is not of type ")
+            .appendText(type.toSourceString());
+      }
+    };
+  }
+
+  public static Matcher<FieldSubject> isFieldOfArrayType(
+      CodeInspector codeInspector, DexType type) {
+    return new TypeSafeMatcher<FieldSubject>() {
+      @Override
+      protected boolean matchesSafely(FieldSubject fieldSubject) {
+        return fieldSubject.getDexField().type.isArrayType()
+            && fieldSubject.getDexField().type.toBaseType(codeInspector.getFactory()) == type;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("is field of type");
+      }
+
+      @Override
+      protected void describeMismatchSafely(FieldSubject item, Description mismatchDescription) {
+        mismatchDescription
+            .appendText(item.getOriginalSignature().toString())
+            .appendText(" is not an array of type ")
+            .appendText(type.toSourceString());
+      }
+    };
+  }
+
   public static Matcher<RetraceMethodResult> isInlineFrame() {
     return new TypeSafeMatcher<RetraceMethodResult>() {
       @Override
diff --git a/tools/r8_release.py b/tools/r8_release.py
index a048c5c..ce56269 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -15,7 +15,7 @@
 
 import utils
 
-R8_DEV_BRANCH = '2.2'
+R8_DEV_BRANCH = '2.3'
 R8_VERSION_FILE = os.path.join(
     'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
 THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/test.py b/tools/test.py
index c949fb3..bb8dbd2 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -112,6 +112,10 @@
   result.add_option('--java-max-memory-size', '--java_max_memory_size',
       help='Set memory for running tests, default 4G',
       default='4G')
+  result.add_option('--test-namespace', '--test_namespace',
+      help='Only run tests in  this namespace. The namespace is relative to '
+          'com/android/tools/r8, e.g., desugar/desugaredlibrary',
+      default=None)
   result.add_option('--shard-count', '--shard_count',
       help='We are running this many shards.')
   result.add_option('--shard-number', '--shard_number',
@@ -193,6 +197,8 @@
     gradle_args.append('-Pjctf')
   if options.only_jctf:
     gradle_args.append('-Ponly_jctf')
+  if options.test_namespace:
+    gradle_args.append('-Ptest_namespace=%s' % options.test_namespace)
   if options.jctf_compile_only:
     gradle_args.append('-Pjctf_compile_only')
   if options.disable_assertions: