Merge commit '1d24aede8c9ed42cd2a06ca2441cad4bc0e4f84a' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 2404763..e58264e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -322,7 +322,6 @@
 
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       timing.begin("Strip unused code");
-      Set<DexType> classesToRetainInnerClassAttributeFor = null;
       RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder =
           new RuntimeTypeCheckInfo.Builder(appView.dexItemFactory());
       try {
@@ -402,11 +401,8 @@
 
           AnnotationRemover annotationRemover =
               annotationRemoverBuilder
-                  .computeClassesToRetainInnerClassAttributeFor(appViewWithLiveness)
                   .build(appViewWithLiveness, removedClasses);
           annotationRemover.ensureValid().run();
-          classesToRetainInnerClassAttributeFor =
-              annotationRemover.getClassesToRetainInnerClassAttributeFor();
           typeVariableRemover.removeDeadGenericSignatureTypeVariables(appView);
           new GenericSignatureRewriter(appView, NamingLens.getIdentityLens())
               .run(appView.appInfo().classes(), executorService);
@@ -656,9 +652,7 @@
                 executorService);
 
             // Remove annotations that refer to types that no longer exist.
-            assert classesToRetainInnerClassAttributeFor != null;
             AnnotationRemover.builder()
-                .setClassesToRetainInnerClassAttributeFor(classesToRetainInnerClassAttributeFor)
                 .build(appView.withLiveness(), removedClasses)
                 .run();
             typeVariableRemover.removeDeadGenericSignatureTypeVariables(appView);
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 0074673..2d0e429 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -969,7 +969,7 @@
   }
 
   public void removeInnerClasses(Predicate<InnerClassAttribute> predicate) {
-    innerClasses.removeIf(predicate::test);
+    innerClasses.removeIf(predicate);
   }
 
   public InnerClassAttribute getInnerClassAttributeForThisClass() {
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index 9cec3cb..1a3984c 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import org.objectweb.asm.ClassWriter;
 
 /**
@@ -89,4 +90,14 @@
         + (enclosingMethod == null ? "null" : enclosingMethod.toSourceString())
         + "]";
   }
+
+  public boolean isEnclosingPinned(AppView<AppInfoWithLiveness> appView) {
+    if (enclosingMethod != null) {
+      return appView.appInfo().isPinned(enclosingMethod);
+    }
+    if (enclosingClass != null) {
+      return appView.appInfo().isPinned(enclosingClass);
+    }
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
index adb256f..c381074 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
@@ -12,7 +12,6 @@
 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.StarFieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
 import com.android.tools.r8.utils.ListUtils;
@@ -133,9 +132,7 @@
     if (typeArguments.isEmpty() || !hasGenericTypeParameters.test(type)) {
       return getEmptyTypeArguments();
     }
-    // Wildcards can only be called be used in certain positions:
-    // https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html
-    return ListUtils.mapOrElse(typeArguments, arg -> visitFieldTypeSignature(arg, true));
+    return ListUtils.mapOrElse(typeArguments, this::visitFieldTypeSignature);
   }
 
   @Override
@@ -208,11 +205,6 @@
 
   @Override
   public FieldTypeSignature visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
-    return visitFieldTypeSignature(fieldSignature, false);
-  }
-
-  private FieldTypeSignature visitFieldTypeSignature(
-      FieldTypeSignature fieldSignature, boolean canUseWildcardInArguments) {
     if (fieldSignature.isStar()) {
       return fieldSignature;
     } else if (fieldSignature.isClassTypeSignature()) {
@@ -228,9 +220,7 @@
         if (substitution == null) {
           substitution = objectType;
         }
-        return substitution == objectType && canUseWildcardInArguments
-            ? StarFieldTypeSignature.getStarFieldTypeSignature()
-            : new ClassTypeSignature(substitution).asArgument(WildcardIndicator.NONE);
+        return new ClassTypeSignature(substitution).asArgument(WildcardIndicator.NONE);
       }
       return fieldSignature;
     }
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 956b076..93b3b47 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -13,8 +13,8 @@
 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.StarFieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
 import com.android.tools.r8.utils.ListUtils;
 import java.util.List;
 import java.util.function.Function;
@@ -230,7 +230,7 @@
           fieldTypeSignature -> {
             FieldTypeSignature rewrittenSignature = visitFieldTypeSignature(fieldTypeSignature);
             return rewrittenSignature == null
-                ? StarFieldTypeSignature.getStarFieldTypeSignature()
+                ? objectTypeSignature.asArgument(WildcardIndicator.NONE)
                 : rewrittenSignature;
           });
     }
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 9b9dc08..0df5d92 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
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.BooleanBox;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -247,8 +248,20 @@
     public void perform(InvokeVirtual classLoaderInvoke, DexMethod method) {
       // Remove the ClassLoader call since this can throw and will not be removed otherwise.
       if (classLoaderInvoke != null) {
-        clearGetClassLoader(classLoaderInvoke);
-        iterator.nextUntil(i -> i == serviceLoaderLoad);
+        BooleanBox allClassLoaderUsersAreServiceLoaders =
+            new BooleanBox(!classLoaderInvoke.outValue().hasPhiUsers());
+        classLoaderInvoke
+            .outValue()
+            .uniqueUsers()
+            .forEach(
+                user -> {
+                  assert !user.isAssume();
+                  allClassLoaderUsersAreServiceLoaders.and(user == serviceLoaderLoad);
+                });
+        if (allClassLoaderUsersAreServiceLoaders.get()) {
+          clearGetClassLoader(classLoaderInvoke);
+          iterator.nextUntil(i -> i == serviceLoaderLoad);
+        }
       }
 
       // Remove the ServiceLoader.load call.
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 c5d7038..e1eabf2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -16,6 +15,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
@@ -23,8 +23,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
-import java.util.IdentityHashMap;
-import java.util.Map;
 import java.util.Set;
 
 public class AnnotationRemover {
@@ -32,19 +30,16 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final InternalOptions options;
   private final Set<DexAnnotation> annotationsToRetain;
-  private final Set<DexType> classesToRetainInnerClassAttributeFor;
   private final ProguardKeepAttributes keep;
   private final Set<DexType> removedClasses;
 
   private AnnotationRemover(
       AppView<AppInfoWithLiveness> appView,
-      Set<DexType> classesToRetainInnerClassAttributeFor,
       Set<DexAnnotation> annotationsToRetain,
       Set<DexType> removedClasses) {
     this.appView = appView;
     this.options = appView.options();
     this.annotationsToRetain = annotationsToRetain;
-    this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
     this.keep = appView.options().getProguardConfiguration().getKeepAttributes();
     this.removedClasses = removedClasses;
   }
@@ -53,10 +48,6 @@
     return new Builder();
   }
 
-  public Set<DexType> getClassesToRetainInnerClassAttributeFor() {
-    return classesToRetainInnerClassAttributeFor;
-  }
-
   /** Used to filter annotations on classes, methods and fields. */
   private boolean filterAnnotations(DexDefinition holder, DexAnnotation annotation) {
     return annotationsToRetain.contains(annotation)
@@ -172,35 +163,16 @@
     return this;
   }
 
-  private static boolean hasGenericEnclosingClass(
-      DexProgramClass clazz,
-      Map<DexType, DexProgramClass> enclosingClasses,
-      Set<DexProgramClass> genericClasses) {
-    while (true) {
-      DexProgramClass enclosingClass = enclosingClasses.get(clazz.type);
-      if (enclosingClass == null) {
-        return false;
-      }
-      if (genericClasses.contains(enclosingClass)) {
-        return true;
-      }
-      clazz = enclosingClass;
-    }
-  }
-
   public void run() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      boolean enclosingMethodPinned = enclosingMethodPinned(appView, clazz);
-      stripAttributes(clazz, enclosingMethodPinned);
+      stripAttributes(clazz);
       clazz.setAnnotations(
           clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation)));
       // Kotlin properties are split over fields and methods. Check if any is pinned before pruning
       // the information.
       Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
-      clazz.forEachMethod(
-          method -> processMethod(method, clazz, pinnedKotlinProperties, enclosingMethodPinned));
-      clazz.forEachField(
-          field -> processField(field, clazz, pinnedKotlinProperties, enclosingMethodPinned));
+      clazz.forEachMethod(method -> processMethod(method, clazz, pinnedKotlinProperties));
+      clazz.forEachField(field -> processField(field, clazz, pinnedKotlinProperties));
       clazz.forEachProgramMember(
           member -> {
             KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
@@ -215,14 +187,13 @@
   private void processMethod(
       DexEncodedMethod method,
       DexProgramClass clazz,
-      Set<KotlinPropertyInfo> pinnedKotlinProperties,
-      boolean enclosingMethodPinned) {
+      Set<KotlinPropertyInfo> pinnedKotlinProperties) {
     method.setAnnotations(
         method.annotations().rewrite(annotation -> rewriteAnnotation(method, annotation)));
     method.parameterAnnotationsList =
         method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
     KeepMethodInfo methodInfo = appView.getKeepInfo().getMethodInfo(method, clazz);
-    if (!enclosingMethodPinned && methodInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
+    if (methodInfo.isSignatureAttributeRemovalAllowed(options)) {
       method.clearGenericSignature();
     }
     if (!methodInfo.isPinned() && method.getKotlinInfo().isFunction()) {
@@ -236,12 +207,11 @@
   private void processField(
       DexEncodedField field,
       DexProgramClass clazz,
-      Set<KotlinPropertyInfo> pinnedKotlinProperties,
-      boolean enclosingMethodPinned) {
+      Set<KotlinPropertyInfo> pinnedKotlinProperties) {
     field.setAnnotations(
         field.annotations().rewrite(annotation -> rewriteAnnotation(field, annotation)));
     KeepFieldInfo fieldInfo = appView.getKeepInfo().getFieldInfo(field, clazz);
-    if (!enclosingMethodPinned && fieldInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
+    if (fieldInfo.isSignatureAttributeRemovalAllowed(options)) {
       field.clearGenericSignature();
     }
     if (fieldInfo.isPinned() && field.getKotlinInfo().isProperty()) {
@@ -291,86 +261,68 @@
     return liveGetter ? original : null;
   }
 
-  private static boolean enclosingMethodPinned(
-      AppView<AppInfoWithLiveness> appView, DexClass clazz) {
-    return clazz.getEnclosingMethodAttribute() != null
-        && clazz.getEnclosingMethodAttribute().getEnclosingClass() != null
-        && appView.appInfo().isPinned(clazz.getEnclosingMethodAttribute().getEnclosingClass());
-  }
-
-  private static boolean hasInnerClassesFromSet(DexProgramClass clazz, Set<DexType> innerClasses) {
-    for (InnerClassAttribute attr : clazz.getInnerClasses()) {
-      if (attr.getOuter() == clazz.type && innerClasses.contains(attr.getInner())) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private void stripAttributes(DexProgramClass clazz, boolean enclosingMethodPinned) {
+  private void stripAttributes(DexProgramClass clazz) {
     // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
     // need to keep the enclosing method and inner classes attributes, if requested. In Proguard
     // compatibility mode we keep these attributes independent of whether the given class is kept.
-    // To ensure reflection from both inner to outer and and outer to inner for kept classes - even
-    // if only one side is kept - keep the attributes is any class mentioned in these attributes
-    // is kept.
-    boolean keptAnyway =
-        appView.appInfo().isPinned(clazz.type)
-            || enclosingMethodPinned
-            || appView.options().forceProguardCompatibility;
-    boolean keepForThisInnerClass = false;
-    boolean keepForThisEnclosingClass = false;
-    if (!keptAnyway) {
-      keepForThisInnerClass = classesToRetainInnerClassAttributeFor.contains(clazz.type);
-      keepForThisEnclosingClass =
-          hasInnerClassesFromSet(clazz, classesToRetainInnerClassAttributeFor);
-    }
-    if (keptAnyway || keepForThisInnerClass || keepForThisEnclosingClass) {
-      if (!keep.enclosingMethod) {
-        clazz.clearEnclosingMethodAttribute();
-      }
-      if (!keep.innerClasses) {
-        clazz.clearInnerClasses();
-      } else if (!keptAnyway) {
-        // We're keeping this only because of classesToRetainInnerClassAttributeFor.
-        final boolean finalKeepForThisInnerClass = keepForThisInnerClass;
-        final boolean finalKeepForThisEnclosingClass = keepForThisEnclosingClass;
-        clazz.removeInnerClasses(
-            ica -> {
-              if (appView.appInfo().isPinned(ica.getInner())) {
-                return false;
-              }
-              DexType outer = ica.getOuter();
-              if (outer != null && appView.appInfo().isPinned(outer)) {
-                return false;
-              }
-              if (finalKeepForThisInnerClass && ica.getInner() == clazz.type) {
-                return false;
-              }
-              if (finalKeepForThisEnclosingClass
-                  && outer == clazz.type
-                  && classesToRetainInnerClassAttributeFor.contains(ica.getInner())) {
-                return false;
-              }
-              return true;
-            });
-      }
-    } else {
-      // These attributes are only relevant for reflection, and this class is not used for
-      // reflection. (Note that clearing these attributes can enable more vertical class merging.)
-      clazz.clearEnclosingMethodAttribute();
-      clazz.clearInnerClasses();
-    }
-    if (!enclosingMethodPinned
-        && clazz.getClassSignature().isValid()
-        && appView
-            .getKeepInfo()
-            .getClassInfo(clazz)
-            .isAllowSignatureAttributeRemovalAllowed(options)) {
+    // In full mode we remove the attribute if not both sides are kept.
+    clazz.removeEnclosingMethodAttribute(
+        enclosingMethodAttribute ->
+            appView
+                .getKeepInfo()
+                .getClassInfo(clazz)
+                .isEnclosingMethodAttributeRemovalAllowed(
+                    options, enclosingMethodAttribute, appView));
+    // It is important that the call to getEnclosingMethodAttribute is done after we potentially
+    // pruned it above.
+    clazz.removeInnerClasses(
+        attribute ->
+            canRemoveInnerClassAttribute(clazz, attribute, clazz.getEnclosingMethodAttribute()));
+    if (clazz.getClassSignature().isValid()
+        && appView.getKeepInfo().getClassInfo(clazz).isSignatureAttributeRemovalAllowed(options)) {
       clazz.clearClassSignature();
     }
   }
 
+  private boolean canRemoveInnerClassAttribute(
+      DexProgramClass clazz,
+      InnerClassAttribute innerClassAttribute,
+      EnclosingMethodAttribute enclosingAttributeMethod) {
+    if (innerClassAttribute.getOuter() == null
+        && (clazz.isLocalClass() || clazz.isAnonymousClass())) {
+      // This is a class that has an enclosing method attribute and the inner class attribute
+      // is related to the enclosed method.
+      assert innerClassAttribute.getInner() != null;
+      return appView
+          .getKeepInfo()
+          .getClassInfo(innerClassAttribute.getInner(), appView)
+          .isInnerClassesAttributeRemovalAllowed(options, enclosingAttributeMethod);
+    } else if (innerClassAttribute.getOuter() == null) {
+      assert !clazz.isLocalClass() && !clazz.isAnonymousClass();
+      return appView
+          .getKeepInfo()
+          .getClassInfo(innerClassAttribute.getInner(), appView)
+          .isInnerClassesAttributeRemovalAllowed(options);
+    } else if (innerClassAttribute.getInner() == null) {
+      assert innerClassAttribute.getOuter() != null;
+      return appView
+          .getKeepInfo()
+          .getClassInfo(innerClassAttribute.getOuter(), appView)
+          .isInnerClassesAttributeRemovalAllowed(options);
+    } else {
+      // If both inner and outer is specified, only keep if both are pinned.
+      assert innerClassAttribute.getOuter() != null && innerClassAttribute.getInner() != null;
+      return appView
+              .getKeepInfo()
+              .getClassInfo(innerClassAttribute.getInner(), appView)
+              .isInnerClassesAttributeRemovalAllowed(options)
+          || appView
+              .getKeepInfo()
+              .getClassInfo(innerClassAttribute.getOuter(), appView)
+              .isInnerClassesAttributeRemovalAllowed(options);
+    }
+  }
+
   public static void clearAnnotations(AppView<?> appView) {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       clazz.clearAnnotations();
@@ -386,82 +338,13 @@
      */
     private final Set<DexAnnotation> annotationsToRetain = Sets.newIdentityHashSet();
 
-    private Set<DexType> classesToRetainInnerClassAttributeFor;
-
-    public Builder computeClassesToRetainInnerClassAttributeFor(
-        AppView<AppInfoWithLiveness> appView) {
-      assert classesToRetainInnerClassAttributeFor == null;
-      // In case of minification for certain inner classes we need to retain their InnerClass
-      // attributes because their minified name still needs to be in hierarchical format
-      // (enclosing$inner) otherwise the GenericSignatureRewriter can't produce the correct,
-      // renamed signature.
-
-      // More precisely:
-      // - we're going to retain the InnerClass attribute that refers to the same class as 'inner'
-      // - for live, inner, nonstatic classes
-      // - that are enclosed by a class with a generic signature.
-
-      // In compat mode we always keep all InnerClass attributes (if requested).
-      // If not requested we never keep any. In these cases don't compute eligible classes.
-      Set<DexType> result = Sets.newIdentityHashSet();
-      if (!appView.options().forceProguardCompatibility
-          && appView.options().getProguardConfiguration().getKeepAttributes().innerClasses) {
-        // Build lookup table and set of the interesting classes.
-        // enclosingClasses.get(clazz) gives the enclosing class of 'clazz'
-        Map<DexType, DexProgramClass> enclosingClasses = new IdentityHashMap<>();
-        Set<DexProgramClass> genericClasses = Sets.newIdentityHashSet();
-        for (DexProgramClass clazz : appView.appInfo().classes()) {
-          if (clazz.getClassSignature().hasSignature()) {
-            genericClasses.add(clazz);
-          }
-          for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
-            if ((innerClassAttribute.getAccess() & Constants.ACC_STATIC) == 0
-                && innerClassAttribute.getOuter() == clazz.type) {
-              enclosingClasses.put(innerClassAttribute.getInner(), clazz);
-            }
-          }
-        }
-        for (DexProgramClass clazz : appView.appInfo().classes()) {
-          // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we
-          // therefore need to keep the enclosing method and inner classes attributes, if requested.
-          if (appView.appInfo().isPinned(clazz) || enclosingMethodPinned(appView, clazz)) {
-            for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
-              DexType inner = innerClassAttribute.getInner();
-              if (appView.appInfo().isNonProgramTypeOrLiveProgramType(inner)) {
-                result.add(inner);
-              }
-              DexType context = innerClassAttribute.getLiveContext(appView);
-              if (context != null && appView.appInfo().isNonProgramTypeOrLiveProgramType(context)) {
-                result.add(context);
-              }
-            }
-          }
-          if (clazz.getInnerClassAttributeForThisClass() != null
-              && appView.appInfo().isNonProgramTypeOrLiveProgramType(clazz.type)
-              && hasGenericEnclosingClass(clazz, enclosingClasses, genericClasses)) {
-            result.add(clazz.type);
-          }
-        }
-      }
-      classesToRetainInnerClassAttributeFor = result;
-      return this;
-    }
-
-    public Builder setClassesToRetainInnerClassAttributeFor(
-        Set<DexType> classesToRetainInnerClassAttributeFor) {
-      this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
-      return this;
-    }
-
     public void retainAnnotation(DexAnnotation annotation) {
       annotationsToRetain.add(annotation);
     }
 
     public AnnotationRemover build(
         AppView<AppInfoWithLiveness> appView, Set<DexType> removedClasses) {
-      assert classesToRetainInnerClassAttributeFor != null;
-      return new AnnotationRemover(
-          appView, classesToRetainInnerClassAttributeFor, annotationsToRetain, removedClasses);
+      return new AnnotationRemover(appView, annotationsToRetain, removedClasses);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index 765fe31..cc11777 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -17,4 +17,8 @@
   boolean isForceProguardCompatibilityEnabled();
 
   boolean isKeepAttributesSignatureEnabled();
+
+  boolean isKeepEnclosingMethodAttributeEnabled();
+
+  boolean isKeepInnerClassesAttributeEnabled();
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 1040ae2..acf36aa 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.shaking.KeepInfo.Builder;
 
 /** Keep information that can be associated with any item, i.e., class, method or field. */
@@ -82,14 +84,47 @@
     return allowAccessModification;
   }
 
-  public boolean isAllowSignatureAttributeRemovalAllowed(
-      GlobalKeepInfoConfiguration configuration) {
+  public boolean isSignatureAttributeRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
     if (!configuration.isKeepAttributesSignatureEnabled()) {
       return true;
     }
     return !(configuration.isForceProguardCompatibilityEnabled() || isPinned());
   }
 
+  public boolean isEnclosingMethodAttributeRemovalAllowed(
+      GlobalKeepInfoConfiguration configuration,
+      EnclosingMethodAttribute enclosingMethodAttribute,
+      AppView<AppInfoWithLiveness> appView) {
+    if (!configuration.isKeepEnclosingMethodAttributeEnabled()) {
+      return true;
+    }
+    if (configuration.isForceProguardCompatibilityEnabled()) {
+      return false;
+    }
+    return !isPinned() || !enclosingMethodAttribute.isEnclosingPinned(appView);
+  }
+
+  public boolean isInnerClassesAttributeRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
+    if (!configuration.isKeepInnerClassesAttributeEnabled()) {
+      return true;
+    }
+    return !(configuration.isForceProguardCompatibilityEnabled() || isPinned());
+  }
+
+  public boolean isInnerClassesAttributeRemovalAllowed(
+      GlobalKeepInfoConfiguration configuration,
+      EnclosingMethodAttribute enclosingMethodAttribute) {
+    if (!configuration.isKeepInnerClassesAttributeEnabled()) {
+      return true;
+    }
+    if (configuration.isForceProguardCompatibilityEnabled()) {
+      return false;
+    }
+    // The inner class is dependent on the enclosingMethodAttribute and since it has been pruned
+    // we can also remove this inner class relationship.
+    return enclosingMethodAttribute == null || !isPinned();
+  }
+
   public abstract boolean isTop();
 
   public abstract boolean isBottom();
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 ff812b6..94b8404 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -602,6 +602,16 @@
     return proguardConfiguration.getKeepAttributes().signature;
   }
 
+  @Override
+  public boolean isKeepEnclosingMethodAttributeEnabled() {
+    return proguardConfiguration.getKeepAttributes().enclosingMethod;
+  }
+
+  @Override
+  public boolean isKeepInnerClassesAttributeEnabled() {
+    return proguardConfiguration.getKeepAttributes().innerClasses;
+  }
+
   /**
    * If any non-static class merging is enabled, information about types referred to by instanceOf
    * and check cast instructions needs to be collected.
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
index 24ba1cd..9b24e0a 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
@@ -28,7 +28,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public InnerClassAttributePublicizerTest(TestParameters parameters) {
@@ -37,12 +37,12 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(InnerClassAttributePublicizerTest.class)
         .addKeepMainRule(TestClass.class)
-        .addKeepAttributes("EnclosingMethod", "InnerClasses")
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
         .allowAccessModification()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
index a6a93ad..8a80d6e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
@@ -5,33 +5,51 @@
 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.onlyIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
 import org.junit.Test;
+import org.junit.runners.Parameterized;
 
 public class InnerOuterClassesTest extends HorizontalClassMergingTestBase {
-  public InnerOuterClassesTest(TestParameters parameters) {
+
+  @Parameterized.Parameters(name = "{0}, isCompat: {1}")
+  public static List<Object[]> newData() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  private final boolean isCompat;
+
+  public InnerOuterClassesTest(TestParameters parameters, boolean isCompat) {
     super(parameters);
+    this.isCompat = isCompat;
   }
 
   @Test
   public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
+    (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .enableNeverClassInliningAnnotations()
-        .addKeepAttributes("InnerClasses", "EnclosingMethod")
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
         .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options ->
+                options.testing.horizontalClassMergingTarget =
+                    (candidates, target) -> candidates.iterator().next())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("a", "b", "c", "d")
         .inspect(
             codeInspector -> {
-              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(A.class), onlyIf(isCompat, isPresent()));
               assertThat(codeInspector.clazz(A.B.class), isPresent());
-              assertThat(codeInspector.clazz(C.class), isPresent());
-              assertThat(codeInspector.clazz(A.D.class), isPresent());
+              assertThat(codeInspector.clazz(C.class), onlyIf(isCompat, isPresent()));
+              assertThat(codeInspector.clazz(A.D.class), onlyIf(isCompat, isPresent()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
similarity index 70%
copy from src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
copy to src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
index ae64e0e..862c386 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
@@ -17,13 +17,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class CollisionWithDefaultMethodOutsideMergeGroupTest extends TestBase {
+public class CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -32,7 +31,8 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public CollisionWithDefaultMethodOutsideMergeGroupTest(TestParameters parameters) {
+  public CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest(
+      TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -41,10 +41,15 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        // I and J are not eligible for merging, since class A (implements I) inherits a default m()
-        // method from K, which is also on J.
+        // I and J are not eligible for merging, since the merging of class B into class A
+        // contributes the default method K.m() to A, and the merging of J into I would contribute
+        // the default method J.m() to A.
         .addHorizontallyMergedClassesInspector(
-            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+            inspector ->
+                inspector
+                    .assertIsCompleteMergeGroup(A.class, B.class)
+                    .assertMergedInto(B.class, A.class)
+                    .assertClassesNotMerged(I.class, J.class, K.class))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -59,19 +64,20 @@
               assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
               assertThat(aClassSubject, isImplementing(inspector.clazz(K.class)));
 
-              ClassSubject bClassSubject = inspector.clazz(B.class);
+              ClassSubject bClassSubject = inspector.clazz(C.class);
               assertThat(bClassSubject, isPresent());
               assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
             })
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("K", "J");
+        .assertSuccessWithOutputLines("A", "K", "J");
   }
 
   static class Main {
 
     public static void main(String[] args) {
-      new A().m();
+      new A().keepA();
       new B().m();
+      new C().m();
     }
   }
 
@@ -99,10 +105,20 @@
   }
 
   @NeverClassInline
-  @NoHorizontalClassMerging
-  static class A implements I, K {}
+  static class A implements I {
+    @NeverInline
+    void keepA() {
+      System.out.println("A");
+    }
+  }
+
+  // Will be merged into A, which as a side effect contributes the default method K.m() to A.
+  // This should prevent merging of J into I, since that would contribute the default method J.m()
+  // to A.
+  @NeverClassInline
+  static class B implements K {}
 
   @NeverClassInline
   @NoHorizontalClassMerging
-  static class B implements J {}
+  static class C implements J {}
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
index ae64e0e..538f7fe 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
@@ -23,7 +23,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class CollisionWithDefaultMethodOutsideMergeGroupTest extends TestBase {
+public class CollisionWithDefaultMethodOutsideMergeGroupClassTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -32,7 +32,7 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public CollisionWithDefaultMethodOutsideMergeGroupTest(TestParameters parameters) {
+  public CollisionWithDefaultMethodOutsideMergeGroupClassTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
similarity index 78%
copy from src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
copy to src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index ae64e0e..6fcd094 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -23,7 +23,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class CollisionWithDefaultMethodOutsideMergeGroupTest extends TestBase {
+public class CollisionWithDefaultMethodOutsideMergeGroupLambdaTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -32,7 +32,7 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public CollisionWithDefaultMethodOutsideMergeGroupTest(TestParameters parameters) {
+  public CollisionWithDefaultMethodOutsideMergeGroupLambdaTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -41,8 +41,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        // I and J are not eligible for merging, since class A (implements I) inherits a default m()
-        // method from K, which is also on J.
+        // I and J are not eligible for merging, since the lambda that implements I & J inherits a
+        // default m() method from K, which is also on J.
         .addHorizontallyMergedClassesInspector(
             HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
@@ -56,12 +56,7 @@
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
-              assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
-              assertThat(aClassSubject, isImplementing(inspector.clazz(K.class)));
-
-              ClassSubject bClassSubject = inspector.clazz(B.class);
-              assertThat(bClassSubject, isPresent());
-              assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
+              assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("K", "J");
@@ -70,14 +65,20 @@
   static class Main {
 
     public static void main(String[] args) {
+      ((I & K)
+              () -> {
+                throw new RuntimeException();
+              })
+          .m();
       new A().m();
-      new B().m();
     }
   }
 
   @NoUnusedInterfaceRemoval
   @NoVerticalClassMerging
-  interface I {}
+  interface I {
+    void f();
+  }
 
   @NoUnusedInterfaceRemoval
   @NoVerticalClassMerging
@@ -100,9 +101,5 @@
 
   @NeverClassInline
   @NoHorizontalClassMerging
-  static class A implements I, K {}
-
-  @NeverClassInline
-  @NoHorizontalClassMerging
-  static class B implements J {}
+  static class A implements J {}
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
index 0fd3ab3..7c647b4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal.interfaces;
 
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -32,6 +34,8 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
@@ -46,10 +50,14 @@
     }
   }
 
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
   interface I {
     void f();
   }
 
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
   interface J {
     void g();
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
new file mode 100644
index 0000000..31aac13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2021, 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.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DisjointFunctionalInterfacesWithIntersectionMergingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DisjointFunctionalInterfacesWithIntersectionMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        // TODO(b/173990042): We should be able to merge I and J.
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I & J", "I & J");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Object o = (I & J) () -> "I & J";
+      System.out.println(((I) o).f());
+      System.out.println(((J) o).f());
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {
+    Object f();
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+    String f();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
new file mode 100644
index 0000000..683ce85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2021, 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.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest
+    extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        // TODO(b/173990042): We should be able to merge I and J.
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I", "J");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      ((I) arg -> System.out.println(arg)).f("I");
+      ((J) arg -> System.out.println(arg)).f("J");
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {
+    void f(Object arg);
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+    void f(String arg);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
new file mode 100644
index 0000000..8cb2212
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2021, 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.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest
+    extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        // TODO(b/173990042): We should be able to merge I and J.
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I", "J");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(((I) () -> "I").f());
+      System.out.println(((J) () -> "J").f());
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {
+    Object f();
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+    String f();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
new file mode 100644
index 0000000..96b8ea9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2021, 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.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdenticalFunctionalInterfacesMergingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IdenticalFunctionalInterfacesMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        // TODO(b/173990042): I and J should be merged.
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I", "J");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      ((I) () -> System.out.println("I")).f();
+      ((J) () -> System.out.println("J")).f();
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {
+    void f();
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+    void f();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
new file mode 100644
index 0000000..cd691d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2021, 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.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdenticalFunctionalInterfacesWithIntersectionMergingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IdenticalFunctionalInterfacesWithIntersectionMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        // TODO(b/173990042): I and J should be merged.
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I & J", "I & J");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Object o = ((I & J) () -> System.out.println("I & J"));
+      ((I) o).f();
+      ((J) o).f();
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {
+    void f();
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+    void f();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
index d65470d..993b56f 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.desugar;
 
 import com.android.tools.r8.DesugarTestConfiguration;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -19,18 +18,18 @@
 @RunWith(Parameterized.class)
 public class DesugarInnerClassesInInterfaces extends TestBase {
 
-  private List<String> EXPECTED_RESULT_WITHOUT_DESUGARING =
+  private final List<String> EXPECTED_RESULT_WITHOUT_DESUGARING =
       ImmutableList.of(
           WithAnonymousInner.class.getName(), "true", WithLocalInner.class.getName(), "true");
 
-  private List<String> EXPECTED_RESULT_WITH_DESUGARING =
+  private final List<String> EXPECTED_RESULT_WITH_DESUGARING =
       ImmutableList.of(
           WithAnonymousInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
           "true",
           WithLocalInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
           "true");
 
-  private List<String> EXPECTED_RESULT_WITH_DESUGARING_B168697955 =
+  private final List<String> EXPECTED_RESULT_WITH_DESUGARING_B168697955 =
       ImmutableList.of(
           WithAnonymousInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
           "false",
@@ -68,44 +67,58 @@
   }
 
   @Test
-  public void testR8() throws Exception {
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(DesugarInnerClassesInInterfaces.class)
-            .setMinApi(parameters.getApiLevel())
-            .addKeepRules("-keep class * { *; }")
-            .addKeepAttributes("InnerClasses", "EnclosingMethod")
-            .compile()
-            .run(parameters.getRuntime(), TestClass.class);
-    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
-      result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING);
-    } else {
-      // The static method which is moved to the companion class is inlined and causing
-      // this different output. The rule "-keep class * { *; }" does not keep the static
-      // method from being inlined after it has moved. Turning off inlining produces the
-      // expected result. The inlining cause the getEnclosingClass() to return null.
-      // See b/168697955.
-      result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING_B168697955);
-    }
+  public void testR8Compat() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(DesugarInnerClassesInInterfaces.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAllClassesRule()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(),
+            result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING),
+            // The static method which is moved to the companion class is inlined and causing
+            // this different output. The rule "-keep class * { *; }" does not keep the static
+            // method from being inlined after it has moved. Turning off inlining produces the
+            // expected result. The inlining cause the getEnclosingClass() to return null.
+            // See b/168697955.
+            result ->
+                result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING_B168697955));
   }
 
   @Test
   public void testR8_B168697955() throws Exception {
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(DesugarInnerClassesInInterfaces.class)
-            .setMinApi(parameters.getApiLevel())
-            .addKeepRules("-keep class * { *; }")
-            .addKeepAttributes("InnerClasses", "EnclosingMethod")
-            // With inlining turned off we get the expected result.
-            .addOptionsModification(options -> options.enableInlining = false)
-            .compile()
-            .run(parameters.getRuntime(), TestClass.class);
-    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
-      result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING);
-    } else {
-      result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING);
-    }
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DesugarInnerClassesInInterfaces.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAllClassesRule()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        // With inlining turned off we get the expected result.
+        .addOptionsModification(options -> options.enableInlining = false)
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(),
+            result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING),
+            // TODO(b/187377562): We remove the attribute due to not pinning the moved methods.
+            result -> result.assertFailureWithErrorThatThrows(NullPointerException.class));
+  }
+
+  @Test
+  public void testR8Full() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DesugarInnerClassesInInterfaces.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAllClassesRule()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(),
+            result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING),
+            // TODO(b/187377562): We remove the attribute due to not pinning the moved methods.
+            result -> result.assertFailureWithErrorThatThrows(NullPointerException.class));
   }
 
   interface WithAnonymousInner {
diff --git a/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
index c61b1fd..e7cc9c9 100644
--- a/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
@@ -3,26 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.enclosingmethod;
 
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
-import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -50,27 +43,45 @@
 }
 
 class TestClass implements A {
-  public static void main(String[] args) throws NoClassDefFoundError, ClassNotFoundException {
+  public static void main(String[] args) throws NoClassDefFoundError {
     System.out.println(new TestClass().def());
   }
 }
 
 @RunWith(Parameterized.class)
 public class EnclosingMethodRewriteTest extends TestBase {
+
   private static final Class<?> MAIN = TestClass.class;
-
   private final TestParameters parameters;
-  private final boolean enableMinification;
 
-  @Parameterized.Parameters(name = "{0} minification: {1}")
-  public static Collection<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  private final String[] EXPECTED =
+      new String[] {
+        "interface " + A.class.getTypeName(),
+        "public default int " + A.class.getTypeName() + ".def()",
+        "42"
+      };
+
+  private final String[] EXPECTED_CC =
+      new String[] {
+        "class " + A.class.getTypeName() + "$-CC",
+        "public static int " + A.class.getTypeName() + "$-CC.a(" + A.class.getTypeName() + ")",
+        "42"
+      };
+
+  private final String[] EXPECTED_NOUGAT =
+      new String[] {
+        "interface " + A.class.getTypeName(), "public int " + A.class.getTypeName() + ".def()", "42"
+      };
+
+  private final String[] EXPECTED_FULL = new String[] {"null", "null", "42"};
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public EnclosingMethodRewriteTest(TestParameters parameters, boolean enableMinification) {
+  public EnclosingMethodRewriteTest(TestParameters parameters) {
     this.parameters = parameters;
-    this.enableMinification = enableMinification;
   }
 
   @Test
@@ -79,121 +90,74 @@
     testForJvm()
         .addTestClasspath()
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutputLines(getExpectedOutput());
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   @Test
-  public void testDesugarAndProguard() throws Exception {
-    assumeTrue("Only run on CF runtimes", parameters.isCfRuntime());
-    Path desugared = temp.newFile("desugared.jar").toPath().toAbsolutePath();
-    R8FullTestBuilder builder =
-        testForR8(parameters.getBackend())
-            .addProgramClassesAndInnerClasses(A.class)
-            .addProgramClasses(C.class, MAIN)
-            .addKeepMainRule(MAIN)
-            .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
-            .setProgramConsumer(new ArchiveConsumer(desugared))
-            .setMinApi(parameters.getApiLevel());
-    if (enableMinification) {
-      builder.addKeepAllClassesRuleWithAllowObfuscation();
-    } else {
-      builder.addKeepAllClassesRule();
-    }
-    builder.compile().assertNoMessages();
-    testForProguard()
-        .addProgramFiles(desugared)
-        .addKeepMainRule(MAIN)
-        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+  public void testR8Compat() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(A.class)
+        .addProgramClasses(C.class, MAIN)
+        .addKeepAllClassesRule()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> inspect(inspector, true))
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccess();
+        .applyIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(),
+            result -> {
+              if (!parameters.isCfRuntime()
+                  && parameters.getApiLevel().isEqualTo(AndroidApiLevel.N)) {
+                result.assertSuccessWithOutputLines(EXPECTED_NOUGAT);
+              } else {
+                result.assertSuccessWithOutputLines(EXPECTED);
+              }
+            },
+            result -> result.assertSuccessWithOutputLines(EXPECTED_CC));
   }
 
   @Test
-  public void testR8() throws Exception {
-    R8FullTestBuilder builder =
-        testForR8(parameters.getBackend())
-            .addProgramClassesAndInnerClasses(A.class)
-            .addProgramClasses(C.class, MAIN)
-            .addKeepMainRule(MAIN)
-            .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
-            .setMinApi(parameters.getApiLevel());
-    if (enableMinification) {
-      builder.addKeepAllClassesRuleWithAllowObfuscation();
-    } else {
-      builder.noTreeShaking().noMinification();
-    }
-    builder
+  public void testR8Full() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(A.class)
+        .addProgramClasses(C.class, MAIN)
+        .addKeepAllClassesRule()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
-            inspect -> {
-              ClassSubject cImplSubject = inspect.clazz(A.class.getTypeName() + "$1");
-              assertThat(cImplSubject, isPresent());
-
-              ClassSubject enclosingClassSubject =
-                  inspect.clazz(
-                      parameters.canUseDefaultAndStaticInterfaceMethods()
-                          ? A.class.getTypeName()
-                          : A.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
-              assertThat(enclosingClassSubject, isPresent());
-              assertEquals(
-                  enclosingClassSubject.getDexProgramClass().getType(),
-                  cImplSubject
-                      .getDexProgramClass()
-                      .getEnclosingMethodAttribute()
-                      .getEnclosingMethod()
-                      .getHolderType());
-            })
+            inspector -> inspect(inspector, parameters.canUseDefaultAndStaticInterfaceMethods()))
         .run(parameters.getRuntime(), MAIN)
-        .apply(
-            result -> result.assertSuccessWithOutputLines(getExpectedOutput(result.inspector())));
+        .applyIf(
+            parameters.canUseDefaultAndStaticInterfaceMethods(),
+            result -> {
+              if (!parameters.isCfRuntime()
+                  && parameters.getApiLevel().isEqualTo(AndroidApiLevel.N)) {
+                result.assertSuccessWithOutputLines(EXPECTED_NOUGAT);
+              } else {
+                result.assertSuccessWithOutputLines(EXPECTED);
+              }
+            },
+            result -> result.assertSuccessWithOutputLines(EXPECTED_FULL));
   }
 
-  private List<String> getExpectedOutput() {
-    return ImmutableList.of(
-        "interface " + A.class.getTypeName(),
-        "public default int " + A.class.getTypeName() + ".def()",
-        "42");
-  }
-
-  private List<String> getExpectedOutput(CodeInspector inspector) {
-    ClassSubject aClassSubject = inspector.clazz(A.class);
-    assertThat(aClassSubject, isPresent());
-
-    MethodSubject defMethodSubject = aClassSubject.uniqueMethodWithName("def");
-    assertThat(defMethodSubject, isPresent());
-
-    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
-      String modifiers =
-          parameters.isCfRuntime()
-                  || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V8_1_0)
-              ? "public default"
-              : "public";
-      return ImmutableList.of(
-          "interface " + aClassSubject.getFinalName(),
-          modifiers
-              + " int "
-              + aClassSubject.getFinalName()
-              + "."
-              + defMethodSubject.getFinalName()
-              + "()",
-          "42");
+  private void inspect(CodeInspector inspector, boolean hasEnclosingMethod) {
+    ClassSubject cImplSubject = inspector.clazz(A.class.getTypeName() + "$1");
+    assertThat(cImplSubject, isPresent());
+    ClassSubject enclosingClassSubject =
+        parameters.canUseDefaultAndStaticInterfaceMethods()
+            ? inspector.clazz(A.class.getTypeName())
+            : inspector.clazz(A.class.getTypeName() + "$-CC");
+    assertThat(enclosingClassSubject, isPresent());
+    EnclosingMethodAttribute enclosingMethodAttribute =
+        cImplSubject.getDexProgramClass().getEnclosingMethodAttribute();
+    if (hasEnclosingMethod) {
+      assertEquals(
+          enclosingClassSubject.getDexProgramClass().getType(),
+          enclosingMethodAttribute.getEnclosingMethod().getHolderType());
+    } else {
+      assertNull(enclosingMethodAttribute);
     }
-
-    ClassSubject aCompanionClassSubject =
-        inspector.clazz(A.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
-    assertThat(aCompanionClassSubject, isPresent());
-
-    String methodNamePrefix = enableMinification ? "" : DEFAULT_METHOD_PREFIX;
-    return ImmutableList.of(
-        "class " + aCompanionClassSubject.getFinalName(),
-        "public static int "
-            + aCompanionClassSubject.getFinalName()
-            + "."
-            + methodNamePrefix
-            + defMethodSubject.getFinalName()
-            + "("
-            + aClassSubject.getFinalName()
-            + ")",
-        "42");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
new file mode 100644
index 0000000..3a923e0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2021, 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.lambdas;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaInStacktraceTest extends TestBase {
+
+  static final String fileName = "LambdaInStacktraceTest.java";
+
+  static final String EXPECTED_JAVAC =
+      StringUtils.lines(
+          "getStacktraceWithFileNames(" + fileName + ")",
+          "lambda$main$0(" + fileName + ")",
+          "main(" + fileName + ")",
+          "getStacktraceWithFileNames(" + fileName + ")",
+          "lambda$main$1(" + fileName + ")",
+          "main(" + fileName + ")");
+
+  // TODO(b/187491007): The "call" frame should have a file name.
+  static final String EXPECTED_D8 =
+      StringUtils.lines(
+          "getStacktraceWithFileNames(" + fileName + ")",
+          "lambda$main$0(" + fileName + ")",
+          "call(NULL)",
+          "main(" + fileName + ")",
+          "getStacktraceWithFileNames(" + fileName + ")",
+          "lambda$main$1(" + fileName + ")",
+          "call(NULL)",
+          "main(" + fileName + ")");
+
+  private final TestParameters parameters;
+  private final boolean isDalvik;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public LambdaInStacktraceTest(TestParameters parameters) {
+    this.parameters = parameters;
+    isDalvik = parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+    testForJvm()
+        .addInnerClasses(LambdaInStacktraceTest.class)
+        .run(parameters.getRuntime(), TestRunner.class, Boolean.toString(false))
+        .assertSuccess()
+        .assertSuccessWithOutput(EXPECTED_JAVAC);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(LambdaInStacktraceTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestRunner.class, Boolean.toString(isDalvik))
+        .assertSuccessWithOutput(EXPECTED_D8);
+  }
+
+  static class TestRunner {
+
+    public static List<String> getStacktraceWithFileNames(boolean isDalvik) {
+      Throwable stackTrace = new RuntimeException().fillInStackTrace();
+
+      // Dalvik has an additional "main(NativeStart.java)" bottom frame.
+      List<String> frames = new ArrayList<>();
+      for (int i = 0; i < stackTrace.getStackTrace().length - (isDalvik ? 1 : 0); i++) {
+        StackTraceElement stackTraceElement = stackTrace.getStackTrace()[i];
+        String fileName = stackTraceElement.getFileName();
+        frames.add(
+            stackTraceElement.getMethodName() + "(" + (fileName != null ? fileName : "NULL") + ")");
+      }
+      return frames;
+    }
+
+    public static void main(String[] args) throws Exception {
+      boolean isDalvik = Boolean.parseBoolean(args[0]);
+      Callable<List<String>> callable = () -> getStacktraceWithFileNames(isDalvik);
+      System.out.println(String.join(System.lineSeparator(), callable.call()));
+
+      Callable<Callable<List<String>>> callableNested =
+          () -> () -> getStacktraceWithFileNames(isDalvik);
+      System.out.println(String.join(System.lineSeparator(), callableNested.call().call()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
index 21cc62c..b091f5e 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
@@ -107,8 +107,13 @@
     assertThat(innerClass, isPresent());
     MethodSubject testMethod = innerClass.uniqueMethodWithName("test");
     assertThat(testMethod, isPresent());
-    // TODO(b/184927364): TO; should be replaced with Supplier
-    assertEquals("(TO;TM;)TI;", testMethod.getFinalSignatureAttribute());
+    if (isCompat) {
+      assertEquals("(TO;TM;)TI;", testMethod.getFinalSignatureAttribute());
+    } else {
+      assertEquals(
+          "(" + descriptor(Supplier.class) + descriptor(Predicate.class) + ")TI;",
+          testMethod.getFinalSignatureAttribute());
+    }
   }
 
   public interface Supplier<T> {}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
index c1d8c5f..64f2ac3 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
@@ -67,7 +67,7 @@
             BiPredicateUtils.alwaysFalse(),
             alwaysTrue(),
             "(LList<TT;>;)LList<TR;>;",
-            "(LList<*>;)LList<*>;")
+            "(LList<Ljava/lang/Object;>;)LList<Ljava/lang/Object;>;")
         .assertNoMessages();
   }
 
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
index ebca08d..af2bb7f 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
@@ -55,18 +55,15 @@
   public void checkSignatures(CodeInspector inspector) {
     checkSignature(
         inspector.clazz(Bar.class.getTypeName() + "$1"),
-        "L" + binaryName(Foo.class) + "<*" + descriptor(Main.class) + ">;");
+        "L" + binaryName(Foo.class) + "<Ljava/lang/Object;" + descriptor(Main.class) + ">;");
     checkSignature(
-        inspector.clazz(Bar.class.getTypeName() + "$2"), "L" + binaryName(Foo.class) + "<**>;");
+        inspector.clazz(Bar.class.getTypeName() + "$2"),
+        "L" + binaryName(Foo.class) + "<Ljava/lang/Object;Ljava/lang/Object;>;");
   }
 
   private void checkSignature(ClassSubject classSubject, String expectedSignature) {
     assertThat(classSubject, isPresent());
-    // TODO(b/185098797): Make sure to work for full mode.
-    if (!isCompat) {
-      return;
-    }
-    assertEquals(expectedSignature, classSubject.getFinalSignatureAttribute());
+    assertEquals(isCompat ? expectedSignature : null, classSubject.getFinalSignatureAttribute());
   }
 
   public abstract static class Foo<T, R> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
index 43ea1b7..380c5be 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
@@ -8,15 +8,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ir.optimize.canonicalization.ConstClassMain.Outer;
 import com.android.tools.r8.ir.optimize.canonicalization.ConstClassMain.Outer.Inner;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -24,6 +23,7 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -55,6 +55,10 @@
 
   public static void main(String... args) {
     Class outer = Inner.class.getDeclaringClass();
+    if (outer == null) {
+      System.out.print("outer is null");
+      return;
+    }
     System.out.println(outer.getSimpleName());
     for (Class<?> inner : outer.getDeclaredClasses()) {
       System.out.println(inner.getSimpleName());
@@ -94,19 +98,23 @@
   private static final int CANONICALIZED_OUTER_COUNT = 1;
   private static final int CANONICALIZED_INNER_COUNT = 1;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+  @Parameterized.Parameters(name = "{0}, isCompat: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
   private final TestParameters parameters;
+  private final boolean isCompat;
 
-  public ConstClassCanonicalizationTest(TestParameters parameters) {
+  public ConstClassCanonicalizationTest(TestParameters parameters, boolean isCompat) {
     this.parameters = parameters;
+    this.isCompat = isCompat;
   }
 
   @Test
   public void testJVMOutput() throws Exception {
+    assumeTrue(isCompat);
     assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
     testForJvm()
         .addTestClasspath()
@@ -116,35 +124,36 @@
 
   @Test
   public void testD8_incremental() throws Exception {
+    assumeTrue(isCompat);
     assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
-
     Path zipA = temp.newFile("a.zip").toPath().toAbsolutePath();
     testForD8()
         .release()
         .addProgramClasses(IncrementalA.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .setProgramConsumer(new ArchiveConsumer(zipA))
         .compile();
     testForD8()
         .release()
         .addProgramClasses(IncrementalMain.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(zipA)
         .run(parameters.getRuntime(), IncrementalMain.class)
         .assertSuccessWithOutput(INCREMENTAL_OUTPUT)
-        .inspect(inspector -> {
-          ClassSubject main = inspector.clazz(IncrementalMain.class);
-          assertThat(main, isPresent());
-          MethodSubject mainMethod = main.mainMethod();
-          assertThat(mainMethod, isPresent());
-          assertEquals(
-              3,
-              mainMethod.streamInstructions().filter(InstructionSubject::isConstClass).count());
-        });
+        .inspect(
+            inspector -> {
+              ClassSubject main = inspector.clazz(IncrementalMain.class);
+              assertThat(main, isPresent());
+              MethodSubject mainMethod = main.mainMethod();
+              assertThat(mainMethod, isPresent());
+              assertEquals(
+                  3,
+                  mainMethod.streamInstructions().filter(InstructionSubject::isConstClass).count());
+            });
   }
 
-  private void test(SingleTestRunResult result, int mainCount, int outerCount, int innerCount)
+  private void test(SingleTestRunResult<?> result, int mainCount, int outerCount, int innerCount)
       throws Exception {
     CodeInspector codeInspector = result.inspector();
     ClassSubject mainClass = codeInspector.clazz(MAIN);
@@ -165,42 +174,51 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
+    assumeTrue(isCompat);
     assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
-
-    D8TestRunResult result =
+    test(
         testForD8()
             .debug()
             .addProgramClassesAndInnerClasses(MAIN)
             .addOptionsModification(InternalOptions::disableNameReflectionOptimization)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, CANONICALIZED_MAIN_COUNT, ORIGINAL_OUTER_COUNT, ORIGINAL_INNER_COUNT);
+            .assertSuccessWithOutput(JAVA_OUTPUT),
+        CANONICALIZED_MAIN_COUNT,
+        ORIGINAL_OUTER_COUNT,
+        ORIGINAL_INNER_COUNT);
+  }
 
-    result =
+  @Test
+  public void testD8Release() throws Exception {
+    assumeTrue(isCompat);
+    assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+    test(
         testForD8()
             .release()
             .addProgramClassesAndInnerClasses(MAIN)
             .addOptionsModification(InternalOptions::disableNameReflectionOptimization)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, CANONICALIZED_MAIN_COUNT, ORIGINAL_OUTER_COUNT, ORIGINAL_INNER_COUNT);
+            .assertSuccessWithOutput(JAVA_OUTPUT),
+        CANONICALIZED_MAIN_COUNT,
+        ORIGINAL_OUTER_COUNT,
+        ORIGINAL_INNER_COUNT);
   }
 
   @Test
   public void testR8() throws Exception {
     R8TestRunResult result =
-        testForR8(parameters.getBackend())
+        (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
             .addProgramClassesAndInnerClasses(MAIN)
             .addKeepMainRule(MAIN)
-            .addKeepAllAttributes()
+            .addKeepAttributeInnerClassesAndEnclosingMethod()
             .noMinification()
             .addOptionsModification(InternalOptions::disableNameReflectionOptimization)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
+            .assertSuccessWithOutput(isCompat ? JAVA_OUTPUT : "outer is null");
     // The number of expected const-class instructions differs because constant canonicalization is
     // only enabled for the DEX backend.
     int expectedMainCount =
@@ -209,6 +227,13 @@
         parameters.isCfRuntime() ? ORIGINAL_OUTER_COUNT : CANONICALIZED_OUTER_COUNT;
     int expectedInnerCount =
         parameters.isCfRuntime() ? ORIGINAL_INNER_COUNT : CANONICALIZED_INNER_COUNT;
+    if (!isCompat) {
+      // When not compat we end up class merging inner an outer.
+      if (parameters.isCfRuntime()) {
+        expectedOuterCount = expectedOuterCount + expectedInnerCount;
+      }
+      expectedInnerCount = 0;
+    }
     test(result, expectedMainCount, expectedOuterCount, expectedInnerCount);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index 81612ee..102d5c7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -11,10 +11,8 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -114,41 +112,26 @@
 }
 
 public class GetSimpleNameTest extends GetNameTestBase {
-  private Collection<Path> classPaths;
-  private static final String JAVA_OUTPUT = StringUtils.lines(
-      "Local_t03",
-      "InnerLocal",
-      "$",
-      "$$",
-      "Local[][][]",
-      "[][][]",
-      "Inner",
-      "Inner"
-  );
-  // JDK8 computes the simple name differently: some assumptions about non-member classes,
-  // e.g., 1 or more digits (followed by the simple name if it's local).
-  // Since JDK9, the simple name is computed by stripping off the package name.
-  // See b/132808897 for more details.
-  private static final String RENAMED_OUTPUT_JDK8 = StringUtils.lines(
-      "d",
-      "a",
-      "a",
-      "a",
-      "c[][][]",
-      "b[][][]",
-      "a",
-      "a"
-  );
-  private static final String RENAMED_OUTPUT = StringUtils.lines(
-      "d",
-      "a",
-      "a",
-      "a",
-      "c[][][]",
-      "[][][]",
-      "a",
-      "a"
-  );
+  private final Collection<Path> classPaths;
+  private static final String JVM_OUTPUT =
+      StringUtils.lines(
+          "Local_t03", "InnerLocal", "$", "$$", "Local[][][]", "[][][]", "Inner", "Inner");
+
+  // When removing the class attributes the simple name of a class becomes prepended with the
+  // outer class.
+  private static final String OUTPUT_NO_ATTRIBUTES =
+      StringUtils.lines(
+          "ClassGetSimpleName$1Local_t03",
+          "InnerLocal",
+          "ClassGetSimpleName$1$",
+          "$$",
+          "ClassGetSimpleName$1Local[][][]",
+          "ClassGetSimpleName$1[][][]",
+          "Inner",
+          "Inner");
+
+  private static final String RENAMED_OUTPUT =
+      StringUtils.lines("d", "a", "a", "a", "c[][][]", "b[][][]", "a", "a");
   private static final Class<?> MAIN = ClassGetSimpleName.class;
 
   public GetSimpleNameTest(TestParameters parameters, boolean enableMinification) throws Exception {
@@ -169,10 +152,11 @@
     assumeTrue(
         "Only run JVM reference on CF runtimes",
         parameters.isCfRuntime() && !enableMinification);
+    CodeInspector inspector = new CodeInspector(classPaths);
     testForJvm()
         .addTestClasspath()
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutput(JAVA_OUTPUT);
+        .assertSuccessWithOutput(JVM_OUTPUT);
   }
 
   private void test(SingleTestRunResult<?> result) throws Exception {
@@ -194,7 +178,7 @@
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
+            .assertSuccessWithOutput(JVM_OUTPUT);
     test(result);
 
     result =
@@ -204,61 +188,67 @@
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
+            .assertSuccessWithOutput(JVM_OUTPUT);
     test(result);
   }
 
   @Test
+  public void testR8_pin_all() throws Exception {
+    // Pinning the test class.
+    testForR8(parameters.getBackend())
+        .addProgramFiles(classPaths)
+        .addForceInliningAnnotations()
+        .enableInliningAnnotations()
+        .addKeepAllClassesRule()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .minification(enableMinification)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(this::configure)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(JVM_OUTPUT)
+        .apply(this::test);
+  }
+
+  @Test
   public void testR8_pinning() throws Exception {
     // Pinning the test class.
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(classPaths)
-            .enableForceInliningAnnotations()
-            .enableInliningAnnotations()
-            .addKeepMainRule(MAIN)
-            .addKeepRules("-keep class **.ClassGetSimpleName*")
-            .addKeepRules("-keep class **.Outer*")
-            .addKeepAttributes("InnerClasses", "EnclosingMethod")
-            .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString())
-            .minification(enableMinification)
-            .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(this::configure)
-            .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result);
+    testForR8(parameters.getBackend())
+        .addProgramFiles(classPaths)
+        .enableForceInliningAnnotations()
+        .enableInliningAnnotations()
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-keep class **.ClassGetSimpleName*")
+        .addKeepRules("-keep class **.Outer*")
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .minification(enableMinification)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(this::configure)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(OUTPUT_NO_ATTRIBUTES)
+        .apply(this::test);
   }
 
   @Test
   public void testR8_shallow_pinning() throws Exception {
     // Shallow pinning the test class.
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(classPaths)
-            .enableInliningAnnotations()
-            .addKeepMainRule(MAIN)
-            .addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
-            // See b/119471127: some old VMs are not resilient to broken attributes.
-            // Comment out the following line to reproduce b/120130435
-            // then use OUTPUT_WITH_SHRUNK_ATTRIBUTES
-            .addKeepRules("-keep,allowobfuscation class **.Outer*")
-            .addKeepAttributes("InnerClasses", "EnclosingMethod")
-            .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString())
-            .enableForceInliningAnnotations()
-            .minification(enableMinification)
-            .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(this::configure)
-            .run(parameters.getRuntime(), MAIN);
-    if (enableMinification) {
-      if (parameters.isCfRuntime()
-          && parameters.getRuntime().asCf().getVm().lessThanOrEqual(CfVm.JDK8)) {
-        result.assertSuccessWithOutput(RENAMED_OUTPUT_JDK8);
-      } else {
-        result.assertSuccessWithOutput(RENAMED_OUTPUT);
-      }
-    } else {
-      result.assertSuccessWithOutput(JAVA_OUTPUT);
-    }
-    test(result);
+    testForR8(parameters.getBackend())
+        .addProgramFiles(classPaths)
+        .enableForceInliningAnnotations()
+        .enableInliningAnnotations()
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
+        // See b/119471127: some old VMs are not resilient to broken attributes.
+        // Comment out the following line to reproduce b/120130435
+        // then use OUTPUT_WITH_SHRUNK_ATTRIBUTES
+        .addKeepRules("-keep,allowobfuscation class **.Outer*")
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .minification(enableMinification)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(this::configure)
+        .run(parameters.getRuntime(), MAIN)
+        .applyIf(enableMinification, result -> result.assertSuccessWithOutput(RENAMED_OUTPUT))
+        .applyIf(
+            !enableMinification, result -> result.assertSuccessWithOutput(OUTPUT_NO_ATTRIBUTES))
+        .apply(this::test);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
index 3ccb9ad..a2127ef 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
@@ -102,12 +102,7 @@
     assertEquals(24, lambdasInInput.getNumberOfKStyleLambdas());
 
     // All K-style Kotlin lambdas are merged if no attributes are kept.
-    if (attributes.isEmpty()) {
-      inspector.assertClassReferencesMerged(lambdasInInput.getKStyleLambdas());
-    } else {
-      // TODO(b/179018501): allow merging classes with inner/outer classes.
-      inspector.assertClassReferencesNotMerged(lambdasInInput.getKStyleLambdas());
-    }
+    inspector.assertClassReferencesMerged(lambdasInInput.getKStyleLambdas());
   }
 
   private String getExpectedOutput() {
diff --git a/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java b/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
index f777cda..5ce4bde 100644
--- a/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
@@ -74,23 +74,16 @@
     }
 
     public String getExpectedOutput(TestParameters parameters, boolean isFullMode) {
+      // In full mode, we remove all attributes since nothing pinned, i.e., no reflection uses.
+      if (isFullMode) {
+        return "";
+      }
       switch (this) {
         case KEEP_INNER_CLASSES:
+        case NO_KEEP_NO_MINIFICATION:
           return JVM_OUTPUT;
         case KEEP_ALLOW_MINIFICATION:
-          if (parameters.isCfRuntime()
-              && parameters.getRuntime().asCf().getVm().lessThanOrEqual(CfVm.JDK8)) {
-            return MINIFIED_OUTPUT_JDK8;
-          }
-          return JVM_OUTPUT;
-        case NO_KEEP_NO_MINIFICATION:
-          // In full mode, we remove all attributes since nothing pinned, i.e., no reflection uses.
-          return isFullMode ? "" : JVM_OUTPUT;
         case NO_KEEP_MINIFICATION:
-          // In full mode, we remove all attributes since nothing pinned, i.e., no reflection uses.
-          if (isFullMode) {
-            return "";
-          }
           if (parameters.isCfRuntime()
               && parameters.getRuntime().asCf().getVm().lessThanOrEqual(CfVm.JDK8)) {
             return MINIFIED_OUTPUT_JDK8;
@@ -102,19 +95,7 @@
     }
 
     public void inspect(boolean isFullMode, CodeInspector inspector) {
-      int expectedNumberOfNonMemberInnerClasses;
-      switch (this) {
-        case KEEP_INNER_CLASSES:
-        case KEEP_ALLOW_MINIFICATION:
-          expectedNumberOfNonMemberInnerClasses = 2;
-          break;
-        case NO_KEEP_NO_MINIFICATION:
-        case NO_KEEP_MINIFICATION:
-          expectedNumberOfNonMemberInnerClasses = isFullMode ? 0 : 2;
-          break;
-        default:
-          throw new Unreachable();
-      }
+      int expectedNumberOfNonMemberInnerClasses = isFullMode ? 0 : 2;
       assertEquals(
           expectedNumberOfNonMemberInnerClasses,
           inspector.allClasses().stream()
@@ -190,7 +171,8 @@
         .addInnerClasses(NonMemberClassTest.class)
         .addKeepMainRule(MAIN)
         .addKeepRules(config.getKeepRules())
-        .addKeepAttributes("Signature", "InnerClasses", "EnclosingMethod")
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .addKeepAttributeSignature()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(options -> options.enableClassInlining = false)
diff --git a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
index b2614a3..fb9c53e 100644
--- a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
+++ b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
@@ -57,7 +57,7 @@
         .compile()
         .inspect(
             inspector -> {
-              String genericTypeDescriptor = "*";
+              String genericTypeDescriptor = "Ljava/lang/Object;";
               if (genericTypeLive) {
                 ClassSubject genericType = inspector.clazz(GenericType.class);
                 assertThat(genericType, isPresentAndRenamed(minify));
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java
index 42f2492..7600af0 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java
@@ -4,10 +4,15 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Iterator;
@@ -15,13 +20,27 @@
 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 RepackageWithCollisionsTest extends RepackageTestBase {
 
+  @Parameters(name = "{1}, kind: {0}, isCompat: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values());
+  }
+
+  private final boolean isCompat;
+
   public RepackageWithCollisionsTest(
-      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+      String flattenPackageHierarchyOrRepackageClasses,
+      TestParameters parameters,
+      boolean isCompat) {
     super(flattenPackageHierarchyOrRepackageClasses, parameters);
+    this.isCompat = isCompat;
   }
 
   @Override
@@ -34,7 +53,7 @@
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
+    (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
         .addInnerClasses(getClass())
         .addProgramClasses(getTestClasses())
         .addKeepMainRule(TestClass.class)
@@ -63,22 +82,22 @@
     Iterator<Class<?>> testClassesIterator = getTestClasses().iterator();
 
     Class<?> firstFoo = testClassesIterator.next();
-    assertThat(firstFoo, isRepackagedAsExpected(inspector, "a"));
+    isRepackagedOrAbsentInFullMode(inspector, firstFoo, "a");
 
     Class<?> firstFooBar = testClassesIterator.next();
     assertThat(firstFooBar, isRepackagedAsExpected(inspector, "a"));
 
     Class<?> firstFirstFoo = testClassesIterator.next();
-    assertThat(firstFirstFoo, isRepackagedAsExpected(inspector, "b"));
+    isRepackagedOrAbsentInFullMode(inspector, firstFirstFoo, "b");
 
     Class<?> firstFirstFooBar = testClassesIterator.next();
-    assertThat(firstFirstFooBar, isRepackagedAsExpected(inspector, "b"));
+    isRepackagedOrAbsentInFullMode(inspector, firstFirstFooBar, "b");
 
     Class<?> secondFoo = testClassesIterator.next();
-    assertThat(secondFoo, isRepackagedAsExpected(inspector, "c"));
+    isRepackagedOrAbsentInFullMode(inspector, secondFoo, "c");
 
     Class<?> secondBar = testClassesIterator.next();
-    assertThat(secondBar, isRepackagedAsExpected(inspector, "c"));
+    isRepackagedOrAbsentInFullMode(inspector, secondBar, "c");
 
     Class<?> destinationFoo = testClassesIterator.next();
     assertThat(inspector.clazz(destinationFoo), isPresentAndNotRenamed());
@@ -93,6 +112,15 @@
     assertThat(inspector.clazz(destinationFirstBar), isPresentAndNotRenamed());
   }
 
+  private void isRepackagedOrAbsentInFullMode(
+      CodeInspector inspector, Class<?> clazz, String packageName) {
+    if (isCompat) {
+      assertThat(clazz, isRepackagedAsExpected(inspector, packageName));
+    } else {
+      assertThat(inspector.clazz(clazz), not(isPresent()));
+    }
+  }
+
   private static List<Class<?>> getTestClasses() {
     return ImmutableList.of(
         com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.Foo.class,
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
index e4c7fd1..3097738 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
@@ -7,8 +7,10 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.repackage.RepackageWithPackagePrivateInnerClassTest.IneligibleForRepackaging.NonPublicKeptClass;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -26,29 +28,45 @@
   }
 
   @Test
-  public void test() throws Exception {
-    testForR8(parameters.getBackend())
+  public void testCompat() throws Exception {
+    test(
+        testForR8Compat(parameters.getBackend()).addKeepClassRules(NonPublicKeptClass.class),
+        false);
+  }
+
+  @Test
+  public void testFull() throws Exception {
+    test(testForR8(parameters.getBackend()).addKeepClassRules(NonPublicKeptClass.class), true);
+  }
+
+  private void test(R8TestBuilder<?> builder, boolean expectRepackaged) throws Exception {
+    builder
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
-        .addKeepClassRules(NonPublicKeptClass.class)
         .addKeepAttributeInnerClassesAndEnclosingMethod()
         .apply(this::configureRepackaging)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, expectRepackaged))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello world!");
   }
 
-  private void inspect(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector, boolean expectRepackaged) {
     ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
     assertThat(classSubject, isPresent());
 
     // Verify that the class was not repackaged.
-    assertEquals(
-        IneligibleForRepackaging.class.getPackage().getName(),
-        classSubject.getDexProgramClass().getType().getPackageName());
+    if (expectRepackaged) {
+      assertNotEquals(
+          IneligibleForRepackaging.class.getPackage().getName(),
+          classSubject.getDexProgramClass().getType().getPackageName());
+    } else {
+      assertEquals(
+          IneligibleForRepackaging.class.getPackage().getName(),
+          classSubject.getDexProgramClass().getType().getPackageName());
+    }
   }
 
   public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderClassLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderClassLoaderRewritingTest.java
new file mode 100644
index 0000000..fbaef02
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderClassLoaderRewritingTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2021, 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;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DataEntryResource;
+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.origin.Origin;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+import java.util.zip.ZipFile;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderClassLoaderRewritingTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!");
+
+  public interface Service {
+
+    void print();
+  }
+
+  public static class ServiceImpl implements Service {
+
+    @Override
+    public void print() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class MainRunner {
+
+    public static void main(String[] args) {
+      run1();
+    }
+
+    @NeverInline
+    public static void run1() {
+      ClassLoader classLoader = Service.class.getClassLoader();
+      checkNotNull(classLoader);
+      for (Service x : ServiceLoader.load(Service.class, classLoader)) {
+        x.print();
+      }
+    }
+
+    @NeverInline
+    public static void checkNotNull(ClassLoader classLoader) {
+      if (classLoader == null) {
+        throw new NullPointerException("ClassLoader should not be null");
+      }
+    }
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ServiceLoaderClassLoaderRewritingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRewritings() throws Exception {
+    Path path = temp.newFile("out.zip").toPath();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ServiceLoaderClassLoaderRewritingTest.class)
+        .addKeepMainRule(MainRunner.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+                "META-INF/services/" + Service.class.getTypeName(),
+                Origin.unknown()))
+        .compile()
+        .writeToZip(path)
+        .inspect(this::verifyNoServiceLoaderLoads)
+        .run(parameters.getRuntime(), MainRunner.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+
+    // Check that we have removed the service configuration from META-INF/services.
+    ZipFile zip = new ZipFile(path.toFile());
+    assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
+  }
+
+  private void verifyNoServiceLoaderLoads(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(MainRunner.class);
+    assertTrue(classSubject.isPresent());
+    classSubject.forAllMethods(
+        method -> MatcherAssert.assertThat(method, not(invokesMethodWithName("load"))));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsSameMethodTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsSameMethodTest.java
new file mode 100644
index 0000000..49589c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsSameMethodTest.java
@@ -0,0 +1,144 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataEntryResource;
+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.origin.Origin;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.ZipFile;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderMultipleCallsSameMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!", "Hello World!");
+
+  public interface Service {
+
+    void print();
+  }
+
+  public static class ServiceImpl implements Service {
+
+    @Override
+    public void print() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class ServiceImpl2 implements Service {
+
+    @Override
+    public void print() {
+      System.out.println("Hello World 2!");
+    }
+  }
+
+  public static class MainRunner {
+
+    public static void main(String[] args) {
+      run1();
+    }
+
+    @NeverInline
+    public static void run1() {
+      ClassLoader classLoader = Service.class.getClassLoader();
+      for (Service x : ServiceLoader.load(Service.class, classLoader)) {
+        x.print();
+      }
+      for (Service x : ServiceLoader.load(Service.class, classLoader)) {
+        x.print();
+      }
+    }
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ServiceLoaderMultipleCallsSameMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRewritings() throws IOException, CompilationFailedException, ExecutionException {
+    Path path = temp.newFile("out.zip").toPath();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ServiceLoaderMultipleCallsSameMethodTest.class)
+        .addKeepMainRule(MainRunner.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+                "META-INF/services/" + Service.class.getTypeName(),
+                Origin.unknown()))
+        .compile()
+        .writeToZip(path)
+        .run(parameters.getRuntime(), MainRunner.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT)
+        // Check that we have actually rewritten the calls to ServiceLoader.load.
+        .inspect(this::verifyNoServiceLoaderLoads)
+        .inspect(this::verifyNoClassLoaders)
+        .inspect(
+            inspector -> {
+              // Check the synthesize service loader method is a single shared method.
+              // Due to minification we just check there is only a single synthetic class with a
+              // single static method.
+              boolean found = false;
+              for (FoundClassSubject clazz : inspector.allClasses()) {
+                if (clazz.isSynthetic()) {
+                  assertFalse(found);
+                  found = true;
+                  assertEquals(1, clazz.allMethods().size());
+                  clazz.forAllMethods(m -> assertTrue(m.isStatic()));
+                }
+              }
+            });
+
+    // Check that we have removed the service configuration from META-INF/services.
+    ZipFile zip = new ZipFile(path.toFile());
+    assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
+  }
+
+  private void verifyNoServiceLoaderLoads(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(MainRunner.class);
+    Assert.assertTrue(classSubject.isPresent());
+    classSubject.forAllMethods(
+        method -> MatcherAssert.assertThat(method, not(invokesMethodWithName("load"))));
+  }
+
+  private void verifyNoClassLoaders(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(MainRunner.class);
+    Assert.assertTrue(classSubject.isPresent());
+    classSubject.forAllMethods(
+        method -> MatcherAssert.assertThat(method, not(invokesMethodWithName("getClassLoader"))));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
index cc1f241..d2e5d79 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.shaking.attributes;
 
-import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
@@ -19,6 +18,7 @@
 public class KeepEnclosingMethodForKeptMethodTest extends TestBase {
 
   private final TestParameters parameters;
+
   private final String[] EXPECTED = {
     "null",
     "class com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest"
@@ -28,13 +28,8 @@
     "public"
         + " com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest$KeptClass()"
   };
-  private final String[] R8_OUTPUT = {
-    "null",
-    "class com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest"
-        + "$KeptClass",
-    "null",
-    "null"
-  };
+
+  private final String[] EXPECTED_FULL = {"null", "null", "null", "null"};
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -58,8 +53,7 @@
 
   @Test
   public void testR8Full() throws Exception {
-    // TODO(b/171194649): This should output EXPECTED.
-    runTest(testForR8(parameters.getBackend())).assertSuccessWithOutputLines(R8_OUTPUT);
+    runTest(testForR8(parameters.getBackend())).assertSuccessWithOutputLines(EXPECTED_FULL);
   }
 
   @Test
@@ -78,7 +72,6 @@
         .addKeepMainRule(KeptClass.class)
         .addKeepClassAndMembersRules(KeptClass.class)
         .setMinApi(parameters.getApiLevel())
-        .enableInliningAnnotations()
         .run(parameters.getRuntime(), KeptClass.class);
   }
 
@@ -116,7 +109,6 @@
       new KeptClass().instanceField.foo();
     }
 
-    @NeverInline
     public static I enclosingFromKeptMethod() {
       return new I() {
         @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java
index 83c55fd..6c7c774 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java
@@ -14,7 +14,9 @@
 
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.attributes.testclasses.Outer;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -23,43 +25,24 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-
-class Main {
-
-  public static void main(String[] args) {
-    Class<?>[] declaredClasses = Outer.class.getDeclaredClasses();
-    if (declaredClasses.length == 0) {
-      System.out.println("No declared classes");
-    } else {
-      for (int i = 0; i < declaredClasses.length; i++) {
-        System.out.println("Declared class: " + declaredClasses[i].getName());
-      }
-    }
-    if (Outer.Inner.class.getDeclaringClass() == null) {
-      System.out.println("No declaring classes");
-    } else {
-      System.out.println("Declaring class: " + Outer.Inner.class.getDeclaringClass().getName());
-    }
-  }
-}
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class KeepInnerClassesEnclosingMethodAnnotationsTest extends TestBase {
 
-  enum TestConfig {
-    R8,
-    PROGUARD
+  private final TestParameters parameters;
+  private final boolean isCompat;
+
+  @Parameters(name = "{0}, isCompat: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
-  @Parameterized.Parameters(name = "{0}")
-  public static Object[] parameters() {
-    return TestConfig.values();
-  }
-
-  private final TestConfig config;
-
-  public KeepInnerClassesEnclosingMethodAnnotationsTest(TestConfig config) {
-    this.config = config;
+  public KeepInnerClassesEnclosingMethodAnnotationsTest(
+      TestParameters parameters, boolean isCompat) {
+    this.parameters = parameters;
+    this.isCompat = isCompat;
   }
 
   private static class TestResult {
@@ -79,27 +62,14 @@
     }
   }
 
-  private TestResult runProguard(List<String> proguardConfiguration) throws Throwable {
-    return new TestResult(
-        testForProguard()
-            .addProgramClasses(ImmutableList.of(Main.class, Outer.class, Outer.Inner.class))
-            .addKeepRules(proguardConfiguration)
-            .run(Main.class));
-  }
-
   private TestResult runR8(List<String> proguardConfiguration) throws Throwable {
     return new TestResult(
-        testForR8(Backend.DEX)
+        (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
             .addProgramClassesAndInnerClasses(Outer.class)
             .addProgramClasses(Main.class)
+            .setMinApi(parameters.getApiLevel())
             .addKeepRules(proguardConfiguration)
-            .run(Main.class));
-  }
-
-  private TestResult runShrinker(List<String> proguardConfiguration) throws Throwable {
-    return config == TestConfig.R8
-        ? runR8(proguardConfiguration)
-        : runProguard(proguardConfiguration);
+            .run(parameters.getRuntime(), Main.class));
   }
 
   private void noInnerClassesEnclosingMethodInformation(TestResult result) {
@@ -119,7 +89,7 @@
   @Test
   public void testKeepAll() throws Throwable {
     TestResult result =
-        runShrinker(
+        runR8(
             ImmutableList.of(
                 "-keepattributes InnerClasses,EnclosingMethod",
                 "-keep class **Outer*",
@@ -133,8 +103,7 @@
   @Test
   public void testKeepAllWithoutAttributes() throws Throwable {
     TestResult result =
-        runShrinker(
-            ImmutableList.of("-keep class **Outer*", keepMainProguardConfiguration(Main.class)));
+        runR8(ImmutableList.of("-keep class **Outer*", keepMainProguardConfiguration(Main.class)));
     assertThat(result.outer, isPresentAndNotRenamed());
     assertThat(result.inner, isPresentAndNotRenamed());
     assertThat(result.inner, not(isMemberClass()));
@@ -144,28 +113,55 @@
   @Test
   public void testKeepInner() throws Throwable {
     TestResult result =
-        runShrinker(
+        runR8(
             ImmutableList.of(
                 "-keepattributes InnerClasses,EnclosingMethod",
                 "-keep class **Outer$Inner",
                 keepMainProguardConfiguration(Main.class)));
-    assertThat(result.outer, isPresentAndNotRenamed());
+    assertThat(result.outer, isCompat ? isPresentAndNotRenamed() : isPresentAndRenamed());
     assertThat(result.inner, isPresentAndNotRenamed());
-    assertThat(result.inner, isMemberClass());
-    fullInnerClassesEnclosingMethodInformation(result);
+    assertThat(result.inner, isCompat ? isMemberClass() : not(isMemberClass()));
+    if (isCompat) {
+      fullInnerClassesEnclosingMethodInformation(result);
+    } else {
+      noInnerClassesEnclosingMethodInformation(result);
+    }
   }
 
   @Test
   public void testKeepOuter() throws Throwable {
     TestResult result =
-        runShrinker(
+        runR8(
             ImmutableList.of(
                 "-keepattributes InnerClasses,EnclosingMethod",
                 "-keep class **Outer",
                 keepMainProguardConfiguration(Main.class)));
     assertThat(result.outer, isPresentAndNotRenamed());
     assertThat(result.inner, isPresentAndRenamed());
-    assertThat(result.inner, isMemberClass());
-    fullInnerClassesEnclosingMethodInformation(result);
+    assertThat(result.inner, isCompat ? isMemberClass() : not(isMemberClass()));
+    if (isCompat) {
+      fullInnerClassesEnclosingMethodInformation(result);
+    } else {
+      noInnerClassesEnclosingMethodInformation(result);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Class<?>[] declaredClasses = Outer.class.getDeclaredClasses();
+      if (declaredClasses.length == 0) {
+        System.out.println("No declared classes");
+      } else {
+        for (int i = 0; i < declaredClasses.length; i++) {
+          System.out.println("Declared class: " + declaredClasses[i].getName());
+        }
+      }
+      if (Outer.Inner.class.getDeclaringClass() == null) {
+        System.out.println("No declaring classes");
+      } else {
+        System.out.println("Declaring class: " + Outer.Inner.class.getDeclaringClass().getName());
+      }
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 4e53505..9933b84 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -455,7 +455,12 @@
               .enableSideEffectAnnotations()
               .setMinApi(parameters.getApiLevel())
               .run(parameters.getRuntime(), TestKeepAttributes.class)
-              .assertSuccessWithOutput(innerClasses || enclosingMethod ? "1" : "0")
+              .applyIf(
+                  forceProguardCompatibility && (innerClasses || enclosingMethod),
+                  result -> result.assertSuccessWithOutput("1"))
+              .applyIf(
+                  !forceProguardCompatibility || !(innerClasses || enclosingMethod),
+                  result -> result.assertSuccessWithOutput("0"))
               .inspector();
     } catch (CompilationFailedException e) {
       assertTrue(!forceProguardCompatibility && (!innerClasses || !enclosingMethod));
@@ -464,7 +469,7 @@
 
     ClassSubject clazz = inspector.clazz(TestKeepAttributes.class);
     assertThat(clazz, isPresent());
-    if (innerClasses || enclosingMethod) {
+    if (forceProguardCompatibility && (innerClasses || enclosingMethod)) {
       assertFalse(clazz.getDexProgramClass().getInnerClasses().isEmpty());
     } else {
       assertTrue(clazz.getDexProgramClass().getInnerClasses().isEmpty());