Merge commit '39aeec9f246b0c59b7819e75989be2d8a243105a' into dev-release
diff --git a/.gitignore b/.gitignore
index b382c4f..7ba42cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,8 +79,10 @@
 third_party/internal/*
 third_party/iosched_2019
 third_party/iosched_2019.tar.gz
-third_party/jacoco.tar.gz
-third_party/jacoco/*
+third_party/jacoco/0.8.2/*
+third_party/jacoco/0.8.2.tar.gz
+third_party/jacoco/0.8.6/*
+third_party/jacoco/0.8.6.tar.gz
 third_party/jasmin
 third_party/jasmin.tar.gz
 third_party/jctf
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index a872ae8..faa8808 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -37,10 +37,14 @@
 
   git diff -U0 $(git cl upstream) | %s -p1 -i
 
+or fix formatting, commit and upload:
+
+  git diff -U0 $(git cl upstream) | %s -p1 -i && git commit -a --amend --no-edit && git cl upload
+
 or bypass the checks with:
 
-  cl upload --bypass-hooks
-  """ % FMT_CMD))
+  git cl upload --bypass-hooks
+  """ % (FMT_CMD, FMT_CMD)))
   return results
 
 def CheckDeterministicDebuggingChanged(input_api, output_api, branch):
diff --git a/build.gradle b/build.gradle
index 54f0898..5153337 100644
--- a/build.gradle
+++ b/build.gradle
@@ -318,7 +318,8 @@
                 "gradle/gradle",
                 "google-java-format",
                 "iosched_2019",
-                "jacoco",
+                "jacoco/0.8.2",
+                "jacoco/0.8.6",
                 "jasmin",
                 "jctf",
                 "junit",
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index d46b12d..945ad60 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 3,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs",
-  "version": "1.0.11",
+  "version": "1.0.12",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "common_flags": [
@@ -246,6 +246,9 @@
     "-keeppackagenames j$",
     "-keepclassmembers class j$.util.IntSummaryStatistics { long count; long sum; int min; int max; }",
     "-keepclassmembers class j$.util.LongSummaryStatistics { long count; long sum; long min; long max; }",
-    "-keepclassmembers class j$.util.DoubleSummaryStatistics { long count; double sum; double min; double max; }"
+    "-keepclassmembers class j$.util.DoubleSummaryStatistics { long count; double sum; double min; double max; }",
+    "-keepattributes Signature",
+    "-keepattributes EnclosingMethod",
+    "-keepattributes InnerClasses"
   ]
 }
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index c295175..6a2a8c5 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -25,6 +25,7 @@
 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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -142,6 +143,7 @@
           new DexEncodedMethod(
               method.method,
               method.accessFlags,
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               code,
@@ -174,7 +176,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
-            ClassSignature.NO_CLASS_SIGNATURE,
+            ClassSignature.noSignature(),
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e767011..ab78b10 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.errors.CheckDiscardDiagnostic;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -559,7 +560,7 @@
             timing.end();
           }
         }
-        if (options.enableHorizontalClassMerging) {
+        if (options.enableHorizontalClassMerging && options.enableInlining) {
           timing.begin("HorizontalClassMerger");
           HorizontalClassMerger merger =
               new HorizontalClassMerger(
@@ -1082,16 +1083,11 @@
             timing);
       }
     }
-    for (DexDefinition definition : failed) {
-      ByteArrayOutputStream baos = new ByteArrayOutputStream();
-      whyAreYouKeepingConsumer.printWhyAreYouKeeping(
-          enqueuer.getGraphReporter().getGraphNode(definition.toReference()),
-          new PrintStream(baos));
-      options.reporter.info(
-          new StringDiagnostic(
-              "Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString()));
-    }
-    throw new CompilationError("Discard checks failed.");
+    options.reporter.error(
+        new CheckDiscardDiagnostic.Builder()
+            .addFailedItems(failed, enqueuer.getGraphReporter(), whyAreYouKeepingConsumer)
+            .build());
+    options.reporter.failIfPendingErrors();
   }
 
   private static boolean verifyNoJarApplicationReaders(Collection<DexProgramClass> classes) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 0375b6b..f9c2050 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -868,6 +868,12 @@
       internal.enableEnumUnboxing = false;
     }
 
+    if (!internal.enableInlining) {
+      // If R8 cannot perform inlining, then the synthetic constructors would not inline the called
+      // constructors, producing invalid code.
+      internal.enableHorizontalClassMerging = false;
+    }
+
     // Amend the proguard-map consumer with options from the proguard configuration.
     internal.proguardMapConsumer =
         wrapStringConsumer(
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 26e029a..3ad8ed2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -351,7 +351,16 @@
   private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
       AppView<?> appView, CfSourceCode code) {
     boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
-    DexEncodedMethod encodedMethod = lookupMethodOnHolder(appView, method);
+    MethodLookupResult lookupResult = appView.graphLens().lookupMethod(method, method, Type.DIRECT);
+    if (lookupResult.getType() == Type.VIRTUAL) {
+      // The method has been publicized. We can't always expect private methods that have been
+      // publicized to be final. For example, if a private method A.m() is publicized, and A is
+      // subsequently merged with a class B, with declares a public non-final method B.m(), then the
+      // horizontal class merger will merge A.m() and B.m() into a new non-final public method.
+      return Type.VIRTUAL;
+    }
+    DexMethod rewrittenMethod = lookupResult.getReference();
+    DexEncodedMethod encodedMethod = lookupMethodOnHolder(appView, rewrittenMethod);
     if (encodedMethod == null) {
       // The method is not defined on the class, we can use super to target. When desugaring
       // default interface methods, it is expected they are targeted with invoke-direct.
@@ -375,12 +384,10 @@
   }
 
   private DexEncodedMethod lookupMethodOnHolder(AppView<?> appView, DexMethod method) {
-    MethodLookupResult lookupResult = appView.graphLens().lookupMethod(method, method, Type.DIRECT);
-    DexMethod rewrittenMethod = lookupResult.getReference();
     // Directly lookup the program type for holder. This bypasses lookup order as well as looks
     // directly on the application data, which bypasses and indirection or validation.
-    DexProgramClass clazz = appView.appInfo().unsafeDirectProgramTypeLookup(rewrittenMethod.holder);
+    DexProgramClass clazz = appView.appInfo().unsafeDirectProgramTypeLookup(method.getHolderType());
     assert clazz != null;
-    return clazz.lookupMethod(rewrittenMethod);
+    return clazz.lookupMethod(method);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
index 8e9752b..01ff28f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -54,4 +56,8 @@
         .thenComparing(c -> c.targets, ComparatorUtils.listComparator(helper::compareLabels))
         .compare(this, other);
   }
+
+  public void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+    guards.forEach(registry::registerExceptionGuard);
+  }
 }
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 f39795b..77a7d70 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -461,6 +461,7 @@
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       insertAttributeAnnotationsForClass(clazz);
       clazz.fields().forEach(this::insertAttributeAnnotationsForField);
+      clazz.methods().forEach(this::insertAttributeAnnotationsForMethod);
     }
   }
 
@@ -546,18 +547,33 @@
   }
 
   private void insertAttributeAnnotationsForField(DexEncodedField field) {
-    if (field.getFieldSignature().hasNoSignature()) {
+    if (field.getGenericSignature().hasNoSignature()) {
       return;
     }
-    // Append the annotations to annotations array of the class.
+    // Append the annotations to annotations array of the field.
     field.setAnnotations(
         new DexAnnotationSet(
             ArrayUtils.appendSingleElement(
                 field.annotations().annotations,
                 DexAnnotation.createSignatureAnnotation(
-                    field.getFieldSignature().toRenamedString(namingLens, isTypeMissing),
+                    field.getGenericSignature().toRenamedString(namingLens, isTypeMissing),
                     options.itemFactory))));
-    field.clearFieldSignature();
+    field.clearGenericSignature();
+  }
+
+  private void insertAttributeAnnotationsForMethod(DexEncodedMethod method) {
+    if (method.getGenericSignature().hasNoSignature()) {
+      return;
+    }
+    // Append the annotations to annotations array of the method.
+    method.setAnnotations(
+        new DexAnnotationSet(
+            ArrayUtils.appendSingleElement(
+                method.annotations().annotations,
+                DexAnnotation.createSignatureAnnotation(
+                    method.getGenericSignature().toRenamedString(namingLens, isTypeMissing),
+                    options.itemFactory))));
+    method.clearGenericSignature();
   }
 
   private void setCallSiteContexts(ExecutorService executorService) throws ExecutionException {
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 6f00265..4941d0b 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -674,11 +674,22 @@
         code = codes.get(codeOff);
       }
       DexMethod method = indexedItems.getMethod(methodIndex);
+      DexAnnotationSet methodAnnotations = annotationIterator.getNextFor(method);
+      String methodSignature = DexAnnotation.getSignature(methodAnnotations, dexItemFactory);
+      if (methodSignature != null) {
+        methodAnnotations = methodAnnotations.getWithout(dexItemFactory.annotationSignature);
+      }
       methods[i] =
           new DexEncodedMethod(
               method,
               accessFlags,
-              annotationIterator.getNextFor(method),
+              GenericSignature.parseMethodSignature(
+                  method.name.toString(),
+                  methodSignature,
+                  origin,
+                  dexItemFactory,
+                  options.reporter),
+              methodAnnotations,
               parameterAnnotationsIterator.getNextFor(method),
               code);
     }
@@ -1343,7 +1354,7 @@
     private EnclosingMethodAttribute enclosingMethodAttribute = null;
     private List<InnerClassAttribute> innerClasses = null;
     private List<DexAnnotation> lazyAnnotations = null;
-    private ClassSignature classSignature = ClassSignature.NO_CLASS_SIGNATURE;
+    private ClassSignature classSignature = ClassSignature.noSignature();
 
     public DexAnnotationSet getAnnotations() {
       if (lazyAnnotations != null) {
diff --git a/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java b/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java
new file mode 100644
index 0000000..8073696
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.shaking.GraphReporter;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+@Keep
+public class CheckDiscardDiagnostic implements Diagnostic {
+
+  private final List<String> messages;
+
+  public static class Builder {
+    private ImmutableList.Builder<String> messagesBuilder = ImmutableList.builder();
+
+    public Builder addFailedItems(
+        List<DexDefinition> failed,
+        GraphReporter graphReporter,
+        WhyAreYouKeepingConsumer whyAreYouKeepingConsumer) {
+      for (DexDefinition definition : failed) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        whyAreYouKeepingConsumer.printWhyAreYouKeeping(
+            graphReporter.getGraphNode(definition.toReference()), new PrintStream(baos));
+        messagesBuilder.add(
+            "Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString());
+      }
+      return this;
+    }
+
+    public CheckDiscardDiagnostic build() {
+      return new CheckDiscardDiagnostic(messagesBuilder.build());
+    }
+  }
+
+  private CheckDiscardDiagnostic(List<String> messages) {
+    this.messages = messages;
+  }
+
+  /** The origin of a -checkdiscarded failure is not unique. (The whole app is to blame.) */
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  /** The position of a -checkdiscarded failure is always unknown. */
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    StringBuilder builder = new StringBuilder("Discard checks failed.");
+    if (messages.size() > 0) {
+      builder.append(System.lineSeparator());
+      builder.append("The following items where not discarded");
+      messages.forEach(
+          message -> {
+            builder.append(System.lineSeparator());
+            builder.append(message);
+          });
+    }
+    return builder.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 6bf8757..a7bd6c1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.Predicates;
@@ -398,6 +399,10 @@
     return appInfo.options();
   }
 
+  public TestingOptions testing() {
+    return options().testing;
+  }
+
   public RootSet rootSet() {
     return rootSet;
   }
@@ -440,6 +445,9 @@
       HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses) {
     assert this.horizontallyMergedLambdaClasses == null;
     this.horizontallyMergedLambdaClasses = horizontallyMergedLambdaClasses;
+    testing()
+        .horizontallyMergedLambdaClassesConsumer
+        .accept(dexItemFactory(), horizontallyMergedLambdaClasses);
   }
 
   /**
@@ -453,6 +461,7 @@
   public void setHorizontallyMergedClasses(HorizontallyMergedClasses horizontallyMergedClasses) {
     assert this.horizontallyMergedClasses == null;
     this.horizontallyMergedClasses = horizontallyMergedClasses;
+    testing().horizontallyMergedClassesConsumer.accept(dexItemFactory(), horizontallyMergedClasses);
   }
 
   /**
@@ -466,6 +475,7 @@
   public void setVerticallyMergedClasses(VerticallyMergedClasses verticallyMergedClasses) {
     assert this.verticallyMergedClasses == null;
     this.verticallyMergedClasses = verticallyMergedClasses;
+    testing().verticallyMergedClassesConsumer.accept(dexItemFactory(), verticallyMergedClasses);
   }
 
   public EnumValueInfoMapCollection unboxedEnums() {
@@ -473,7 +483,9 @@
   }
 
   public void setUnboxedEnums(EnumValueInfoMapCollection unboxedEnums) {
+    assert this.unboxedEnums.isEmpty();
     this.unboxedEnums = unboxedEnums;
+    testing().unboxedEnumsConsumer.accept(dexItemFactory(), unboxedEnums);
   }
 
   public boolean validateUnboxedEnumsHaveBeenPruned() {
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 141484c..a08d65e 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -508,7 +508,7 @@
     for (CfInstruction instruction : instructions) {
       instruction.registerUse(registry, method);
     }
-    tryCatchRanges.forEach(tryCatch -> tryCatch.guards.forEach(registry::registerTypeReference));
+    tryCatchRanges.forEach(tryCatch -> tryCatch.internalRegisterUse(registry, method));
   }
 
   @Override
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 d883e3e..f23ffde 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -769,7 +769,7 @@
   }
 
   public void clearClassSignature() {
-    classSignature = ClassSignature.NO_CLASS_SIGNATURE;
+    classSignature = ClassSignature.noSignature();
   }
 
   public void removeInnerClasses(Predicate<InnerClassAttribute> predicate) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 8e51e49..b484342 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 
@@ -31,7 +30,7 @@
   private DexValue staticValue;
   private final boolean deprecated;
   /** Generic signature information if the attribute is present in the input */
-  private FieldTypeSignature fieldSignature;
+  private FieldTypeSignature genericSignature;
 
   private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
   private KotlinFieldLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
@@ -39,7 +38,7 @@
   public DexEncodedField(
       DexField field,
       FieldAccessFlags accessFlags,
-      FieldTypeSignature fieldSignature,
+      FieldTypeSignature genericSignature,
       DexAnnotationSet annotations,
       DexValue staticValue,
       boolean deprecated) {
@@ -48,18 +47,18 @@
     this.accessFlags = accessFlags;
     this.staticValue = staticValue;
     this.deprecated = deprecated;
-    this.fieldSignature = fieldSignature;
-    assert fieldSignature != null;
-    assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(fieldSignature, annotations);
+    this.genericSignature = genericSignature;
+    assert genericSignature != null;
+    assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations);
   }
 
   public DexEncodedField(
       DexField field,
       FieldAccessFlags accessFlags,
-      FieldTypeSignature fieldSignature,
+      FieldTypeSignature genericSignature,
       DexAnnotationSet annotations,
       DexValue staticValue) {
-    this(field, accessFlags, fieldSignature, annotations, staticValue, false);
+    this(field, accessFlags, genericSignature, annotations, staticValue, false);
   }
 
   public DexType type() {
@@ -299,7 +298,7 @@
     }
     // TODO(b/169923358): Consider removing the fieldSignature here.
     DexEncodedField result =
-        new DexEncodedField(field, accessFlags, fieldSignature, annotations(), staticValue);
+        new DexEncodedField(field, accessFlags, genericSignature, annotations(), staticValue);
     result.optimizationInfo =
         optimizationInfo.isMutableFieldOptimizationInfo()
             ? optimizationInfo.asMutableFieldOptimizationInfo().mutableCopy()
@@ -322,16 +321,16 @@
     return true;
   }
 
-  public FieldTypeSignature getFieldSignature() {
-    return fieldSignature;
+  public FieldTypeSignature getGenericSignature() {
+    return genericSignature;
   }
 
-  public void setFieldSignature(FieldTypeSignature fieldSignature) {
-    assert fieldSignature != null;
-    this.fieldSignature = fieldSignature;
+  public void setGenericSignature(FieldTypeSignature genericSignature) {
+    assert genericSignature != null;
+    this.genericSignature = genericSignature;
   }
 
-  public void clearFieldSignature() {
-    this.fieldSignature = NO_FIELD_TYPE_SIGNATURE;
+  public void clearGenericSignature() {
+    this.genericSignature = FieldTypeSignature.noSignature();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c30f69c..a972aed 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.NumericType;
@@ -131,6 +132,7 @@
       new DexEncodedMethod(
           null,
           MethodAccessFlags.fromDexAccessFlags(0),
+          MethodTypeSignature.noSignature(),
           DexAnnotationSet.empty(),
           ParameterAnnotationsList.empty(),
           null);
@@ -149,6 +151,8 @@
   private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.bottom();
   private int classFileVersion;
   private KotlinMethodLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
+  /** Generic signature information if the attribute is present in the input */
+  private MethodTypeSignature genericSignature;
 
   private DexEncodedMethod defaultInterfaceMethodImplementation = null;
 
@@ -225,25 +229,44 @@
   public DexEncodedMethod(
       DexMethod method,
       MethodAccessFlags accessFlags,
+      MethodTypeSignature genericSignature,
       DexAnnotationSet annotations,
       ParameterAnnotationsList parameterAnnotationsList,
       Code code) {
-    this(method, accessFlags, annotations, parameterAnnotationsList, code, false, -1);
+    this(
+        method,
+        accessFlags,
+        genericSignature,
+        annotations,
+        parameterAnnotationsList,
+        code,
+        false,
+        -1);
   }
 
   public DexEncodedMethod(
       DexMethod method,
       MethodAccessFlags accessFlags,
+      MethodTypeSignature genericSignature,
       DexAnnotationSet annotations,
       ParameterAnnotationsList parameterAnnotationsList,
       Code code,
       boolean d8R8Synthesized) {
-    this(method, accessFlags, annotations, parameterAnnotationsList, code, d8R8Synthesized, -1);
+    this(
+        method,
+        accessFlags,
+        genericSignature,
+        annotations,
+        parameterAnnotationsList,
+        code,
+        d8R8Synthesized,
+        -1);
   }
 
   public DexEncodedMethod(
       DexMethod method,
       MethodAccessFlags accessFlags,
+      MethodTypeSignature genericSignature,
       DexAnnotationSet annotations,
       ParameterAnnotationsList parameterAnnotationsList,
       Code code,
@@ -252,6 +275,7 @@
     this(
         method,
         accessFlags,
+        genericSignature,
         annotations,
         parameterAnnotationsList,
         code,
@@ -263,6 +287,7 @@
   public DexEncodedMethod(
       DexMethod method,
       MethodAccessFlags accessFlags,
+      MethodTypeSignature genericSignature,
       DexAnnotationSet annotations,
       ParameterAnnotationsList parameterAnnotationsList,
       Code code,
@@ -273,6 +298,7 @@
     this.method = method;
     this.accessFlags = accessFlags;
     this.deprecated = deprecated;
+    this.genericSignature = genericSignature;
     this.parameterAnnotationsList = parameterAnnotationsList;
     this.code = code;
     this.classFileVersion = classFileVersion;
@@ -1146,6 +1172,7 @@
         new DexEncodedMethod(
             newMethod,
             accessFlags,
+            MethodTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             code,
@@ -1175,6 +1202,7 @@
     return new DexEncodedMethod(
         newMethod,
         accessFlags,
+        MethodTypeSignature.noSignature(),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
         code,
@@ -1278,6 +1306,7 @@
     return new DexEncodedMethod(
         newMethod,
         newFlags,
+        MethodTypeSignature.noSignature(),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
         new SynthesizedCode(forwardSourceCodeBuilder::build),
@@ -1399,6 +1428,19 @@
     }
   }
 
+  public MethodTypeSignature getGenericSignature() {
+    return genericSignature;
+  }
+
+  public void setGenericSignature(MethodTypeSignature genericSignature) {
+    assert genericSignature != null;
+    this.genericSignature = genericSignature;
+  }
+
+  public void clearGenericSignature() {
+    this.genericSignature = MethodTypeSignature.noSignature();
+  }
+
   private static Builder syntheticBuilder(DexEncodedMethod from) {
     return new Builder(from, true);
   }
@@ -1411,6 +1453,7 @@
 
     private DexMethod method;
     private final MethodAccessFlags accessFlags;
+    private final MethodTypeSignature genericSignature;
     private final DexAnnotationSet annotations;
     private OptionalBool isLibraryMethodOverride = OptionalBool.UNKNOWN;
     private ParameterAnnotationsList parameterAnnotations;
@@ -1429,6 +1472,7 @@
       // Copy all the mutable state of a DexEncodedMethod here.
       method = from.method;
       accessFlags = from.accessFlags.copy();
+      genericSignature = from.getGenericSignature();
       annotations = from.annotations();
       code = from.code;
       compilationState = CompilationState.NOT_PROCESSED;
@@ -1529,6 +1573,7 @@
           new DexEncodedMethod(
               method,
               accessFlags,
+              genericSignature,
               annotations,
               parameterAnnotations,
               code,
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 012030b..87adbf7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.Reference;
+import java.util.Collections;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -116,6 +117,11 @@
   }
 
   @Override
+  public Iterable<DexType> getReferencedTypes() {
+    return Collections.singleton(type);
+  }
+
+  @Override
   public int slowCompareTo(DexField other) {
     int result = holder.slowCompareTo(other.holder);
     if (result != 0) {
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 a1767c3..5c34e83 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.google.common.collect.Iterables;
+
 public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
     extends DexReference implements PresortedComparable<R> {
 
@@ -33,4 +35,10 @@
   public DexMember<D, R> asDexMember() {
     return this;
   }
+
+  public abstract Iterable<DexType> getReferencedTypes();
+
+  public Iterable<DexType> getReferencedBaseTypes(DexItemFactory dexItemFactory) {
+    return Iterables.transform(getReferencedTypes(), type -> type.toBaseType(dexItemFactory));
+  }
 }
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 212f5ca..9757af4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -142,6 +142,11 @@
   }
 
   @Override
+  public Iterable<DexType> getReferencedTypes() {
+    return proto.getTypes();
+  }
+
+  @Override
   public int computeHashCode() {
     return holder.hashCode()
         + proto.hashCode() * 7
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 e61ff61..1a8d506 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -3,7 +3,6 @@
 // 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;
 
@@ -480,7 +479,26 @@
     methodCollection.addDirectMethod(directMethod);
   }
 
-  public void addExtraInterfaces(List<DexType> extraInterfaces, DexItemFactory factory) {
+  public void replaceInterfaces(List<ClassTypeSignature> newInterfaces) {
+    if (newInterfaces.isEmpty()) {
+      return;
+    }
+    clearInterfaces();
+    addExtraInterfaces(newInterfaces);
+  }
+
+  private void clearInterfaces() {
+    interfaces = DexTypeList.empty();
+    if (classSignature.hasSignature()) {
+      classSignature =
+          new ClassSignature(
+              classSignature.formalTypeParameters,
+              classSignature.superClassSignature,
+              ImmutableList.of());
+    }
+  }
+
+  public void addExtraInterfaces(List<ClassTypeSignature> extraInterfaces) {
     if (extraInterfaces.isEmpty()) {
       return;
     }
@@ -488,25 +506,24 @@
     addExtraInterfacesToSignatureIfPresent(extraInterfaces);
   }
 
-  private void addExtraInterfacesToInterfacesArray(List<DexType> extraInterfaces) {
+  private void addExtraInterfacesToInterfacesArray(List<ClassTypeSignature> extraInterfaces) {
     DexType[] newInterfaces =
         Arrays.copyOf(interfaces.values, interfaces.size() + extraInterfaces.size());
     for (int i = interfaces.size(); i < newInterfaces.length; i++) {
-      newInterfaces[i] = extraInterfaces.get(i - interfaces.size());
+      newInterfaces[i] = extraInterfaces.get(i - interfaces.size()).type();
     }
     interfaces = new DexTypeList(newInterfaces);
   }
 
-  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.
+  private void addExtraInterfacesToSignatureIfPresent(List<ClassTypeSignature> extraInterfaces) {
+    // We introduce the extra interfaces to the generic signature.
     if (classSignature.hasNoSignature() || extraInterfaces.isEmpty()) {
       return;
     }
     ImmutableList.Builder<ClassTypeSignature> interfacesBuilder =
         ImmutableList.<ClassTypeSignature>builder().addAll(classSignature.superInterfaceSignatures);
-    for (DexType extraInterface : extraInterfaces) {
-      interfacesBuilder.add(new ClassTypeSignature(extraInterface, EMPTY_TYPE_ARGUMENTS));
+    for (ClassTypeSignature extraInterface : extraInterfaces) {
+      interfacesBuilder.add(extraInterface);
     }
     classSignature =
         new ClassSignature(
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 bbff32c..a92dc75 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -128,6 +128,12 @@
     default MethodTypeSignature asMethodTypeSignature() {
       return null;
     }
+
+    boolean hasSignature();
+
+    default boolean hasNoSignature() {
+      return !hasSignature();
+    }
   }
 
   public static class FormalTypeParameter {
@@ -168,7 +174,7 @@
 
   public static class ClassSignature implements DexDefinitionSignature<DexClass> {
 
-    public static final ClassSignature NO_CLASS_SIGNATURE =
+    private static final ClassSignature NO_CLASS_SIGNATURE =
         new ClassSignature(EMPTY_TYPE_PARAMS, NO_FIELD_TYPE_SIGNATURE, EMPTY_SUPER_INTERFACES);
 
     final List<FormalTypeParameter> formalTypeParameters;
@@ -195,14 +201,11 @@
       return superInterfaceSignatures;
     }
 
+    @Override
     public boolean hasSignature() {
       return this != NO_CLASS_SIGNATURE;
     }
 
-    public boolean hasNoSignature() {
-      return !hasSignature();
-    }
-
     @Override
     public boolean isClassSignature() {
       return true;
@@ -235,6 +238,10 @@
     public String toString() {
       return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue());
     }
+
+    public static ClassSignature noSignature() {
+      return NO_CLASS_SIGNATURE;
+    }
   }
 
   public abstract static class TypeSignature {
@@ -318,14 +325,11 @@
       return null;
     }
 
+    @Override
     public boolean hasSignature() {
       return this != GenericSignature.NO_FIELD_TYPE_SIGNATURE;
     }
 
-    public boolean hasNoSignature() {
-      return !hasSignature();
-    }
-
     public abstract FieldTypeSignature asArgument(WildcardIndicator indicator);
 
     public boolean isStar() {
@@ -346,6 +350,10 @@
     public String toString() {
       return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue());
     }
+
+    public static FieldTypeSignature noSignature() {
+      return NO_FIELD_TYPE_SIGNATURE;
+    }
   }
 
   static final class StarFieldTypeSignature extends FieldTypeSignature {
@@ -367,7 +375,7 @@
     }
   }
 
-  public static final ClassTypeSignature NO_FIELD_TYPE_SIGNATURE =
+  private static final ClassTypeSignature NO_FIELD_TYPE_SIGNATURE =
       new ClassTypeSignature(DexItemFactory.nullValueType, EMPTY_TYPE_ARGUMENTS);
 
   public static class ClassTypeSignature extends FieldTypeSignature {
@@ -383,7 +391,11 @@
     ClassTypeSignature enclosingTypeSignature;
     ClassTypeSignature innerTypeSignature;
 
-    ClassTypeSignature(DexType type, List<FieldTypeSignature> typeArguments) {
+    public ClassTypeSignature(DexType type) {
+      this(type, EMPTY_TYPE_ARGUMENTS);
+    }
+
+    public ClassTypeSignature(DexType type, List<FieldTypeSignature> typeArguments) {
       this(type, typeArguments, WildcardIndicator.NOT_AN_ARGUMENT);
     }
 
@@ -579,7 +591,7 @@
 
   public static class MethodTypeSignature implements DexDefinitionSignature<DexEncodedMethod> {
 
-    public static final MethodTypeSignature NO_METHOD_TYPE_SIGNATURE =
+    private static final MethodTypeSignature NO_METHOD_TYPE_SIGNATURE =
         new MethodTypeSignature(
             EMPTY_TYPE_PARAMS, EMPTY_TYPE_SIGNATURES, ReturnType.VOID, EMPTY_TYPE_SIGNATURES);
 
@@ -588,6 +600,10 @@
     final ReturnType returnType;
     final List<TypeSignature> throwsSignatures;
 
+    public static MethodTypeSignature noSignature() {
+      return NO_METHOD_TYPE_SIGNATURE;
+    }
+
     MethodTypeSignature(
         final List<FormalTypeParameter> formalTypeParameters,
         List<TypeSignature> typeSignatures,
@@ -623,6 +639,7 @@
       return true;
     }
 
+    @Override
     public boolean hasSignature() {
       return this != NO_METHOD_TYPE_SIGNATURE;
     }
@@ -642,6 +659,21 @@
     public List<FormalTypeParameter> getFormalTypeParameters() {
       return formalTypeParameters;
     }
+
+    public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+      if (hasNoSignature()) {
+        return null;
+      }
+      GenericSignaturePrinter genericSignaturePrinter =
+          new GenericSignaturePrinter(namingLens, isTypeMissing);
+      genericSignaturePrinter.visitMethodSignature(this);
+      return genericSignaturePrinter.toString();
+    }
+
+    @Override
+    public String toString() {
+      return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue());
+    }
   }
 
   public static ClassSignature parseClassSignature(
@@ -1013,7 +1045,6 @@
       if (symbol == '^') {
         do {
           scanSymbol();
-
           // ThrowsSignature ::= ("^" ClassTypeSignature) | ("^" TypeVariableSignature).
           if (symbol == 'T') {
             throwsSignatureBuilder.add(updateTypeVariableSignature());
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
index e75ee78..f68675f 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -8,6 +8,8 @@
 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.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ReturnType;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
 import com.android.tools.r8.naming.NamingLens;
@@ -38,6 +40,35 @@
   }
 
   @Override
+  public void visitMethodSignature(MethodTypeSignature methodSignature) {
+    methodSignature.visit(this);
+  }
+
+  @Override
+  public void visitMethodTypeSignatures(List<TypeSignature> typeSignatures) {
+    sb.append("(");
+    typeSignatures.forEach(this::visitTypeSignature);
+    sb.append(")");
+  }
+
+  @Override
+  public void visitReturnType(ReturnType returnType) {
+    if (returnType.isVoidDescriptor()) {
+      sb.append("V");
+    } else {
+      visitTypeSignature(returnType.typeSignature);
+    }
+  }
+
+  @Override
+  public void visitThrowsSignatures(List<TypeSignature> typeSignatures) {
+    for (TypeSignature typeSignature : typeSignatures) {
+      sb.append("^");
+      visitTypeSignature(typeSignature);
+    }
+  }
+
+  @Override
   public void visitFormalTypeParameters(List<FormalTypeParameter> formalTypeParameters) {
     if (formalTypeParameters.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 4cce25e..7804c5f 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -7,7 +7,8 @@
 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.EMPTY_TYPE_SIGNATURES;
+import static com.android.tools.r8.graph.GenericSignature.FieldTypeSignature.noSignature;
 import static com.android.tools.r8.graph.GenericSignature.StarFieldTypeSignature.STAR_FIELD_TYPE_SIGNATURE;
 
 import com.android.tools.r8.graph.GenericSignature.ArrayTypeSignature;
@@ -15,6 +16,8 @@
 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.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ReturnType;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.ArrayList;
@@ -48,6 +51,13 @@
     return new TypeSignatureRewriter().run(fieldTypeSignature);
   }
 
+  public MethodTypeSignature rewrite(MethodTypeSignature methodTypeSignature) {
+    if (methodTypeSignature.hasNoSignature() || appView.graphLens().isIdentityLens()) {
+      return methodTypeSignature;
+    }
+    return new MethodTypeSignatureRewriter().run(methodTypeSignature);
+  }
+
   private class ClassSignatureRewriter implements GenericSignatureVisitor {
 
     private final List<FormalTypeParameter> rewrittenTypeParameters = new ArrayList<>();
@@ -90,7 +100,7 @@
           && rewrittenSuperInterfaces.isEmpty()
           && rewrittenSuperClass.isNoSignature()
           && rewrittenSuperClass.type == appView.dexItemFactory().objectType) {
-        return ClassSignature.NO_CLASS_SIGNATURE;
+        return ClassSignature.noSignature();
       }
       return new ClassSignature(
           rewrittenTypeParameters.isEmpty() ? EMPTY_TYPE_PARAMS : rewrittenTypeParameters,
@@ -99,9 +109,77 @@
     }
   }
 
+  private class MethodTypeSignatureRewriter implements GenericSignatureVisitor {
+
+    private final List<FormalTypeParameter> rewrittenTypeParameters = new ArrayList<>();
+    private final List<TypeSignature> rewrittenTypeSignatures = new ArrayList<>();
+
+    ReturnType rewrittenReturnType = null;
+    private final List<TypeSignature> rewrittenThrowsSignatures = new ArrayList<>();
+
+    @Override
+    public void visitFormalTypeParameters(List<FormalTypeParameter> formalTypeParameters) {
+      for (FormalTypeParameter formalTypeParameter : formalTypeParameters) {
+        rewrittenTypeParameters.add(new FormalTypeParameterRewriter().run(formalTypeParameter));
+      }
+    }
+
+    @Override
+    public void visitMethodTypeSignatures(List<TypeSignature> typeSignatures) {
+      for (TypeSignature typeSignature : typeSignatures) {
+        TypeSignature rewrittenType = new TypeSignatureRewriter().run(typeSignature);
+        rewrittenTypeSignatures.add(rewrittenType == null ? objectTypeSignature : rewrittenType);
+      }
+    }
+
+    @Override
+    public void visitReturnType(ReturnType returnType) {
+      if (returnType.isVoidDescriptor()) {
+        rewrittenReturnType = ReturnType.VOID;
+      } else {
+        TypeSignature originalType = returnType.typeSignature();
+        TypeSignature rewrittenType = new TypeSignatureRewriter().run(originalType);
+        if (rewrittenType == null) {
+          rewrittenReturnType = ReturnType.VOID;
+        } else if (rewrittenType == originalType) {
+          rewrittenReturnType = returnType;
+        } else {
+          rewrittenReturnType = new ReturnType(rewrittenType);
+        }
+      }
+    }
+
+    @Override
+    public void visitThrowsSignatures(List<TypeSignature> typeSignatures) {
+      for (TypeSignature typeSignature : typeSignatures) {
+        TypeSignature rewrittenType = new TypeSignatureRewriter().run(typeSignature);
+        // If a throwing type is no longer found we remove it from the signature.
+        if (rewrittenType != null) {
+          rewrittenThrowsSignatures.add(rewrittenType);
+        }
+      }
+    }
+
+    private MethodTypeSignature run(MethodTypeSignature methodTypeSignature) {
+      methodTypeSignature.visit(this);
+      assert rewrittenReturnType != null;
+      if (rewrittenTypeParameters.isEmpty()
+          && rewrittenTypeSignatures.isEmpty()
+          && rewrittenReturnType.isVoidDescriptor()
+          && rewrittenThrowsSignatures.isEmpty()) {
+        return MethodTypeSignature.noSignature();
+      }
+      return new MethodTypeSignature(
+          rewrittenTypeParameters.isEmpty() ? EMPTY_TYPE_PARAMS : rewrittenTypeParameters,
+          rewrittenTypeSignatures.isEmpty() ? EMPTY_TYPE_SIGNATURES : rewrittenTypeSignatures,
+          rewrittenReturnType,
+          rewrittenThrowsSignatures.isEmpty() ? EMPTY_TYPE_SIGNATURES : rewrittenThrowsSignatures);
+    }
+  }
+
   private class FormalTypeParameterRewriter implements GenericSignatureVisitor {
 
-    private FieldTypeSignature rewrittenClassBound = NO_FIELD_TYPE_SIGNATURE;
+    private FieldTypeSignature rewrittenClassBound = noSignature();
     private final List<FieldTypeSignature> rewrittenInterfaceBounds = new ArrayList<>();
 
     @Override
@@ -126,7 +204,7 @@
       }
       return new FormalTypeParameter(
           formalTypeParameter.name,
-          rewrittenClassBound == null ? NO_FIELD_TYPE_SIGNATURE : rewrittenClassBound,
+          rewrittenClassBound == null ? noSignature() : rewrittenClassBound,
           rewrittenInterfaceBounds.isEmpty() ? EMPTY_TYPE_ARGUMENTS : rewrittenInterfaceBounds);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureUtils.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureUtils.java
index 7c0dcd2..a3d2eb4 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureUtils.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureUtils.java
@@ -4,10 +4,6 @@
 
 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 {
@@ -15,10 +11,7 @@
   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) {
+    if (signature.hasNoSignature() || annotations == null) {
       return true;
     }
     // The check is on the string descriptor to allow for not passing in a factory.
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
index 6d9385e..d3840dd 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
@@ -9,9 +9,9 @@
 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.MethodTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.ReturnType;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import java.util.List;
 
 public interface GenericSignatureVisitor {
@@ -20,7 +20,7 @@
     throw new Unreachable("Implement if visited");
   }
 
-  default void visitMethodSignature(MethodSignature methodSignature) {
+  default void visitMethodSignature(MethodTypeSignature methodSignature) {
     throw new Unreachable("Implement if visited");
   }
 
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 7681f24..f2de6db 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -950,10 +950,7 @@
 
     @Override
     public DexMethod getOriginalMethodSignature(DexMethod method) {
-      DexMethod originalMethod =
-          originalMethodSignatures != null
-              ? originalMethodSignatures.getOrDefault(method, method)
-              : method;
+      DexMethod originalMethod = internalGetPreviousMethodSignature(method);
       return getPrevious().getOriginalMethodSignature(originalMethod);
     }
 
@@ -971,9 +968,7 @@
         return originalMethod;
       }
       DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied);
-      return originalMethodSignatures != null
-          ? originalMethodSignatures.inverse().getOrDefault(renamedMethod, renamedMethod)
-          : renamedMethod;
+      return internalGetNextMethodSignature(renamedMethod);
     }
 
     @Override
@@ -1064,6 +1059,12 @@
           : method;
     }
 
+    protected DexMethod internalGetNextMethodSignature(DexMethod method) {
+      return originalMethodSignatures != null
+          ? originalMethodSignatures.inverse().getOrDefault(method, method)
+          : method;
+    }
+
     @Override
     public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
       return getPrevious().lookupGetFieldForMethod(field, context);
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 ccaa837..b71bfa0 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -208,7 +209,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 ClassSignature classSignature = ClassSignature.noSignature();
     private List<DexAnnotation> annotations = null;
     private List<DexAnnotationElement> defaultAnnotations = null;
     private final List<DexEncodedField> staticFields = new ArrayList<>();
@@ -680,6 +681,7 @@
     private List<List<DexAnnotation>> parameterAnnotationsLists = null;
     private List<DexValue> parameterNames = null;
     private List<DexValue> parameterFlags = null;
+    private final MethodTypeSignature genericSignature;
     final DexMethod method;
     final MethodAccessFlags flags;
     final boolean deprecated;
@@ -702,10 +704,13 @@
         addAnnotation(DexAnnotation.createThrowsAnnotation(
             values, parent.application.getFactory()));
       }
-      if (signature != null && !signature.isEmpty()) {
-        addAnnotation(DexAnnotation.createSignatureAnnotation(
-            signature, parent.application.getFactory()));
-      }
+      genericSignature =
+          GenericSignature.parseMethodSignature(
+              name,
+              signature,
+              parent.origin,
+              parent.application.getFactory(),
+              parent.application.options.reporter);
     }
 
     @Override
@@ -832,6 +837,7 @@
           new DexEncodedMethod(
               method,
               flags,
+              genericSignature,
               createAnnotationSet(annotations, options),
               parameterAnnotationsList,
               code,
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 033182e..4a3327a 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -75,6 +75,10 @@
     registerTypeReference(type);
   }
 
+  public void registerExceptionGuard(DexType guard) {
+    registerTypeReference(guard);
+  }
+
   public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
     switch (methodHandle.type) {
       case INSTANCE_GET:
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index d60a589..eb82383 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -4,12 +4,14 @@
 
 package com.android.tools.r8.graph.analysis;
 
+import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfGoto;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
@@ -90,6 +92,15 @@
   // 16: invokevirtual #43                 // Method java/lang/Class.desiredAssertionStatus:()Z
   // 19: putstatic     #45                 // Field ENABLED:Z
   // 22: return
+  //
+  // When JaCoCo instrumentation is applied the following instruction sequence is injected into
+  // each branch to register code coverage. This includes <clinit> where the vsalue for field
+  // $assertionsDisabled is determined by a branch structure.
+  //
+  // +0: aload_X
+  // +1: iconst_Y / ldc
+  // +2: iconst_Z / ldc
+  // +3  bastore
 
   private static List<Class<?>> javacInstructionSequence =
       ImmutableList.of(
@@ -100,6 +111,8 @@
           CfFieldInstruction.class);
   private static List<Class<?>> r8InstructionSequence =
       ImmutableList.of(CfConstNumber.class, CfLogicalBinop.class, CfFieldInstruction.class);
+  private static List<Class<?>> jacocoInstructionSequence =
+      ImmutableList.of(CfLoad.class, CfConstNumber.class, CfConstNumber.class, CfArrayStore.class);
 
   private boolean hasJavacClinitAssertionCode(CfCode code) {
     for (int i = 0; i < code.getInstructions().size(); i++) {
@@ -160,6 +173,17 @@
     return false;
   }
 
+  private boolean skipSequence(List<Class<?>> sequence, CfCode code, int fromIndex) {
+    int i = fromIndex;
+    for (; i < code.getInstructions().size() && i - fromIndex < sequence.size(); i++) {
+      CfInstruction instruction = code.getInstructions().get(i);
+      if (instruction.getClass() != sequence.get(i - fromIndex)) {
+        return false;
+      }
+    }
+    return i - fromIndex == sequence.size();
+  }
+
   private CfFieldInstruction isJavacInstructionSequence(CfCode code, int fromIndex) {
     List<Class<?>> sequence = javacInstructionSequence;
     int nextExpectedInstructionIndex = 0;
@@ -168,12 +192,27 @@
         i < code.getInstructions().size() && nextExpectedInstructionIndex < sequence.size();
         i++) {
       instruction = code.getInstructions().get(i);
-      if (instruction.isLabel() || instruction.isFrame()) {
-        // Just ignore labels and frames.
+      if (instruction.isLabel() || instruction.isFrame() || instruction.isPosition()) {
+        // Just ignore labels, frames and positions.
         continue;
       }
       if (instruction.getClass() != sequence.get(nextExpectedInstructionIndex)) {
-        break;
+        // Skip injected JaCoCo injected code.
+        if (instruction.getClass() == jacocoInstructionSequence.get(0)
+            && skipSequence(jacocoInstructionSequence, code, i)) {
+          i += jacocoInstructionSequence.size();
+          if (i >= code.getInstructions().size()) {
+            break;
+          }
+          instruction = code.getInstructions().get(i);
+        }
+        if (instruction.isLabel() || instruction.isFrame() || instruction.isPosition()) {
+          // Just ignore labels, frames and positions.
+          continue;
+        }
+        if (instruction.getClass() != sequence.get(nextExpectedInstructionIndex)) {
+          break;
+        }
       }
       nextExpectedInstructionIndex++;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerExceptionGuardAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerExceptionGuardAnalysis.java
new file mode 100644
index 0000000..1bf392e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerExceptionGuardAnalysis.java
@@ -0,0 +1,12 @@
+// 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.analysis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnqueuerExceptionGuardAnalysis {
+  void traceExceptionGuard(DexType guard, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
index 505dfd5..285e397 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
 import java.util.Set;
 
 public class HorizontallyMergedLambdaClasses implements MergedClasses {
@@ -17,6 +18,10 @@
     this.sources = sources;
   }
 
+  public static HorizontallyMergedLambdaClasses empty() {
+    return new HorizontallyMergedLambdaClasses(ImmutableSet.of());
+  }
+
   @Override
   public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
     for (DexType source : sources) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 4a4936b..f4158c5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
-
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -18,6 +16,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
@@ -153,7 +152,7 @@
             classIdField,
             FieldAccessFlags.fromSharedAccessFlags(
                 Constants.ACC_PUBLIC + Constants.ACC_FINAL + Constants.ACC_SYNTHETIC),
-            NO_FIELD_TYPE_SIGNATURE,
+            FieldTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             null);
     target.appendInstanceField(encodedField);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index 9baf6ad..b6f3058 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
@@ -90,6 +91,10 @@
 
     DexEncodedMethod encodedMethod = constructor.toTypeSubstitutedMethod(method);
     encodedMethod.getMutableOptimizationInfo().markForceInline();
+    encodedMethod.accessFlags.unsetConstructor();
+    encodedMethod.accessFlags.unsetPublic();
+    encodedMethod.accessFlags.unsetProtected();
+    encodedMethod.accessFlags.setPrivate();
     target.addDirectMethod(encodedMethod);
     return method;
   }
@@ -164,6 +169,7 @@
         new DexEncodedMethod(
             newConstructorReference,
             getAccessFlags(),
+            MethodTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             synthesizedCode,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index adc4f12..b1d0dbe 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.horizontalclassmerging.policies.DontMergeIntoLessVisible;
 import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAbstractClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
@@ -48,6 +49,7 @@
       MainDexTracingResult mainDexTracingResult,
       ClassMergingEnqueuerExtension classMergingEnqueuerExtension) {
     this.appView = appView;
+    assert appView.options().enableInlining;
 
     List<Policy> policies =
         ImmutableList.of(
@@ -71,7 +73,10 @@
             new PreventChangingVisibility(),
             new SameFeatureSplit(appView),
             new RespectPackageBoundaries(appView),
-            new DontMergeSynchronizedClasses(appView)
+            new DontMergeSynchronizedClasses(appView),
+            // TODO(b/166577694): no policies should be run after this policy, as it would
+            // potentially break tests
+            new DontMergeIntoLessVisible()
             // TODO: add policies
             );
 
@@ -91,6 +96,7 @@
     Collection<Collection<DexProgramClass>> groups = policyExecutor.run(classes.values());
     // If there are no groups, then end horizontal class merging.
     if (groups.isEmpty()) {
+      appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty());
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
index 01058e2..653e31a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -15,12 +15,17 @@
 import java.util.Set;
 
 public class HorizontallyMergedClasses implements MergedClasses {
+
   private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses;
 
   public HorizontallyMergedClasses(BidirectionalManyToOneMap<DexType, DexType> mergedClasses) {
     this.mergedClasses = mergedClasses;
   }
 
+  public static HorizontallyMergedClasses empty() {
+    return new HorizontallyMergedClasses(new BidirectionalManyToOneMap<>());
+  }
+
   public DexType getMergeTargetOrDefault(DexType type) {
     return mergedClasses.getOrDefault(type, type);
   }
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 ec8d495..28ec4a9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -76,7 +76,7 @@
               Collections.emptyList(),
               null,
               Collections.emptyList(),
-              ClassSignature.NO_CLASS_SIGNATURE,
+              ClassSignature.noSignature(),
               DexAnnotationSet.empty(),
               DexEncodedField.EMPTY_ARRAY,
               DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 0f9fdc1..7342124 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -11,6 +11,7 @@
 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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -106,7 +108,11 @@
 
   private MethodAccessFlags getAccessFlags() {
     // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
-    return methods.iterator().next().getDefinition().getAccessFlags();
+    MethodAccessFlags flags = methods.iterator().next().getDefinition().getAccessFlags().copy();
+    if (flags.isFinal() && Iterables.any(methods, method -> !method.getAccessFlags().isFinal())) {
+      flags.unsetFinal();
+    }
+    return flags;
   }
 
 
@@ -154,12 +160,12 @@
     }
 
     // Use the first of the original methods as the original method for the merged constructor.
+    DexMethod templateReference = methods.iterator().next().getReference();
     DexMethod originalMethodReference =
-        appView.graphLens().getOriginalMethodSignature(methods.iterator().next().getReference());
+        appView.graphLens().getOriginalMethodSignature(templateReference);
 
     DexMethod newMethodReference =
-        dexItemFactory.createMethod(
-            target.type, originalMethodReference.proto, originalMethodReference.name);
+        dexItemFactory.createMethod(target.type, templateReference.proto, templateReference.name);
     AbstractSynthesizedCode synthesizedCode =
         new VirtualMethodEntryPointSynthesizedCode(
             classIdToMethodMap,
@@ -171,6 +177,7 @@
         new DexEncodedMethod(
             newMethodReference,
             getAccessFlags(),
+            MethodTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             synthesizedCode,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java
new file mode 100644
index 0000000..e579310
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class DontMergeIntoLessVisible extends MultiClassPolicy {
+  @Override
+  public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) {
+    Iterator<DexProgramClass> iterator = group.iterator();
+    while (iterator.hasNext()) {
+      DexProgramClass clazz = iterator.next();
+      if (clazz.isPublic()) {
+        iterator.remove();
+        List<DexProgramClass> newGroup = new LinkedList<>();
+        newGroup.add(clazz);
+        newGroup.addAll(group);
+        group = newGroup;
+        break;
+      }
+    }
+
+    return Collections.singletonList(group);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
index 6181c41..969f43a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
@@ -4,27 +4,50 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Set;
 
 public class NoKeepRules extends SingleClassPolicy {
   private final AppView<AppInfoWithLiveness> appView;
+  private final Set<DexType> dontMergeTypes = Sets.newIdentityHashSet();
 
   public NoKeepRules(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
+
+    appView.appInfo().classes().forEach(this::processClass);
+  }
+
+  private void processClass(DexProgramClass programClass) {
+    DexType type = programClass.getType();
+    boolean pinProgramClass = appView.appInfo().isPinned(type);
+
+    for (DexEncodedMember<?, ?> member : programClass.members()) {
+      DexMember<?, ?> reference = member.toReference();
+      if (appView.appInfo().isPinned(reference)) {
+        pinProgramClass = true;
+        Iterables.addAll(
+            dontMergeTypes,
+            Iterables.filter(
+                reference.getReferencedBaseTypes(appView.dexItemFactory()), DexType::isClassType));
+      }
+    }
+
+    if (pinProgramClass) {
+      dontMergeTypes.add(type);
+    }
   }
 
   @Override
   public boolean canMerge(DexProgramClass program) {
-    DexType type = program.getType();
-    boolean anyPinned =
-        appView.appInfo().isPinned(type)
-            || Iterables.any(
-                program.members(), member -> appView.appInfo().isPinned(member.toReference()));
-    return !anyPinned;
+    return !dontMergeTypes.contains(program.getType());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java
index dff9967..23f10b2 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java
@@ -18,7 +18,6 @@
   @Override
   public boolean canMerge(DexProgramClass clazz) {
     // We currently assume we only merge classes that implement the same set of interfaces.
-    return !(this.classMergingEnqueuerExtension.isCheckCastType(clazz)
-        || this.classMergingEnqueuerExtension.isInstanceOfType(clazz));
+    return !classMergingEnqueuerExtension.isRuntimeCheckType(clazz);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index 8b63913..94b9489 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -64,6 +64,13 @@
     analyzeValues(values, Mode.WIDENING);
   }
 
+  public void narrowing(IRCode code) {
+    mode = Mode.NARROWING;
+    assert worklist.isEmpty();
+    code.topologicallySortedBlocks().forEach(this::analyzeBasicBlock);
+    analyze();
+  }
+
   public void narrowing(Iterable<? extends Value> values) {
     // TODO(b/125492155) Not sorting causes us to have non-deterministic behaviour. This should be
     //  removed when the bug is fixed.
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java b/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
index 144bbdb..0ae5aaf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
@@ -18,6 +18,10 @@
     return false;
   }
 
+  default boolean isStackMapPhi() {
+    return false;
+  }
+
   default Phi asPhi() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index c1de847..d7abfac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.EdgeType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -448,9 +449,9 @@
     return result;
   }
 
-  // TODO(b/169120386) This class is added to ensure we do not narrow or widen phi's in D8 when
-  //  having stack map information. It should be removed when we are certain to never widen or
-  //  narrowing phi's in D8.
+  // TODO(b/169120386) This class is added to ensure we do not narrow or widen the type of phi's
+  //  in D8 when having stack map information. We do allow for narrowing on nullable information.
+  //  It should be removed when we are certain to never widen or narrowing phi's in D8.
   public static class StackMapPhi extends Phi {
 
     public StackMapPhi(
@@ -478,7 +479,20 @@
     @Override
     public TypeElement computePhiType(AppView<?> appView) {
       assert !appView.enableWholeProgramOptimizations();
-      return type;
+      if (type.isPrimitiveType()) {
+        return type;
+      }
+      assert type.isReferenceType();
+      Nullability nullability = Nullability.bottom();
+      for (Value operand : getOperands()) {
+        nullability = nullability.join(operand.type.nullability());
+      }
+      return type.asReferenceType().getOrCreateVariant(nullability);
+    }
+
+    @Override
+    public boolean isStackMapPhi() {
+      return true;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 6382b86..8f2c15e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -723,9 +723,11 @@
       // TODO(b/169137397): We may have ended up generating StackMapPhi's before concluding
       //  having incorrect stack map types. Figure out a way to clean that up.
       new TypeAnalysis(appView).widening(ir);
+    } else {
+      assert canUseStackMapTypes() && !hasIncorrectStackMapTypes;
+      assert allPhisAreStackMapPhis(ir);
+      new TypeAnalysis(appView).narrowing(ir);
     }
-    // TODO(b/169137397): If we have canUseStackMapTypes() && !hasIncorrectStackMapTypes we should
-    //  have that all phi's are stack-map phis.
 
     // Update the IR code if collected call site optimization info has something useful.
     // While aggregation of parameter information at call sites would be more precise than static
@@ -755,6 +757,15 @@
     return !appView.enableWholeProgramOptimizations() && source.hasValidTypesFromStackMap();
   }
 
+  private boolean allPhisAreStackMapPhis(IRCode ir) {
+    ir.instructions()
+        .forEach(
+            instruction -> {
+              assert !instruction.isPhi() || instruction.isStackMapPhi();
+            });
+    return true;
+  }
+
   public void constrainType(Value value, ValueTypeConstraint constraint) {
     value.constrainType(constraint, method.getReference(), origin, appView.options().reporter);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index f475e89..b7964c8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -22,8 +22,10 @@
 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.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
@@ -705,6 +707,8 @@
     }
     if (enumUnboxer != null) {
       enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
+    } else {
+      appView.setUnboxedEnums(EnumValueInfoMapCollection.empty());
     }
     if (!options.debug) {
       new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
@@ -935,7 +939,10 @@
     if (lambdaMerger != null) {
       lambdaMerger.applyLambdaClassMapping(
           application, this, feedback, builder, executorService);
+    } else {
+      appView.setHorizontallyMergedLambdaClasses(HorizontallyMergedLambdaClasses.empty());
     }
+    assert appView.horizontallyMergedLambdaClasses() != null;
   }
 
   private void generateDesugaredLibraryAPIWrappers(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 3f40cba..f2ae9a1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -16,6 +16,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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -363,6 +364,7 @@
         new DexEncodedMethod(
             newMethod,
             MethodAccessFlags.fromCfAccessFlags(Opcodes.ACC_PUBLIC, false),
+            MethodTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             new SynthesizedCode(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 01fb238..fd4abda 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -167,6 +167,7 @@
         new DexEncodedMethod(
             newMethod,
             newAccessFlags,
+            methodDefinition.getGenericSignature(),
             methodDefinition
                 .annotations()
                 .keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation)),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java
new file mode 100644
index 0000000..6f72091
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class DesugaredLibraryEmulatedInterfaceDuplicator {
+
+  final AppView<?> appView;
+  final Map<DexType, DexType> emulatedInterfaces;
+
+  public DesugaredLibraryEmulatedInterfaceDuplicator(AppView<?> appView) {
+    this.appView = appView;
+    emulatedInterfaces =
+        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+  }
+
+  public void duplicateEmulatedInterfaces() {
+    // All classes implementing an emulated interface now implements the interface and the
+    // emulated one, as well as hidden overrides, for correct emulated dispatch.
+    // We not that duplicated interfaces won't feature the correct type parameters in the
+    // class signature since such signature is expected to be unused.
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.type == appView.dexItemFactory().objectType) {
+        continue;
+      }
+      if (emulatedInterfaces.containsKey(clazz.type)) {
+        transformEmulatedInterfaces(clazz);
+      } else {
+        duplicateEmulatedInterfaces(clazz);
+      }
+    }
+  }
+
+  private void transformEmulatedInterfaces(DexProgramClass clazz) {
+    List<ClassTypeSignature> newInterfaces = new ArrayList<>();
+    GenericSignature.ClassSignature classSignature = clazz.getClassSignature();
+    for (int i = 0; i < clazz.interfaces.size(); i++) {
+      DexType itf = clazz.interfaces.values[i];
+      assert emulatedInterfaces.containsKey(itf);
+      List<FieldTypeSignature> typeArguments;
+      if (classSignature == null) {
+        typeArguments = Collections.emptyList();
+      } else {
+        ClassTypeSignature classTypeSignature = classSignature.superInterfaceSignatures().get(i);
+        assert itf == classTypeSignature.type();
+        typeArguments = classTypeSignature.typeArguments();
+      }
+      newInterfaces.add(new ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments));
+    }
+    clazz.replaceInterfaces(newInterfaces);
+  }
+
+  private void duplicateEmulatedInterfaces(DexProgramClass clazz) {
+    List<DexType> extraInterfaces = new ArrayList<>();
+    LinkedList<DexClass> workList = new LinkedList<>();
+    Set<DexType> processed = Sets.newIdentityHashSet();
+    workList.add(clazz);
+    while (!workList.isEmpty()) {
+      DexClass dexClass = workList.removeFirst();
+      if (processed.contains(dexClass.type)) {
+        continue;
+      }
+      processed.add(dexClass.type);
+      if (dexClass.superType != appView.dexItemFactory().objectType) {
+        processSuperType(clazz.superType, extraInterfaces, workList);
+      }
+      for (DexType itf : dexClass.interfaces) {
+        processSuperType(itf, extraInterfaces, workList);
+      }
+    }
+    extraInterfaces = removeDuplicates(extraInterfaces);
+    List<ClassTypeSignature> extraInterfaceSignatures = new ArrayList<>();
+    for (DexType extraInterface : extraInterfaces) {
+      extraInterfaceSignatures.add(new ClassTypeSignature(extraInterface));
+    }
+    clazz.addExtraInterfaces(extraInterfaceSignatures);
+  }
+
+  private List<DexType> removeDuplicates(List<DexType> extraInterfaces) {
+    if (extraInterfaces.size() <= 1) {
+      return extraInterfaces;
+    }
+    // TODO(b/161399032): It would be nice to remove duplicate based on inheritance, i.e.,
+    //  if there is ConcurrentMap<K,V> and Map<K,V>, Map<K,V> can be removed.
+    return new ArrayList<>(new HashSet<>(extraInterfaces));
+  }
+
+  void processSuperType(
+      DexType superType, List<DexType> extraInterfaces, LinkedList<DexClass> workList) {
+    if (emulatedInterfaces.containsKey(superType)) {
+      extraInterfaces.add(emulatedInterfaces.get(superType));
+    } else {
+      DexClass superClass = appView.definitionFor(superType);
+      if (shouldProcessSuperclass(superClass)) {
+        workList.add(superClass);
+      }
+    }
+  }
+
+  private boolean shouldProcessSuperclass(DexClass superclazz) {
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      return false;
+    }
+    // TODO(b/161399032): Pay-as-you-go design: stop duplication on library boundaries.
+    return superclazz != null && superclazz.isLibraryClass();
+  }
+}
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 2d4fb70..68a2df3 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
@@ -23,6 +23,8 @@
 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.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -112,7 +114,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
-            ClassSignature.NO_CLASS_SIGNATURE,
+            ClassSignature.noSignature(),
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
@@ -374,7 +376,7 @@
       // applies up to 24.
       for (DexEncodedMethod method : methods) {
         clazz.addExtraInterfaces(
-            Collections.singletonList(dispatchInterfaceTypeFor(method)), appView.dexItemFactory());
+            Collections.singletonList(new ClassTypeSignature(dispatchInterfaceTypeFor(method))));
         if (clazz.lookupVirtualMethod(method.getReference()) == null) {
           DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
           clazz.addVirtualMethod(newMethod);
@@ -447,7 +449,13 @@
                   emulatedDispatchMethod.getProto(),
                   emulatedDispatchMethod.getName());
       return new DexEncodedMethod(
-          newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null, true);
+          newMethod,
+          flags,
+          MethodTypeSignature.noSignature(),
+          DexAnnotationSet.empty(),
+          ParameterAnnotationsList.empty(),
+          null,
+          true);
     }
 
     private DexEncodedMethod generateHolderDispatchMethod(
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 88fc45a..e2f3271 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
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
-
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
@@ -28,6 +26,8 @@
 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.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -233,7 +233,7 @@
         Collections.emptyList(),
         null,
         Collections.emptyList(),
-        ClassSignature.NO_CLASS_SIGNATURE,
+        ClassSignature.noSignature(),
         DexAnnotationSet.empty(),
         DexEncodedField.EMPTY_ARRAY, // No static fields.
         new DexEncodedField[] {wrapperField},
@@ -375,6 +375,7 @@
     return new DexEncodedMethod(
         methodToInstall,
         newFlags,
+        MethodTypeSignature.noSignature(),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
         code,
@@ -430,7 +431,7 @@
     FieldAccessFlags fieldAccessFlags =
         FieldAccessFlags.fromCfAccessFlags(Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
     return new DexEncodedField(
-        field, fieldAccessFlags, NO_FIELD_TYPE_SIGNATURE, DexAnnotationSet.empty(), null);
+        field, fieldAccessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
   }
 
   private DexEncodedMethod synthesizeConstructor(DexField field) {
@@ -452,6 +453,7 @@
     return new DexEncodedMethod(
         methodToInstall,
         accessFlags,
+        MethodTypeSignature.noSignature(),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
         code,
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 6d37c32..61a5c56 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
@@ -54,7 +54,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.IdentityHashMap;
-import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -713,7 +712,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
-            ClassSignature.NO_CLASS_SIGNATURE,
+            ClassSignature.noSignature(),
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
@@ -923,63 +922,6 @@
     return newMethods;
   }
 
-  private void duplicateEmulatedInterfaces() {
-    // All classes implementing an emulated interface now implements the interface and the
-    // emulated one, as well as hidden overrides, for correct emulated dispatch.
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (clazz.type == appView.dexItemFactory().objectType) {
-        continue;
-      }
-      List<DexType> extraInterfaces = new ArrayList<>();
-      for (DexType type : clazz.interfaces.values) {
-        if (emulatedInterfaces.containsKey(type)) {
-          extraInterfaces.add(emulatedInterfaces.get(type));
-        }
-      }
-      if (!appView.options().isDesugaredLibraryCompilation()) {
-        assert clazz.superType != null;
-        DexClass superClazz = appView.definitionFor(clazz.superType);
-        if (superClazz != null && superClazz.isLibraryClass()) {
-          List<DexType> itfs = emulatedInterfacesOf(superClazz);
-          for (DexType itf : itfs) {
-            extraInterfaces.add(emulatedInterfaces.get(itf));
-          }
-        }
-        // Remove duplicates.
-        if (extraInterfaces.size() > 1) {
-          extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
-        }
-      }
-      clazz.addExtraInterfaces(extraInterfaces, appView.dexItemFactory());
-    }
-  }
-
-  private List<DexType> emulatedInterfacesOf(DexClass superClazz) {
-    if (superClazz.type == factory.objectType) {
-      return Collections.emptyList();
-    }
-    ArrayList<DexType> itfs = new ArrayList<>();
-    LinkedList<DexType> workList = new LinkedList<>();
-    workList.add(superClazz.type);
-    while (!workList.isEmpty()) {
-      DexType dexType = workList.removeFirst();
-      DexClass dexClass = appView.definitionFor(dexType);
-      if (dexClass != null) {
-        if (dexClass.superType != factory.objectType) {
-          workList.add(dexClass.superType);
-        }
-        for (DexType itf : dexClass.interfaces.values) {
-          if (emulatedInterfaces.containsKey(itf)) {
-            itfs.add(itf);
-          } else {
-            workList.add(itf);
-          }
-        }
-      }
-    }
-    return itfs;
-  }
-
   /**
    * Move static and default interface methods to companion classes, add missing methods to forward
    * to moved default methods implementation.
@@ -992,7 +934,7 @@
     if (appView.options().isDesugaredLibraryCompilation()) {
       generateEmulateInterfaceLibrary(builder);
     }
-    duplicateEmulatedInterfaces();
+    new DesugaredLibraryEmulatedInterfaceDuplicator(appView).duplicateEmulatedInterfaces();
 
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
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 069f4b3..99230aa 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
@@ -27,6 +27,7 @@
 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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -99,6 +100,7 @@
             new DexEncodedMethod(
                 companionMethod,
                 newFlags,
+                virtual.getGenericSignature(),
                 virtual.annotations(),
                 virtual.parameterAnnotationsList,
                 code,
@@ -106,7 +108,7 @@
         implMethod.copyMetadata(virtual);
         virtual.setDefaultInterfaceMethodImplementation(implMethod);
         companionMethods.add(implMethod);
-        graphLensBuilder.recordOrigin(implMethod.method, virtual.method);
+        graphLensBuilder.recordCodeMovedToCompanionClass(virtual.method, implMethod.method);
       }
 
       // Remove bridge methods.
@@ -139,6 +141,7 @@
             new DexEncodedMethod(
                 companionMethod,
                 newFlags,
+                direct.getGenericSignature(),
                 direct.annotations(),
                 direct.parameterAnnotationsList,
                 direct.getCode(),
@@ -166,6 +169,7 @@
               new DexEncodedMethod(
                   companionMethod,
                   newFlags,
+                  direct.getGenericSignature(),
                   direct.annotations(),
                   direct.parameterAnnotationsList,
                   code,
@@ -213,7 +217,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
-            ClassSignature.NO_CLASS_SIGNATURE,
+            ClassSignature.noSignature(),
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
@@ -271,6 +275,7 @@
               newMethod,
               MethodAccessFlags.fromSharedAccessFlags(
                   Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false),
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               forwardMethodBuilder.build(),
@@ -298,7 +303,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
-            ClassSignature.NO_CLASS_SIGNATURE,
+            ClassSignature.noSignature(),
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
@@ -448,15 +453,17 @@
     }
 
     @Override
-    public DexMethod getOriginalMethodSignature(DexMethod method) {
-      DexMethod originalMethod = extraOriginalMethodSignatures.get(method);
-      if (originalMethod == null) {
-        originalMethod =
-            originalMethodSignatures != null
-                ? originalMethodSignatures.getOrDefault(method, method)
-                : method;
-      }
-      return getPrevious().getOriginalMethodSignature(originalMethod);
+    protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+      return extraOriginalMethodSignatures.getOrDefault(
+          method, originalMethodSignatures.getOrDefault(method, method));
+    }
+
+    @Override
+    protected DexMethod internalGetNextMethodSignature(DexMethod method) {
+      return originalMethodSignatures
+          .inverse()
+          .getOrDefault(
+              method, extraOriginalMethodSignatures.inverse().getOrDefault(method, method));
     }
 
     @Override
@@ -472,11 +479,10 @@
 
       private final BiMap<DexMethod, DexMethod> extraOriginalMethodSignatures = HashBiMap.create();
 
-      public void recordOrigin(DexMethod method, DexMethod origin) {
-        if (method == origin) {
-          return;
-        }
-        extraOriginalMethodSignatures.put(method, origin);
+      public void recordCodeMovedToCompanionClass(DexMethod from, DexMethod to) {
+        assert from != to;
+        originalMethodSignatures.put(from, from);
+        extraOriginalMethodSignatures.put(to, from);
       }
 
       @Override
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 6f4cad3..c9e47c2 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
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
-
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
@@ -26,6 +24,8 @@
 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.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -163,7 +163,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
-            ClassSignature.NO_CLASS_SIGNATURE,
+            ClassSignature.noSignature(),
             DexAnnotationSet.empty(),
             synthesizeStaticFields(),
             synthesizeInstanceFields(),
@@ -234,6 +234,7 @@
             mainMethod,
             MethodAccessFlags.fromSharedAccessFlags(
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
+            MethodTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             LambdaMainMethodSourceCode.build(this, mainMethod),
@@ -252,6 +253,7 @@
                       | Constants.ACC_SYNTHETIC
                       | Constants.ACC_BRIDGE,
                   false),
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               LambdaBridgeMethodSourceCode.build(this, bridgeMethod, mainMethod),
@@ -273,6 +275,7 @@
                 (stateless ? Constants.ACC_PRIVATE : Constants.ACC_PUBLIC)
                     | Constants.ACC_SYNTHETIC,
                 true),
+            MethodTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             LambdaConstructorSourceCode.build(this),
@@ -285,6 +288,7 @@
               classConstructor,
               MethodAccessFlags.fromSharedAccessFlags(
                   Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               LambdaClassConstructorSourceCode.build(this),
@@ -306,7 +310,7 @@
           new DexEncodedField(
               getCaptureField(i),
               accessFlags,
-              NO_FIELD_TYPE_SIGNATURE,
+              FieldTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               null);
     }
@@ -330,7 +334,7 @@
                     | Constants.ACC_FINAL
                     | Constants.ACC_SYNTHETIC
                     | Constants.ACC_STATIC),
-            NO_FIELD_TYPE_SIGNATURE,
+            FieldTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             DexValueNull.NULL);
     return fields;
@@ -622,12 +626,13 @@
                         new DexEncodedMethod(
                             callTarget,
                             newAccessFlags,
+                            encodedMethod.getGenericSignature(),
                             encodedMethod.annotations(),
                             encodedMethod.parameterAnnotationsList,
                             encodedMethod.getCode(),
                             true);
                     newMethod.copyMetadata(encodedMethod);
-                    rewriter.lensBuilder.move(encodedMethod.method, callTarget);
+                    rewriter.forcefullyMoveMethod(encodedMethod.method, callTarget);
 
                     DexEncodedMethod.setDebugInfoWithFakeThisParameter(
                         newMethod.getCode(), callTarget.getArity(), appView);
@@ -694,12 +699,13 @@
                         new DexEncodedMethod(
                             callTarget,
                             newAccessFlags,
+                            encodedMethod.getGenericSignature(),
                             encodedMethod.annotations(),
                             encodedMethod.parameterAnnotationsList,
                             encodedMethod.getCode(),
                             true);
                     newMethod.copyMetadata(encodedMethod);
-                    rewriter.lensBuilder.move(encodedMethod.method, callTarget);
+                    rewriter.forcefullyMoveMethod(encodedMethod.method, callTarget);
                     return newMethod;
                   });
       return new ProgramMethod(implMethodHolder, replacement);
@@ -723,6 +729,7 @@
           new DexEncodedMethod(
               callTarget,
               accessorFlags,
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               new SynthesizedCode(
@@ -761,6 +768,7 @@
           new DexEncodedMethod(
               callTarget,
               accessorFlags,
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               AccessorMethodSourceCode.build(LambdaClass.this, callTarget),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 7bb8d44..11cff1a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -46,6 +46,7 @@
 import com.google.common.base.Suppliers;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
@@ -77,7 +78,8 @@
 
   final DexString instanceFieldName;
 
-  final LambdaRewriterLens.Builder lensBuilder = LambdaRewriterLens.builder();
+  private final LambdaRewriterLens.Builder lensBuilder = LambdaRewriterLens.builder();
+  private final Set<DexMethod> forcefullyMovedMethods = Sets.newIdentityHashSet();
 
   // Maps call sites seen so far to inferred lambda descriptor. It is intended
   // to help avoid re-matching call sites we already seen. Note that same call
@@ -95,6 +97,15 @@
     this.instanceFieldName = appView.dexItemFactory().createString(LAMBDA_INSTANCE_FIELD_NAME);
   }
 
+  void forcefullyMoveMethod(DexMethod from, DexMethod to) {
+    lensBuilder.move(from, to);
+    forcefullyMovedMethods.add(from);
+  }
+
+  public Set<DexMethod> getForcefullyMovedMethods() {
+    return forcefullyMovedMethods;
+  }
+
   private void synthesizeAccessibilityBridgesForLambdaClassesD8(
       Collection<LambdaClass> lambdaClasses, IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
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 01b87d2..c0a1269 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
@@ -186,7 +186,7 @@
         Collections.emptyList(),
         null,
         Collections.emptyList(),
-        ClassSignature.NO_CLASS_SIGNATURE,
+        ClassSignature.noSignature(),
         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 7b0d6b0..23cd37d 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
@@ -20,6 +20,7 @@
 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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.IRCode;
@@ -135,8 +136,15 @@
             options, twrCloseResourceMethod);
     MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(
         Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
-    DexEncodedMethod method = new DexEncodedMethod(twrCloseResourceMethod,
-        flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code, true);
+    DexEncodedMethod method =
+        new DexEncodedMethod(
+            twrCloseResourceMethod,
+            flags,
+            MethodTypeSignature.noSignature(),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            code,
+            true);
 
     // Create utility class.
     DexProgramClass utilityClass =
@@ -152,7 +160,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
-            ClassSignature.NO_CLASS_SIGNATURE,
+            ClassSignature.noSignature(),
             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 56d0a08..9e16ab0 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
@@ -26,6 +26,7 @@
 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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -1361,6 +1362,7 @@
           new DexEncodedMethod(
               method,
               methodAccess,
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               new OutlineCode(outline),
@@ -1392,7 +1394,7 @@
         Collections.emptyList(),
         null,
         Collections.emptyList(),
-        ClassSignature.NO_CLASS_SIGNATURE,
+        ClassSignature.noSignature(),
         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 7a81edc..1694a7b 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
@@ -19,6 +19,7 @@
 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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodCollection;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -218,6 +219,7 @@
               methodReference,
               MethodAccessFlags.fromSharedAccessFlags(
                   Constants.ACC_PUBLIC | Constants.ACC_STATIC, false),
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               ServiceLoaderSourceCode.generate(serviceType, classes, appView.dexItemFactory()),
@@ -253,7 +255,7 @@
                       Collections.emptyList(),
                       null,
                       Collections.emptyList(),
-                      ClassSignature.NO_CLASS_SIGNATURE,
+                      ClassSignature.noSignature(),
                       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/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 4a2c485..d815826 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
-import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
 import com.android.tools.r8.dex.Constants;
@@ -24,6 +23,8 @@
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -422,7 +423,7 @@
         field,
         FieldAccessFlags.fromSharedAccessFlags(
             Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC),
-        NO_FIELD_TYPE_SIGNATURE,
+        FieldTypeSignature.noSignature(),
         DexAnnotationSet.empty(),
         null);
   }
@@ -633,6 +634,7 @@
     return new DexEncodedMethod(
         method,
         synthesizedMethodAccessFlags(sync),
+        MethodTypeSignature.noSignature(),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
         cfCode,
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 df641af..4df9c8d 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
@@ -144,7 +144,7 @@
   private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
     DexClass holder = appView.definitionFor(encodedMethod.holder());
     assert holder != null;
-    if (encodedMethod.isInstanceInitializer()) {
+    if (newMethod.isInstanceInitializer(appView.dexItemFactory())) {
       newMethod =
           factory.createInstanceInitializerWithFreshProto(
               newMethod,
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 b199c47..c203772 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
@@ -122,7 +122,7 @@
               Collections.emptyList(),
               null,
               Collections.emptyList(),
-              ClassSignature.NO_CLASS_SIGNATURE,
+              ClassSignature.noSignature(),
               DexAnnotationSet.empty(),
               DexEncodedField.EMPTY_ARRAY,
               DexEncodedField.EMPTY_ARRAY,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 3bb366d..ee35087 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -252,15 +252,16 @@
         .filter(cls -> !appView.appInfo().isPinned(cls.type))
         .filter(
             cls ->
-                cls.getKotlinInfo().isSyntheticClass()
-                    && cls.getKotlinInfo().asSyntheticClass().isLambda()
+                appView.testing().kotlinLambdaMergerFactoryForClass.apply(cls) != null
                     && KotlinLambdaGroupIdFactory.hasValidAnnotations(kotlin, cls)
                     && !appView.appInfo().getClassToFeatureSplitMap().isInFeature(cls))
         .sorted((a, b) -> a.type.slowCompareTo(b.type)) // Ensure stable ordering.
         .forEachOrdered(
             lambda -> {
               try {
-                LambdaGroupId id = KotlinLambdaGroupIdFactory.create(appView, kotlin, lambda);
+                KotlinLambdaGroupIdFactory lambdaGroupIdFactory =
+                    appView.testing().kotlinLambdaMergerFactoryForClass.apply(lambda);
+                LambdaGroupId id = lambdaGroupIdFactory.validateAndCreate(appView, kotlin, lambda);
                 LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup);
                 group.add(lambda);
                 lambdas.put(lambda.type, group);
@@ -341,6 +342,7 @@
       ExecutorService executorService)
       throws ExecutionException {
     if (lambdas.isEmpty()) {
+      appView.setHorizontallyMergedLambdaClasses(HorizontallyMergedLambdaClasses.empty());
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
index 2e4e1a9..df72677 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -15,19 +15,22 @@
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
-final class JStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
-  static final JStyleLambdaGroupIdFactory INSTANCE = new JStyleLambdaGroupIdFactory();
+public final class JStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
+  private static final JStyleLambdaGroupIdFactory INSTANCE = new JStyleLambdaGroupIdFactory();
+
+  private JStyleLambdaGroupIdFactory() {}
+
+  public static JStyleLambdaGroupIdFactory getInstance() {
+    return INSTANCE;
+  }
 
   @Override
-  LambdaGroupId validateAndCreate(
+  public LambdaGroupId validateAndCreate(
       AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
       throws LambdaStructureError {
     boolean accessRelaxed =
         appView.options().getProguardConfiguration().isAccessModificationAllowed();
 
-    assert lambda.getKotlinInfo().isSyntheticClass();
-    assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
-
     // Ignore ACC_SUPER.
     ClassAccessFlags copy = lambda.accessFlags.copy();
     copy.unsetSuper();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
index 6f7650b..cc212bf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -15,19 +15,22 @@
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
-final class KStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
-  static final KotlinLambdaGroupIdFactory INSTANCE = new KStyleLambdaGroupIdFactory();
+public final class KStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
+  private static final KStyleLambdaGroupIdFactory INSTANCE = new KStyleLambdaGroupIdFactory();
+
+  private KStyleLambdaGroupIdFactory() {}
+
+  public static KStyleLambdaGroupIdFactory getInstance() {
+    return INSTANCE;
+  }
 
   @Override
-  LambdaGroupId validateAndCreate(
+  public LambdaGroupId validateAndCreate(
       AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
       throws LambdaStructureError {
     boolean accessRelaxed =
         appView.options().getProguardConfiguration().isAccessModificationAllowed();
 
-    assert lambda.getKotlinInfo().isSyntheticClass();
-    assert lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda();
-
     // Ignore ACC_SUPER.
     ClassAccessFlags copy = lambda.accessFlags.copy();
     copy.unsetSuper();
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 a29db28..977bdcf 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
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
-import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -25,6 +24,8 @@
 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.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -158,6 +159,7 @@
                   new DexEncodedMethod(
                       method,
                       accessFlags,
+                      MethodTypeSignature.noSignature(),
                       isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
                       isMainMethod
                           ? id.mainMethodParamAnnotations
@@ -252,6 +254,7 @@
         new DexEncodedMethod(
             initializerMethod,
             CONSTRUCTOR_FLAGS_RELAXED, // always create access-relaxed constructor.
+            MethodTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             new SynthesizedCode(
@@ -271,6 +274,7 @@
           new DexEncodedMethod(
               method,
               CLASS_INITIALIZER_FLAGS,
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               new SynthesizedCode(
@@ -293,7 +297,7 @@
         new DexEncodedField(
             group.getLambdaIdField(factory),
             CAPTURE_FIELD_FLAGS_RELAXED,
-            NO_FIELD_TYPE_SIGNATURE,
+            FieldTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             null);
 
@@ -302,7 +306,7 @@
           new DexEncodedField(
               group.getCaptureField(factory, id),
               CAPTURE_FIELD_FLAGS_RELAXED,
-              NO_FIELD_TYPE_SIGNATURE,
+              FieldTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               null);
     }
@@ -326,7 +330,7 @@
                 new DexEncodedField(
                     field,
                     SINGLETON_FIELD_FLAGS,
-                    NO_FIELD_TYPE_SIGNATURE,
+                    FieldTypeSignature.noSignature(),
                     DexAnnotationSet.empty(),
                     DexValueNull.NULL);
             result.add(encodedField);
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 5a90f80..472d861 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
@@ -10,6 +10,7 @@
 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.InnerClassAttribute;
 import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
@@ -23,27 +24,26 @@
   KotlinLambdaGroupIdFactory() {
   }
 
-  // Creates a lambda group id for kotlin style lambda. Should never return null, if the lambda
-  // does not pass pre-requirements (mostly by not meeting high-level structure expectations)
-  // should throw LambdaStructureError leaving the caller to decide if/how it needs to be reported.
+  public static KotlinLambdaGroupIdFactory getFactoryForClass(DexProgramClass clazz) {
+    if (clazz.getKotlinInfo().isSyntheticClass()
+        && clazz.getKotlinInfo().asSyntheticClass().isLambda()) {
+      if (clazz.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda()) {
+        return KStyleLambdaGroupIdFactory.getInstance();
+      }
+      assert clazz.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
+      return JStyleLambdaGroupIdFactory.getInstance();
+    }
+    return null;
+  }
+
+  // Creates a lambda group id for a Java or Kotlin style lambda. Never returns null, but may throw
+  // a LambdaStructureError if the lambda does not pass pre-requirements (mostly by not meeting
+  // high-level structure expectations).
   //
   // At this point we only perform high-level checks before qualifying the lambda as a candidate
   // for merging and assigning lambda group id. We can NOT perform checks on method bodies since
   // they may not be converted yet, we'll do that in KStyleLambdaClassValidator.
-  public static LambdaGroupId create(
-      AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
-      throws LambdaStructureError {
-
-    assert lambda.getKotlinInfo().isSyntheticClass();
-    if (lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda()) {
-      return KStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(appView, kotlin, lambda);
-    }
-
-    assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
-    return JStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(appView, kotlin, lambda);
-  }
-
-  abstract LambdaGroupId validateAndCreate(
+  public abstract LambdaGroupId validateAndCreate(
       AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
       throws LambdaStructureError;
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
index a20b8bf..c411d08 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.optimize.library;
 
 import static com.android.tools.r8.graph.DexLibraryClass.asLibraryClassOrNull;
-import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
@@ -14,6 +13,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 
 /**
  * This class synthesizes library fields that we rely on for modeling.
@@ -39,7 +39,7 @@
                       field,
                       FieldAccessFlags.fromCfAccessFlags(
                           Constants.ACC_PRIVATE | Constants.ACC_FINAL),
-                      NO_FIELD_TYPE_SIGNATURE,
+                      FieldTypeSignature.noSignature(),
                       DexAnnotationSet.empty(),
                       null));
             }
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 ae7fcf5..b914a14 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -274,20 +274,6 @@
     return encodedAnnotation.elements[0].value;
   }
 
-  private String getSignature(DexAnnotationSet annotations) {
-    DexValue value =
-        getSystemAnnotationValue(annotations, application.dexItemFactory.annotationSignature);
-    if (value == null) {
-      return null;
-    }
-    // Signature has already been minified by ClassNameMinifier.renameTypesInGenericSignatures().
-    StringBuilder res = new StringBuilder();
-    for (DexValue part : value.asDexValueArray().getValues()) {
-      res.append(part.asDexValueString().getValue().toString());
-    }
-    return res.toString();
-  }
-
   private String getSourceDebugExtension(DexAnnotationSet annotations) {
     DexValue debugExtensions =
         getSystemAnnotationValue(
@@ -340,7 +326,7 @@
     }
     String name = namingLens.lookupName(field.field).toString();
     String desc = namingLens.lookupDescriptor(field.field.type).toString();
-    String signature = field.getFieldSignature().toRenamedString(namingLens, isTypeMissing);
+    String signature = field.getGenericSignature().toRenamedString(namingLens, isTypeMissing);
     Object value = getStaticValue(field);
     FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
     writeAnnotations(visitor::visitAnnotation, field.annotations().annotations);
@@ -360,7 +346,8 @@
     }
     String name = namingLens.lookupName(method.getReference()).toString();
     String desc = definition.descriptor(namingLens);
-    String signature = getSignature(definition.annotations());
+    String signature =
+        method.getDefinition().getGenericSignature().toRenamedString(namingLens, isTypeMissing);
     String[] exceptions = getExceptions(definition.annotations());
     MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
     if (defaults.containsKey(definition.getName())) {
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 1a51edf..2c57aa4 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
@@ -4,30 +4,15 @@
 
 package com.android.tools.r8.naming.signature;
 
-import static com.android.tools.r8.utils.DescriptorUtils.getClassBinaryNameFromDescriptor;
-import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
-
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationSet;
 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;
 import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
-import java.lang.reflect.GenericSignatureFormatError;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
 
 // TODO(b/169516860): We should generalize this to handle rewriting of attributes in general.
 public class GenericSignatureRewriter {
@@ -60,238 +45,16 @@
         clazz -> {
           GenericSignatureTypeRewriter genericSignatureTypeRewriter =
               new GenericSignatureTypeRewriter(appView, clazz);
-          GenericSignatureCollector genericSignatureCollector =
-              new GenericSignatureCollector(clazz);
-          GenericSignatureParser<DexType> genericSignatureParser =
-              new GenericSignatureParser<>(genericSignatureCollector);
-          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.setClassSignature(genericSignatureTypeRewriter.rewrite(clazz.getClassSignature()));
           clazz.forEachField(
               field ->
-                  field.setFieldSignature(
-                      genericSignatureTypeRewriter.rewrite(field.getFieldSignature())));
+                  field.setGenericSignature(
+                      genericSignatureTypeRewriter.rewrite(field.getGenericSignature())));
           clazz.forEachMethod(
               method ->
-                  method.setAnnotations(
-                      rewriteGenericSignatures(
-                          method.annotations(),
-                          genericSignatureParser::parseMethodSignature,
-                          genericSignatureCollector::getRenamedSignature,
-                          (signature, e) ->
-                              options.warningInvalidSignature(
-                                  method, clazz.getOrigin(), signature, e))));
+                  method.setGenericSignature(
+                      genericSignatureTypeRewriter.rewrite(method.getGenericSignature())));
         },
         executorService);
   }
-
-  // TODO(b/129925954): Remove this when using modeled signatures for methods and fields.
-  private DexAnnotationSet rewriteGenericSignatures(
-      DexAnnotationSet annotations,
-      Consumer<String> parser,
-      Supplier<String> collector,
-      BiConsumer<String, GenericSignatureFormatError> parseError) {
-    // There can be no more than one signature annotation in an annotation set.
-    final int VALID = -1;
-    int invalid = VALID;
-    DexAnnotation[] rewrittenAnnotations = null;
-    for (int i = 0; i < annotations.annotations.length && invalid == VALID; i++) {
-      DexAnnotation annotation = annotations.annotations[i];
-      if (DexAnnotation.isSignatureAnnotation(annotation, appView.dexItemFactory())) {
-        if (rewrittenAnnotations == null) {
-          rewrittenAnnotations = new DexAnnotation[annotations.annotations.length];
-          System.arraycopy(annotations.annotations, 0, rewrittenAnnotations, 0, i);
-        }
-        String signature = DexAnnotation.getSignature(annotation);
-        try {
-          parser.accept(signature);
-          String renamedSignature = collector.get();
-          assert verifyConsistentRenaming(parser, collector, renamedSignature);
-          DexAnnotation signatureAnnotation =
-              DexAnnotation.createSignatureAnnotation(renamedSignature, appView.dexItemFactory());
-          rewrittenAnnotations[i] = signatureAnnotation;
-        } catch (GenericSignatureFormatError e) {
-          parseError.accept(signature, e);
-          invalid = i;
-        }
-      } else if (rewrittenAnnotations != null) {
-        rewrittenAnnotations[i] = annotation;
-      }
-    }
-
-    // Return the rewritten signatures if it was valid and could be rewritten.
-    if (invalid == VALID) {
-      return rewrittenAnnotations != null
-          ? new DexAnnotationSet(rewrittenAnnotations)
-          : annotations;
-    }
-    // Remove invalid signature if found.
-    DexAnnotation[] prunedAnnotations =
-        new DexAnnotation[annotations.annotations.length - 1];
-    int dest = 0;
-    for (int i = 0; i < annotations.annotations.length; i++) {
-      if (i != invalid) {
-        prunedAnnotations[dest++] = annotations.annotations[i];
-      }
-    }
-    assert dest == prunedAnnotations.length;
-    return new DexAnnotationSet(prunedAnnotations);
-  }
-
-  /**
-   * Calling this method will clobber the parsed signature in the collector - ideally with the same
-   * string. Only use this after the original result has been collected.
-   */
-  private boolean verifyConsistentRenaming(
-      Consumer<String> parser, Supplier<String> collector, String renamedSignature) {
-    if (!options.testing.assertConsistentRenamingOfSignature) {
-      return true;
-    }
-    parser.accept(renamedSignature);
-    String reRenamedSignature = collector.get();
-    assert renamedSignature.equals(reRenamedSignature);
-    return true;
-  }
-
-  private class GenericSignatureCollector implements GenericSignatureAction<DexType> {
-    private StringBuilder renamedSignature;
-    private final DexProgramClass currentClassContext;
-    private DexType lastWrittenType = null;
-
-    GenericSignatureCollector(DexProgramClass clazz) {
-      this.currentClassContext = clazz;
-    }
-
-    String getRenamedSignature() {
-      return renamedSignature.toString();
-    }
-
-    @Override
-    public void parsedSymbol(char symbol) {
-      if (symbol == ';' && lastWrittenType == null) {
-        // The type was never written (maybe because it was merged with it's subtype).
-        return;
-      }
-      // If the super-class or interface has been merged, we will stop writing out type
-      // arguments, resulting in a signature on the form '<>' if we do not remove it.
-      if (symbol == '>' && removeWrittenCharacter(c -> c == '<')) {
-        return;
-      }
-      renamedSignature.append(symbol);
-    }
-
-    @Override
-    public void parsedIdentifier(String identifier) {
-      renamedSignature.append(identifier);
-    }
-
-    @Override
-    public DexType parsedTypeName(String name, ParserPosition parserPosition) {
-      if (parserPosition == ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION
-          && lastWrittenType == null) {
-        // We are writing type-arguments for a merged class.
-        removeWrittenClassCharacter();
-        return null;
-      }
-      String originalDescriptor = getDescriptorFromClassBinaryName(name);
-      DexType type =
-          appView.graphLens().lookupType(appView.dexItemFactory().createType(originalDescriptor));
-      if (appView.appInfo().hasLiveness() && appView.withLiveness().appInfo().wasPruned(type)) {
-        type = appView.dexItemFactory().objectType;
-      }
-      DexString renamedDescriptor = namingLens.lookupDescriptor(type);
-      if (parserPosition == ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION
-          && currentClassContext != null) {
-        // We may have merged the type down to the current class type.
-        DexString classDescriptor = currentClassContext.type.descriptor;
-        if (!originalDescriptor.equals(classDescriptor.toString())
-            && renamedDescriptor.equals(classDescriptor)) {
-          lastWrittenType = null;
-          removeWrittenClassCharacter();
-          return type;
-        }
-      }
-      renamedSignature.append(getClassBinaryNameFromDescriptor(renamedDescriptor.toString()));
-      lastWrittenType = type;
-      return type;
-    }
-
-    private boolean removeWrittenCharacter(Predicate<Character> removeIf) {
-      int index = renamedSignature.length() - 1;
-      if (index < 0 || !removeIf.test(renamedSignature.charAt(index))) {
-        return false;
-      }
-      renamedSignature.deleteCharAt(index);
-      return true;
-    }
-
-    private void removeWrittenClassCharacter() {
-      removeWrittenCharacter(c -> c == 'L');
-    }
-
-    @Override
-    public DexType parsedInnerTypeName(DexType enclosingType, String name) {
-      if (enclosingType == null) {
-        // We are writing inner type names
-        removeWrittenClassCharacter();
-        return null;
-      }
-      assert enclosingType.isClassType();
-      String enclosingDescriptor = enclosingType.toDescriptorString();
-      DexType type =
-          appView
-              .dexItemFactory()
-              .createType(
-                  getDescriptorFromClassBinaryName(
-                      getClassBinaryNameFromDescriptor(enclosingDescriptor)
-                          + DescriptorUtils.INNER_CLASS_SEPARATOR
-                          + name));
-      type = appView.graphLens().lookupType(type);
-      String renamedDescriptor = namingLens.lookupDescriptor(type).toString();
-      if (!renamedDescriptor.equals(type.toDescriptorString())) {
-        // TODO(b/147504070): If this is a merged class equal to the class context, do not add.
-        // Pick the renamed inner class from the fully renamed binary name.
-        String fullRenamedBinaryName = getClassBinaryNameFromDescriptor(renamedDescriptor);
-        String enclosingRenamedBinaryName =
-            getClassBinaryNameFromDescriptor(namingLens.lookupDescriptor(enclosingType).toString());
-        int innerClassPos = enclosingRenamedBinaryName.length() + 1;
-        if (innerClassPos < fullRenamedBinaryName.length()) {
-          renamedSignature.append(fullRenamedBinaryName.substring(innerClassPos));
-        } else if (appView.options().keepInnerClassStructure()) {
-          reporter.warning(
-              new StringDiagnostic(
-                  "Should have retained InnerClasses attribute of " + type + ".",
-                  appView.appInfo().originFor(type)));
-          renamedSignature.append(name);
-        }
-      } else {
-        // Did not find the class - keep the inner class name as is.
-        // TODO(b/110085899): Warn about missing classes in signatures?
-        renamedSignature.append(name);
-      }
-      return type;
-    }
-
-    @Override
-    public void start() {
-      renamedSignature = new StringBuilder();
-    }
-
-    @Override
-    public void stop() {
-      // nothing to do
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
index d36cbee..5d2fef0 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
@@ -60,7 +60,7 @@
     assert clazz != null;
     DexEncodedMethod actualEncodedTarget = clazz.lookupVirtualMethod(signatureInCurrentWorld);
     assert actualEncodedTarget != null;
-    assert actualEncodedTarget.isPublicized();
+    assert actualEncodedTarget.isPublic();
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/DirectClassNameMapperProguardMapProducer.java b/src/main/java/com/android/tools/r8/retrace/DirectClassNameMapperProguardMapProducer.java
new file mode 100644
index 0000000..8efe05a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/DirectClassNameMapperProguardMapProducer.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
+
+public interface DirectClassNameMapperProguardMapProducer extends ProguardMapProducer {
+
+  ClassNameMapper getClassNameMapper();
+
+  @Override
+  default String get() {
+    throw new RuntimeException("Should not be called for DirectClassNameMapperProguardMapProducer");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/InvalidMappingFileException.java b/src/main/java/com/android/tools/r8/retrace/InvalidMappingFileException.java
new file mode 100644
index 0000000..86e81e1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/InvalidMappingFileException.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public final class InvalidMappingFileException extends RuntimeException {
+
+  public InvalidMappingFileException(Throwable throwable) {
+    super(throwable);
+  }
+
+  @Override
+  public String getMessage() {
+    return "Unable to parse mapping file";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index f34a5aa..adf9583 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -10,9 +10,9 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.Version;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
 import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -120,7 +120,15 @@
           new StringDiagnostic(String.format("Could not find mapping file '%s'.", mappingPath)));
       throw new RetraceAbortException();
     }
-    return () -> new String(Files.readAllBytes(path));
+    return () -> {
+      try {
+        return new String(Files.readAllBytes(path));
+      } catch (IOException e) {
+        diagnosticsHandler.error(
+            new StringDiagnostic(String.format("Could not open mapping file '%s'.", mappingPath)));
+        throw new RuntimeException(e);
+      }
+    };
   }
 
   private static List<String> getStackTraceFromFile(
@@ -142,11 +150,9 @@
     try {
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       timing.begin("Read proguard map");
-      ClassNameMapper classNameMapper =
-          ClassNameMapper.mapperFromString(
-              command.proguardMapProducer.get(), command.diagnosticsHandler);
+      RetraceApi retracer =
+          Retracer.create(command.proguardMapProducer, command.diagnosticsHandler);
       timing.end();
-      RetraceApi retracer = Retracer.create(classNameMapper);
       RetraceCommandLineResult result;
       timing.begin("Parse and Retrace");
       if (command.regularExpression != null) {
@@ -171,10 +177,9 @@
       if (command.printTimes()) {
         timing.report();
       }
-    } catch (IOException ex) {
-      command.diagnosticsHandler.error(
-          new StringDiagnostic("Could not open mapping input stream: " + ex.getMessage()));
-      throw new RetraceAbortException();
+    } catch (InvalidMappingFileException e) {
+      command.diagnosticsHandler.error(new ExceptionDiagnostic(e));
+      throw e;
     }
   }
 
@@ -207,6 +212,7 @@
         return;
       }
       assert Arrays.asList(mappedArgs).contains("--help");
+      System.out.println("Retrace " + Version.getVersionString());
       System.out.print(USAGE_MESSAGE);
       return;
     }
@@ -233,6 +239,7 @@
   }
 
   private static List<String> getStackTraceFromStandardInput() {
+    System.out.println("Waiting for stack-trace input...");
     Scanner sc = new Scanner(new InputStreamReader(System.in, Charsets.UTF_8));
     List<String> readLines = new ArrayList<>();
     while (sc.hasNext()) {
@@ -252,9 +259,11 @@
       action.run();
     } catch (RetraceAbortException e) {
       // Detail of the errors were already reported
+      System.err.println(StringUtils.LINE_SEPARATOR + USAGE_MESSAGE + StringUtils.LINE_SEPARATOR);
       System.exit(STATUS_ERROR);
     } catch (RuntimeException e) {
       System.err.println("Retrace failed with an internal error.");
+      System.err.println(StringUtils.LINE_SEPARATOR + USAGE_MESSAGE + StringUtils.LINE_SEPARATOR);
       Throwable cause = e.getCause() == null ? e : e.getCause();
       cause.printStackTrace();
       System.exit(STATUS_ERROR);
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 9b6694f..b6247df 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 java.io.IOException;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -147,6 +146,6 @@
 
   @Keep
   public interface ProguardMapProducer {
-    String get() throws IOException;
+    String get();
   }
 }
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 31619e7..583ca7c 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -4,12 +4,14 @@
 
 package com.android.tools.r8.retrace;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.ClassNameMapper;
 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.TypeReference;
+import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
 
 /** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
 @Keep
@@ -22,8 +24,19 @@
     assert classNameMapper != null;
   }
 
-  public static RetraceApi create(ClassNameMapper classNameMapper) {
-    return new Retracer(classNameMapper);
+  public static RetraceApi create(
+      ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
+    if (proguardMapProducer instanceof DirectClassNameMapperProguardMapProducer) {
+      return new Retracer(
+          ((DirectClassNameMapperProguardMapProducer) proguardMapProducer).getClassNameMapper());
+    }
+    try {
+      ClassNameMapper classNameMapper =
+          ClassNameMapper.mapperFromString(proguardMapProducer.get(), diagnosticsHandler);
+      return new Retracer(classNameMapper);
+    } catch (Throwable throwable) {
+      throw new InvalidMappingFileException(throwable);
+    }
   }
 
   @Override
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 fe2861a..46e4629 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -83,15 +83,10 @@
         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);
+        assert !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
         if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) {
           return true;
         }
-        if (config.signature && DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory)) {
-          return true;
-        }
         if (DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
           assert holder.isDexClass();
           appView.setSourceDebugExtensionForType(
@@ -202,13 +197,16 @@
         method.annotations().rewrite(annotation -> rewriteAnnotation(method, annotation)));
     method.parameterAnnotationsList =
         method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
+    if (!keep.signature) {
+      method.clearGenericSignature();
+    }
   }
 
   private void processField(DexEncodedField field) {
     field.setAnnotations(
         field.annotations().rewrite(annotation -> rewriteAnnotation(field, annotation)));
     if (!keep.signature) {
-      field.clearFieldSignature();
+      field.clearGenericSignature();
     }
   }
 
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 4786db4..3c83acd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.shaking;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -16,6 +15,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Visibility;
@@ -81,7 +81,7 @@
           new DexEncodedField(
               appView.dexItemFactory().createField(clazz.type, clinitField.type, clinitField.name),
               accessFlags,
-              NO_FIELD_TYPE_SIGNATURE,
+              FieldTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               null);
       clazz.appendStaticField(encodedClinitField);
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java b/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java
index a6d7326..6a2151b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java
@@ -9,15 +9,19 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
 public class ClassMergingEnqueuerExtension
-    implements EnqueuerInstanceOfAnalysis, EnqueuerCheckCastAnalysis {
+    implements EnqueuerInstanceOfAnalysis,
+        EnqueuerCheckCastAnalysis,
+        EnqueuerExceptionGuardAnalysis {
 
   private final Set<DexType> instanceOfTypes = Sets.newIdentityHashSet();
   private final Set<DexType> checkCastTypes = Sets.newIdentityHashSet();
+  private final Set<DexType> exceptionGuardTypes = Sets.newIdentityHashSet();
   private final DexItemFactory factory;
 
   public ClassMergingEnqueuerExtension(DexItemFactory factory) {
@@ -34,6 +38,11 @@
     instanceOfTypes.add(type.toBaseType(factory));
   }
 
+  @Override
+  public void traceExceptionGuard(DexType guard, ProgramMethod context) {
+    exceptionGuardTypes.add(guard);
+  }
+
   public boolean isCheckCastType(DexProgramClass clazz) {
     return checkCastTypes.contains(clazz.type);
   }
@@ -42,7 +51,18 @@
     return instanceOfTypes.contains(clazz.type);
   }
 
+  public boolean isExceptionGuardType(DexProgramClass clazz) {
+    return exceptionGuardTypes.contains(clazz.type);
+  }
+
+  public boolean isRuntimeCheckType(DexProgramClass clazz) {
+    return isInstanceOfType(clazz) || isCheckCastType(clazz) || isExceptionGuardType(clazz);
+  }
+
   public void attach(Enqueuer enqueuer) {
-    enqueuer.registerInstanceOfAnalysis(this).registerCheckCastAnalysis(this);
+    enqueuer
+        .registerInstanceOfAnalysis(this)
+        .registerCheckCastAnalysis(this)
+        .registerExceptionGuardAnalysis(this);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 34fb593..b67d90d 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -134,6 +134,11 @@
   }
 
   @Override
+  public void registerExceptionGuard(DexType guard) {
+    enqueuer.traceExceptionGuard(guard, context);
+  }
+
+  @Override
   public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
     super.registerMethodHandle(methodHandle, use);
     enqueuer.traceMethodHandle(methodHandle, use, context);
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 d1200b4..ec4e675 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -65,6 +65,7 @@
 import com.android.tools.r8.graph.analysis.DesugaredLibraryConversionWrapperAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
@@ -170,6 +171,10 @@
     public boolean isTracingMainDex() {
       return this == MAIN_DEX_TRACING;
     }
+
+    public boolean isWhyAreYouKeeping() {
+      return this == WHY_ARE_YOU_KEEPING;
+    }
   }
 
   private final boolean forceProguardCompatibility;
@@ -178,6 +183,7 @@
   private Set<EnqueuerAnalysis> analyses = Sets.newIdentityHashSet();
   private Set<EnqueuerInvokeAnalysis> invokeAnalyses = Sets.newIdentityHashSet();
   private Set<EnqueuerInstanceOfAnalysis> instanceOfAnalyses = Sets.newIdentityHashSet();
+  private Set<EnqueuerExceptionGuardAnalysis> exceptionGuardAnalyses = Sets.newIdentityHashSet();
   private Set<EnqueuerCheckCastAnalysis> checkCastAnalyses = Sets.newIdentityHashSet();
 
   // Don't hold a direct pointer to app info (use appView).
@@ -263,8 +269,6 @@
    * Set of direct methods that are the immediate target of an invoke-dynamic.
    */
   private final Set<DexMethod> methodsTargetedByInvokeDynamic = Sets.newIdentityHashSet();
-  /** Set of direct lambda implementation methods that have been desugared, thus they may move. */
-  private final Set<DexMethod> desugaredLambdaImplementationMethods = Sets.newIdentityHashSet();
   /**
    * Set of virtual methods that are the immediate target of an invoke-direct.
    */
@@ -438,6 +442,11 @@
     return this;
   }
 
+  public Enqueuer registerExceptionGuardAnalysis(EnqueuerExceptionGuardAnalysis analysis) {
+    exceptionGuardAnalyses.add(analysis);
+    return this;
+  }
+
   public void setAnnotationRemoverBuilder(AnnotationRemover.Builder annotationRemoverBuilder) {
     this.annotationRemoverBuilder = annotationRemoverBuilder;
   }
@@ -865,9 +874,6 @@
           classesWithSerializableLambdas.add(context.getHolder());
         }
       }
-      if (descriptor.delegatesToLambdaImplMethod()) {
-        desugaredLambdaImplementationMethods.add(descriptor.implHandle.asMethod());
-      }
     } else {
       markLambdaAsInstantiated(descriptor, context);
       transitionMethodsForInstantiatedLambda(descriptor);
@@ -1032,6 +1038,11 @@
     traceTypeReference(type, currentMethod);
   }
 
+  void traceExceptionGuard(DexType guard, ProgramMethod currentMethod) {
+    exceptionGuardAnalyses.forEach(analysis -> analysis.traceExceptionGuard(guard, currentMethod));
+    traceTypeReference(guard, currentMethod);
+  }
+
   void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
@@ -2728,6 +2739,11 @@
     finalizeLibraryMethodOverrideInformation();
     analyses.forEach(analyses -> analyses.done(this));
     assert verifyKeptGraph();
+    if (mode.isWhyAreYouKeeping()) {
+      // For why are you keeping the information is reported through the kept graph callbacks and
+      // no AppInfo is returned.
+      return null;
+    }
     AppInfoWithLiveness appInfoWithLiveness = createAppInfo(appInfo);
     if (options.testing.enqueuerInspector != null) {
       options.testing.enqueuerInspector.accept(appInfoWithLiveness, mode);
@@ -3088,6 +3104,16 @@
                 liveMethods.add(accessor, graphReporter.fakeReportShouldNotBeUsed());
               }
             });
+    unpinLambdaMethods();
+  }
+
+  // TODO(b/157700141): Determine if this is the right way to allow modification of pinned lambdas.
+  private void unpinLambdaMethods() {
+    assert lambdaRewriter != null;
+    for (DexMethod method : lambdaRewriter.getForcefullyMovedMethods()) {
+      keepInfo.unsafeUnpinMethod(method);
+      rootSet.prune(method);
+    }
   }
 
   private boolean verifyMissingTypes() {
@@ -3296,7 +3322,6 @@
     } finally {
       timing.end();
     }
-    unpinLambdaMethods();
   }
 
   private long getNumberOfLiveItems() {
@@ -3392,17 +3417,6 @@
     action.getAction().accept(builder);
   }
 
-  // TODO(b/157700141): Determine if this is the right way to avoid modification of pinned lambdas.
-  private void unpinLambdaMethods() {
-    assert desugaredLambdaImplementationMethods.isEmpty()
-        || options.desugarState == DesugarState.ON;
-    for (DexMethod method : desugaredLambdaImplementationMethods) {
-      keepInfo.unsafeUnpinMethod(method);
-      rootSet.prune(method);
-    }
-    desugaredLambdaImplementationMethods.clear();
-  }
-
   void retainAnnotationForFinalTreeShaking(List<DexAnnotation> annotations) {
     assert mode.isInitialTreeShaking();
     if (annotationRemoverBuilder != null) {
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 0881bef..d0834d5 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -383,25 +383,6 @@
       joinMethod(method, KeepInfo.Joiner::pin);
     }
 
-    public void unsafeAllowMinificationOfMethod(ProgramMethod method) {
-      KeepMethodInfo info = keepMethodInfo.get(method.getReference());
-      if (info != null && !info.internalIsMinificationAllowed()) {
-        keepMethodInfo.put(method.getReference(), info.builder().allowMinification().build());
-      }
-    }
-
-    // Unpinning a method represents a non-monotonic change to the keep info of that item.
-    // This is generally unsound as it requires additional analysis to determine that a method that
-    // was pinned no longer is. A known sound example is the enum analysis that will identify
-    // non-escaping enums on enum types that are not pinned, thus their methods do not need to be
-    // retained even if a rule has marked them as conditionally pinned.
-    public void unsafeUnpinMethod(ProgramMethod method) {
-      // This asserts that the holder is not pinned as some analysis must have established that the
-      // type is not "present" and thus the method need not be pinned.
-      assert !getClassInfo(method.getHolder()).isPinned();
-      unsafeUnpinMethod(method.getReference());
-    }
-
     // TODO(b/157700141): Avoid pinning/unpinning references.
     @Deprecated
     public void unsafeUnpinMethod(DexMethod method) {
@@ -455,22 +436,6 @@
       joinField(field, KeepInfo.Joiner::pin);
     }
 
-    public void unsafeAllowMinificationOfField(ProgramField field) {
-      assert !getClassInfo(field.getHolder()).isPinned();
-      KeepFieldInfo info = keepFieldInfo.get(field.getReference());
-      if (info != null && !info.internalIsMinificationAllowed()) {
-        keepFieldInfo.put(field.getReference(), info.builder().allowAccessModification().build());
-      }
-    }
-
-    public void unsafeUnpinField(ProgramField field) {
-      assert !getClassInfo(field.getHolder()).isPinned();
-      KeepFieldInfo info = this.keepFieldInfo.get(field.getReference());
-      if (info != null && info.isPinned()) {
-        keepFieldInfo.put(field.getReference(), info.builder().unpin().build());
-      }
-    }
-
     @Override
     public KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator) {
       mutator.accept(this);
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 3f8077f..9462690 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -27,6 +27,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.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
@@ -1257,6 +1258,7 @@
           new DexEncodedMethod(
               newMethod,
               accessFlags,
+              MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               code,
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 80c47f2..4f289c7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -106,7 +106,7 @@
         nestMembers,
         enclosingMembers,
         innerClasses,
-        ClassSignature.NO_CLASS_SIGNATURE,
+        ClassSignature.noSignature(),
         DexAnnotationSet.empty(),
         staticFields,
         instanceFields,
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index 7cc0c32..ee17db8 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -4,14 +4,13 @@
 package com.android.tools.r8.synthesis;
 
 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.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import java.util.List;
 
 public class SyntheticMethodBuilder {
 
@@ -24,7 +23,6 @@
   private DexProto proto = null;
   private SyntheticCodeGenerator codeGenerator = null;
   private MethodAccessFlags accessFlags = null;
-  private List<DexAnnotation> annotations = null;
 
   SyntheticMethodBuilder(SyntheticClassBuilder parent, String name) {
     this.parent = parent;
@@ -53,8 +51,9 @@
         new DexEncodedMethod(
             methodSignature,
             getAccessFlags(),
-            getAnnotations(),
-            getParameterAnnotations(),
+            MethodTypeSignature.noSignature(),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
             getCodeObject(methodSignature),
             isCompilerSynthesized);
     assert isValidSyntheticMethod(method);
@@ -83,16 +82,6 @@
     return accessFlags;
   }
 
-  private DexAnnotationSet getAnnotations() {
-    return annotations == null
-        ? DexAnnotationSet.empty()
-        : new DexAnnotationSet(annotations.toArray(DexAnnotation.EMPTY_ARRAY));
-  }
-
-  private ParameterAnnotationsList getParameterAnnotations() {
-    return ParameterAnnotationsList.empty();
-  }
-
   private Code getCodeObject(DexMethod methodSignature) {
     return codeGenerator.generate(methodSignature);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
index c1c7d5a..8d66524 100644
--- a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 public class ConsumerUtils {
@@ -21,6 +22,10 @@
     return ignore -> {};
   }
 
+  public static <S, T> BiConsumer<S, T> emptyBiConsumer() {
+    return (s, t) -> {};
+  }
+
   public static <T> ThrowingConsumer<T, RuntimeException> emptyThrowingConsumer() {
     return ignore -> {};
   }
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 a4cef91..bfd8334 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -339,6 +339,16 @@
   }
 
   /**
+   * Convert class to a binary name.
+   *
+   * @param clazz a java.lang.Class reference
+   * @return class binary name i.e. "java/lang/Object"
+   */
+  public static String getClassBinaryName(Class<?> clazz) {
+    return getBinaryNameFromJavaType(clazz.getTypeName());
+  }
+
+  /**
    * Get package java name from a class descriptor.
    *
    * @param descriptor a class descriptor i.e. "Ljava/lang/Object;"
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 a429fb1..8bc1a55 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -33,13 +33,19 @@
 import com.android.tools.r8.graph.DexItem;
 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.DexType;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.references.Reference;
@@ -1214,6 +1220,9 @@
 
     public BiConsumer<AppInfoWithLiveness, Enqueuer.Mode> enqueuerInspector = null;
 
+    public Function<DexProgramClass, KotlinLambdaGroupIdFactory> kotlinLambdaMergerFactoryForClass =
+        KotlinLambdaGroupIdFactory::getFactoryForClass;
+
     public BiConsumer<ProgramMethod, MethodProcessingId> methodProcessingIdConsumer = null;
 
     public Function<AppView<AppInfoWithLiveness>, RepackagingConfiguration>
@@ -1222,6 +1231,18 @@
                 new DefaultRepackagingConfiguration(
                     appView.dexItemFactory(), appView.options().getProguardConfiguration());
 
+    public BiConsumer<DexItemFactory, HorizontallyMergedClasses> horizontallyMergedClassesConsumer =
+        ConsumerUtils.emptyBiConsumer();
+
+    public BiConsumer<DexItemFactory, HorizontallyMergedLambdaClasses>
+        horizontallyMergedLambdaClassesConsumer = ConsumerUtils.emptyBiConsumer();
+
+    public BiConsumer<DexItemFactory, EnumValueInfoMapCollection> unboxedEnumsConsumer =
+        ConsumerUtils.emptyBiConsumer();
+
+    public BiConsumer<DexItemFactory, VerticallyMergedClasses> verticallyMergedClassesConsumer =
+        ConsumerUtils.emptyBiConsumer();
+
     public Consumer<Deque<SortedProgramMethodSet>> waveModifier = waves -> {};
 
     /**
@@ -1274,6 +1295,7 @@
         System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
     public boolean forceLibBackportsInL8CfToCf = false;
     public boolean enumUnboxingRewriteJavaCGeneratedMethod = false;
+    // TODO(b/154793333): Enable assertions always when resolved.
     public boolean assertConsistentRenamingOfSignature = false;
     public boolean allowStaticInterfaceMethodsForPreNApiLevel = false;
     public int verificationSizeLimitInBytesOverride = -1;
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index af3208f..15a8198 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -49,4 +49,8 @@
     iterable.forEach(result::add);
     return result;
   }
+
+  public static <T> boolean isEmpty(Iterable<T> iterable) {
+    return !iterable.iterator().hasNext();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
index 8edbd69..9ac01a0 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -51,7 +51,6 @@
 
   @Test
   public void invokeCustomWithShrinking() throws Throwable {
-    expectThrowsWithHorizontalClassMerging();
     test("invokecustom-with-shrinking", "invokecustom", "InvokeCustom")
         .withMinApiLevel(AndroidApiLevel.P.getLevel())
         .withBuilderTransformation(builder ->
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 85c6b96..9b63b86 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -173,6 +173,12 @@
     return proguardMap;
   }
 
+  public Path writeProguardMap() throws IOException {
+    Path file = state.getNewTempFolder().resolve("out.zip");
+    writeProguardMap(file);
+    return file;
+  }
+
   public R8TestCompileResult writeProguardMap(Path path) throws IOException {
     FileUtils.writeTextFile(path, getProguardMap());
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d7bebd8..a92fefd 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1648,6 +1648,10 @@
     return path;
   }
 
+  public static DexType toDexType(Class<?> clazz, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createType(descriptor(clazz));
+  }
+
   public static String binaryName(Class<?> clazz) {
     return DescriptorUtils.getBinaryNameFromJavaType(typeName(clazz));
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 3e5ba5f..4149659 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -17,6 +17,10 @@
 import com.android.tools.r8.utils.ForwardingOutputStream;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingOutputStream;
+import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedLambdaClassesInspector;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import com.google.common.base.Suppliers;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -103,6 +107,47 @@
         });
   }
 
+  public T addEnumUnboxingInspector(Consumer<EnumUnboxingInspector> inspector) {
+    return addOptionsModification(
+        options ->
+            options.testing.unboxedEnumsConsumer =
+                ((dexItemFactory, unboxedEnums) ->
+                    inspector.accept(new EnumUnboxingInspector(dexItemFactory, unboxedEnums))));
+  }
+
+  public T addHorizontallyMergedClassesInspector(
+      Consumer<HorizontallyMergedClassesInspector> inspector) {
+    return addOptionsModification(
+        options ->
+            options.testing.horizontallyMergedClassesConsumer =
+                ((dexItemFactory, horizontallyMergedClasses) ->
+                    inspector.accept(
+                        new HorizontallyMergedClassesInspector(
+                            dexItemFactory, horizontallyMergedClasses))));
+  }
+
+  public T addHorizontallyMergedLambdaClassesInspector(
+      Consumer<HorizontallyMergedLambdaClassesInspector> inspector) {
+    return addOptionsModification(
+        options ->
+            options.testing.horizontallyMergedLambdaClassesConsumer =
+                ((dexItemFactory, horizontallyMergedLambdaClasses) ->
+                    inspector.accept(
+                        new HorizontallyMergedLambdaClassesInspector(
+                            dexItemFactory, horizontallyMergedLambdaClasses))));
+  }
+
+  public T addVerticallyMergedClassesInspector(
+      Consumer<VerticallyMergedClassesInspector> inspector) {
+    return addOptionsModification(
+        options ->
+            options.testing.verticallyMergedClassesConsumer =
+                ((dexItemFactory, verticallyMergedClasses) ->
+                    inspector.accept(
+                        new VerticallyMergedClassesInspector(
+                            dexItemFactory, verticallyMergedClasses))));
+  }
+
   public CR compile() throws CompilationFailedException {
     AndroidAppConsumers sink = new AndroidAppConsumers();
     builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9740354..7e1a627 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -159,7 +159,9 @@
   private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard";
   private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
   private static final String PROGUARD = PROGUARD5_2_1;
-  public static final String JACOCO_AGENT = "third_party/jacoco/org.jacoco.agent-0.8.2-runtime.jar";
+  public static final String JACOCO_AGENT =
+      "third_party/jacoco/0.8.2/org.jacoco.agent-0.8.2-runtime.jar";
+  public static final String JACOCO_CLI = "third_party/jacoco/0.8.6/lib/jacococli.jar";
   public static final String PROGUARD_SETTINGS_FOR_INTERNAL_APPS = "third_party/proguardsettings/";
 
   private static final String RETRACE6_0_1 = "third_party/proguard/proguard6.0.1/bin/retrace";
@@ -264,6 +266,10 @@
         return isOlderThanOrEqual(Version.V4_4_4);
       }
 
+      public boolean isDefault() {
+        return this == DEFAULT;
+      }
+
       public boolean isLatest() {
         return this == V10_0_0;
       }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 09bf5e1..76ebd58 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -64,7 +64,6 @@
    */
   @Test
   public void test_bridgeTargetInBase_differentBridges() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     JasminBuilder jasminBuilder = new JasminBuilder();
 
     ClassBuilder absCls = jasminBuilder.addClass("AbsCls");
@@ -186,7 +185,6 @@
    */
   @Test
   public void test_bridgeTargetInBase_bridgeAndNonBridge() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     JasminBuilder jasminBuilder = new JasminBuilder();
 
     ClassBuilder baseCls = jasminBuilder.addClass("Base");
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java b/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java
index 6eb081f..f19d1c1 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java
@@ -4,10 +4,11 @@
 
 package com.android.tools.r8.checkdiscarded;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
+import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.AssumeNoSideEffects;
@@ -54,10 +55,17 @@
               .enableAssumeNoSideEffectsAnnotations()
               .enableInliningAnnotations()
               .setMinApi(parameters.getApiLevel())
-              .compile();
+              .compileWithExpectedDiagnostics(
+                  diagnostics -> {
+                    if (checkLogIsDiscarded) {
+                      diagnostics.assertErrorsMatch(
+                          diagnosticMessage(containsString("Discard checks failed.")));
+                    } else {
+                      diagnostics.assertNoErrors();
+                    }
+                  });
     } catch (CompilationFailedException e) {
       assertTrue(checkLogIsDiscarded);
-      assertEquals(e.getCause().getMessage(), "Discard checks failed.");
       return;
     }
 
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
new file mode 100644
index 0000000..bac9468
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2017, 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.checkdiscarded;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsLevel;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.checkdiscarded.testclasses.Main;
+import com.android.tools.r8.checkdiscarded.testclasses.UnusedClass;
+import com.android.tools.r8.checkdiscarded.testclasses.UsedClass;
+import com.android.tools.r8.errors.CheckDiscardDiagnostic;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.hamcrest.Matcher;
+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 CheckDiscardModifyDiagnosticsLevelTest extends TestBase {
+
+  @Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withNoneRuntime().build(), DiagnosticsLevel.values());
+  }
+
+  private final DiagnosticsLevel mappedLevel;
+
+  public CheckDiscardModifyDiagnosticsLevelTest(TestParameters parameters, DiagnosticsLevel level) {
+    parameters.assertNoneRuntime();
+    this.mappedLevel = level;
+  }
+
+  private void noInlining(InternalOptions options) {
+    options.enableInlining = false;
+  }
+
+  private Matcher<Diagnostic> discardCheckFailedMatcher() {
+    return diagnosticMessage(
+        allOf(
+            startsWith("Discard checks failed"),
+            containsString("UsedClass was not discarded"),
+            containsString("is instantiated in")));
+  }
+
+  private Collection<Matcher<Diagnostic>> errorMatchers() {
+    return mappedLevel == DiagnosticsLevel.ERROR
+        ? ImmutableList.of(discardCheckFailedMatcher())
+        : ImmutableList.of();
+  }
+
+  private Collection<Matcher<Diagnostic>> warningMatchers() {
+    return mappedLevel == DiagnosticsLevel.WARNING
+        ? ImmutableList.of(discardCheckFailedMatcher())
+        : ImmutableList.of();
+  }
+
+  private Collection<Matcher<Diagnostic>> infoMatchers() {
+    return mappedLevel == DiagnosticsLevel.INFO
+        ? ImmutableList.of(discardCheckFailedMatcher())
+        : ImmutableList.of();
+  }
+
+  @Test
+  public void dontFailCompilationOnCheckDiscardedFailure() {
+    try {
+      testForR8(Backend.DEX)
+          .addProgramClasses(UnusedClass.class, UsedClass.class, Main.class)
+          .addKeepMainRule(Main.class)
+          .addKeepRules("-checkdiscard class " + UsedClass.class.getTypeName())
+          .addOptionsModification(this::noInlining)
+          .setDiagnosticsLevelModifier(
+              (level, diagnostic) -> {
+                if (diagnostic instanceof CheckDiscardDiagnostic) {
+                  return mappedLevel;
+                } else {
+                  return level;
+                }
+              })
+          .allowDiagnosticMessages()
+          .compileWithExpectedDiagnostics(
+              diagnostics ->
+                  diagnostics
+                      .assertErrorsMatch(errorMatchers())
+                      .assertWarningsMatch(warningMatchers())
+                      .assertInfosMatch(infoMatchers()));
+      assertTrue(mappedLevel == DiagnosticsLevel.INFO || mappedLevel == DiagnosticsLevel.WARNING);
+    } catch (CompilationFailedException e) {
+      assertEquals(mappedLevel, DiagnosticsLevel.ERROR);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index df38cb2..61f8917 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.checkdiscarded.testclasses.WillStay;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.Consumer;
 import org.junit.Test;
@@ -90,16 +89,16 @@
     Consumer<TestDiagnosticMessages> check =
         diagnostics ->
             diagnostics
+                .assertNoInfos()
                 .assertNoWarnings()
-                .assertErrorsMatch(diagnosticMessage(containsString("Discard checks failed")))
-                .assertInfosMatch(
-                    ImmutableList.of(
+                .assertErrorsMatch(
+                    diagnosticMessage(
                         allOf(
-                            diagnosticMessage(containsString("UsedClass was not discarded")),
-                            diagnosticMessage(containsString("is instantiated in"))),
-                        allOf(
-                            diagnosticMessage(containsString("Main was not discarded")),
-                            diagnosticMessage(containsString("is referenced in keep rule")))));
+                            containsString("Discard checks failed"),
+                            containsString("UsedClass was not discarded"),
+                            containsString("is instantiated in"),
+                            containsString("Main was not discarded"),
+                            containsString("is referenced in keep rule"))));
     compile(WillStay.class, false, check);
   }
 
@@ -113,12 +112,14 @@
     Consumer<TestDiagnosticMessages> check =
         diagnostics ->
             diagnostics
+                .assertNoInfos()
                 .assertNoWarnings()
-                .assertErrorsMatch(diagnosticMessage(containsString("Discard checks failed")))
-                .assertInfosMatch(
-                    allOf(
-                        diagnosticMessage(containsString("was not discarded")),
-                        diagnosticMessage(containsString("is invoked from"))));
+                .assertErrorsMatch(
+                    diagnosticMessage(
+                        allOf(
+                            containsString("Discard checks failed"),
+                            containsString("was not discarded"),
+                            containsString("is invoked from"))));
     compile(WillStay.class, true, check);
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
new file mode 100644
index 0000000..9ccc2d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+
+public class CompatKeepConstructorLiveTest extends HorizontalClassMergingTestBase {
+  public CompatKeepConstructorLiveTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("b: main", "true")
+        .inspect(
+            codeInspector -> {
+              ClassSubject aClassSubject = codeInspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              assertThat(aClassSubject.init(), isPresent());
+
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+            });
+  }
+
+  @NeverClassInline
+  public static class A {}
+
+  @NeverClassInline
+  public static class B {
+    public B(String v) {
+      System.out.println("b: " + v);
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) throws Exception {
+      new B("main");
+      System.out.println(A.class.toString().length() > 0);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
new file mode 100644
index 0000000..c751735
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class DistinguishExceptionClassesTest extends HorizontalClassMergingTestBase {
+  public DistinguishExceptionClassesTest(
+      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)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("test success")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(Exception1.class), isPresent());
+              assertThat(codeInspector.clazz(Exception2.class), isPresent());
+            });
+  }
+
+  public static class Exception1 extends Exception {}
+
+  public static class Exception2 extends Exception {}
+
+  public static class Main {
+    public static void main(String[] args) {
+      try {
+        try {
+          if (System.currentTimeMillis() > 0) {
+            throw new Exception2();
+          } else {
+            throw new Exception1();
+          }
+        } catch (Exception1 ex) {
+          System.out.println("test failed");
+        }
+      } catch (Exception2 ex) {
+        System.out.println("test success");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergePackagePrivateWithPublicClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergePackagePrivateWithPublicClassTest.java
new file mode 100644
index 0000000..a1c8faa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergePackagePrivateWithPublicClassTest.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.testclasses.PackagePrivateClassRunner;
+import org.junit.Test;
+
+public class MergePackagePrivateWithPublicClassTest extends HorizontalClassMergingTestBase {
+
+  public MergePackagePrivateWithPublicClassTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramClasses(
+            PackagePrivateClassRunner.class, PackagePrivateClassRunner.getPrivateClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("package private")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(PackagePrivateClassRunner.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(PackagePrivateClassRunner.getPrivateClass()),
+                  notIf(isPresent(), enableHorizontalClassMerging));
+            });
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new PackagePrivateClassRunner().run();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
new file mode 100644
index 0000000..91dc3b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
@@ -0,0 +1,69 @@
+// 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 com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.testclasses.NonReboundFieldAccessOnMergedClassTestClasses;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NonReboundFieldAccessOnMergedClassTest extends HorizontalClassMergingTestBase {
+
+  public NonReboundFieldAccessOnMergedClassTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void test() throws Exception {
+    if (enableHorizontalClassMerging) {
+      thrown.expect(Throwable.class);
+    }
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addInnerClasses(NonReboundFieldAccessOnMergedClassTestClasses.class)
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .addHorizontallyMergedLambdaClassesInspector(
+            inspector -> {
+              if (enableHorizontalClassMerging) {
+                inspector.assertMerged(C.class, D.class);
+              }
+            })
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.print(new C("Hello").greeting);
+      System.out.println(new D(" world!").greeting);
+    }
+  }
+
+  @NeverClassInline
+  static class C extends NonReboundFieldAccessOnMergedClassTestClasses.B {
+
+    public C(String greeting) {
+      super(greeting);
+    }
+  }
+
+  @NeverClassInline
+  static class D extends NonReboundFieldAccessOnMergedClassTestClasses.B {
+
+    public D(String greeting) {
+      super(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
new file mode 100644
index 0000000..3581801
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
@@ -0,0 +1,81 @@
+// 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 com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.NonReboundFieldAccessOnMergedClassTest.C;
+import com.android.tools.r8.classmerging.horizontal.testclasses.NonReboundFieldAccessWithMergedTypeTestClasses;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NonReboundFieldAccessWithMergedTypeTest extends HorizontalClassMergingTestBase {
+
+  public NonReboundFieldAccessWithMergedTypeTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void test() throws Exception {
+    if (enableHorizontalClassMerging) {
+      thrown.expect(Throwable.class);
+    }
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addInnerClasses(NonReboundFieldAccessWithMergedTypeTestClasses.class)
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> {
+              if (enableHorizontalClassMerging) {
+                inspector.assertMerged(HelloGreeting.class, WorldGreeting.class);
+                inspector.assertMergedIntoDifferentType(WorldGreeting.class);
+              }
+            })
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.print(new HelloGreeting());
+      System.out.println(new C(new WorldGreeting()).greeting);
+    }
+  }
+
+  @NeverClassInline
+  static class C extends NonReboundFieldAccessWithMergedTypeTestClasses.B {
+
+    public C(WorldGreeting greeting) {
+      super(greeting);
+    }
+  }
+
+  public static class HelloGreeting {
+
+    @Override
+    public String toString() {
+      return "Hello";
+    }
+  }
+
+  public static class WorldGreeting {
+
+    @Override
+    public String toString() {
+      return " world!";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberReferenceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberReferenceTest.java
new file mode 100644
index 0000000..4a22fe3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberReferenceTest.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+
+public class PinnedClassMemberReferenceTest extends HorizontalClassMergingTestBase {
+  public PinnedClassMemberReferenceTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  private R8FullTestBuilder testCommon() throws Exception {
+    return testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .noMinification()
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel());
+  }
+
+  private R8TestRunResult runAndAssertOutput(R8FullTestBuilder builder) throws Exception {
+    return builder
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "a", "b", "foo a: bar", "foo b: baz", "fields a: bar", "fields b: baz");
+  }
+
+  @Test
+  public void testWithoutKeepRules() throws Exception {
+    // This is just a small check ensure that without the keep rules the classes are merged.
+    assumeTrue(enableHorizontalClassMerging);
+    assumeTrue(parameters.isCfRuntime());
+
+    runAndAssertOutput(testCommon())
+        .inspect(
+            codeInspector -> {
+              ClassSubject aClassSubject = codeInspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              assertThat(codeInspector.clazz(B.class), not(isPresent()));
+
+              ClassSubject cClassSubject = codeInspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+              assertThat(cClassSubject.field(aClassSubject.getFinalName(), "a"), isPresent());
+              assertThat(cClassSubject.field(aClassSubject.getFinalName(), "b"), isPresent());
+
+              assertThat(
+                  cClassSubject.method("void", "foo", aClassSubject.getFinalName()), isPresent());
+            });
+  }
+
+  @Test
+  public void testWithKeepRules() throws Exception {
+    runAndAssertOutput(
+            testCommon()
+                .addKeepRules(
+                    "-keepclassmembers class " + C.class.getTypeName() + " { ",
+                    "  " + A.class.getTypeName() + " a;",
+                    "  " + C.class.getTypeName() + " c;",
+                    "  void foo(" + A.class.getTypeName() + ");",
+                    "  void foo(" + B.class.getTypeName() + ");",
+                    "}"))
+        .inspect(
+            codeInspector -> {
+              ClassSubject aClassSubject = codeInspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              ClassSubject bClassSubject = codeInspector.clazz(B.class);
+              assertThat(bClassSubject, isPresent());
+
+              ClassSubject cClassSubject = codeInspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+              assertThat(cClassSubject.field(aClassSubject.getFinalName(), "a"), isPresent());
+              assertThat(cClassSubject.field(bClassSubject.getFinalName(), "b"), isPresent());
+
+              assertThat(
+                  cClassSubject.method("void", "foo", aClassSubject.getFinalName()), isPresent());
+              assertThat(
+                  cClassSubject.method("void", "foo", bClassSubject.getFinalName()), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("a");
+    }
+
+    @NeverInline
+    public String bar() {
+      return "bar";
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      System.out.println("b");
+    }
+
+    @NeverInline
+    public String baz() {
+      return "baz";
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+    A a;
+    B b;
+
+    public C(A a, B b) {
+      this.a = a;
+      this.b = b;
+    }
+
+    @NeverInline
+    public void foo(A a2) {
+      System.out.println("foo a: " + a2.bar());
+    }
+
+    @NeverInline
+    public void foo(B b) {
+      System.out.println("foo b: " + b.baz());
+    }
+
+    @NeverInline
+    public void fields() {
+      System.out.println("fields a: " + a.bar());
+      System.out.println("fields b: " + b.baz());
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      C c = new C(a, b);
+      c.foo(a);
+      c.foo(b);
+      c.fields();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticalMergingPreoptimizedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticalMergingPreoptimizedTest.java
new file mode 100644
index 0000000..7731d5e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticalMergingPreoptimizedTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class VerticalMergingPreoptimizedTest extends HorizontalClassMergingTestBase {
+
+  public VerticalMergingPreoptimizedTest(
+      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()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "changed", "print a", "foo", "print b", "foo", "unused argument")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(Parent.class), not(isPresent()));
+              assertThat(codeInspector.clazz(Changed.class), isPresent());
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+            });
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class Parent {
+    @NeverInline
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class Changed extends Parent {
+    public Changed() {
+      System.out.println("changed");
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public void print(Parent p) {
+      System.out.println("print a");
+      p.foo();
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    @NeverInline
+    public void print(Parent p) {
+      System.out.println("print b");
+      p.foo();
+    }
+
+    @NeverInline
+    public void unusedArgument(Parent p) {
+      System.out.println("unused argument");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      Parent p = new Changed();
+      A a = new A();
+      B b = new B();
+      a.print(p);
+      b.print(p);
+      b.unusedArgument(p);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java
new file mode 100644
index 0000000..f9eff69
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+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 com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class VirtualMethodMergingOfFinalAndNonFinalMethodTest
+    extends HorizontalClassMergingTestBase {
+
+  public VirtualMethodMergingOfFinalAndNonFinalMethodTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(
+                  inspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(inspector.clazz(C.class), isPresent());
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("A.foo()", "B.foo()", "C.foo()");
+  }
+
+  @NeverClassInline
+  public static class A {
+
+    @NeverInline
+    public final void foo() {
+      System.out.println("A.foo()");
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class B {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("B.foo()");
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends B {
+
+    @NeverInline
+    @Override
+    public void foo() {
+      System.out.println("C.foo()");
+    }
+  }
+
+  public static class TestClass {
+    public static void main(String[] args) {
+      new A().foo();
+      new B().foo();
+      new C().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java
new file mode 100644
index 0000000..5df1264
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java
@@ -0,0 +1,180 @@
+// 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 com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VirtualMethodMergingOfPublicizedMethodsTest extends HorizontalClassMergingTestBase {
+
+  public VirtualMethodMergingOfPublicizedMethodsTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .allowAccessModification()
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(
+            "A.privateAndPrivate()",
+            "B.privateAndPrivate()",
+            "A.privateAndPackagePrivate()",
+            "B.privateAndPackagePrivate()",
+            "A.privateAndPublic()",
+            "B.privateAndPublic()",
+            "A.packagePrivateAndPrivate()",
+            "B.packagePrivateAndPrivate()",
+            "A.packagePrivateAndPackagePrivate()",
+            "B.packagePrivateAndPackagePrivate()",
+            "A.packagePrivateAndPublic()",
+            "B.packagePrivateAndPublic()",
+            "A.publicAndPrivate()",
+            "B.publicAndPrivate()",
+            "A.publicAndPackagePrivate()",
+            "B.publicAndPackagePrivate()",
+            "A.publicAndPublic()",
+            "B.publicAndPublic()");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      a.privateAndPrivate();
+      b.privateAndPrivate();
+      a.privateAndPackagePrivate();
+      b.privateAndPackagePrivate();
+      a.privateAndPublic();
+      b.privateAndPublic();
+      a.packagePrivateAndPrivate();
+      b.packagePrivateAndPrivate();
+      a.packagePrivateAndPackagePrivate();
+      b.packagePrivateAndPackagePrivate();
+      a.packagePrivateAndPublic();
+      b.packagePrivateAndPublic();
+      a.publicAndPrivate();
+      b.publicAndPrivate();
+      a.publicAndPackagePrivate();
+      b.publicAndPackagePrivate();
+      a.publicAndPublic();
+      b.publicAndPublic();
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    @NeverInline
+    private void privateAndPrivate() {
+      System.out.println("A.privateAndPrivate()");
+    }
+
+    @NeverInline
+    private void privateAndPackagePrivate() {
+      System.out.println("A.privateAndPackagePrivate()");
+    }
+
+    @NeverInline
+    private void privateAndPublic() {
+      System.out.println("A.privateAndPublic()");
+    }
+
+    @NeverInline
+    void packagePrivateAndPrivate() {
+      System.out.println("A.packagePrivateAndPrivate()");
+    }
+
+    @NeverInline
+    void packagePrivateAndPackagePrivate() {
+      System.out.println("A.packagePrivateAndPackagePrivate()");
+    }
+
+    @NeverInline
+    void packagePrivateAndPublic() {
+      System.out.println("A.packagePrivateAndPublic()");
+    }
+
+    @NeverInline
+    public void publicAndPrivate() {
+      System.out.println("A.publicAndPrivate()");
+    }
+
+    @NeverInline
+    public void publicAndPackagePrivate() {
+      System.out.println("A.publicAndPackagePrivate()");
+    }
+
+    @NeverInline
+    public void publicAndPublic() {
+      System.out.println("A.publicAndPublic()");
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    @NeverInline
+    private void privateAndPrivate() {
+      System.out.println("B.privateAndPrivate()");
+    }
+
+    @NeverInline
+    void privateAndPackagePrivate() {
+      System.out.println("B.privateAndPackagePrivate()");
+    }
+
+    @NeverInline
+    public void privateAndPublic() {
+      System.out.println("B.privateAndPublic()");
+    }
+
+    @NeverInline
+    private void packagePrivateAndPrivate() {
+      System.out.println("B.packagePrivateAndPrivate()");
+    }
+
+    @NeverInline
+    void packagePrivateAndPackagePrivate() {
+      System.out.println("B.packagePrivateAndPackagePrivate()");
+    }
+
+    @NeverInline
+    public void packagePrivateAndPublic() {
+      System.out.println("B.packagePrivateAndPublic()");
+    }
+
+    @NeverInline
+    private void publicAndPrivate() {
+      System.out.println("B.publicAndPrivate()");
+    }
+
+    @NeverInline
+    void publicAndPackagePrivate() {
+      System.out.println("B.publicAndPackagePrivate()");
+    }
+
+    @NeverInline
+    public void publicAndPublic() {
+      System.out.println("B.publicAndPublic()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/NonReboundFieldAccessOnMergedClassTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/NonReboundFieldAccessOnMergedClassTestClasses.java
new file mode 100644
index 0000000..0aaf197
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/NonReboundFieldAccessOnMergedClassTestClasses.java
@@ -0,0 +1,28 @@
+// 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.testclasses;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+
+public class NonReboundFieldAccessOnMergedClassTestClasses {
+
+  @NoVerticalClassMerging
+  static class A {
+
+    public String greeting;
+
+    A(String greeting) {
+      this.greeting = greeting;
+    }
+  }
+
+  @NoVerticalClassMerging
+  public static class B extends A {
+
+    public B(String greeting) {
+      super(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/NonReboundFieldAccessWithMergedTypeTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/NonReboundFieldAccessWithMergedTypeTestClasses.java
new file mode 100644
index 0000000..ce3354e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/NonReboundFieldAccessWithMergedTypeTestClasses.java
@@ -0,0 +1,31 @@
+// 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.testclasses;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.classmerging.horizontal.NonReboundFieldAccessWithMergedTypeTest.WorldGreeting;
+
+public class NonReboundFieldAccessWithMergedTypeTestClasses {
+
+  @NoHorizontalClassMerging
+  @NoVerticalClassMerging
+  static class A {
+
+    public WorldGreeting greeting;
+
+    A(WorldGreeting greeting) {
+      this.greeting = greeting;
+    }
+  }
+
+  @NoVerticalClassMerging
+  public static class B extends A {
+
+    public B(WorldGreeting greeting) {
+      super(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/PackagePrivateClass.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/PackagePrivateClass.java
new file mode 100644
index 0000000..7deccb2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/PackagePrivateClass.java
@@ -0,0 +1,14 @@
+// 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.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+
+@NeverClassInline
+class PackagePrivateClass {
+  public PackagePrivateClass() {
+    System.out.println("package private");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/PackagePrivateClassRunner.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/PackagePrivateClassRunner.java
new file mode 100644
index 0000000..9c10bcc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/PackagePrivateClassRunner.java
@@ -0,0 +1,23 @@
+// 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.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
+public class PackagePrivateClassRunner {
+
+  public PackagePrivateClassRunner() {}
+
+  @NeverInline
+  public void run() {
+    new PackagePrivateClass();
+  }
+
+  public static Class<?> getPrivateClass() {
+    return PackagePrivateClass.class;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
new file mode 100644
index 0000000..6ed6dd9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ClassesHaveBeenMergedTest.java
@@ -0,0 +1,193 @@
+// 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.vertical;
+
+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.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+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 ClassesHaveBeenMergedTest extends VerticalClassMergerTestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassesHaveBeenMergedTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test
+  public void testClassesHaveBeenMerged() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassesHaveBeenMergedTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+  }
+
+  private void inspectVerticallyMergedClasses(VerticallyMergedClassesInspector inspector) {
+    inspector.assertMergedIntoSubtype(
+        GenericInterface.class,
+        GenericAbstractClass.class,
+        Outer.SuperClass.class,
+        SuperClass.class);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(GenericInterfaceImpl.class), isPresent());
+    assertThat(inspector.clazz(Outer.SubClass.class), isPresent());
+    assertThat(inspector.clazz(SubClass.class), isPresent());
+    assertThat(inspector.clazz(GenericInterface.class), not(isPresent()));
+    assertThat(inspector.clazz(GenericAbstractClass.class), not(isPresent()));
+    assertThat(inspector.clazz(Outer.SuperClass.class), not(isPresent()));
+    assertThat(inspector.clazz(SuperClass.class), not(isPresent()));
+  }
+
+  public static class TestClass {
+
+    public static void main(String... args) {
+      GenericInterface<?> iface = new GenericInterfaceImpl();
+      callMethodOnIface(iface);
+      GenericAbstractClass<?> clazz = new GenericAbstractClassImpl();
+      callMethodOnAbstractClass(clazz);
+      Outer outer = new Outer();
+      Outer.SubClass inner = outer.getInstance();
+      System.out.println(outer.getInstance().method());
+      System.out.println(new SubClass(42));
+
+      // Ensure that the instantiations are not dead code eliminated.
+      escape(clazz);
+      escape(iface);
+      escape(inner);
+      escape(outer);
+    }
+
+    private static void callMethodOnIface(GenericInterface<?> iface) {
+      System.out.println(iface.method());
+    }
+
+    private static void callMethodOnAbstractClass(GenericAbstractClass<?> clazz) {
+      System.out.println(clazz.method());
+      System.out.println(clazz.otherMethod());
+    }
+
+    @NeverInline
+    static void escape(Object o) {
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(o);
+      }
+    }
+  }
+
+  public abstract static class GenericAbstractClass<T> {
+
+    public abstract T method();
+
+    public T otherMethod() {
+      return null;
+    }
+  }
+
+  public static class GenericAbstractClassImpl extends GenericAbstractClass<String> {
+
+    @Override
+    public String method() {
+      return "Hello from GenericAbstractClassImpl";
+    }
+
+    @Override
+    public String otherMethod() {
+      return "otherMethod";
+    }
+  }
+
+  public interface GenericInterface<T> {
+
+    T method();
+  }
+
+  @NoHorizontalClassMerging
+  public static class GenericInterfaceImpl implements GenericInterface<String> {
+
+    @Override
+    public String method() {
+      return "method";
+    }
+  }
+
+  public static class SuperClass {
+
+    private final int field;
+
+    public SuperClass(int field) {
+      this.field = field;
+    }
+
+    public int getField() {
+      return field;
+    }
+  }
+
+  public static class SubClass extends SuperClass {
+
+    private int field;
+
+    public SubClass(int field) {
+      this(field, field + 100);
+    }
+
+    public SubClass(int one, int other) {
+      super(one);
+      field = other;
+    }
+
+    public String toString() {
+      return "is " + field + " " + getField();
+    }
+  }
+
+  static class Outer {
+
+    /**
+     * This class is package private to trigger the generation of bridge methods for the visibility
+     * change of methods from public subtypes.
+     */
+    static class SuperClass {
+
+      public String method() {
+        return "Method in SuperClass.";
+      }
+    }
+
+    @NoHorizontalClassMerging
+    public static class SubClass extends SuperClass {
+      // Intentionally left empty.
+    }
+
+    public SubClass getInstance() {
+      return new SubClass();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java
new file mode 100644
index 0000000..ffe849e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ExceptionTablesTest.java
@@ -0,0 +1,124 @@
+// 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.vertical;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+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.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+import java.util.stream.IntStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// If an exception class A is merged into another exception class B, then all exception tables
+// should be updated, and class A should be removed entirely.
+@RunWith(Parameterized.class)
+public class ExceptionTablesTest extends VerticalClassMergerTestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ExceptionTablesTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test
+  public void testClassesHaveBeenMerged() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ExceptionTablesTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+  }
+
+  private void inspectVerticallyMergedClasses(VerticallyMergedClassesInspector inspector) {
+    inspector.assertMergedIntoSubtype(ExceptionA.class, Exception1.class);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(TestClass.class), isPresent());
+    assertThat(inspector.clazz(ExceptionB.class), isPresent());
+    assertThat(inspector.clazz(Exception2.class), isPresent());
+    assertThat(inspector.clazz(ExceptionA.class), not(isPresent()));
+    assertThat(inspector.clazz(Exception1.class), not(isPresent()));
+
+    // Check that the second catch handler has been removed.
+    MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertEquals(
+        2,
+        mainMethodSubject
+            .streamTryCatches()
+            .flatMapToInt(x -> IntStream.of(x.getNumberOfHandlers()))
+            .sum());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      // The following will lead to a catch handler for ExceptionA, which is merged into ExceptionB.
+      try {
+        doSomethingThatMightThrowExceptionB();
+        doSomethingThatMightThrowException2();
+      } catch (ExceptionB exception) {
+        System.out.println("Caught exception: " + exception.getMessage());
+      } catch (ExceptionA exception) {
+        System.out.println("Caught exception: " + exception.getMessage());
+      } catch (Exception2 exception) {
+        System.out.println("Caught exception: " + exception.getMessage());
+      } catch (Exception1 exception) {
+        System.out.println("Caught exception: " + exception.getMessage());
+      }
+    }
+
+    private static void doSomethingThatMightThrowExceptionB() throws ExceptionB {
+      throw new ExceptionB("Ouch!");
+    }
+
+    private static void doSomethingThatMightThrowException2() throws Exception2 {
+      throw new Exception2("Ouch!");
+    }
+  }
+
+  // Will be merged into ExceptionB when class merging is enabled.
+  public static class ExceptionA extends Exception {
+    public ExceptionA(String message) {
+      super(message);
+    }
+  }
+
+  public static class ExceptionB extends ExceptionA {
+    public ExceptionB(String message) {
+      super(message);
+    }
+  }
+
+  public static class Exception1 extends Exception {
+    public Exception1(String message) {
+      super(message);
+    }
+  }
+
+  public static class Exception2 extends Exception1 {
+    public Exception2(String message) {
+      super(message);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/NoIllegalClassAccessWithAccessModificationsTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NoIllegalClassAccessWithAccessModificationsTest.java
new file mode 100644
index 0000000..35e27eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NoIllegalClassAccessWithAccessModificationsTest.java
@@ -0,0 +1,113 @@
+// 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.vertical;
+
+import static com.android.tools.r8.classmerging.vertical.testclasses.NoIllegalClassAccessWithAccessModificationsTestClasses.getSimpleInterfaceImplClass;
+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.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.classmerging.vertical.testclasses.NoIllegalClassAccessWithAccessModificationsTestClasses;
+import com.android.tools.r8.classmerging.vertical.testclasses.NoIllegalClassAccessWithAccessModificationsTestClasses.SimpleInterfaceFactory;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+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 NoIllegalClassAccessWithAccessModificationsTest extends VerticalClassMergerTestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NoIllegalClassAccessWithAccessModificationsTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(NoIllegalClassAccessWithAccessModificationsTest.class)
+        .addInnerClasses(NoIllegalClassAccessWithAccessModificationsTestClasses.class)
+        .addKeepMainRule(TestClass.class)
+        .addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
+        .allowAccessModification()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+  }
+
+  private void inspectVerticallyMergedClasses(VerticallyMergedClassesInspector inspector) {
+    inspector.assertMergedIntoSubtype(SimpleInterface.class, OtherSimpleInterface.class);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(TestClass.class), isPresent());
+    assertThat(inspector.clazz(getSimpleInterfaceImplClass()), isPresent());
+    assertThat(inspector.clazz(OtherSimpleInterfaceImpl.class), isPresent());
+    assertThat(inspector.clazz(SimpleInterface.class), not(isPresent()));
+    assertThat(inspector.clazz(OtherSimpleInterface.class), not(isPresent()));
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      // Without access modifications, it is not possible to merge the interface SimpleInterface
+      // into
+      // SimpleInterfaceImpl, since this would lead to an illegal class access here.
+      SimpleInterface x = SimpleInterfaceFactory.create();
+      x.foo();
+
+      // Without access modifications, it is not possible to merge the interface
+      // OtherSimpleInterface
+      // into OtherSimpleInterfaceImpl, since this could lead to an illegal class access if another
+      // package references OtherSimpleInterface.
+      OtherSimpleInterface y = new OtherSimpleInterfaceImpl();
+      y.bar();
+
+      // Ensure that the instantiations are not dead code eliminated.
+      escape(x);
+      escape(y);
+    }
+
+    @NeverInline
+    static void escape(Object o) {
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(o);
+      }
+    }
+  }
+
+  // Should only be merged into OtherSimpleInterfaceImpl if access modifications are allowed.
+  public interface SimpleInterface {
+
+    void foo();
+  }
+
+  // Should only be merged into OtherSimpleInterfaceImpl if access modifications are allowed.
+  public interface OtherSimpleInterface {
+
+    void bar();
+  }
+
+  private static class OtherSimpleInterfaceImpl implements OtherSimpleInterface {
+
+    @Override
+    public void bar() {
+      System.out.println("In bar on OtherSimpleInterfaceImpl");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
new file mode 100644
index 0000000..ecf49fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
@@ -0,0 +1,70 @@
+// 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.vertical;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.classmerging.vertical.NonReboundFieldAccessWithMergedTypeTest.GreetingBase;
+import com.android.tools.r8.classmerging.vertical.testclasses.NonReboundFieldAccessOnMergedClassTestClasses;
+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 NonReboundFieldAccessOnMergedClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NonReboundFieldAccessOnMergedClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    thrown.expect(Throwable.class);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addInnerClasses(NonReboundFieldAccessOnMergedClassTestClasses.class)
+        .addKeepMainRule(Main.class)
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(GreetingBase.class))
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      C c = new D("Hello world!");
+      System.out.println(c.greeting);
+    }
+  }
+
+  static class C extends NonReboundFieldAccessOnMergedClassTestClasses.B {
+
+    public C(String greeting) {
+      super(greeting);
+    }
+  }
+
+  @NeverClassInline
+  static class D extends C {
+
+    public D(String greeting) {
+      super(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java
new file mode 100644
index 0000000..5a3f831
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java
@@ -0,0 +1,72 @@
+// 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.vertical;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.classmerging.vertical.testclasses.NonReboundFieldAccessWithMergedTypeTestClasses;
+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 NonReboundFieldAccessWithMergedTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NonReboundFieldAccessWithMergedTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    thrown.expect(Throwable.class);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addInnerClasses(NonReboundFieldAccessWithMergedTypeTestClasses.class)
+        .addKeepMainRule(Main.class)
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(GreetingBase.class))
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new C(new Greeting()).greeting);
+    }
+  }
+
+  @NeverClassInline
+  static class C extends NonReboundFieldAccessWithMergedTypeTestClasses.B {
+
+    public C(GreetingBase greeting) {
+      super(greeting);
+    }
+  }
+
+  public static class GreetingBase {}
+
+  public static class Greeting extends GreetingBase {
+
+    @Override
+    public String toString() {
+      return "Hello world!";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/SyntheticBridgeSignaturesTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/SyntheticBridgeSignaturesTest.java
new file mode 100644
index 0000000..829e56b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/SyntheticBridgeSignaturesTest.java
@@ -0,0 +1,133 @@
+// 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.vertical;
+
+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.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+import com.google.common.collect.ImmutableSet;
+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 SyntheticBridgeSignaturesTest extends VerticalClassMergerTestBase {
+
+  // Try both with and without inlining. If the bridge signatures are not updated properly, and
+  // inlining is enabled, then there can be issues with our inlining invariants regarding the
+  // outermost caller. If inlining is disabled, there is a risk that the methods will end up
+  // having the wrong signatures, or that the generated Proguard maps are incorrect (this will be
+  // caught by the debugging test, which is carried out by the call to runTestOnInput()).
+  private final boolean allowInlining;
+
+  @Parameters(name = "{1}, inlining: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public SyntheticBridgeSignaturesTest(boolean allowInlining, TestParameters parameters) {
+    super(parameters);
+    this.allowInlining = allowInlining;
+  }
+
+  @Test
+  public void test() throws Throwable {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addKeepMainRule(TestClass.class)
+            .addOptionsModification(
+                options -> {
+                  if (!allowInlining) {
+                    options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+                  }
+                })
+            .addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
+            .enableInliningAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    compileResult
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccess();
+
+    if (parameters.isDexRuntime()) {
+      runDebugTest(TestClass.class, compileResult);
+    }
+  }
+
+  private void inspectVerticallyMergedClasses(VerticallyMergedClassesInspector inspector) {
+    inspector.assertMergedIntoSubtype(A.class, B.class);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(TestClass.class), isPresent());
+    assertThat(inspector.clazz(ASub.class), isPresent());
+    assertThat(inspector.clazz(BSub.class), isPresent());
+    assertThat(inspector.clazz(A.class), not(isPresent()));
+    assertThat(inspector.clazz(B.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    // If A is merged into ASub first, then the synthetic bridge for "void A.m(B)" will originally
+    // get the signature "void ASub.m(B)". Otherwise, if B is merged into BSub first, then the
+    // synthetic bridge will get the signature "void BSub.m(A)". In either case, it is important
+    // that
+    // the signatures of the bridge methods are updated after all classes have been merged
+    // vertically.
+    public static void main(String[] args) {
+      ASub a = new ASub();
+      BSub b = new BSub();
+      a.m(b);
+      b.m(a);
+
+      // Ensure that the instantiations are not dead code eliminated.
+      escape(a);
+      escape(b);
+    }
+
+    @NeverInline
+    static void escape(Object o) {
+      if (System.currentTimeMillis() < 0) {
+        System.out.println(o);
+      }
+    }
+  }
+
+  private static class A {
+
+    public void m(B object) {
+      System.out.println("In A.m()");
+    }
+  }
+
+  private static class ASub extends A {}
+
+  private static class B {
+
+    public void m(A object) {
+      System.out.println("In B.m()");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  private static class BSub extends B {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerDebugTestRunner.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerDebugTestRunner.java
new file mode 100644
index 0000000..e542559
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerDebugTestRunner.java
@@ -0,0 +1,100 @@
+// 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.vertical;
+
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.debug.DexDebugTestConfig;
+import com.android.tools.r8.utils.AndroidApp;
+import java.io.File;
+import java.nio.file.Path;
+import org.junit.rules.TemporaryFolder;
+
+public class VerticalClassMergerDebugTestRunner extends DebugTestBase {
+
+  private final String main;
+  private final TemporaryFolder temp;
+
+  private DebugTestRunner runner = null;
+
+  public VerticalClassMergerDebugTestRunner(String main, TemporaryFolder temp) {
+    this.main = main;
+    this.temp = temp;
+  }
+
+  public void run(AndroidApp app, Path proguardMapPath) throws Throwable {
+    Path appPath = File.createTempFile("app", ".zip", temp.getRoot()).toPath();
+    app.writeToZip(appPath, OutputMode.DexIndexed);
+
+    DexDebugTestConfig config = new DexDebugTestConfig(appPath);
+    config.allowUnprocessedCommands();
+    config.setProguardMap(proguardMapPath);
+
+    this.runner =
+        getDebugTestRunner(
+            config, main, breakpoint(main, "main"), run(), stepIntoUntilNoLongerInApp());
+    this.runner.runBare();
+  }
+
+  private void checkState(DebuggeeState state) {
+    // If a class pkg.A is merged into pkg.B, and a method pkg.A.m() needs to be renamed, then
+    // it will be renamed to pkg.B.m$pkg$A(). Since all tests are in the package "classmerging",
+    // we check that no methods in the debugging state (i.e., after the Proguard map has been
+    // applied) contain "$classmerging$.
+    String qualifiedMethodSignature =
+        state.getClassSignature() + "->" + state.getMethodName() + state.getMethodSignature();
+    boolean holderIsCompanionClass = state.getClassName().endsWith(COMPANION_CLASS_NAME_SUFFIX);
+    if (!holderIsCompanionClass) {
+      assertThat(qualifiedMethodSignature, not(containsString("$classmerging$")));
+    }
+  }
+
+  // Keeps stepping in until it is no longer in a class from the classmerging package.
+  // Then starts stepping out until it is again in the classmerging package.
+  private Command stepIntoUntilNoLongerInApp() {
+    return stepUntil(
+        StepKind.INTO,
+        StepLevel.INSTRUCTION,
+        state -> {
+          if (state.getClassSignature().contains("classmerging")) {
+            checkState(state);
+
+            // Continue stepping into.
+            return false;
+          }
+
+          // Stop stepping into.
+          runner.enqueueCommandFirst(stepOutUntilInApp());
+          return true;
+        });
+  }
+
+  // Keeps stepping out until it is in a class from the classmerging package.
+  // Then starts stepping in until it is no longer in the classmerging package.
+  private Command stepOutUntilInApp() {
+    return stepUntil(
+        StepKind.OUT,
+        StepLevel.INSTRUCTION,
+        state -> {
+          if (state.getClassSignature().contains("classmerging")) {
+            checkState(state);
+
+            // Stop stepping out.
+            runner.enqueueCommandFirst(stepIntoUntilNoLongerInApp());
+            return true;
+          }
+
+          // Continue stepping out.
+          return false;
+        });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index e831c02..4a3cc91 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -3,11 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.classmerging.vertical;
 
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.smali.SmaliBuilder.buildCode;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -16,7 +14,6 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
@@ -25,10 +22,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.debug.DebugTestBase;
-import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
-import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
-import com.android.tools.r8.debug.DexDebugTestConfig;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
@@ -49,7 +42,6 @@
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Streams;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -57,10 +49,8 @@
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import java.util.stream.IntStream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -101,7 +91,7 @@
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
-      throws IOException, ExecutionException, CompilationFailedException {
+      throws IOException, CompilationFailedException {
     inspector =
         testForR8(parameters.getBackend())
             .addProgramFiles(EXAMPLE_JAR)
@@ -124,19 +114,6 @@
   );
 
   @Test
-  public void testClassesHaveBeenMerged() throws Throwable {
-    expectThrowsWithHorizontalClassMerging();
-    runR8(EXAMPLE_KEEP, this::configure);
-    // GenericInterface should be merged into GenericInterfaceImpl.
-    for (String candidate : CAN_BE_MERGED) {
-      assertThat(inspector.clazz(candidate), not(isPresent()));
-    }
-    assertThat(inspector.clazz("classmerging.GenericInterfaceImpl"), isPresent());
-    assertThat(inspector.clazz("classmerging.Outer$SubClass"), isPresent());
-    assertThat(inspector.clazz("classmerging.SubClass"), isPresent());
-  }
-
-  @Test
   public void testClassesHaveNotBeenMerged() throws Throwable {
     runR8(DONT_OPTIMIZE, null);
     for (String candidate : CAN_BE_MERGED) {
@@ -1008,48 +985,6 @@
         preservedClassNames::contains);
   }
 
-  @Test
-  public void testSyntheticBridgeSignatures() throws Throwable {
-    expectThrowsWithHorizontalClassMerging();
-    // Try both with and without inlining. If the bridge signatures are not updated properly, and
-    // inlining is enabled, then there can be issues with our inlining invariants regarding the
-    // outermost caller. If inlining is disabled, there is a risk that the methods will end up
-    // having the wrong signatures, or that the generated Proguard maps are incorrect (this will be
-    // caught by the debugging test, which is carried out by the call to runTestOnInput()).
-    for (boolean allowInlining : ImmutableList.of(false, true)) {
-      String main = "classmerging.SyntheticBridgeSignaturesTest";
-      Path[] programFiles =
-          new Path[] {
-            CF_DIR.resolve("SyntheticBridgeSignaturesTest.class"),
-            CF_DIR.resolve("SyntheticBridgeSignaturesTest$A.class"),
-            CF_DIR.resolve("SyntheticBridgeSignaturesTest$ASub.class"),
-            CF_DIR.resolve("SyntheticBridgeSignaturesTest$B.class"),
-            CF_DIR.resolve("SyntheticBridgeSignaturesTest$BSub.class")
-          };
-      Set<String> preservedClassNames =
-          ImmutableSet.of(
-              "classmerging.SyntheticBridgeSignaturesTest",
-              "classmerging.SyntheticBridgeSignaturesTest$ASub",
-              "classmerging.SyntheticBridgeSignaturesTest$BSub");
-      runTestOnInput(
-          testForR8(parameters.getBackend())
-              .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
-              .addOptionsModification(this::configure)
-              .addOptionsModification(
-                  options -> {
-                    if (!allowInlining) {
-                      options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-                    }
-                  })
-              .allowUnusedProguardConfigurationRules(),
-          main,
-          readProgramFiles(programFiles),
-          preservedClassNames::contains,
-          // TODO(christofferqa): The debug test fails when inlining is not allowed.
-          allowInlining ? new VerticalClassMergerDebugTest(main) : null);
-    }
-  }
-
   private static String jasminCodeForPrinting(String message) {
     return buildCode(
         "getstatic java/lang/System/out Ljava/io/PrintStream;",
@@ -1080,50 +1015,6 @@
         preservedClassNames::contains);
   }
 
-  // If an exception class A is merged into another exception class B, then all exception tables
-  // should be updated, and class A should be removed entirely.
-  @Test
-  public void testExceptionTables() throws Throwable {
-    expectThrowsWithHorizontalClassMerging();
-    String main = "classmerging.ExceptionTest";
-    Path[] programFiles =
-        new Path[] {
-          CF_DIR.resolve("ExceptionTest.class"),
-          CF_DIR.resolve("ExceptionTest$ExceptionA.class"),
-          CF_DIR.resolve("ExceptionTest$ExceptionB.class"),
-          CF_DIR.resolve("ExceptionTest$Exception1.class"),
-          CF_DIR.resolve("ExceptionTest$Exception2.class")
-        };
-    Set<String> preservedClassNames =
-        ImmutableSet.of(
-            "classmerging.ExceptionTest",
-            "classmerging.ExceptionTest$ExceptionB",
-            "classmerging.ExceptionTest$Exception2");
-    CodeInspector inspector =
-        runTest(
-                testForR8(parameters.getBackend())
-                    .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
-                    .allowUnusedProguardConfigurationRules(),
-                main,
-                programFiles,
-                preservedClassNames::contains)
-            .inspector();
-
-    ClassSubject mainClass = inspector.clazz(main);
-    assertThat(mainClass, isPresent());
-
-    MethodSubject mainMethod =
-        mainClass.method("void", "main", ImmutableList.of("java.lang.String[]"));
-    assertThat(mainMethod, isPresent());
-
-    // Check that the second catch handler has been removed.
-    assertEquals(
-        2,
-        Streams.stream(mainMethod.iterateTryCatches())
-            .flatMapToInt(x -> IntStream.of(x.getNumberOfHandlers()))
-            .sum());
-  }
-
   @Test
   public void testMergeDefaultMethodIntoClass() throws Throwable {
     String main = "classmerging.MergeDefaultMethodIntoClassTest";
@@ -1219,42 +1110,6 @@
         preservedClassNames::contains);
   }
 
-  @Test
-  public void testNoIllegalClassAccessWithAccessModifications() throws Throwable {
-    expectThrowsWithHorizontalClassMerging();
-    // If access modifications are allowed then SimpleInterface should be merged into
-    // SimpleInterfaceImpl.
-    String main = "classmerging.SimpleInterfaceAccessTest";
-    Path[] programFiles =
-        new Path[] {
-          CF_DIR.resolve("SimpleInterfaceAccessTest.class"),
-          CF_DIR.resolve("SimpleInterfaceAccessTest$SimpleInterface.class"),
-          CF_DIR.resolve("SimpleInterfaceAccessTest$OtherSimpleInterface.class"),
-          CF_DIR.resolve("SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl.class"),
-          CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"),
-          CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class")
-        };
-    ImmutableSet<String> preservedClassNames =
-        ImmutableSet.of(
-            "classmerging.SimpleInterfaceAccessTest",
-            "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl",
-            "classmerging.pkg.SimpleInterfaceImplRetriever",
-            "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
-    // Allow access modifications (and prevent SimpleInterfaceImplRetriever from being removed as
-    // a result of inlining).
-    runTest(
-        testForR8(parameters.getBackend())
-            .addKeepRules(
-                getProguardConfig(
-                    EXAMPLE_KEEP,
-                    "-allowaccessmodification",
-                    "-keep public class classmerging.pkg.SimpleInterfaceImplRetriever"))
-            .allowUnusedProguardConfigurationRules(),
-        main,
-        programFiles,
-        preservedClassNames::contains);
-  }
-
   // TODO(christofferqa): This test checks that the invoke-super instruction in B is not rewritten
   // into an invoke-direct instruction after it gets merged into class C. We should add a test that
   // checks that this works with and without inlining of the method B.m().
@@ -1325,7 +1180,7 @@
         main,
         input,
         preservedClassNames,
-        new VerticalClassMergerDebugTest(main));
+        new VerticalClassMergerDebugTestRunner(main, temp));
   }
 
   private R8TestCompileResult runTestOnInput(
@@ -1333,7 +1188,7 @@
       String main,
       AndroidApp input,
       Predicate<String> preservedClassNames,
-      VerticalClassMergerDebugTest debugTestRunner)
+      VerticalClassMergerDebugTestRunner debugTestRunner)
       throws Throwable {
     R8TestCompileResult compileResult =
         builder
@@ -1379,7 +1234,7 @@
     // Check that we never come across a method that has a name with "$classmerging$" in it during
     // debugging.
     if (debugTestRunner != null && parameters.isDexRuntime()) {
-      debugTestRunner.test(compileResult.app, proguardMapPath);
+      debugTestRunner.run(compileResult.app, proguardMapPath);
     }
     return compileResult;
   }
@@ -1397,81 +1252,4 @@
     return builder.toString();
   }
 
-  private class VerticalClassMergerDebugTest extends DebugTestBase {
-
-    private final String main;
-    private DebugTestRunner runner = null;
-
-    public VerticalClassMergerDebugTest(String main) {
-      this.main = main;
-    }
-
-    public void test(AndroidApp app, Path proguardMapPath) throws Throwable {
-      Path appPath =
-          File.createTempFile("app", ".zip", VerticalClassMergerTest.this.temp.getRoot()).toPath();
-      app.writeToZip(appPath, OutputMode.DexIndexed);
-
-      DexDebugTestConfig config = new DexDebugTestConfig(appPath);
-      config.allowUnprocessedCommands();
-      config.setProguardMap(proguardMapPath);
-
-      this.runner =
-          getDebugTestRunner(
-              config, main, breakpoint(main, "main"), run(), stepIntoUntilNoLongerInApp());
-      this.runner.runBare();
-    }
-
-    private void checkState(DebuggeeState state) {
-      // If a class pkg.A is merged into pkg.B, and a method pkg.A.m() needs to be renamed, then
-      // it will be renamed to pkg.B.m$pkg$A(). Since all tests are in the package "classmerging",
-      // we check that no methods in the debugging state (i.e., after the Proguard map has been
-      // applied) contain "$classmerging$.
-      String qualifiedMethodSignature =
-          state.getClassSignature() + "->" + state.getMethodName() + state.getMethodSignature();
-      boolean holderIsCompanionClass = state.getClassName().endsWith(COMPANION_CLASS_NAME_SUFFIX);
-      if (!holderIsCompanionClass) {
-        assertThat(qualifiedMethodSignature, not(containsString("$classmerging$")));
-      }
-    }
-
-    // Keeps stepping in until it is no longer in a class from the classmerging package.
-    // Then starts stepping out until it is again in the classmerging package.
-    private Command stepIntoUntilNoLongerInApp() {
-      return stepUntil(
-          StepKind.INTO,
-          StepLevel.INSTRUCTION,
-          state -> {
-            if (state.getClassSignature().contains("classmerging")) {
-              checkState(state);
-
-              // Continue stepping into.
-              return false;
-            }
-
-            // Stop stepping into.
-            runner.enqueueCommandFirst(stepOutUntilInApp());
-            return true;
-          });
-    }
-
-    // Keeps stepping out until it is in a class from the classmerging package.
-    // Then starts stepping in until it is no longer in the classmerging package.
-    private Command stepOutUntilInApp() {
-      return stepUntil(
-          StepKind.OUT,
-          StepLevel.INSTRUCTION,
-          state -> {
-            if (state.getClassSignature().contains("classmerging")) {
-              checkState(state);
-
-              // Stop stepping out.
-              runner.enqueueCommandFirst(stepIntoUntilNoLongerInApp());
-              return true;
-            }
-
-            // Continue stepping out.
-            return false;
-          });
-    }
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTestBase.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTestBase.java
new file mode 100644
index 0000000..c11fcbd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTestBase.java
@@ -0,0 +1,28 @@
+// 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.vertical;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+
+public abstract class VerticalClassMergerTestBase extends TestBase {
+
+  protected final TestParameters parameters;
+
+  public VerticalClassMergerTestBase(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public void runDebugTest(Class<?> mainClass, R8TestCompileResult compileResult) throws Throwable {
+    assertTrue(parameters.isDexRuntime());
+    new VerticalClassMergerDebugTestRunner(mainClass.getTypeName(), temp)
+        .run(compileResult.app, compileResult.writeProguardMap());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotation.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
similarity index 78%
rename from src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotation.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
index bb33801..795837e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotation.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
@@ -14,8 +14,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.classmerging.vertical.testclasses.Outer;
-import com.android.tools.r8.classmerging.vertical.testclasses.Outer.Base;
+import com.android.tools.r8.classmerging.vertical.testclasses.VerticalClassMergingWithNonVisibleAnnotationTestClasses;
+import com.android.tools.r8.classmerging.vertical.testclasses.VerticalClassMergingWithNonVisibleAnnotationTestClasses.Base;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -26,7 +26,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class VerticalClassMergingWithNonVisibleAnnotation extends TestBase {
+public class VerticalClassMergingWithNonVisibleAnnotationTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -35,18 +35,20 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public VerticalClassMergingWithNonVisibleAnnotation(TestParameters parameters) {
+  public VerticalClassMergingWithNonVisibleAnnotationTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(Outer.class)
+        .addInnerClasses(VerticalClassMergingWithNonVisibleAnnotationTestClasses.class)
         .addProgramClasses(Sub.class)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Sub.class)
-        .addKeepClassRules(Outer.class.getPackage().getName() + ".Outer$Private* { *; }")
+        .addKeepClassRules(
+            VerticalClassMergingWithNonVisibleAnnotationTestClasses.class.getTypeName()
+                + "$Private* { *; }")
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
@@ -65,7 +67,8 @@
               assertThat(foo, isPresent());
               AnnotationSubject privateMethodAnnotation =
                   foo.annotation(
-                      Outer.class.getPackage().getName() + ".Outer$PrivateMethodAnnotation");
+                      VerticalClassMergingWithNonVisibleAnnotationTestClasses.class.getTypeName()
+                          + "$PrivateMethodAnnotation");
               assertThat(privateMethodAnnotation, isPresent());
             });
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/NoIllegalClassAccessWithAccessModificationsTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/NoIllegalClassAccessWithAccessModificationsTestClasses.java
new file mode 100644
index 0000000..b16d11b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/NoIllegalClassAccessWithAccessModificationsTestClasses.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.classmerging.vertical.testclasses;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.classmerging.vertical.NoIllegalClassAccessWithAccessModificationsTest.SimpleInterface;
+
+public class NoIllegalClassAccessWithAccessModificationsTestClasses {
+
+  public static Class<?> getSimpleInterfaceImplClass() {
+    return SimpleInterfaceImpl.class;
+  }
+
+  public static class SimpleInterfaceFactory {
+
+    public static SimpleInterface create() {
+      return new SimpleInterfaceImpl();
+    }
+  }
+
+  // This class is intentionally marked private. It is not possible to merge the interface
+  // SimpleInterface into SimpleInterfaceImpl, since this would lead to an illegal class access
+  // in SimpleInterfaceAccessTest.
+  @NoHorizontalClassMerging
+  private static class SimpleInterfaceImpl implements SimpleInterface {
+
+    @Override
+    public void foo() {
+      System.out.println("In foo on SimpleInterfaceImpl");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/NonReboundFieldAccessOnMergedClassTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/NonReboundFieldAccessOnMergedClassTestClasses.java
new file mode 100644
index 0000000..7653f54
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/NonReboundFieldAccessOnMergedClassTestClasses.java
@@ -0,0 +1,27 @@
+// 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.vertical.testclasses;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+
+public class NonReboundFieldAccessOnMergedClassTestClasses {
+
+  @NoVerticalClassMerging
+  static class A {
+
+    public String greeting;
+
+    A(String greeting) {
+      this.greeting = greeting;
+    }
+  }
+
+  public static class B extends A {
+
+    public B(String greeting) {
+      super(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/NonReboundFieldAccessWithMergedTypeTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/NonReboundFieldAccessWithMergedTypeTestClasses.java
new file mode 100644
index 0000000..aac0481
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/NonReboundFieldAccessWithMergedTypeTestClasses.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.classmerging.vertical.testclasses;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.classmerging.vertical.NonReboundFieldAccessWithMergedTypeTest.GreetingBase;
+
+public class NonReboundFieldAccessWithMergedTypeTestClasses {
+
+  @NoVerticalClassMerging
+  static class A {
+
+    public GreetingBase greeting;
+
+    A(GreetingBase greeting) {
+      this.greeting = greeting;
+    }
+  }
+
+  @NoVerticalClassMerging
+  public static class B extends A {
+
+    public B(GreetingBase greeting) {
+      super(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/Outer.java b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/VerticalClassMergingWithNonVisibleAnnotationTestClasses.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/Outer.java
rename to src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/VerticalClassMergingWithNonVisibleAnnotationTestClasses.java
index 46ef8c8..3b8bf5b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/Outer.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/VerticalClassMergingWithNonVisibleAnnotationTestClasses.java
@@ -8,7 +8,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-public class Outer {
+public class VerticalClassMergingWithNonVisibleAnnotationTestClasses {
 
   @Retention(RetentionPolicy.RUNTIME)
   private @interface PrivateClassAnnotation {}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
index b2d5b54..5d84439 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
@@ -113,18 +113,17 @@
   public void testR8() throws Exception {
     try {
       testForR8(parameters.getBackend())
-          // TODO(b/158752316, b/157700141): Disable inlining to keep the synthetic lambda methods.
-          .addOptionsModification(options -> options.enableInlining = false)
           .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
           .setMinApi(parameters.getApiLevel())
-          .addKeepRules("-keep class * { *; }")
-          // Keeping the synthetic lambda methods is currently not supported - they are
-          // forcefully unpinned. The following rule has no effect. See b/b/157700141.
-          .addKeepRules("-keep class **.*$TestClass { synthetic *; }")
+          // Keep the synthesized inner classes.
+          .addKeepRules("-keep class **.*$TestClass$1")
+          .addKeepRules("-keep class **.*$TestClass$2")
+          // Keep the outer context: TestClass *and* the synthetic lambda methods.
+          .addKeepRules("-keep class **.*$TestClass { private synthetic void lambda$*(*); }")
           .addKeepAttributes("InnerClasses", "EnclosingMethod")
-          .compile()
-          .inspect(this::checkEnclosingMethod)
+          .addKeepMainRule(TestClass.class)
           .run(parameters.getRuntime(), TestClass.class)
+          .inspect(this::checkEnclosingMethod)
           .assertSuccessWithOutputLines(
               parameters.isCfRuntime() ? EXPECTED_JAVAC_RESULT : EXPECTED_DESUGARED_RESULT);
       assertFalse(parameters.isDexRuntime());
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
index 8c539d2..f272f26 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
@@ -36,9 +36,6 @@
           "Hello from inside lambda$test$0$DesugarLambdaWithLocalClass$TestClass",
           "Hello from inside lambda$testStatic$1");
 
-  private List<String> EXPECTED_DESUGARED_RESULT_R8_WITHOUT_INLINING =
-      ImmutableList.of("Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
-
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
@@ -113,22 +110,18 @@
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        // TODO(b/158752316, b/157700141): Disable inlining to keep the synthetic lambda methods.
-        .addOptionsModification(options -> options.enableInlining = false)
         .addInnerClasses(DesugarLambdaWithLocalClass.class)
         .setMinApi(parameters.getApiLevel())
-        .addKeepRules("-keep class * { *; }")
-        // Keeping the synthetic lambda methods is currently not supported - they are
-        // forcefully unpinned. The following rule has no effect. See b/b/157700141.
-        .addKeepRules("-keep class **.*$TestClass { synthetic *; }")
+        // Keep the synthesized inner classes.
+        .addKeepRules("-keep class **.*$TestClass$*MyConsumerImpl")
+        // Keep the outer context: TestClass *and* the synthetic lambda methods.
+        .addKeepRules("-keep class **.*$TestClass { private synthetic void lambda$*(*); }")
         .addKeepAttributes("InnerClasses", "EnclosingMethod")
-        .compile()
-        .inspect(this::checkEnclosingMethod)
+        .addKeepMainRule(TestClass.class)
         .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::checkEnclosingMethod)
         .assertSuccessWithOutputLines(
-            parameters.isCfRuntime()
-                ? EXPECTED_JAVAC_RESULT
-                : EXPECTED_DESUGARED_RESULT_R8_WITHOUT_INLINING);
+            parameters.isCfRuntime() ? EXPECTED_JAVAC_RESULT : EXPECTED_JAVAC_RESULT);
   }
 
   public interface MyConsumer<T> {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index 380703c..c2a8275 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -150,8 +150,6 @@
 
   @Test
   public void testBufferedReaderD8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     Assume.assumeTrue(parameters.getRuntime().isDex());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
@@ -180,8 +178,6 @@
 
   @Test
   public void testBufferedReaderR8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     Assume.assumeTrue(parameters.getRuntime().isDex());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
index 0a3ce0a..42582e6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
@@ -46,7 +46,9 @@
   @Test
   public void testCustomCollectionD8() throws Exception {
     expectThrowsWithHorizontalClassMergingIf(
-        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
+        shrinkDesugaredLibrary
+            && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
+            && !parameters.getDexRuntimeVersion().isDefault());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(CustomCollectionForwardingTest.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index eadaa9b..2330854 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -53,8 +52,6 @@
 
   @Test
   public void testCustomCollectionD8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     D8TestRunResult d8TestRunResult =
         testForD8()
@@ -87,8 +84,6 @@
 
   @Test
   public void testCustomCollectionR8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     R8TestRunResult r8TestRunResult =
         testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
index 98e1917..ced1c31 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
@@ -8,7 +8,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -58,9 +57,7 @@
       return;
     }
     String keepRules = keepRuleConsumer.get();
-    assertThat(keepRules, containsString("-keep class j$.util.Map"));
     assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentHashMap"));
-    assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentMap"));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
index 9bce171..7fe00d5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -66,7 +66,6 @@
 
   @Test
   public void testStatsD8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(shrinkDesugaredLibrary && shrinkDesugaredLibrary);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
@@ -86,7 +85,6 @@
 
   @Test
   public void testStatsR8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(shrinkDesugaredLibrary && shrinkDesugaredLibrary);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/AllMapsTestClass.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/AllMapsTestClass.java
new file mode 100644
index 0000000..4c15672
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/AllMapsTestClass.java
@@ -0,0 +1,347 @@
+// 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 java.util.concurrent.ConcurrentMap;
+
+public class AllMapsTestClass {
+  // 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 HashMap<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);
+    }
+
+    @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(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();
+    }
+
+    @Override
+    public Set<K> keySet() {
+      return map.keySet();
+    }
+
+    @Override
+    public Collection<V> values() {
+      return map.values();
+    }
+
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+      return map.entrySet();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (!(o instanceof NullableMap)) return false;
+      NullableMap<?, ?> that = (NullableMap<?, ?>) o;
+      return map.equals(that.map);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(map);
+    }
+  }
+  // Program class implementing ConcurrentMap.
+  static class NullableConcurrentMap<K, V> implements ConcurrentMap<K, V> {
+    private HashMap<K, V> map = new HashMap<>();
+
+    NullableConcurrentMap() {
+      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);
+    }
+
+    @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(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();
+    }
+
+    @Override
+    public Set<K> keySet() {
+      return map.keySet();
+    }
+
+    @Override
+    public Collection<V> values() {
+      return map.values();
+    }
+
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+      return map.entrySet();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (!(o instanceof NullableConcurrentMap)) return false;
+      NullableConcurrentMap<?, ?> that = (NullableConcurrentMap<?, ?>) o;
+      return map.equals(that.map);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(map);
+    }
+
+    @Override
+    public V putIfAbsent(K key, V value) {
+      return null;
+    }
+
+    @Override
+    public boolean remove(Object key, Object value) {
+      return false;
+    }
+
+    @Override
+    public boolean replace(K key, V oldValue, V newValue) {
+      return false;
+    }
+
+    @Override
+    public V replace(K key, V value) {
+      return null;
+    }
+  }
+
+  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 + '\'' + '}';
+    }
+  }
+
+  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<>();
+    NullableConcurrentMap<Integer, Data> nullableConcurrentMap = new NullableConcurrentMap<>();
+    ConcurrentHashMap<Integer, Data> concurrentHashMap = new ConcurrentHashMap<>();
+    NullableConcurrentHashMap<Integer, Data> nullableConcurrentHashMap =
+        new NullableConcurrentHashMap<>();
+
+    fillMap(hashMap);
+    fillMap(nullableHashMap);
+    fillMap(nullableMap);
+    fillMap(nullableConcurrentMap);
+    fillMap(concurrentHashMap);
+    fillMap(nullableConcurrentHashMap);
+
+    // Serialization.
+    String hashMapJson = gson.toJson(hashMap);
+    String nullableHashMapJson = gson.toJson(nullableHashMap);
+    String nullableMapJson = gson.toJson(nullableMap);
+    String nullableConcurrentMapJson = gson.toJson(nullableConcurrentMap);
+    String concurrentHashMapJson = gson.toJson(concurrentHashMap);
+    String nullableConcurrentHashMapJson = gson.toJson(nullableConcurrentHashMap);
+
+    // Deserialization.
+    Type hashMapType = new TypeToken<HashMap<Integer, Data>>() {}.getType();
+    HashMap<Integer, Data> hashMapDeserialized = gson.fromJson(hashMapJson, hashMapType);
+    Type nullableHashMapType = new TypeToken<NullableHashMap<Integer, Data>>() {}.getType();
+    NullableHashMap<Integer, Data> nullableHashMapDeserialized =
+        gson.fromJson(nullableHashMapJson, nullableHashMapType);
+    Type nullableMapType = new TypeToken<NullableMap<Integer, Data>>() {}.getType();
+    NullableMap<Integer, Data> nullableMapDeserialized =
+        gson.fromJson(nullableMapJson, nullableMapType);
+    Type nullableConcurrentMapType =
+        new TypeToken<NullableConcurrentMap<Integer, Data>>() {}.getType();
+    NullableConcurrentMap<Integer, Data> nullableConcurrentMapDeserialized =
+        gson.fromJson(nullableConcurrentMapJson, nullableConcurrentMapType);
+    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);
+
+    // Printing.
+    System.out.println(hashMap.getClass() == hashMapDeserialized.getClass());
+    System.out.println(hashMap.equals(hashMapDeserialized));
+    System.out.println(nullableHashMap.getClass() == nullableHashMapDeserialized.getClass());
+    System.out.println(nullableHashMap.equals(nullableHashMapDeserialized));
+    System.out.println(nullableMap.getClass() == nullableMapDeserialized.getClass());
+    System.out.println(nullableMap.equals(nullableMapDeserialized));
+    System.out.println(
+        nullableConcurrentMap.getClass() == nullableConcurrentMapDeserialized.getClass());
+    System.out.println(nullableConcurrentMap.equals(nullableConcurrentMapDeserialized));
+    System.out.println(concurrentHashMap.getClass() == concurrentHashMapDeserialized.getClass());
+    System.out.println(concurrentHashMap.equals(concurrentHashMapDeserialized));
+    System.out.println(
+        nullableConcurrentHashMap.getClass() == nullableConcurrentHashMapDeserialized.getClass());
+    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/LibrarySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
rename to src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
index 29bb1cf..249f26f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
@@ -1,14 +1,14 @@
 // 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
+// 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;
+package com.android.tools.r8.desugar.desugaredlibrary.gson;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import dalvik.system.PathClassLoader;
 import java.sql.SQLDataException;
@@ -32,7 +32,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class LibrarySubclassInterfaceTest extends DesugaredLibraryTestBase {
+public class GetGenericInterfaceTest extends DesugaredLibraryTestBase {
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
@@ -43,7 +43,7 @@
         BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
   }
 
-  public LibrarySubclassInterfaceTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+  public GetGenericInterfaceTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
@@ -53,7 +53,7 @@
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdOut =
         testForD8()
-            .addInnerClasses(LibrarySubclassInterfaceTest.class)
+            .addInnerClasses(GetGenericInterfaceTest.class)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
@@ -73,7 +73,7 @@
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdOut =
         testForR8(Backend.DEX)
-            .addInnerClasses(LibrarySubclassInterfaceTest.class)
+            .addInnerClasses(GetGenericInterfaceTest.class)
             .addKeepMainRule(Executor.class)
             .noMinification()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java
new file mode 100644
index 0000000..45e6af7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.desugaredlibrary.gson;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+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 GsonAllMapsTest extends GsonDesugaredLibraryTestBase {
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final String[] EXPECTED_RESULT =
+      new String[] {
+        "true", "true", "true", "true", "true", "true", "true", "true", "true", "true", "true",
+        "true"
+      };
+
+  @Parameters(name = "shrinkDesugaredLibrary: {0}, runtime: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public GsonAllMapsTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testGsonMapD8() throws Exception {
+    Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(AllMapsTestClass.class)
+        .addProgramFiles(GSON_2_8_1_JAR)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), AllMapsTestClass.class)
+        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testGsonMapR8() throws Exception {
+    Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(AllMapsTestClass.class)
+        .addProgramFiles(GSON_2_8_1_JAR)
+        .addKeepMainRule(AllMapsTestClass.class)
+        .addKeepRuleFiles(GSON_CONFIGURATION)
+        .allowUnusedProguardConfigurationRules()
+        .allowDiagnosticMessages()
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), AllMapsTestClass.class)
+        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonDesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonDesugaredLibraryTestBase.java
new file mode 100644
index 0000000..1904ff7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonDesugaredLibraryTestBase.java
@@ -0,0 +1,14 @@
+// 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.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public abstract class GsonDesugaredLibraryTestBase extends DesugaredLibraryTestBase {
+  protected static final Path GSON_CONFIGURATION =
+      Paths.get("src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg");
+  protected static final Path GSON_2_8_1_JAR = Paths.get("third_party/iosched_2019/gson-2.8.1.jar");
+}
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
deleted file mode 100644
index fa5c6d3..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonMapTest.java
+++ /dev/null
@@ -1,71 +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.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/GsonOptionalTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonOptionalTest.java
new file mode 100644
index 0000000..d3fd68a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonOptionalTest.java
@@ -0,0 +1,73 @@
+// 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.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class GsonOptionalTest extends GsonDesugaredLibraryTestBase {
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameterized.Parameters(name = "shrinkDesugaredLibrary: {0}, runtime: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public GsonOptionalTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testGsonOptionalD8() throws Exception {
+    Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(OptionalTestClass.class)
+        .addProgramFiles(GSON_2_8_1_JAR)
+        .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), OptionalTestClass.class)
+        .assertSuccessWithOutputLines("true", "true");
+  }
+
+  @Test
+  public void testGsonOptionalR8() throws Exception {
+    Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(OptionalTestClass.class)
+        .addProgramFiles(GSON_2_8_1_JAR)
+        .addKeepMainRule(OptionalTestClass.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(), OptionalTestClass.class)
+        .assertSuccessWithOutputLines("true", "true");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/OptionalTestClass.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/OptionalTestClass.java
new file mode 100644
index 0000000..fbc2c31
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/OptionalTestClass.java
@@ -0,0 +1,95 @@
+// 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.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Objects;
+import java.util.Optional;
+
+public class OptionalTestClass {
+  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 OptionalAdapter<T> extends TypeAdapter<Optional<T>> {
+    private final TypeAdapter<T> delegate;
+
+    public OptionalAdapter(TypeAdapter<T> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void write(JsonWriter out, Optional<T> value) throws IOException {
+      if (!value.isPresent()) {
+        out.nullValue();
+        return;
+      }
+      delegate.write(out, value.get());
+    }
+
+    @Override
+    public Optional<T> read(JsonReader in) throws IOException {
+      if (in.peek() == JsonToken.NULL) {
+        in.nextNull();
+        return Optional.empty();
+      }
+      return Optional.of(delegate.read(in));
+    }
+
+    @SuppressWarnings("unchecked")
+    public static OptionalAdapter getInstance(TypeToken typeToken) {
+      TypeAdapter delegate;
+      Type type = typeToken.getType();
+      assert type instanceof ParameterizedType;
+      Type innerType = ((ParameterizedType) type).getActualTypeArguments()[0];
+      delegate = new Gson().getAdapter(TypeToken.get(innerType));
+      return new OptionalAdapter<>(delegate);
+    }
+  }
+
+  public static void main(String[] args) {
+    GsonBuilder builder = new GsonBuilder();
+    builder.registerTypeAdapter(
+        Optional.class, OptionalAdapter.getInstance(new TypeToken<Optional<Data>>() {}));
+    Gson gson = builder.create();
+    Optional<Data> optionalData = Optional.of(new Data(1, "a"));
+    String optionalDataSerialized = gson.toJson(optionalData);
+    Optional<Data> optionalDataDeserialized = gson.fromJson(optionalDataSerialized, Optional.class);
+    System.out.println(optionalData.getClass() == optionalDataDeserialized.getClass());
+    System.out.println(optionalData.equals(optionalDataDeserialized));
+  }
+}
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
deleted file mode 100644
index 7b57814..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/TestClasses.java
+++ /dev/null
@@ -1,221 +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.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
index 51f38a6..da7bcba 100644
--- 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
@@ -1,9 +1,8 @@
 # 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.
+# 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
@@ -14,10 +13,13 @@
 # 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>; }
+-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$Data { <fields>; }
+-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableConcurrentHashMap
+-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableHashMap
+-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableMap
+-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.AllMapsTestClasses$NullableConcurrentMap
+-keep class com.android.tools.r8.desugar.desugaredlibrary.gson.OptionalTestClass$Data { <fields>; }
 
 # Prevent R8 from stripping interface information from TypeAdapter, TypeAdapterFactory,
 # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
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 762916e..e1d892d 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,6 +47,7 @@
 
   @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/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
index c72cf47..e5b4c08 100644
--- a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -155,7 +156,12 @@
     MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false);
     DexEncodedMethod method =
         new DexEncodedMethod(
-            null, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code);
+            null,
+            flags,
+            MethodTypeSignature.noSignature(),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            code);
     return new JumboStringRewriter(method, string, factory).rewrite();
   }
 }
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 b134ae5..81ae312 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -26,6 +26,7 @@
 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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
@@ -87,6 +88,7 @@
         dexItemFactory.createMethod(
             holder, dexItemFactory.createProto(dexItemFactory.voidType), "theMethod"),
         MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false),
+        MethodTypeSignature.noSignature(),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
         code);
@@ -113,7 +115,7 @@
             Collections.emptyList(),
             null,
             Collections.emptyList(),
-            ClassSignature.NO_CLASS_SIGNATURE,
+            ClassSignature.noSignature(),
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
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 2519477..0011245 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -23,13 +23,13 @@
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
 import com.android.tools.r8.graph.GenericSignatureTestClassA.I;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.List;
 import java.util.function.Consumer;
@@ -140,7 +140,7 @@
     DexEncodedField field = yyInZZ.getField();
     assertNotNull(field);
 
-    fieldTypeSignature = field.getFieldSignature();
+    fieldTypeSignature = field.getGenericSignature();
     assertTrue(fieldTypeSignature.hasSignature());
 
     // field type: A$Y$YY
@@ -157,13 +157,7 @@
     method = newYY.getMethod();
     assertNotNull(method);
 
-    methodTypeSignature =
-        GenericSignature.parseMethodSignature(
-            method.qualifiedName(),
-            getGenericSignature(method, appView),
-            Origin.unknown(),
-            appView.dexItemFactory(),
-            appView.options().reporter);
+    methodTypeSignature = method.getGenericSignature();
     assertNotNull(methodTypeSignature);
 
     assertEquals(1, methodTypeSignature.formalTypeParameters.size());
@@ -205,13 +199,7 @@
     method = convertToYY.getMethod();
     assertNotNull(method);
 
-    methodTypeSignature =
-        GenericSignature.parseMethodSignature(
-            method.qualifiedName(),
-            getGenericSignature(method, appView),
-            Origin.unknown(),
-            appView.dexItemFactory(),
-            appView.options().reporter);
+    methodTypeSignature = method.getGenericSignature();
     assertNotNull(methodTypeSignature);
 
     // return type: Function<A$Y$ZZ<TT>, A$Y$YY>
@@ -248,13 +236,7 @@
     assertNotNull(method);
 
     // return type: void
-    methodTypeSignature =
-        GenericSignature.parseMethodSignature(
-            method.qualifiedName(),
-            getGenericSignature(method, appView),
-            Origin.unknown(),
-            appView.dexItemFactory(),
-            appView.options().reporter);
+    methodTypeSignature = method.getGenericSignature();
     assertNotNull(methodTypeSignature);
     returnType = methodTypeSignature.returnType();
     assertTrue(returnType.isVoidDescriptor());
@@ -302,7 +284,7 @@
     checkMethodWildCard(y.uniqueMethodWithName("bar"), appView, WildcardIndicator.NEGATIVE);
     // Check for star
     checkFieldTypeSignature(
-        y.uniqueMethodWithName("baz"),
+        y.uniqueMethodWithName("baz").asFoundMethodSubject(),
         appView,
         typeSignature -> {
           assertTrue(typeSignature.isStar());
@@ -314,7 +296,7 @@
       AppView<AppInfoWithLiveness> appView,
       WildcardIndicator indicator) {
     checkFieldTypeSignature(
-        methodSubject,
+        methodSubject.asFoundMethodSubject(),
         appView,
         typeSignature -> {
           assertTrue(typeSignature.isTypeVariableSignature());
@@ -323,16 +305,10 @@
   }
 
   private void checkFieldTypeSignature(
-      MethodSubject methodSubject,
+      FoundMethodSubject methodSubject,
       AppView<AppInfoWithLiveness> appView,
       Consumer<FieldTypeSignature> fieldTypeConsumer) {
-    MethodTypeSignature methodTypeSignature =
-        GenericSignature.parseMethodSignature(
-            methodSubject.getOriginalName(),
-            getGenericSignature(methodSubject.getMethod(), appView),
-            Origin.unknown(),
-            appView.dexItemFactory(),
-            appView.options().reporter);
+    MethodTypeSignature methodTypeSignature = methodSubject.getMethod().getGenericSignature();
     TypeSignature typeSignature = methodTypeSignature.returnType.typeSignature;
     FieldTypeSignature fieldTypeSignature = typeSignature.asFieldTypeSignature();
     assertTrue(fieldTypeSignature.isClassTypeSignature());
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
index fade8ed..e02f313 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
@@ -5,7 +5,6 @@
 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;
@@ -111,9 +110,9 @@
 
   @Test
   public void testSuperClassError() {
-    TestDiagnosticMessages testDiagnosticMessages = testParsingAndPrintingError("Lfoo/bar/baz");
-    testDiagnosticMessages.assertAllWarningsMatch(
-        diagnosticMessage(containsString("Invalid signature 'Lfoo/bar/baz'")));
+    testParsingAndPrintingError("Lfoo/bar/baz")
+        .assertAllWarningsMatch(
+            diagnosticMessage(containsString("Invalid signature 'Lfoo/bar/baz'")));
   }
 
   @Test
@@ -143,7 +142,12 @@
   }
 
   @Test
-  public void testFormalTypeParameters2() {}
+  public void testFormalTypeParameterEmptyInterfaceError() {
+    testParsingAndPrintingError("<T:Ljava/lang/Object;:>Lfoo/bar/baz<TT;>;")
+        .assertAllWarningsMatch(
+            diagnosticMessage(
+                containsString("Invalid signature '<T:Ljava/lang/Object;:>Lfoo/bar/baz<TT;>;'")));
+  }
 
   @Test
   public void testFormalTypeParametersEmptyError() {
@@ -171,7 +175,7 @@
             Origin.unknown(),
             new DexItemFactory(),
             new Reporter(testDiagnosticMessages));
-    assertEquals(NO_CLASS_SIGNATURE, parsed);
+    assertEquals(ClassSignature.noSignature(), parsed);
     return testDiagnosticMessages;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
index b0200fa..b6e34ad 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.graph.genericsignature;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 import static com.google.common.base.Predicates.alwaysFalse;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
@@ -104,7 +103,7 @@
             Origin.unknown(),
             new DexItemFactory(),
             new Reporter(testDiagnosticMessages));
-    assertEquals(NO_FIELD_TYPE_SIGNATURE, parsed);
+    assertEquals(FieldTypeSignature.noSignature(), parsed);
     return testDiagnosticMessages;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/FormalTypeParameterClassBoundPruneTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/FormalTypeParameterClassBoundPruneTest.java
new file mode 100644
index 0000000..1221f73
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/FormalTypeParameterClassBoundPruneTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature;
+
+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.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.lang.reflect.TypeVariable;
+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 FormalTypeParameterClassBoundPruneTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String INTERFACE_BOUND =
+      "L" + DescriptorUtils.getBinaryNameFromJavaType(Interface.class.getTypeName()) + "<TT;>;";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public FormalTypeParameterClassBoundPruneTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntimeWithNoPrunedSuperType() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(
+            transformer(Main.class).removeInnerClasses().transform(),
+            transformer(Super.class).removeInnerClasses().transform(),
+            transformer(Interface.class).removeInnerClasses().transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            Super.class.getTypeName() + "<T>", Interface.class.getTypeName() + "<T>");
+  }
+
+  @Test
+  public void testRuntimeWithPrunedSuperType() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(
+            transformer(Main.class)
+                .removeInnerClasses()
+                .setGenericSignature("<T::" + INTERFACE_BOUND + ">Ljava/lang/Object;")
+                .transform(),
+            transformer(Super.class).removeInnerClasses().transform(),
+            transformer(Interface.class).removeInnerClasses().transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(Interface.class.getTypeName() + "<T>");
+  }
+
+  @Test
+  public void testRuntimeWithObjectSuperType() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(
+            transformer(Main.class)
+                .removeInnerClasses()
+                .setGenericSignature(
+                    "<T:Ljava/lang/Object;:" + INTERFACE_BOUND + ">Ljava/lang/Object;")
+                .transform(),
+            transformer(Super.class).removeInnerClasses().transform(),
+            transformer(Interface.class).removeInnerClasses().transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "class java.lang.Object", Interface.class.getTypeName() + "<T>");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(Main.class).removeInnerClasses().transform(),
+            transformer(Super.class).removeInnerClasses().transform(),
+            transformer(Interface.class).removeInnerClasses().transform())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(Interface.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAttributes(
+            ProguardKeepAttributes.SIGNATURE,
+            ProguardKeepAttributes.INNER_CLASSES,
+            ProguardKeepAttributes.ENCLOSING_METHOD)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(Interface.class.getTypeName() + "<T>")
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(Super.class), not(isPresent()));
+            });
+  }
+
+  public static class Super<T> {}
+
+  public interface Interface<T> {}
+
+  public static class Main<T extends Super<T> & Interface<T>> {
+
+    public static void main(String[] args) {
+      TypeVariable<Class<Main>> typeParameter = Main.class.getTypeParameters()[0];
+      for (java.lang.reflect.Type bound : typeParameter.getBounds()) {
+        System.out.println(bound.toString());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/MethodSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/MethodSignatureTest.java
new file mode 100644
index 0000000..c751918
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/MethodSignatureTest.java
@@ -0,0 +1,136 @@
+// 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.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.MethodTypeSignature;
+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 MethodSignatureTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public MethodSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testEmptyVoid() {
+    testParsingAndPrintingEqual("()V");
+  }
+
+  @Test
+  public void testThrowsOnly() {
+    testParsingAndPrintingError("^TT;")
+        .assertAllErrorsMatch(
+            diagnosticMessage(containsString("Invalid signature '^TT;' for method A.")));
+  }
+
+  @Test
+  public void testArguments() {
+    testParsingAndPrintingEqual("(TT;Lfoo/bar/Baz;[I)V");
+  }
+
+  @Test
+  public void testReturnType() {
+    testParsingAndPrintingEqual("()Lfoo/Baz;");
+  }
+
+  @Test
+  public void testTypeVariableReturn() {
+    testParsingAndPrintingEqual("()TR;");
+  }
+
+  @Test
+  public void testThrowsSingle() {
+    testParsingAndPrintingEqual("(Lfoo/Bar;)V^Ljava/lang/Exception;");
+  }
+
+  @Test
+  public void testThrowsMultiple() {
+    testParsingAndPrintingEqual("(Lfoo/Bar;)V^Ljava/lang/Exception;^TT;^Lfoo/bar/Baz;");
+  }
+
+  @Test
+  public void testThrowsMultipleError() {
+    // TODO(b/170287583): This should throw an error.
+    assertThrows(
+        AssertionError.class,
+        () -> testParsingAndPrintingEqual("(Lfoo/Bar;)V^Ljava/lang/Exception;^TT;Lfoo/bar/Baz;"));
+  }
+
+  @Test
+  public void testTypeArgument() {
+    testParsingAndPrintingEqual("<T:Ljava/lang/Object;>([I)V");
+  }
+
+  @Test
+  public void testTypeArgumentMultiple() {
+    testParsingAndPrintingEqual("<T:Ljava/lang/Object;R::LConsumer<TT;>;>([I)V");
+  }
+
+  @Test
+  public void testTypeArgumentMultipleThrows() {
+    testParsingAndPrintingEqual(
+        "<T:Ljava/lang/Object;R::LConsumer<TT;>;>([Lfoo<TR;>;)Lbaz<TR;>;^TR;^Lfoo<TT;>;");
+  }
+
+  @Test
+  public void testFormalTypeParameterEmptyInterfaceError() {
+    testParsingAndPrintingError("<T:Ljava/lang/Object;:>Lfoo/bar/baz<TT;>;")
+        .assertAllWarningsMatch(
+            diagnosticMessage(
+                containsString("Invalid signature '<T:Ljava/lang/Object;:>Lfoo/bar/baz<TT;>;'")));
+  }
+
+  private void testParsingAndPrintingEqual(String signature) {
+    MethodTypeSignature parsed =
+        GenericSignature.parseMethodSignature(
+            "A", signature, Origin.unknown(), new DexItemFactory(), new Reporter());
+    GenericSignaturePrinter genericSignaturePrinter =
+        new GenericSignaturePrinter(NamingLens.getIdentityLens(), alwaysFalse());
+    genericSignaturePrinter.visitMethodSignature(parsed);
+    String outSignature = genericSignaturePrinter.toString();
+    assertEquals(signature, outSignature);
+  }
+
+  private TestDiagnosticMessages testParsingAndPrintingError(String signature) {
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+    MethodTypeSignature parsed =
+        GenericSignature.parseMethodSignature(
+            "A",
+            signature,
+            Origin.unknown(),
+            new DexItemFactory(),
+            new Reporter(testDiagnosticMessages));
+    assertEquals(MethodTypeSignature.noSignature(), 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 99b186f..4e41e64 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
@@ -13,6 +13,7 @@
 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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -36,7 +37,7 @@
           Collections.emptyList(),
           null,
           Collections.emptyList(),
-          ClassSignature.NO_CLASS_SIGNATURE,
+          ClassSignature.noSignature(),
           DexAnnotationSet.empty(),
           DexEncodedField.EMPTY_ARRAY,
           DexEncodedField.EMPTY_ARRAY,
@@ -55,6 +56,7 @@
             new DexEncodedMethod(
                 signature,
                 MethodAccessFlags.fromDexAccessFlags(0),
+                MethodTypeSignature.noSignature(),
                 DexAnnotationSet.empty(),
                 ParameterAnnotationsList.empty(),
                 null));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/PruneNotNullBranchD8Test.java b/src/test/java/com/android/tools/r8/ir/optimize/PruneNotNullBranchD8Test.java
index 870787f..ae08b92 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/PruneNotNullBranchD8Test.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/PruneNotNullBranchD8Test.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
@@ -48,8 +49,13 @@
               assertThat(main, isPresent());
               MethodSubject mainMethod = main.mainMethod();
               assertThat(mainMethod, isPresent());
-              // TODO(b/170060113): We should be able to remove the throw.
-              assertTrue(mainMethod.streamInstructions().anyMatch(InstructionSubject::isThrow));
+              if (parameters.isDexRuntime()) {
+                assertFalse(mainMethod.streamInstructions().anyMatch(InstructionSubject::isThrow));
+              } else {
+                assert parameters.isCfRuntime();
+                // We are not going through IR when running in CF
+                assertTrue(mainMethod.streamInstructions().anyMatch(InstructionSubject::isThrow));
+              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
new file mode 100644
index 0000000..ec4c859
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.outliner;
+
+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.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class OutlineFromStaticInterfaceMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public OutlineFromStaticInterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              if (parameters.isCfRuntime()) {
+                assert !options.outline.enabled;
+                options.outline.enabled = true;
+              }
+              options.outline.threshold = 2;
+              options.outline.minSize = 2;
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!", "Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject interfaceSubject =
+        parameters.isCfRuntime()
+                || parameters
+                    .getApiLevel()
+                    .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())
+            ? inspector.clazz(I.class)
+            : inspector.clazz(
+                I.class.getTypeName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX);
+    assertThat(interfaceSubject, isPresent());
+
+    MethodSubject greetMethodSubject = interfaceSubject.uniqueMethodWithName("greet");
+    assertThat(greetMethodSubject, isPresent());
+    assertEquals(
+        1,
+        greetMethodSubject.streamInstructions().filter(InstructionSubject::isInvokeStatic).count());
+  }
+
+  static class TestClass {
+
+    public static void main(String... args) {
+      greet();
+      I.greet();
+    }
+
+    @NeverInline
+    static void greet() {
+      Greeter.hello();
+      Greeter.world();
+    }
+  }
+
+  interface I {
+
+    @NeverInline
+    static void greet() {
+      Greeter.hello();
+      Greeter.world();
+    }
+  }
+
+  @NeverClassInline
+  public static class Greeter {
+
+    @NeverInline
+    public static void hello() {
+      System.out.print("Hello");
+    }
+
+    @NeverInline
+    public static void world() {
+      System.out.println(" world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java
new file mode 100644
index 0000000..eb7b242
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java
@@ -0,0 +1,122 @@
+// 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.kotlin.lambda;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.optimize.lambda.kotlin.JStyleLambdaGroupIdFactory;
+import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
+import com.android.tools.r8.kotlin.lambda.JStyleKotlinLambdaMergingWithEnumUnboxingTest.Main.EnumUnboxingCandidate;
+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 JStyleKotlinLambdaMergingWithEnumUnboxingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public JStyleKotlinLambdaMergingWithEnumUnboxingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addDefaultRuntimeLibrary(parameters)
+        .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options ->
+                options.testing.kotlinLambdaMergerFactoryForClass =
+                    this::getKotlinLambdaMergerFactoryForClass)
+        .addHorizontallyMergedLambdaClassesInspector(
+            inspector -> inspector.assertMerged(Lambda1.class, Lambda2.class))
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(EnumUnboxingCandidate.class))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Lambda1.method()", "Lambda2.method()");
+  }
+
+  private KotlinLambdaGroupIdFactory getKotlinLambdaMergerFactoryForClass(DexProgramClass clazz) {
+    String typeName = clazz.getType().toSourceString();
+    if (typeName.equals(Lambda1.class.getTypeName())
+        || typeName.equals(Lambda2.class.getTypeName())) {
+      return JStyleLambdaGroupIdFactory.getInstance();
+    }
+    return null;
+  }
+
+  static class Main {
+
+    @NeverClassInline
+    public enum EnumUnboxingCandidate {
+      LAMBDA1,
+      LAMBDA2
+    }
+
+    public static void main(String[] args) {
+      accept(createLambda(EnumUnboxingCandidate.LAMBDA1));
+      accept(createLambda(EnumUnboxingCandidate.LAMBDA2));
+    }
+
+    @NeverInline
+    static I createLambda(EnumUnboxingCandidate value) {
+      switch (value) {
+        case LAMBDA1:
+          return new Lambda1();
+        case LAMBDA2:
+          return new Lambda2();
+        default:
+          throw new RuntimeException();
+      }
+    }
+
+    @NeverInline
+    static void accept(I instance) {
+      instance.method();
+    }
+  }
+
+  interface I {
+
+    void method();
+  }
+
+  @NeverClassInline
+  public static final class Lambda1 implements I {
+
+    @NeverInline
+    @Override
+    public final void method() {
+      System.out.println("Lambda1.method()");
+    }
+  }
+
+  @NeverClassInline
+  public static final class Lambda2 implements I {
+
+    @NeverInline
+    @Override
+    public final void method() {
+      System.out.println("Lambda2.method()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
new file mode 100644
index 0000000..61abad2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
@@ -0,0 +1,129 @@
+// 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.kotlin.lambda;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.optimize.lambda.kotlin.KStyleLambdaGroupIdFactory;
+import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
+import com.android.tools.r8.kotlin.lambda.KStyleKotlinLambdaMergingWithEnumUnboxingTest.Main.EnumUnboxingCandidate;
+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 KStyleKotlinLambdaMergingWithEnumUnboxingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public KStyleKotlinLambdaMergingWithEnumUnboxingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options ->
+                options.testing.kotlinLambdaMergerFactoryForClass =
+                    this::getKotlinLambdaMergerFactoryForClass)
+        .addHorizontallyMergedLambdaClassesInspector(
+            inspector -> inspector.assertMerged(Lambda1.class, Lambda2.class))
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(EnumUnboxingCandidate.class))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Lambda1.method()", "Lambda2.method()");
+  }
+
+  private KotlinLambdaGroupIdFactory getKotlinLambdaMergerFactoryForClass(DexProgramClass clazz) {
+    String typeName = clazz.getType().toSourceString();
+    if (typeName.equals(Lambda1.class.getTypeName())
+        || typeName.equals(Lambda2.class.getTypeName())) {
+      return KStyleLambdaGroupIdFactory.getInstance();
+    }
+    return null;
+  }
+
+  static class Main {
+
+    @NeverClassInline
+    public enum EnumUnboxingCandidate {
+      LAMBDA1,
+      LAMBDA2
+    }
+
+    public static void main(String[] args) {
+      accept(createLambda(EnumUnboxingCandidate.LAMBDA1));
+      accept(createLambda(EnumUnboxingCandidate.LAMBDA2));
+    }
+
+    @NeverInline
+    static kotlin.jvm.functions.Function0<kotlin.Unit> createLambda(EnumUnboxingCandidate value) {
+      switch (value) {
+        case LAMBDA1:
+          return new Lambda1();
+        case LAMBDA2:
+          return new Lambda2();
+        default:
+          throw new RuntimeException();
+      }
+    }
+
+    @NeverInline
+    static void accept(kotlin.jvm.functions.Function0<kotlin.Unit> instance) {
+      instance.invoke();
+    }
+  }
+
+  @NeverClassInline
+  public static final class Lambda1 extends kotlin.jvm.internal.Lambda<kotlin.Unit>
+      implements kotlin.jvm.functions.Function0<kotlin.Unit> {
+
+    public Lambda1() {
+      super(0);
+    }
+
+    @NeverInline
+    @Override
+    public final kotlin.Unit invoke() {
+      System.out.println("Lambda1.method()");
+      return null;
+    }
+  }
+
+  @NeverClassInline
+  public static final class Lambda2 extends kotlin.jvm.internal.Lambda<kotlin.Unit>
+      implements kotlin.jvm.functions.Function0<kotlin.Unit> {
+
+    public Lambda2() {
+      super(0);
+    }
+
+    @NeverInline
+    @Override
+    public final kotlin.Unit invoke() {
+      System.out.println("Lambda2.method()");
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
index 3705c2d..2498467 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
@@ -302,7 +302,6 @@
 
   @Test
   public void testTrivialKs() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final String mainClassName = "lambdas_kstyle_trivial.MainKt";
     runTest(
         "lambdas_kstyle_trivial",
@@ -384,7 +383,6 @@
 
   @Test
   public void testGenericsNoSignatureKs() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     final String mainClassName = "lambdas_kstyle_generics.MainKt";
     runTest(
         "lambdas_kstyle_generics",
@@ -413,7 +411,6 @@
 
   @Test
   public void testInnerClassesAndEnclosingMethodsKs() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     final String mainClassName = "lambdas_kstyle_generics.MainKt";
     runTest(
         "lambdas_kstyle_generics",
@@ -478,7 +475,6 @@
 
   @Test
   public void testTrivialJs() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final String mainClassName = "lambdas_jstyle_trivial.MainKt";
     runTest(
         "lambdas_jstyle_trivial",
@@ -528,7 +524,6 @@
 
   @Test
   public void testSingleton() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final String mainClassName = "lambdas_singleton.MainKt";
     runTest(
         "lambdas_singleton",
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 fd09ef3..da157c2 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -47,6 +47,7 @@
 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.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -831,7 +832,7 @@
               Collections.emptyList(),
               null,
               Collections.emptyList(),
-              ClassSignature.NO_CLASS_SIGNATURE,
+              ClassSignature.noSignature(),
               DexAnnotationSet.empty(),
               DexEncodedField.EMPTY_ARRAY,
               DexEncodedField.EMPTY_ARRAY,
@@ -857,6 +858,7 @@
             new DexEncodedMethod(
                 voidReturnMethod,
                 access,
+                MethodTypeSignature.noSignature(),
                 DexAnnotationSet.empty(),
                 ParameterAnnotationsList.empty(),
                 code);
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index a275bdb..f88443d 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -24,12 +27,13 @@
 import static org.objectweb.asm.Opcodes.RETURN;
 import static org.objectweb.asm.Opcodes.V1_8;
 
-import com.android.tools.r8.DiagnosticsChecker;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -47,6 +51,180 @@
 
 @RunWith(Parameterized.class)
 public class MinifierMethodSignatureTest extends TestBase {
+
+  private final String genericSignature = "<T:Ljava/lang/Throwable;>(TT;LMethods<TT;>.Inner;)TT;";
+  private final String parameterizedReturnSignature = "()LMethods<TX;>.Inner;";
+  private final String parameterizedArgumentsSignature = "(TX;LMethods<TX;>.Inner;)V";
+  private final String parametrizedThrowsSignature = "()V^TX;";
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MinifierMethodSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void originalJavacSignatures() throws Exception {
+    // Test using the signatures generated by javac.
+    runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
+  }
+
+  @Test
+  public void signatureEmpty() throws Exception {
+    testSingleMethod(
+        "generic",
+        "",
+        this::noWarnings,
+        inspector -> {
+          noSignatureAttribute(lookupGeneric(inspector));
+        });
+  }
+
+  @Test
+  public void signatureInvalid() throws Exception {
+    testSingleMethod(
+        "generic",
+        "X",
+        diagnostics -> {
+          diagnostics.assertWarningsCount(1);
+          diagnostics.assertWarningsMatch(
+              diagnosticMessage(
+                  allOf(
+                      containsString("Invalid signature 'X' for method generic"),
+                      containsString("Expected ( at position 1"))));
+        },
+        inspector -> noSignatureAttribute(lookupGeneric(inspector)));
+  }
+
+  @Test
+  public void classNotFound() throws Exception {
+    String signature = "<T:LNotFound;>(TT;LAlsoNotFound<TT;>.InnerNotFound.InnerAlsoNotFound;)TT;";
+    testSingleMethod(
+        "generic",
+        signature,
+        this::noWarnings,
+        inspector -> {
+          ClassSubject methods = inspector.clazz("Methods");
+          MethodSubject method =
+              methods.method(
+                  "java.lang.Throwable",
+                  "generic",
+                  ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
+          assertThat(inspector.clazz("NotFound"), not(isPresent()));
+          assertEquals(signature, method.getOriginalSignatureAttribute());
+        });
+  }
+
+  @Test
+  public void multipleWarnings() throws Exception {
+    runTest(
+        ImmutableMap.of(
+            "generic", "X",
+            "parameterizedReturn", "X",
+            "parameterizedArguments", "X"),
+        diagnostics -> {
+          diagnostics.assertWarningsCount(3);
+        },
+        inspector -> {
+          noSignatureAttribute(lookupGeneric(inspector));
+          noSignatureAttribute(lookupParameterizedReturn(inspector));
+          noSignatureAttribute(lookupParameterizedArguments(inspector));
+        });
+  }
+
+  private void testSingleMethod(
+      String name,
+      String signature,
+      Consumer<TestDiagnosticMessages> diagnostics,
+      Consumer<CodeInspector> inspector)
+      throws Exception {
+    ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
+    runTest(signatures, diagnostics, inspector);
+  }
+
+  private void isOriginUnknown(Origin origin) {
+    assertSame(Origin.unknown(), origin);
+  }
+
+  private void noWarnings(TestDiagnosticMessages messages) {
+    messages.assertNoWarnings();
+  }
+
+  private void noInspection(CodeInspector inspector) {}
+
+  private void noSignatureAttribute(MethodSubject method) {
+    assertThat(method, isPresent());
+    assertNull(method.getFinalSignatureAttribute());
+    assertNull(method.getOriginalSignatureAttribute());
+  }
+
+  public void runTest(
+      ImmutableMap<String, String> signatures,
+      Consumer<TestDiagnosticMessages> diagnostics,
+      Consumer<CodeInspector> inspect)
+      throws Exception {
+
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(dumpMethods(signatures), dumpInner())
+            .addKeepAttributes(
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD,
+                ProguardKeepAttributes.SIGNATURE)
+            .addKeepAllClassesRuleWithAllowObfuscation()
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(
+                internalOptions ->
+                    internalOptions.testing.disableMappingToOriginalProgramVerification = true)
+            .allowDiagnosticMessages()
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+
+    // All classes are kept, and renamed.
+    ClassSubject clazz = inspector.clazz("Methods");
+    assertThat(clazz, isPresentAndRenamed());
+    assertThat(inspector.clazz("Methods$Inner"), isPresentAndRenamed());
+
+    MethodSubject generic = lookupGeneric(inspector);
+    MethodSubject parameterizedReturn = lookupParameterizedReturn(inspector);
+    MethodSubject parameterizedArguments = lookupParameterizedArguments(inspector);
+    MethodSubject parametrizedThrows =
+        clazz.method("void", "parametrizedThrows", ImmutableList.of());
+
+    // Check that all methods have been renamed
+    assertThat(generic, isPresentAndRenamed());
+    assertThat(parameterizedReturn, isPresentAndRenamed());
+    assertThat(parameterizedArguments, isPresentAndRenamed());
+    assertThat(parametrizedThrows, isPresentAndRenamed());
+
+    // Test that methods have their original signature if the default was provided.
+    if (!signatures.containsKey("generic")) {
+      assertEquals(genericSignature, generic.getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("parameterizedReturn")) {
+      assertEquals(
+          parameterizedReturnSignature, parameterizedReturn.getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("parameterizedArguments")) {
+      assertEquals(
+          parameterizedArgumentsSignature, parameterizedArguments.getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("parametrizedThrows")) {
+      assertEquals(parametrizedThrowsSignature, parametrizedThrows.getOriginalSignatureAttribute());
+    }
+
+    inspect.accept(inspector);
+    compileResult.getDiagnosticMessages().assertNoErrors();
+    compileResult.getDiagnosticMessages().assertNoInfos();
+    diagnostics.accept(compileResult.getDiagnosticMessages());
+  }
+
   /*
 
   class Methods<X extends Throwable> {
@@ -59,23 +237,7 @@
   }
 
   */
-
-  private String genericSignature = "<T:Ljava/lang/Throwable;>(TT;LMethods<TT;>.Inner;)TT;";
-  private String parameterizedReturnSignature = "()LMethods<TX;>.Inner;";
-  private String parameterizedArgumentsSignature = "(TX;LMethods<TX;>.Inner;)V";
-  private String parametrizedThrowsSignature = "()V^TX;";
-  private Backend backend;
-
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
-  }
-
-  public MinifierMethodSignatureTest(Backend backend) {
-    this.backend = backend;
-  }
-
-  private byte[] dumpMethods(Map<String, String> signatures) throws Exception {
+  private byte[] dumpMethods(Map<String, String> signatures) {
 
     ClassWriter cw = new ClassWriter(0);
     MethodVisitor mv;
@@ -192,140 +354,4 @@
         "void", "parameterizedArguments", ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
   }
 
-  public void runTest(
-      ImmutableMap<String, String> signatures,
-      Consumer<DiagnosticsChecker> diagnostics,
-      Consumer<CodeInspector> inspect)
-      throws Exception {
-    DiagnosticsChecker checker = new DiagnosticsChecker();
-    CodeInspector inspector =
-        new CodeInspector(
-            ToolHelper.runR8(
-                R8Command.builder(checker)
-                    .addClassProgramData(dumpMethods(signatures), Origin.unknown())
-                    .addClassProgramData(dumpInner(), Origin.unknown())
-                    .addProguardConfiguration(
-                        ImmutableList.of(
-                            "-keepattributes InnerClasses,EnclosingMethod,Signature",
-                            "-keep,allowobfuscation class ** { *; }"),
-                        Origin.unknown())
-                    .setProgramConsumer(emptyConsumer(backend))
-                    .addLibraryFiles(runtimeJar(backend))
-                    .setProguardMapConsumer(StringConsumer.emptyConsumer())
-                    .build()));
-    // All classes are kept, and renamed.
-    ClassSubject clazz = inspector.clazz("Methods");
-    assertThat(clazz, isPresentAndRenamed());
-    assertThat(inspector.clazz("Methods$Inner"), isPresentAndRenamed());
-
-    MethodSubject generic = lookupGeneric(inspector);
-    MethodSubject parameterizedReturn = lookupParameterizedReturn(inspector);
-    MethodSubject parameterizedArguments = lookupParameterizedArguments(inspector);
-    MethodSubject parametrizedThrows =
-        clazz.method("void", "parametrizedThrows", ImmutableList.of());
-
-    // Check that all methods have been renamed
-    assertThat(generic, isPresentAndRenamed());
-    assertThat(parameterizedReturn, isPresentAndRenamed());
-    assertThat(parameterizedArguments, isPresentAndRenamed());
-    assertThat(parametrizedThrows, isPresentAndRenamed());
-
-    // Test that methods have their original signature if the default was provided.
-    if (!signatures.containsKey("generic")) {
-      assertEquals(genericSignature, generic.getOriginalSignatureAttribute());
-    }
-    if (!signatures.containsKey("parameterizedReturn")) {
-      assertEquals(
-          parameterizedReturnSignature, parameterizedReturn.getOriginalSignatureAttribute());
-    }
-    if (!signatures.containsKey("parameterizedArguments")) {
-      assertEquals(
-          parameterizedArgumentsSignature, parameterizedArguments.getOriginalSignatureAttribute());
-    }
-    if (!signatures.containsKey("parametrizedThrows")) {
-      assertEquals(
-          parametrizedThrowsSignature, parametrizedThrows.getOriginalSignatureAttribute());
-    }
-
-    diagnostics.accept(checker);
-    inspect.accept(inspector);
-  }
-
-  private void testSingleMethod(String name, String signature,
-      Consumer<DiagnosticsChecker> diagnostics,
-      Consumer<CodeInspector> inspector)
-      throws Exception {
-    ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
-    runTest(signatures, diagnostics, inspector);
-  }
-
-  private void isOriginUnknown(Origin origin) {
-    assertSame(Origin.unknown(), origin);
-  }
-
-  private void noWarnings(DiagnosticsChecker checker) {
-    assertEquals(0, checker.warnings.size());
-  }
-
-  private void noInspection(CodeInspector inspector) {
-  }
-
-  private void noSignatureAttribute(MethodSubject method) {
-    assertThat(method, isPresent());
-    assertNull(method.getFinalSignatureAttribute());
-    assertNull(method.getOriginalSignatureAttribute());
-  }
-
-  @Test
-  public void originalJavacSignatures() throws Exception {
-    // Test using the signatures generated by javac.
-    runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
-  }
-
-  @Test
-  public void signatureEmpty() throws Exception {
-    testSingleMethod("generic", "", this::noWarnings, inspector -> {
-      noSignatureAttribute(lookupGeneric(inspector));
-    });
-  }
-
-  @Test
-  public void signatureInvalid() throws Exception {
-    testSingleMethod("generic", "X", diagnostics -> {
-      assertEquals(1, diagnostics.warnings.size());
-      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature 'X' for method",
-          "java.lang.Throwable Methods.generic(java.lang.Throwable, Methods$Inner)",
-          "Expected ( at position 1");
-    }, inspector -> noSignatureAttribute(lookupGeneric(inspector)));
-  }
-
-  @Test
-  public void classNotFound() throws Exception {
-    String signature = "<T:LNotFound;>(TT;LAlsoNotFound<TT;>.InnerNotFound.InnerAlsoNotFound;)TT;";
-    testSingleMethod("generic", signature, this::noWarnings,
-        inspector -> {
-          ClassSubject methods = inspector.clazz("Methods");
-          MethodSubject method =
-              methods.method("java.lang.Throwable", "generic",
-                  ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
-          assertThat(inspector.clazz("NotFound"), not(isPresent()));
-          assertEquals(signature, method.getOriginalSignatureAttribute());
-        });
-  }
-
-  @Test
-  public void multipleWarnings() throws Exception {
-    runTest(ImmutableMap.of(
-        "generic", "X",
-        "parameterizedReturn", "X",
-        "parameterizedArguments", "X"
-    ), diagnostics -> {
-      assertEquals(3, diagnostics.warnings.size());
-    }, inspector -> {
-      noSignatureAttribute(lookupGeneric(inspector));
-      noSignatureAttribute(lookupParameterizedReturn(inspector));
-      noSignatureAttribute(lookupParameterizedArguments(inspector));
-    });
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
index 8757685..6ababf1 100644
--- a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
+++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -10,19 +10,15 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+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 java.lang.reflect.Method;
@@ -50,17 +46,7 @@
     this.minification = minification;
   }
 
-  private void checkSignatureAnnotation(CodeInspector inspector, AnnotationSubject signature) {
-    DexAnnotationElement[] elements = signature.getAnnotation().elements;
-    assertEquals(1, elements.length);
-    assertEquals("value", elements[0].name.toString());
-    assertTrue(elements[0].value.isDexValueArray());
-    DexValueArray array = elements[0].value.asDexValueArray();
-    StringBuilder builder = new StringBuilder();
-    for (DexValue value : array.getValues()) {
-      assertTrue(value.isDexValueString());
-      builder.append(value.asDexValueString().value);
-    }
+  private void checkSignature(CodeInspector inspector, String signature) {
     String fooImplFinalDescriptor =
         DescriptorUtils.javaTypeToDescriptor(inspector.clazz(FooImpl.class).getFinalName());
     StringBuilder expected =
@@ -71,7 +57,7 @@
             .append("<Ljava/lang/String;>")
             // Add the ; after the generic type.
             .append(";");
-    assertEquals(expected.toString(), builder.toString());
+    assertEquals(expected.toString(), signature);
   }
 
   @Test
@@ -90,14 +76,16 @@
             .inspect(
                 inspector -> {
                   assertThat(inspector.clazz(Main.class), isPresentAndNotRenamed());
-                  assertThat(inspector.clazz(Service.class), isPresentAndRenamed(minification));
                   assertThat(inspector.clazz(Foo.class), not(isPresent()));
                   assertThat(inspector.clazz(FooImpl.class), isPresentAndRenamed(minification));
+                  ClassSubject serviceClass = inspector.clazz(Service.class);
+                  assertThat(serviceClass, isPresentAndRenamed(minification));
                   // TODO(124477502): Using uniqueMethodWithName("fooList") does not work.
-                  assertEquals(1, inspector.clazz(Service.class).allMethods().size());
-                  MethodSubject fooList = inspector.clazz(Service.class).allMethods().get(0);
-                  AnnotationSubject signature = fooList.annotation("dalvik.annotation.Signature");
-                  checkSignatureAnnotation(inspector, signature);
+                  assertEquals(1, serviceClass.allMethods().size());
+                  MethodSubject fooList = serviceClass.allMethods().get(0);
+                  assertThat(fooList, isPresent());
+                  checkSignature(
+                      inspector, fooList.asFoundMethodSubject().getFinalSignatureAttribute());
                 });
 
     String fooImplFinalName = compileResult.inspector().clazz(FooImpl.class).getFinalName();
diff --git a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
index adf7ba7..e945182 100644
--- a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.naming.signature;
 
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
@@ -71,18 +70,13 @@
   }
 
   @Test
-  public void testR8WithAssertEnabled() {
-    // TODO(b/154793333): Enable assertions always when resolved.
-    assertThrows(
-        AssertionError.class,
-        () -> {
-          test(
-              testForR8(parameters.getBackend())
-                  .addKeepRules("-dontobfuscate")
-                  .addOptionsModification(
-                      internalOptions ->
-                          internalOptions.testing.assertConsistentRenamingOfSignature = true));
-        });
+  public void testR8WithAssertEnabled() throws Exception {
+    test(
+        testForR8(parameters.getBackend())
+            .addKeepRules("-dontobfuscate")
+            .addOptionsModification(
+                internalOptions ->
+                    internalOptions.testing.assertConsistentRenamingOfSignature = true));
   }
 
   private void test(R8TestBuilder<?> builder) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 15ed0d9..85e6cbc 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -38,6 +39,8 @@
 public class RetraceCommandLineTests {
 
   private static final boolean testExternal = false;
+  private static final String WAITING_MESSAGE =
+      "Waiting for stack-trace input..." + StringUtils.LINE_SEPARATOR;
 
   @Rule public TemporaryFolder folder = new TemporaryFolder();
 
@@ -64,6 +67,18 @@
   }
 
   @Test
+  public void testInvalidMappingFile() throws IOException {
+    Path mappingFile = folder.newFile("mapping.txt").toPath();
+    Files.write(mappingFile, "foo.bar.baz <- is invalid mapping".getBytes());
+    Path stackTraceFile = folder.newFile("stacktrace.txt").toPath();
+    Files.write(stackTraceFile, new byte[0]);
+    runAbortTest(
+        containsString("Unable to parse mapping file"),
+        mappingFile.toString(),
+        stackTraceFile.toString());
+  }
+
+  @Test
   public void testVerbose() throws IOException {
     FoundMethodVerboseStackTrace stackTrace = new FoundMethodVerboseStackTrace();
     runTest(
@@ -135,7 +150,7 @@
   public void testHelp() throws IOException {
     ProcessResult processResult = runRetraceCommandLine(null, Arrays.asList("--help"));
     assertEquals(0, processResult.exitCode);
-    assertEquals(Retrace.USAGE_MESSAGE, processResult.stdout);
+    assertThat(processResult.stdout, containsString(Retrace.USAGE_MESSAGE));
   }
 
   @Test
@@ -155,6 +170,12 @@
     runTest("", SMILEY_EMOJI, true, SMILEY_EMOJI + StringUtils.LINE_SEPARATOR);
   }
 
+  @Test
+  public void testHelpMessageOnStdIn() throws IOException {
+    ProcessResult processResult = runRetrace("", "", true);
+    assertTrue(processResult.stdout.startsWith(WAITING_MESSAGE));
+  }
+
   private final String nonMappableStackTrace =
       StringUtils.lines(
           "com.android.r8.R8Exception: Problem when compiling program",
@@ -171,7 +192,12 @@
       throws IOException {
     ProcessResult result = runRetrace(mapping, stackTrace, stacktraceStdIn, args);
     assertEquals(0, result.exitCode);
-    assertEquals(expected, result.stdout);
+    String stdOut = result.stdout;
+    if (stacktraceStdIn) {
+      assertTrue(result.stdout.startsWith(WAITING_MESSAGE));
+      stdOut = result.stdout.substring(WAITING_MESSAGE.length());
+    }
+    assertEquals(expected, stdOut);
   }
 
   private void runAbortTest(Matcher<String> errorMatch, String... args) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java
new file mode 100644
index 0000000..3a6ffe6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.retrace.mappings.FieldsWithSameMinifiedNameMapping;
+import com.android.tools.r8.retrace.mappings.MappingForTest;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class RetraceFieldTests {
+
+  @Test
+  public void testFieldsWithSameMinifiedName() throws Exception {
+    FieldsWithSameMinifiedNameMapping mapping = new FieldsWithSameMinifiedNameMapping();
+    runRetraceTest(mapping, mapping::inspect);
+  }
+
+  private void runRetraceTest(MappingForTest mappingForTest, Consumer<RetraceApi> inspection)
+      throws Exception {
+    inspection.accept(Retracer.create(mappingForTest::mapping, new TestDiagnosticMessagesImpl()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 731e807..bffcd14 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.retrace.Retrace.RetraceAbortException;
 import com.android.tools.r8.retrace.stacktraces.ActualBotStackTraceBase;
 import com.android.tools.r8.retrace.stacktraces.ActualIdentityStackTrace;
@@ -203,11 +202,8 @@
 
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<RetraceApi> inspection) throws Exception {
-    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
-    ClassNameMapper classNameMapper =
-        ClassNameMapper.mapperFromString(stackTraceForTest.mapping(), diagnosticsHandler);
-    RetraceApi retracer = Retracer.create(classNameMapper);
-    inspection.accept(retracer);
+    inspection.accept(
+        Retracer.create(stackTraceForTest::mapping, new TestDiagnosticMessagesImpl()));
   }
 
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
diff --git a/src/test/java/com/android/tools/r8/retrace/mappings/FieldsWithSameMinifiedNameMapping.java b/src/test/java/com/android/tools/r8/retrace/mappings/FieldsWithSameMinifiedNameMapping.java
new file mode 100644
index 0000000..488a00a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/mappings/FieldsWithSameMinifiedNameMapping.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.mappings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceApi;
+import com.android.tools.r8.retrace.RetraceFieldResult;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class FieldsWithSameMinifiedNameMapping implements MappingForTest {
+
+  @Override
+  public String mapping() {
+    // TODO(b/169953605): Add enough information to the map to allow precise retracing.
+    return StringUtils.lines(
+        "foo.bar.Baz -> foo.bar.Baz:", "  java.lang.Object f1 -> a", "  java.lang.String f2 -> a");
+  }
+
+  public void inspect(RetraceApi retracer) {
+    FieldReference f1FieldReference =
+        Reference.field(
+            Reference.classFromTypeName("foo.bar.Baz"),
+            "f1",
+            Reference.classFromTypeName("java.lang.Object"));
+    FieldReference f2FieldReference =
+        Reference.field(
+            Reference.classFromTypeName("foo.bar.Baz"),
+            "f2",
+            Reference.classFromTypeName("java.lang.String"));
+
+    FieldReference mappedF1FieldReference =
+        Reference.field(
+            Reference.classFromTypeName("foo.bar.Baz"),
+            "a",
+            Reference.classFromTypeName("java.lang.Object"));
+
+    RetraceFieldResult result = retracer.retrace(mappedF1FieldReference);
+    // TODO(b/169829306): Result should not be ambigious.
+    assertTrue(result.isAmbiguous());
+
+    List<FieldReference> retracedFields =
+        result.stream()
+            .map(f -> f.getField().asKnown().getFieldReference())
+            .collect(Collectors.toList());
+    assertEquals(ImmutableList.of(f1FieldReference, f2FieldReference), retracedFields);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/mappings/MappingForTest.java b/src/test/java/com/android/tools/r8/retrace/mappings/MappingForTest.java
new file mode 100644
index 0000000..8008ee4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/mappings/MappingForTest.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.mappings;
+
+public interface MappingForTest {
+
+  String mapping();
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java
index 9071654..3ca3297 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MemberFieldOverlapStackTrace.java
@@ -4,15 +4,18 @@
 
 package com.android.tools.r8.retrace.stacktraces;
 
+import static junit.framework.TestCase.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.RetraceApi;
 import com.android.tools.r8.retrace.RetraceFieldResult;
+import com.android.tools.r8.retrace.RetraceFieldResult.Element;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 
 public class MemberFieldOverlapStackTrace implements StackTraceForTest {
 
@@ -41,8 +44,11 @@
 
   public void inspectField(RetraceApi retracer) {
     RetraceFieldResult result =
-        retracer.retrace(Reference.classFromTypeName("a.A")).lookupField("field");
-    assertTrue(result.stream().findAny().isPresent());
+        retracer.retrace(Reference.classFromTypeName("a.A")).lookupField("a");
     assertFalse(result.isAmbiguous());
+    assertEquals(1, result.stream().count());
+    Optional<Element> field = result.stream().findFirst();
+    assertTrue(field.isPresent());
+    assertEquals("field", field.get().getField().getFieldName());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsCheckerUtils.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsCheckerUtils.java
new file mode 100644
index 0000000..c9236d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsCheckerUtils.java
@@ -0,0 +1,33 @@
+// 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.rewrite.assertions;
+
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
+
+public class AssertionsCheckerUtils {
+
+  static void checkAssertionCodeEnabled(ClassSubject subject, String methodName) {
+    MatcherAssert.assertThat(subject, Matchers.isPresent());
+    // <clinit> is removed by R8.
+    if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
+      Assert.assertFalse(
+          subject
+              .uniqueMethodWithName("<clinit>")
+              .streamInstructions()
+              .anyMatch(InstructionSubject::isStaticPut));
+    }
+    Assert.assertTrue(
+        subject
+            .uniqueMethodWithName(methodName)
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isThrow));
+  }
+
+  private AssertionsCheckerUtils() {}
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
new file mode 100644
index 0000000..11829f6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
@@ -0,0 +1,135 @@
+// 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.rewrite.assertions;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.rewrite.assertions.testclasses.A;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class AssertionsConfigurationJacocoTest extends TestBase implements Opcodes {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public AssertionsConfigurationJacocoTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private void checkAssertionCodeEnabled(CodeInspector inspector) {
+    AssertionsCheckerUtils.checkAssertionCodeEnabled(inspector.clazz(A.class), "m");
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(TestClass.class, MockJacocoInit.class)
+        .addProgramClassFileData(transformClassWithJacocoInstrumentation(A.class))
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions)
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::checkAssertionCodeEnabled)
+        .assertSuccessWithOutputLines("AssertionError in A");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, MockJacocoInit.class)
+        .addProgramClassFileData(transformClassWithJacocoInstrumentation(A.class))
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(A.class, MockJacocoInit.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions)
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::checkAssertionCodeEnabled)
+        .assertSuccessWithOutputLines("AssertionError in A");
+  }
+
+  private byte[] transformClassWithJacocoInstrumentation(Class<?> clazz) throws IOException {
+    // Instrument the class with Jacoco.
+    Path dir = temp.newFolder().toPath();
+    runJacoco(ToolHelper.getClassFileForTestClass(clazz), dir);
+    // Rewrite the invocation of Jacoco initialization (getProbes invocation) for class with a Mock.
+    Path jacoco =
+        dir.resolve(
+            A.class.getTypeName().substring(clazz.getPackage().getName().length() + 1) + ".class");
+    return transformer(jacoco, Reference.classFromClass(clazz))
+        .transformMethodInsnInMethod(
+            "$jacocoInit",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              assertEquals(INVOKESTATIC, opcode);
+              assertEquals("getProbes", name);
+              continuation.visitMethodInsn(
+                  INVOKESTATIC,
+                  DescriptorUtils.getClassBinaryName(MockJacocoInit.class),
+                  "getProbes",
+                  descriptor,
+                  isInterface);
+            })
+        .transform();
+  }
+
+  private void runJacoco(Path input, Path outdir) throws IOException {
+    List<String> cmdline = new ArrayList<>();
+    cmdline.add(TestRuntime.getSystemRuntime().asCf().getJavaExecutable().toString());
+    cmdline.add("-jar");
+    cmdline.add(ToolHelper.JACOCO_CLI);
+    cmdline.add("instrument");
+    cmdline.add(input.toString());
+    cmdline.add("--dest");
+    cmdline.add(outdir.toString());
+    ProcessBuilder builder = new ProcessBuilder(cmdline);
+    ProcessResult javacResult = ToolHelper.runProcess(builder);
+    assertEquals(javacResult.toString(), 0, javacResult.exitCode);
+  }
+
+  public static class MockJacocoInit {
+
+    public static boolean[] getProbes(long a, String b, int c) {
+      // For the test class A an array of size 6 is sufficient.
+      return new boolean[6];
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      try {
+        A.m();
+      } catch (AssertionError e) {
+        System.out.println("AssertionError in A");
+      }
+    }
+  }
+
+  // The test class A cannot be an inner class of AssertionsConfigurationJacocoTest, as then the
+  // desiredAssertionStatus() on the outer AssertionsConfigurationJacocoTest will be used and
+  // and that is not part of the input.
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
index 01c28c1..064cfdb 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationTest.java
@@ -143,25 +143,8 @@
     checkAssertionCodeRemoved(inspector.clazz(clazz));
   }
 
-  private void checkAssertionCodeEnabled(ClassSubject subject) {
-    assertThat(subject, isPresent());
-    // <clinit> is removed by R8.
-    if (subject.uniqueMethodWithName("<clinit>").isPresent()) {
-      assertFalse(
-          subject
-              .uniqueMethodWithName("<clinit>")
-              .streamInstructions()
-              .anyMatch(InstructionSubject::isStaticPut));
-    }
-    assertTrue(
-        subject
-            .uniqueMethodWithName("m")
-            .streamInstructions()
-            .anyMatch(InstructionSubject::isThrow));
-  }
-
   private void checkAssertionCodeEnabled(CodeInspector inspector, Class<?> clazz) {
-    checkAssertionCodeEnabled(inspector.clazz(clazz));
+    AssertionsCheckerUtils.checkAssertionCodeEnabled(inspector.clazz(clazz), "m");
   }
 
   private void checkAssertionCodeLeft(CodeInspector inspector, Class<?> clazz) {
@@ -421,8 +404,8 @@
         .compile()
         .inspect(
             inspector -> {
-              checkAssertionCodeEnabled(inspector.clazz("Class1"));
-              checkAssertionCodeEnabled(inspector.clazz("Class2"));
+              AssertionsCheckerUtils.checkAssertionCodeEnabled(inspector.clazz("Class1"), "m");
+              AssertionsCheckerUtils.checkAssertionCodeEnabled(inspector.clazz("Class2"), "m");
               checkAssertionCodeRemoved(inspector.clazz(class1));
               checkAssertionCodeRemoved(inspector.clazz(class2));
             })
@@ -472,8 +455,10 @@
         .compile()
         .inspect(
             inspector -> {
-              checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.class));
-              checkAssertionCodeEnabled(inspector.clazz(TestClassForInnerClass.InnerClass.class));
+              AssertionsCheckerUtils.checkAssertionCodeEnabled(
+                  inspector.clazz(TestClassForInnerClass.class), "m");
+              AssertionsCheckerUtils.checkAssertionCodeEnabled(
+                  inspector.clazz(TestClassForInnerClass.InnerClass.class), "m");
             })
         .run(parameters.getRuntime(), TestClassForInnerClass.class)
         .assertSuccessWithOutputLines(
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/A.java b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/A.java
new file mode 100644
index 0000000..2115568
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/testclasses/A.java
@@ -0,0 +1,12 @@
+// 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.rewrite.assertions.testclasses;
+
+public class A {
+
+  public static void m() {
+    assert false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index a1a2748..d91a2fd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -39,7 +39,6 @@
 
   @Test
   public void test() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     runTest(
         TreeShaking18Test::unusedRemoved,
         null,
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
index a82fede..b008254 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
@@ -37,7 +37,6 @@
 
   @Test
   public void test() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     runTest(
         null,
         null,
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index d9f3b1d..269c6c2 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -306,6 +306,22 @@
         });
   }
 
+  public ClassFileTransformer setGenericSignature(String newGenericSignature) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            super.visit(version, access, name, newGenericSignature, superName, interfaces);
+          }
+        });
+  }
+
   public ClassFileTransformer setNest(Class<?> host, Class<?>... members) {
     assert !Arrays.asList(members).contains(host);
     return setMinVersion(CfVm.JDK11)
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 efaf58b..1772e48 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
@@ -3,10 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
-import static org.junit.Assert.assertTrue;
-
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.code.Instruction;
@@ -16,7 +15,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -25,8 +23,6 @@
 import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -38,6 +34,7 @@
 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.retrace.DirectClassNameMapperProguardMapProducer;
 import com.android.tools.r8.retrace.RetraceApi;
 import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.AndroidApp;
@@ -227,23 +224,6 @@
     return null;
   }
 
-  public String getFinalSignatureAttribute(DexAnnotationSet annotations) {
-    DexAnnotation annotation = findAnnotation("dalvik.annotation.Signature", annotations);
-    if (annotation == null) {
-      return null;
-    }
-    assert annotation.annotation.elements.length == 1;
-    DexAnnotationElement element = annotation.annotation.elements[0];
-    assert element.value.isDexValueArray();
-    StringBuilder builder = new StringBuilder();
-    DexValueArray valueArray = element.value.asDexValueArray();
-    for (DexValue value : valueArray.getValues()) {
-      assertTrue(value.isDexValueString());
-      builder.append(value.asDexValueString().getValue());
-    }
-    return builder.toString();
-  }
-
   public String getOriginalSignatureAttribute(
       String finalSignature, BiConsumer<GenericSignatureParser, String> parse) {
     if (finalSignature == null || mapping == null) {
@@ -484,6 +464,24 @@
   }
 
   public RetraceApi retrace() {
-    return Retracer.create(mapping == null ? ClassNameMapper.builder().build() : mapping);
+    return Retracer.create(
+        new InternalProguardMapProducer(
+            mapping == null ? ClassNameMapper.builder().build() : mapping),
+        new TestDiagnosticMessagesImpl());
+  }
+
+  public static class InternalProguardMapProducer
+      implements DirectClassNameMapperProguardMapProducer {
+
+    public final ClassNameMapper prebuiltMapper;
+
+    public InternalProguardMapProducer(ClassNameMapper prebuiltMapper) {
+      this.prebuiltMapper = prebuiltMapper;
+    }
+
+    @Override
+    public ClassNameMapper getClassNameMapper() {
+      return prebuiltMapper;
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
new file mode 100644
index 0000000..8e5495e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import static com.android.tools.r8.TestBase.toDexType;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection;
+
+public class EnumUnboxingInspector {
+
+  private final DexItemFactory dexItemFactory;
+  private final EnumValueInfoMapCollection unboxedEnums;
+
+  public EnumUnboxingInspector(
+      DexItemFactory dexItemFactory, EnumValueInfoMapCollection unboxedEnums) {
+    this.dexItemFactory = dexItemFactory;
+    this.unboxedEnums = unboxedEnums;
+  }
+
+  public EnumUnboxingInspector assertUnboxed(Class<? extends Enum<?>> clazz) {
+    assertTrue(unboxedEnums.containsEnum(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
+
+  @SafeVarargs
+  public final EnumUnboxingInspector assertUnboxed(Class<? extends Enum<?>>... classes) {
+    for (Class<? extends Enum<?>> clazz : classes) {
+      assertUnboxed(clazz);
+    }
+    return this;
+  }
+}
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 fce43c7..4504213 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,12 +134,12 @@
   @Override
   public String getOriginalSignatureAttribute() {
     return codeInspector.getOriginalSignatureAttribute(
-        dexField.getFieldSignature().toString(), GenericSignatureParser::parseFieldSignature);
+        getFinalSignatureAttribute(), GenericSignatureParser::parseFieldSignature);
   }
 
   @Override
   public String getFinalSignatureAttribute() {
-    return dexField.getFieldSignature().toString();
+    return dexField.getGenericSignature().toString();
   }
 
   @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 4e49582..4d5e0d3 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,8 +186,7 @@
   @Override
   public String getOriginalSignatureAttribute() {
     return codeInspector.getOriginalSignatureAttribute(
-        codeInspector.getFinalSignatureAttribute(dexMethod.annotations()),
-        GenericSignatureParser::parseMethodSignature);
+        getFinalSignatureAttribute(), GenericSignatureParser::parseMethodSignature);
   }
 
   public DexMethod getOriginalDexMethod(DexItemFactory dexItemFactory) {
@@ -201,7 +200,7 @@
 
   @Override
   public String getFinalSignatureAttribute() {
-    return codeInspector.getFinalSignatureAttribute(dexMethod.annotations());
+    return dexMethod.getGenericSignature().toString();
   }
 
   public Iterable<InstructionSubject> instructions() {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
new file mode 100644
index 0000000..1f14df7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -0,0 +1,49 @@
+// 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.utils.codeinspector;
+
+import static com.android.tools.r8.TestBase.toDexType;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
+
+public class HorizontallyMergedClassesInspector {
+
+  private final DexItemFactory dexItemFactory;
+  private final HorizontallyMergedClasses horizontallyMergedClasses;
+
+  public HorizontallyMergedClassesInspector(
+      DexItemFactory dexItemFactory, HorizontallyMergedClasses horizontallyMergedClasses) {
+    this.dexItemFactory = dexItemFactory;
+    this.horizontallyMergedClasses = horizontallyMergedClasses;
+  }
+
+  public HorizontallyMergedClassesInspector assertMerged(Class<?> clazz) {
+    assertTrue(
+        horizontallyMergedClasses.hasBeenMergedOrIsMergeTarget(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
+
+  public HorizontallyMergedClassesInspector assertMerged(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      assertMerged(clazz);
+    }
+    return this;
+  }
+
+  public HorizontallyMergedClassesInspector assertMergedIntoDifferentType(Class<?> clazz) {
+    assertTrue(
+        horizontallyMergedClasses.hasBeenMergedIntoDifferentType(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
+
+  public HorizontallyMergedClassesInspector assertMergedIntoDifferentType(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      assertMergedIntoDifferentType(clazz);
+    }
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java
new file mode 100644
index 0000000..4b3579f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import static com.android.tools.r8.TestBase.toDexType;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
+
+public class HorizontallyMergedLambdaClassesInspector {
+
+  private final DexItemFactory dexItemFactory;
+  private final HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
+
+  public HorizontallyMergedLambdaClassesInspector(
+      DexItemFactory dexItemFactory,
+      HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses) {
+    this.dexItemFactory = dexItemFactory;
+    this.horizontallyMergedLambdaClasses = horizontallyMergedLambdaClasses;
+  }
+
+  public HorizontallyMergedLambdaClassesInspector assertMerged(Class<?> clazz) {
+    assertTrue(horizontallyMergedLambdaClasses.hasBeenMerged(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
+
+  public HorizontallyMergedLambdaClassesInspector assertMerged(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      assertMerged(clazz);
+    }
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
new file mode 100644
index 0000000..bd64711
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
@@ -0,0 +1,35 @@
+// 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.utils.codeinspector;
+
+import static com.android.tools.r8.TestBase.toDexType;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+
+public class VerticallyMergedClassesInspector {
+
+  private final DexItemFactory dexItemFactory;
+  private final VerticallyMergedClasses verticallyMergedClasses;
+
+  public VerticallyMergedClassesInspector(
+      DexItemFactory dexItemFactory, VerticallyMergedClasses verticallyMergedClasses) {
+    this.dexItemFactory = dexItemFactory;
+    this.verticallyMergedClasses = verticallyMergedClasses;
+  }
+
+  public VerticallyMergedClassesInspector assertMergedIntoSubtype(Class<?> clazz) {
+    assertTrue(verticallyMergedClasses.hasBeenMergedIntoSubtype(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
+
+  public VerticallyMergedClassesInspector assertMergedIntoSubtype(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      assertMergedIntoSubtype(clazz);
+    }
+    return this;
+  }
+}
diff --git a/third_party/jacoco.tar.gz.sha1 b/third_party/jacoco.tar.gz.sha1
deleted file mode 100644
index 8a0462f..0000000
--- a/third_party/jacoco.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b968df99f88434e2a2a35445ac860ee6a2fa16e3
\ No newline at end of file
diff --git a/third_party/jacoco/0.8.2.tar.gz.sha1 b/third_party/jacoco/0.8.2.tar.gz.sha1
new file mode 100644
index 0000000..f33b98a
--- /dev/null
+++ b/third_party/jacoco/0.8.2.tar.gz.sha1
@@ -0,0 +1 @@
+b8ee2e058a52ff8ca73ae9b6a99426baa2be1626
\ No newline at end of file
diff --git a/third_party/jacoco/0.8.6.tar.gz.sha1 b/third_party/jacoco/0.8.6.tar.gz.sha1
new file mode 100644
index 0000000..47fefc1
--- /dev/null
+++ b/third_party/jacoco/0.8.6.tar.gz.sha1
@@ -0,0 +1 @@
+471ccc4aa6d69a684aa400f08dc6a15e17b4ba25
\ No newline at end of file
diff --git a/tools/test.py b/tools/test.py
index bb8dbd2..64d2ae0 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -335,7 +335,12 @@
     runtimes = ['dex-' + art_vm]
     # Only append the "none" runtime and JVMs if running on the "default" DEX VM.
     if art_vm == "default":
-      runtimes.extend(['jdk8', 'jdk9', 'jdk11', 'none'])
+      # TODO(b/170454076): Remove special casing for bot when rex-script has
+      #  been migrated to account for runtimes.
+      if utils.is_bot():
+        runtimes.extend(['jdk11', 'none'])
+      else:
+        runtimes.extend(['jdk8', 'jdk9', 'jdk11', 'none'])
     return_code = gradle.RunGradle(
         gradle_args + [
           '-Pdex_vm=%s' % art_vm + vm_suffix,