Merge commit 'dd2cf8c8ad27604acfe7f4c0f1e71deeda5abc73' into dev-release
diff --git a/README.md b/README.md
index 1c5dc9f..2d24142 100644
--- a/README.md
+++ b/README.md
@@ -84,8 +84,12 @@
 If your contribution is owned by your employer you need the
 [Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate).
 
-Once the license agreement is in place, you can upload your patches
-using 'git cl' which is available in depot_tools. Once you have a
+Once the license agreement is in place, please send an email to
+[r8-dev@googlegroups.com](mailto:r8-dev@googlegroups.com) to be added as a
+contributor.
+
+After being added as a contributer you can upload your patches
+using `git cl` which is available in `depot_tools`. Once you have a
 change that you are happy with you should make sure that it passes
 all tests and then upload the change to our code review tool using:
 
@@ -101,7 +105,8 @@
 
 ## Getting help
 
-For questions, reach out to us at r8-dev@googlegroups.com.
+For questions, reach out to us at
+[r8-dev@googlegroups.com](mailto:r8-dev@googlegroups.com).
 
 For D8, find known issues in the
 [D8 issue tracker](https://issuetracker.google.com/issues?q=componentid:317603)
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 11ad90f..f4975b7 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -39,6 +39,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64,6 +68,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -89,6 +97,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -114,6 +126,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -139,6 +155,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -165,6 +185,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -191,6 +215,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -217,6 +245,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -243,6 +275,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -269,6 +305,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -295,6 +335,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -321,6 +365,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -347,6 +395,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -373,6 +425,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -399,6 +455,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -425,6 +485,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -451,6 +515,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -477,6 +545,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -503,6 +575,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -529,6 +605,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -555,6 +635,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -581,6 +665,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -607,6 +695,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -633,6 +725,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -659,6 +755,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -685,6 +785,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -711,6 +815,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -738,6 +846,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -765,6 +877,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -791,6 +907,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -817,6 +937,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -843,6 +967,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -869,6 +997,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -895,6 +1027,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -921,6 +1057,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -946,6 +1086,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -972,6 +1116,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -998,6 +1146,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1024,6 +1176,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1050,6 +1206,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1076,6 +1236,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1102,6 +1266,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1127,6 +1295,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1152,6 +1324,10 @@
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg
index 97e5a27..1659d63 100644
--- a/infra/config/global/generated/project.cfg
+++ b/infra/config/global/generated/project.cfg
@@ -6,3 +6,9 @@
 
 name: "r8"
 access: "group:all"
+lucicfg {
+  version: "1.29.1"
+  package_dir: ".."
+  config_dir: "generated"
+  entry_point: "main.star"
+}
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 0d94768..238f87c 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -6,7 +6,10 @@
 lucicfg.enable_experiment("crbug.com/1085650")
 
 # Launch 0% of Builds in "realms-aware mode"
-luci.builder.defaults.experiments.set({"luci.use_realms": 100})
+luci.builder.defaults.experiments.set({
+    "luci.use_realms": 100,
+    "luci.recipes.use_python3": 100
+})
 
 
 luci.project(
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
index 07102c1..e57f798 100644
--- a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
@@ -63,6 +63,17 @@
         });
   }
 
+  public void forEachImmediateSuperClassMatching(
+      DexClass clazz, Predicate<? super DexClass> predicate, Consumer<? super DexClass> consumer) {
+    clazz.forEachImmediateSupertype(
+        supertype -> {
+          DexClass superclass = appView.definitionFor(supertype);
+          if (superclass != null && predicate.test(superclass)) {
+            consumer.accept(superclass);
+          }
+        });
+  }
+
   public void forEachImmediateProgramSuperClass(
       DexProgramClass clazz, Consumer<? super DexProgramClass> consumer) {
     forEachImmediateProgramSuperClassMatching(clazz, alwaysTrue(), consumer);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index eea6e6b..e68cc71 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -275,14 +275,25 @@
     }
 
     public void addInstancePut(DexField field, InstanceFieldInitializationInfo value) {
+      if (parentConstructor == null) {
+        instanceFieldAssignmentsPre.put(field, value);
+        return;
+      }
+
       // If the parent constructor is java.lang.Object.<init>() then group all the field assignments
       // before the parent constructor call to allow more sharing.
-      if (parentConstructor == null
-          || parentConstructor == dexItemFactory.objectMembers.constructor) {
-        instanceFieldAssignmentsPre.put(field, value);
-      } else {
-        instanceFieldAssignmentsPost.put(field, value);
+      //
+      // Note that field assignments that store the receiver cannot be hoisted to before the
+      // Object.<init>() call, since this would lead to an illegal use of the uninitialized 'this'.
+      if (parentConstructor == dexItemFactory.objectMembers.constructor) {
+        if (!value.isArgumentInitializationInfo()
+            || value.asArgumentInitializationInfo().getArgumentIndex() != 0) {
+          instanceFieldAssignmentsPre.put(field, value);
+          return;
+        }
       }
+
+      instanceFieldAssignmentsPost.put(field, value);
     }
 
     public boolean addInvokeConstructor(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 162ad98..fb5cfa3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
@@ -38,6 +39,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
 import com.android.tools.r8.horizontalclassmerging.policies.NoVerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoVirtualMethodMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.NoWeakerAccessPrivileges;
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
 import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
@@ -194,6 +196,8 @@
       Mode mode,
       RuntimeTypeCheckInfo runtimeTypeCheckInfo,
       ImmutableList.Builder<Policy> builder) {
+    ImmediateProgramSubtypingInfo immediateSubtypingInfo =
+        ImmediateProgramSubtypingInfo.create(appView);
     builder.add(
         new CheckAbstractClasses(appView),
         new NoClassAnnotationCollisions(),
@@ -206,7 +210,8 @@
         new RespectPackageBoundaries(appView),
         new NoDifferentApiReferenceLevel(appView),
         new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
-        new PreventClassMethodAndDefaultMethodCollisions(appView));
+        new NoWeakerAccessPrivileges(appView, immediateSubtypingInfo),
+        new PreventClassMethodAndDefaultMethodCollisions(appView, immediateSubtypingInfo));
   }
 
   private static void addMultiClassPoliciesForMergingNonSyntheticClasses(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoWeakerAccessPrivileges.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoWeakerAccessPrivileges.java
new file mode 100644
index 0000000..399e807
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoWeakerAccessPrivileges.java
@@ -0,0 +1,172 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class NoWeakerAccessPrivileges extends MultiClassPolicy {
+
+  private final ProgramClassesBidirectedGraph graph;
+  private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+
+  private final Map<DexClass, DexMethodSignatureSet> inheritedInterfaceMethodsCache =
+      new IdentityHashMap<>();
+  private final Map<Set<DexProgramClass>, DexMethodSignatureSet>
+      nonPublicVirtualMethodSignaturesCache = new IdentityHashMap<>();
+  private final Map<DexProgramClass, Set<DexProgramClass>> stronglyConnectedComponentsCache =
+      new IdentityHashMap<>();
+
+  public NoWeakerAccessPrivileges(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+    this.graph = new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo);
+    this.immediateSubtypingInfo = immediateSubtypingInfo;
+  }
+
+  @Override
+  public Collection<MergeGroup> apply(MergeGroup group) {
+    List<MergeGroup> newMergeGroups = new LinkedList<>();
+    Map<MergeGroup, DexMethodSignatureSet> inheritedInterfaceMethodsPerGroup =
+        new IdentityHashMap<>();
+    for (DexProgramClass clazz : group) {
+      // Find an existing merge group that the current class can be added to.
+      MergeGroup newMergeGroup = null;
+      for (MergeGroup candidateMergeGroup : newMergeGroups) {
+        DexMethodSignatureSet inheritedInterfaceMethodsInGroup =
+            inheritedInterfaceMethodsPerGroup.get(candidateMergeGroup);
+        if (canAddToGroup(clazz, candidateMergeGroup, inheritedInterfaceMethodsInGroup)) {
+          newMergeGroup = candidateMergeGroup;
+          break;
+        }
+      }
+
+      DexMethodSignatureSet inheritedInterfaceMethodsInGroup;
+      if (newMergeGroup == null) {
+        // Form a new singleton merge group from the current class.
+        newMergeGroup = new MergeGroup(clazz);
+        newMergeGroups.add(newMergeGroup);
+        inheritedInterfaceMethodsInGroup = DexMethodSignatureSet.create();
+        inheritedInterfaceMethodsPerGroup.put(newMergeGroup, inheritedInterfaceMethodsInGroup);
+      } else {
+        // Add the current class to the existing merge group.
+        newMergeGroup.add(clazz);
+        inheritedInterfaceMethodsInGroup = inheritedInterfaceMethodsPerGroup.get(newMergeGroup);
+      }
+
+      // Record that the merge group now contains the direct and indirect interface methods of the
+      // current class.
+      inheritedInterfaceMethodsInGroup.addAll(getOrComputeInheritedInterfaceMethods(clazz));
+    }
+
+    // Remove any singleton merge groups.
+    return removeTrivialGroups(newMergeGroups);
+  }
+
+  private boolean canAddToGroup(
+      DexProgramClass clazz,
+      MergeGroup group,
+      DexMethodSignatureSet inheritedInterfaceMethodsInGroup) {
+    // We need to ensure that adding class to the group is OK.
+    DexMethodSignatureSet nonPublicVirtualMethodSignaturesInClassComponent =
+        getOrComputeNonPublicVirtualMethodSignaturesInComponentOf(clazz);
+    if (nonPublicVirtualMethodSignaturesInClassComponent.containsAnyOf(
+        inheritedInterfaceMethodsInGroup)) {
+      return false;
+    }
+
+    // We need to ensure adding all classes in the group to the class is OK.
+    Set<Set<DexProgramClass>> components = Sets.newIdentityHashSet();
+    for (DexProgramClass member : group) {
+      components.add(getOrComputeStronglyConnectedComponent(member));
+    }
+    for (Set<DexProgramClass> component : components) {
+      if (getOrComputeNonPublicVirtualMethodSignaturesInComponent(component)
+          .containsAnyOf(getOrComputeInheritedInterfaceMethods(clazz))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private DexMethodSignatureSet getOrComputeInheritedInterfaceMethods(DexClass clazz) {
+    if (inheritedInterfaceMethodsCache.containsKey(clazz)) {
+      return inheritedInterfaceMethodsCache.get(clazz);
+    }
+    DexMethodSignatureSet inheritedInterfaceMethods = DexMethodSignatureSet.create();
+    immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+        clazz,
+        DexClass::isInterface,
+        superclass ->
+            inheritedInterfaceMethods.addAll(getOrComputeInheritedInterfaceMethods(superclass)));
+    if (clazz.isInterface()) {
+      clazz.forEachClassMethodMatching(
+          DexEncodedMethod::belongsToVirtualPool, inheritedInterfaceMethods::add);
+    }
+    inheritedInterfaceMethodsCache.put(clazz, inheritedInterfaceMethods);
+    return inheritedInterfaceMethods;
+  }
+
+  private Set<DexProgramClass> getOrComputeStronglyConnectedComponent(DexProgramClass clazz) {
+    if (stronglyConnectedComponentsCache.containsKey(clazz)) {
+      return stronglyConnectedComponentsCache.get(clazz);
+    }
+    Set<DexProgramClass> stronglyConnectedComponent =
+        graph.computeStronglyConnectedComponent(clazz);
+    for (DexProgramClass member : stronglyConnectedComponent) {
+      stronglyConnectedComponentsCache.put(member, stronglyConnectedComponent);
+    }
+    return stronglyConnectedComponent;
+  }
+
+  private DexMethodSignatureSet getOrComputeNonPublicVirtualMethodSignaturesInComponentOf(
+      DexProgramClass clazz) {
+    return getOrComputeNonPublicVirtualMethodSignaturesInComponent(
+        getOrComputeStronglyConnectedComponent(clazz));
+  }
+
+  private DexMethodSignatureSet getOrComputeNonPublicVirtualMethodSignaturesInComponent(
+      Set<DexProgramClass> stronglyConnectedComponent) {
+    if (nonPublicVirtualMethodSignaturesCache.containsKey(stronglyConnectedComponent)) {
+      return nonPublicVirtualMethodSignaturesCache.get(stronglyConnectedComponent);
+    }
+    DexMethodSignatureSet nonPublicVirtualMethodSignatures = DexMethodSignatureSet.create();
+    for (DexProgramClass clazz : stronglyConnectedComponent) {
+      clazz.forEachProgramVirtualMethodMatching(
+          method -> method.getAccessFlags().isPackagePrivateOrProtected(),
+          nonPublicVirtualMethodSignatures::add);
+    }
+    nonPublicVirtualMethodSignaturesCache.put(
+        stronglyConnectedComponent, nonPublicVirtualMethodSignatures);
+    return nonPublicVirtualMethodSignatures;
+  }
+
+  @Override
+  public void clear() {
+    inheritedInterfaceMethodsCache.clear();
+    nonPublicVirtualMethodSignaturesCache.clear();
+    stronglyConnectedComponentsCache.clear();
+  }
+
+  @Override
+  public String getName() {
+    return "NoWeakerAccessPriviledges";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
index 7c53bc3..d25c21d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
@@ -12,10 +12,10 @@
 import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import com.android.tools.r8.horizontalclassmerging.SubtypingForrestForClasses;
 import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -52,7 +52,7 @@
 public class PreventClassMethodAndDefaultMethodCollisions extends MultiClassPolicy {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final SubtypingForrestForClasses subtypingForrestForClasses;
+  private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
 
   private final InterfaceDefaultSignaturesCache interfaceDefaultMethodsCache =
       new InterfaceDefaultSignaturesCache();
@@ -119,16 +119,16 @@
     void process(DexProgramClass clazz, DexMethodSignatureSet signatures) {
       signatures.addAll(
           clazz.getInterfaces(), interfaceDefaultMethodsCache::getOrComputeSignatures);
-      signatures.addAll(
-          subtypingForrestForClasses.getSubtypesFor(clazz), this::getOrComputeSignatures);
+      signatures.addAll(immediateSubtypingInfo.getSubclasses(clazz), this::getOrComputeSignatures);
       signatures.removeAllMethods(clazz.methods());
     }
   }
 
   public PreventClassMethodAndDefaultMethodCollisions(
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
     this.appView = appView;
-    this.subtypingForrestForClasses = new SubtypingForrestForClasses(appView);
+    this.immediateSubtypingInfo = immediateSubtypingInfo;
   }
 
   enum MethodCategory {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 9413849..b2fd06d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -71,6 +71,7 @@
     factory.createSynthesizedType("Ljava/lang/NoSuchMethodException;");
     factory.createSynthesizedType("Ljava/lang/NullPointerException;");
     factory.createSynthesizedType("Ljava/lang/NumberFormatException;");
+    factory.createSynthesizedType("Ljava/lang/OutOfMemoryError;");
     factory.createSynthesizedType("Ljava/lang/Runnable;");
     factory.createSynthesizedType("Ljava/lang/SecurityException;");
     factory.createSynthesizedType("Ljava/lang/reflect/InvocationTargetException;");
@@ -8354,6 +8355,9 @@
     CfLabel label11 = new CfLabel();
     CfLabel label12 = new CfLabel();
     CfLabel label13 = new CfLabel();
+    CfLabel label14 = new CfLabel();
+    CfLabel label15 = new CfLabel();
+    CfLabel label16 = new CfLabel();
     return new CfCode(
         method.holder,
         4,
@@ -8469,6 +8473,111 @@
                       FrameType.initialized(options.itemFactory.intType)
                     }),
                 new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringType,
+                    options.itemFactory.createProto(options.itemFactory.intType),
+                    options.itemFactory.createString("length")),
+                false),
+            new CfConstNumber(2147483647, ValueType.INT),
+            new CfLoad(ValueType.INT, 1),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Div, NumericType.INT),
+            new CfIfCmp(If.Type.LE, ValueType.INT, label10),
+            label8,
+            new CfNew(options.itemFactory.createType("Ljava/lang/OutOfMemoryError;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfNew(options.itemFactory.stringBuilderType),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfConstString(options.itemFactory.createString("Repeating ")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfLoad(ValueType.OBJECT, 0),
+            label9,
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringType,
+                    options.itemFactory.createProto(options.itemFactory.intType),
+                    options.itemFactory.createString("length")),
+                false),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.intType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfConstString(options.itemFactory.createString(" bytes String ")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfLoad(ValueType.INT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.intType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfConstString(
+                options.itemFactory.createString(
+                    " times will produce a String exceeding maximum size.")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                false),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/OutOfMemoryError;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.voidType, options.itemFactory.stringType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label10,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.INT, 2),
@@ -8483,10 +8592,10 @@
                     options.itemFactory.createString("<init>")),
                 false),
             new CfStore(ValueType.OBJECT, 3),
-            label8,
+            label11,
             new CfConstNumber(0, ValueType.INT),
             new CfStore(ValueType.INT, 4),
-            label9,
+            label12,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
@@ -8500,8 +8609,8 @@
                 new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 4),
             new CfLoad(ValueType.INT, 1),
-            new CfIfCmp(If.Type.GE, ValueType.INT, label12),
-            label10,
+            new CfIfCmp(If.Type.GE, ValueType.INT, label15),
+            label13,
             new CfLoad(ValueType.OBJECT, 3),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
@@ -8513,10 +8622,10 @@
                     options.itemFactory.createString("append")),
                 false),
             new CfStackInstruction(CfStackInstruction.Opcode.Pop),
-            label11,
+            label14,
             new CfIinc(4, 1),
-            new CfGoto(label9),
-            label12,
+            new CfGoto(label12),
+            label15,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
@@ -8536,7 +8645,7 @@
                     options.itemFactory.createString("toString")),
                 false),
             new CfReturn(ValueType.OBJECT),
-            label13),
+            label16),
         ImmutableList.of(),
         ImmutableList.of());
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 9d7d84c..15a3b02 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.ChainableStringConsumer;
@@ -30,8 +31,10 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 
 public class ClassNameMapper implements ProguardMap {
 
@@ -41,7 +44,9 @@
   }
 
   public static class Builder extends ProguardMap.Builder {
+
     private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
+    private LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
 
     private Builder() {
 
@@ -58,7 +63,7 @@
 
     @Override
     public ClassNameMapper build() {
-      return new ClassNameMapper(buildClassNameMappings());
+      return new ClassNameMapper(buildClassNameMappings(), mapVersions);
     }
 
     private ImmutableMap<String, ClassNamingForNameMapper> buildClassNameMappings() {
@@ -68,6 +73,12 @@
           (renamedName, valueBuilder) -> builder.put(renamedName, valueBuilder.build()));
       return builder.build();
     }
+
+    @Override
+    ProguardMap.Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion) {
+      mapVersions.add(mapVersion);
+      return this;
+    }
   }
 
   public static Builder builder() {
@@ -138,9 +149,13 @@
   private final ImmutableMap<String, ClassNamingForNameMapper> classNameMappings;
   private BiMapContainer<String, String> nameMapping;
   private final Map<Signature, Signature> signatureMap = new HashMap<>();
+  private final Set<MapVersionMappingInformation> mapVersions;
 
-  private ClassNameMapper(ImmutableMap<String, ClassNamingForNameMapper> classNameMappings) {
+  private ClassNameMapper(
+      ImmutableMap<String, ClassNamingForNameMapper> classNameMappings,
+      Set<MapVersionMappingInformation> mapVersions) {
     this.classNameMappings = classNameMappings;
+    this.mapVersions = mapVersions;
   }
 
   public Map<String, ClassNamingForNameMapper> getClassNameMappings() {
@@ -216,7 +231,7 @@
     ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
     builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
     classNameMappings.forEach(builder::put);
-    return new ClassNameMapper(builder.build());
+    return new ClassNameMapper(builder.build(), mapVersions);
   }
 
   public boolean verifyIsSorted() {
@@ -328,4 +343,8 @@
   public String originalNameOf(DexType clazz) {
     return deobfuscateType(clazz.descriptor.toString());
   }
+
+  public Set<MapVersionMappingInformation> getMapVersions() {
+    return mapVersions;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MapVersion.java b/src/main/java/com/android/tools/r8/naming/MapVersion.java
index 9c9c8d3..87412e8 100644
--- a/src/main/java/com/android/tools/r8/naming/MapVersion.java
+++ b/src/main/java/com/android/tools/r8/naming/MapVersion.java
@@ -8,7 +8,8 @@
 public enum MapVersion implements Ordered<MapVersion> {
   MAP_VERSION_NONE("none"),
   MAP_VERSION_1_0("1.0"),
-  MAP_VERSION_EXPERIMENTAL("experimental");
+  MAP_VERSION_EXPERIMENTAL("experimental"),
+  MAP_VERSION_UNKNOWN("unknown");
 
   public static final MapVersion STABLE = MAP_VERSION_1_0;
 
@@ -30,4 +31,8 @@
     }
     return null;
   }
+
+  public boolean isUnknown() {
+    return this == MAP_VERSION_UNKNOWN;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
index 221f064..6b95444 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMap.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.position.Position;
 
 public interface ProguardMap {
@@ -12,6 +13,8 @@
     abstract ClassNaming.Builder classNamingBuilder(
         String renamedName, String originalName, Position position);
 
+    abstract Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion);
+
     abstract ProguardMap build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 6dd1fa3..b2cb740 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
 import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IdentifierUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.gson.JsonObject;
@@ -232,6 +233,9 @@
             info -> {
               assert info.isMapVersionMappingInformation()
                   || info.isUnknownJsonMappingInformation();
+              if (info.isMapVersionMappingInformation()) {
+                mapBuilder.setCurrentMapVersion(info.asMapVersionMappingInformation());
+              }
             });
         // Skip reading the rest of the line.
         lineOffset = line.length();
@@ -298,8 +302,12 @@
       if (isCommentLineWithJsonBrace()) {
         final MemberNaming currentMember = activeMemberNaming;
         final MappedRange currentRange = activeMappedRange;
+        // Reading global info should cause member mapping to return since we are now reading
+        // headers pertaining to what could be a concatinated file.
+        BooleanBox readGlobalInfo = new BooleanBox(false);
         parseMappingInformation(
             info -> {
+              readGlobalInfo.set(info.isGlobalMappingInformation());
               if (currentMember == null) {
                 classNamingBuilder.addMappingInformation(
                     info,
@@ -316,6 +324,9 @@
                                 info, conflictingInfo, lineNo)));
               }
             });
+        if (readGlobalInfo.isTrue()) {
+          break;
+        }
         // Skip reading the rest of the line.
         lineOffset = line.length();
         continue;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 362f5f5..524790c 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -122,7 +122,7 @@
     if (mapVersion.isGreaterThan(MapVersion.MAP_VERSION_NONE)) {
       builder
           .append("# ")
-          .append(new MapVersionMappingInformation(mapVersion).serialize())
+          .append(new MapVersionMappingInformation(mapVersion, mapVersion.getName()).serialize())
           .append("\n");
     }
     builder.append("# " + MARKER_KEY_PG_MAP_ID + ": " + id.getId() + "\n");
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index dee503b..40fc12f 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableMap;
@@ -37,6 +38,7 @@
 public class SeedMapper implements ProguardMap {
 
   static class Builder extends ProguardMap.Builder {
+
     final Map<String, ClassNamingForMapApplier.Builder> map = new HashMap<>();
     final Set<String> mappedToDescriptorNames = new HashSet<>();
     private final Reporter reporter;
@@ -61,6 +63,12 @@
     }
 
     @Override
+    ProguardMap.Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion) {
+      // Do nothing
+      return this;
+    }
+
+    @Override
     SeedMapper build() {
       reporter.failIfPendingErrors();
       return new SeedMapper(ImmutableMap.copyOf(map), mappedToDescriptorNames, reporter);
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MapVersionMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MapVersionMappingInformation.java
index 357be56..32f11f4 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MapVersionMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MapVersionMappingInformation.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.noKeyForObjectWithId;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MapVersion;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
@@ -18,9 +17,11 @@
   public static final String MAP_VERSION_KEY = "version";
 
   private final MapVersion mapVersion;
+  private final String value;
 
-  public MapVersionMappingInformation(MapVersion mapVersion) {
+  public MapVersionMappingInformation(MapVersion mapVersion, String value) {
     this.mapVersion = mapVersion;
+    this.value = value;
   }
 
   @Override
@@ -47,6 +48,15 @@
     return mapVersion;
   }
 
+  public String getValue() {
+    return value;
+  }
+
+  @Override
+  public boolean isGlobalMappingInformation() {
+    return true;
+  }
+
   @Override
   public String serialize() {
     JsonObject result = new JsonObject();
@@ -56,11 +66,7 @@
   }
 
   public static void deserialize(
-      MapVersion ignoredCurrentMapVersion,
-      JsonObject object,
-      DiagnosticsHandler diagnosticsHandler,
-      int lineNumber,
-      Consumer<MappingInformation> onMappingInfo) {
+      JsonObject object, int lineNumber, Consumer<MappingInformation> onMappingInfo) {
     // Parsing the generator information must support parsing at all map versions as it itself is
     // what establishes the version.
     String mapVersionString = object.get(MAP_VERSION_KEY).getAsString();
@@ -70,8 +76,8 @@
     }
     MapVersion mapVersion = MapVersion.fromName(mapVersionString);
     if (mapVersion == null) {
-      return;
+      mapVersion = MapVersion.MAP_VERSION_UNKNOWN;
     }
-    onMappingInfo.accept(new MapVersionMappingInformation(mapVersion));
+    onMappingInfo.accept(new MapVersionMappingInformation(mapVersion, mapVersionString));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
index 00a411f..787a0bc 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -70,6 +70,10 @@
     return null;
   }
 
+  public boolean isGlobalMappingInformation() {
+    return false;
+  }
+
   public abstract boolean allowOther(MappingInformation information);
 
   public static void fromJsonObject(
@@ -112,8 +116,7 @@
       Consumer<MappingInformation> onMappingInfo) {
     switch (id) {
       case MapVersionMappingInformation.ID:
-        MapVersionMappingInformation.deserialize(
-            version, object, diagnosticsHandler, lineNumber, onMappingInfo);
+        MapVersionMappingInformation.deserialize(object, lineNumber, onMappingInfo);
         return;
       case FileNameInformation.ID:
         FileNameInformation.deserialize(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/BidirectedGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/BidirectedGraph.java
index 8e76917..b7442b5 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/BidirectedGraph.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/BidirectedGraph.java
@@ -29,14 +29,14 @@
           if (seen.contains(node)) {
             return;
           }
-          Set<T> stronglyConnectedComponent = internalComputeStronglyConnectedProgramClasses(node);
+          Set<T> stronglyConnectedComponent = computeStronglyConnectedComponent(node);
           stronglyConnectedComponents.add(stronglyConnectedComponent);
           seen.addAll(stronglyConnectedComponent);
         });
     return stronglyConnectedComponents;
   }
 
-  private Set<T> internalComputeStronglyConnectedProgramClasses(T node) {
+  public Set<T> computeStronglyConnectedComponent(T node) {
     WorkList<T> worklist = WorkList.newEqualityWorkList(node);
     while (worklist.hasNext()) {
       T current = worklist.next();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ProgramClassesBidirectedGraph.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ProgramClassesBidirectedGraph.java
index 474c445..e5d7179 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ProgramClassesBidirectedGraph.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ProgramClassesBidirectedGraph.java
@@ -4,19 +4,20 @@
 
 package com.android.tools.r8.optimize.argumentpropagation.utils;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.function.Consumer;
 
 public class ProgramClassesBidirectedGraph extends BidirectedGraph<DexProgramClass> {
 
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
 
   public ProgramClassesBidirectedGraph(
-      AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
     this.appView = appView;
     this.immediateSubtypingInfo = immediateSubtypingInfo;
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 2b01885..14b393c 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -314,11 +314,20 @@
       // The setup of a retracer should likely also follow a builder pattern instead of having
       // static create methods. That would avoid the need to method overload the construction here
       // and the default create would become the default build of a retracer.
-      Retracer retracer =
+      RetracerImpl retracer =
           RetracerImpl.create(
               options.getProguardMapProducer(),
               options.getDiagnosticsHandler(),
               allowExperimentalMapping);
+      retracer
+          .getMapVersions()
+          .forEach(
+              mapVersionInfo -> {
+                if (mapVersionInfo.getMapVersion().isUnknown()) {
+                  diagnosticsHandler.warning(
+                      RetraceUnknownMapVersionDiagnostic.create(mapVersionInfo.getValue()));
+                }
+              });
       timing.end();
       timing.begin("Report result");
       StringRetrace stringRetrace =
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index c627248..b6d70e9 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -19,24 +19,14 @@
   private final RetraceOptions options;
 
   private RetraceCommand(
-      String regularExpression,
-      DiagnosticsHandler diagnosticsHandler,
-      ProguardMapProducer proguardMapProducer,
       List<String> stackTrace,
       Consumer<List<String>> retracedStackTraceConsumer,
-      boolean isVerbose,
-      boolean verifyMappingFileHash) {
+      RetraceOptions options) {
     this.stackTrace = stackTrace;
     this.retracedStackTraceConsumer = retracedStackTraceConsumer;
-    this.options =
-        RetraceOptions.builder(diagnosticsHandler)
-            .setRegularExpression(regularExpression)
-            .setProguardMapProducer(proguardMapProducer)
-            .setVerbose(isVerbose)
-            .setVerifyMappingFileHash(verifyMappingFileHash)
-            .build();
+    this.options = options;
 
-    assert this.stackTrace != null || verifyMappingFileHash;
+    assert this.stackTrace != null || options.isVerifyMappingFileHash();
     assert this.retracedStackTraceConsumer != null;
   }
 
@@ -157,14 +147,15 @@
       if (this.retracedStackTraceConsumer == null) {
         throw new RuntimeException("RetracedStackConsumer not specified");
       }
-      return new RetraceCommand(
-          regularExpression,
-          diagnosticsHandler,
-          proguardMapProducer,
-          stackTrace,
-          retracedStackTraceConsumer,
-          isVerbose,
-          verifyMappingFileHash);
+      RetraceOptions retraceOptions =
+          RetraceOptions.builder(diagnosticsHandler)
+              .setRegularExpression(regularExpression)
+              .setProguardMapProducer(proguardMapProducer)
+              .setVerbose(isVerbose)
+              .setVerifyMappingFileHash(verifyMappingFileHash)
+              .build();
+      return new RetraceCommand(stackTrace, retracedStackTraceConsumer, retraceOptions);
     }
+
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
index 54c8519..e18c6e6 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
@@ -14,8 +14,6 @@
 public class RetraceInvalidStackTraceLineDiagnostics implements Diagnostic {
 
   private static final String NULL_STACK_TRACE_LINE_MESSAGE = "The stack trace line is <null>";
-  private static final String PARSE_STACK_TRACE_LINE_MESSAGE =
-      "Could not parse the stack trace line '%s'";
 
   private final int lineNumber;
   private final String message;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUnknownMapVersionDiagnostic.java b/src/main/java/com/android/tools/r8/retrace/RetraceUnknownMapVersionDiagnostic.java
new file mode 100644
index 0000000..0a7a7bb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUnknownMapVersionDiagnostic.java
@@ -0,0 +1,40 @@
+// 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.retrace;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class RetraceUnknownMapVersionDiagnostic implements Diagnostic {
+
+  private final String versionName;
+
+  private RetraceUnknownMapVersionDiagnostic(String versionName) {
+    this.versionName = versionName;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return String.format(
+        "Map version '%s' is unknown or introduced later than this retrace version.", versionName);
+  }
+
+  public static RetraceUnknownMapVersionDiagnostic create(String versionName) {
+    return new RetraceUnknownMapVersionDiagnostic(versionName);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index 563de26..3d62354 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -17,6 +18,7 @@
 import com.android.tools.r8.retrace.Retracer;
 import java.io.BufferedReader;
 import java.util.OptionalInt;
+import java.util.Set;
 
 /** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
 public class RetracerImpl implements Retracer {
@@ -96,4 +98,8 @@
       ClassReference exception, RetraceStackTraceContext context) {
     return retraceClass(exception).lookupThrownException(context);
   }
+
+  public Set<MapVersionMappingInformation> getMapVersions() {
+    return classNameMapper.getMapVersions();
+  }
 }
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 bc0344a..7da1e8e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -245,6 +245,7 @@
     enableInitializedClassesAnalysis = false;
     callSiteOptimizationOptions.disableOptimization();
     horizontalClassMergerOptions.setRestrictToSynthetics();
+    apiModelTestingOptions.disableApiCallerIdentification();
   }
 
   public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
@@ -1527,6 +1528,10 @@
                 });
           });
     }
+
+    public void disableApiCallerIdentification() {
+      enableApiCallerIdentification = false;
+    }
   }
 
   public static class ProtoShrinkingOptions {
diff --git a/src/test/examplesJava11/backport/StringBackportJava11Main.java b/src/test/examplesJava11/backport/StringBackportJava11Main.java
index 6af33fa..eaf2f8a 100644
--- a/src/test/examplesJava11/backport/StringBackportJava11Main.java
+++ b/src/test/examplesJava11/backport/StringBackportJava11Main.java
@@ -25,6 +25,13 @@
     assertEquals("heyhey", "hey".repeat(2));
     assertEquals("heyheyhey", "hey".repeat(3));
     assertEquals("heyheyheyhey", "hey".repeat(4));
+
+    try {
+      "\u03B1\u03B2".repeat(Integer.MAX_VALUE);
+      throw new AssertionError("Expected to throw OutOfMemoryError");
+    } catch (OutOfMemoryError e) {
+      // Expected.
+    }
   }
 
   /** Per {@link Character#isWhitespace(int)} */
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java
new file mode 100644
index 0000000..f540d9d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java
@@ -0,0 +1,116 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject iClassSubject = inspector.clazz(I.class);
+              assertThat(iClassSubject, isPresent());
+              assertThat(iClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPublic()));
+
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertThat(aClassSubject, not(isImplementing(iClassSubject)));
+              assertThat(
+                  aClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPackagePrivate()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.m()", "Y.m()");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new B().m();
+      (System.currentTimeMillis() > 0 ? new Y() : new Z()).m();
+    }
+  }
+
+  interface I {
+
+    void m();
+  }
+
+  @NoVerticalClassMerging
+  abstract static class A {
+
+    @NeverInline
+    void m() {
+      System.out.println("A.m()");
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class B extends A {}
+
+  @NoVerticalClassMerging
+  abstract static class X implements I {}
+
+  @NoHorizontalClassMerging
+  static class Y extends X {
+
+    @Override
+    public void m() {
+      System.out.println("Y.m()");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class Z implements I {
+
+    @Override
+    public void m() {
+      System.out.println("Z.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
index de26733..e9cd1c1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8.classmerging.horizontal.interfaces;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -20,6 +20,7 @@
 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;
@@ -43,8 +44,7 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
-            inspector ->
-                inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -56,27 +56,16 @@
             inspector -> {
               ClassSubject iClassSubject = inspector.clazz(I.class);
               assertThat(iClassSubject, isPresent());
-              assertThat(iClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPublic()));
+              assertThat(iClassSubject.uniqueMethodWithName("m"), isAbsent());
 
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
               assertThat(aClassSubject, isImplementing(iClassSubject));
-              // TODO(b/203446070): Package private A.m() should not override public I.m().
               assertThat(
                   aClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPackagePrivate()));
             })
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/203446070): Should always succeed.
-        .applyIf(
-            parameters.isCfRuntime(),
-            runResult -> runResult.assertSuccessWithOutputLines("A.m()", "B.m()"),
-            runResult ->
-                runResult.applyIf(
-                    parameters.getDexRuntimeVersion().isDalvik(),
-                    ignore ->
-                        runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
-                    ignore ->
-                        runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class)));
+        .assertSuccessWithOutputLines("A.m()", "B.m()");
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
index a75fd7e..00a1394 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.classmerging.horizontal.interfaces;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtending;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -21,6 +21,7 @@
 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;
@@ -44,8 +45,7 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
-            inspector ->
-                inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -57,7 +57,7 @@
             inspector -> {
               ClassSubject iClassSubject = inspector.clazz(I.class);
               assertThat(iClassSubject, isPresent());
-              assertThat(iClassSubject.uniqueMethodWithName("m"), allOf(isPresent(), isPublic()));
+              assertThat(iClassSubject.uniqueMethodWithName("m"), isAbsent());
 
               ClassSubject a0ClassSubject = inspector.clazz(A0.class);
               assertThat(a0ClassSubject, isPresent());
@@ -70,17 +70,7 @@
               assertThat(aClassSubject, isImplementing(iClassSubject));
             })
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/203446070): Should always succeed.
-        .applyIf(
-            parameters.isCfRuntime(),
-            runResult -> runResult.assertSuccessWithOutputLines("A.m()", "B.m()"),
-            runResult ->
-                runResult.applyIf(
-                    parameters.getDexRuntimeVersion().isDalvik(),
-                    ignore ->
-                        runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
-                    ignore ->
-                        runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class)));
+        .assertSuccessWithOutputLines("A.m()", "B.m()");
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
index 9a2fc52..ce8a4a2 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
@@ -47,6 +47,16 @@
     if (count == 1) {
       return receiver;
     }
+    // Condition `count * receiver.length() <= Integer.MAX_VALUE` must hold for the String to be
+    // allocated.
+    if (receiver.length() > Integer.MAX_VALUE / count) {
+      throw new OutOfMemoryError(
+          "Repeating "
+              + receiver.length()
+              + " bytes String "
+              + count
+              + " times will produce a String exceeding maximum size.");
+    }
     StringBuilder builder = new StringBuilder(length * count);
     for (int i = 0; i < count; i++) {
       builder.append(receiver);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
new file mode 100644
index 0000000..1979d34
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
@@ -0,0 +1,150 @@
+// 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.ir.optimize.outliner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class OutlineMappingInformationTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean throwInFirstOutline;
+  private final boolean throwOnFirstCall;
+
+  @Parameterized.Parameters(name = "{0}, throwInFirstOutline: {1}, throwOnFirstCall: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
+  }
+
+  public OutlineMappingInformationTest(
+      TestParameters parameters, boolean throwInFirstOutline, boolean throwOnFirstCall) {
+    this.parameters = parameters;
+    this.throwInFirstOutline = throwInFirstOutline;
+    this.throwOnFirstCall = throwOnFirstCall;
+  }
+
+  StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    expectedStackTrace =
+        testForRuntime(parameters)
+            .addProgramClasses(TestClass.class, TestClass2.class, Greeter.class)
+            .run(
+                parameters.getRuntime(),
+                TestClass.class,
+                throwInFirstOutline ? "0" : "1",
+                throwOnFirstCall ? "0" : "1")
+            .assertFailureWithErrorThatThrows(ArrayStoreException.class)
+            .getStackTrace();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, TestClass2.class, Greeter.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              options.outline.threshold = 2;
+              options.outline.minSize = 2;
+            })
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .enableNoHorizontalClassMergingAnnotations()
+        .noHorizontalClassMergingOfSynthetics()
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(
+            parameters.getRuntime(),
+            TestClass.class,
+            throwInFirstOutline ? "0" : "1",
+            throwOnFirstCall ? "0" : "1")
+        .assertFailureWithErrorThatThrows(ArrayStoreException.class)
+        .inspectStackTrace(
+            (stackTrace, inspector) -> {
+              // Two outlines are created, one for
+              //   Greeter.throwExceptionFirst();
+              //   Greeter.throwExceptionSecond();
+              // and one for
+              //   new ArrayStoreException("Foo")
+              assertEquals(5, inspector.allClasses().size());
+              // TODO(b/201397823): Should always be equal.
+              if (throwInFirstOutline && !throwOnFirstCall) {
+                assertNotEquals(expectedStackTrace, stackTrace);
+              } else {
+                assertEquals(expectedStackTrace, stackTrace);
+              }
+            });
+  }
+
+  @NoHorizontalClassMerging
+  static class TestClass {
+
+    public static boolean shouldThrowInGreeter;
+    public static boolean throwOnFirst;
+
+    public static void main(String... args) {
+      shouldThrowInGreeter = args[0].equals("0");
+      throwOnFirst = args[1].equals("0");
+      greet();
+      shouldThrowInGreeter = true;
+      TestClass2.greet();
+    }
+
+    @NeverInline
+    static void greet() {
+      Greeter.throwExceptionFirst();
+      Greeter.throwExceptionSecond();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class TestClass2 {
+
+    @NeverInline
+    static void greet() {
+      // Keep on same line
+      Greeter.throwExceptionFirst(); Greeter.throwExceptionSecond();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  public static class Greeter {
+
+    @NeverInline
+    public static void throwExceptionFirst() {
+      if (TestClass.shouldThrowInGreeter && TestClass.throwOnFirst) {
+        throw new ArrayStoreException("Foo");
+      }
+    }
+
+    @NeverInline
+    public static void throwExceptionSecond() {
+      if (TestClass.shouldThrowInGreeter && !TestClass.throwOnFirst) {
+        throw new ArrayStoreException("Foo");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 16aa702..3f97cd1 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -9,8 +9,10 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.DiagnosticsMatcher;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
@@ -40,9 +42,11 @@
 import com.android.tools.r8.retrace.stacktraces.InlineSourceFileContextStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
+import com.android.tools.r8.retrace.stacktraces.MapVersionWarningStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleDotsInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleLinesNoLineNumberStackTrace;
+import com.android.tools.r8.retrace.stacktraces.MultipleMapVersionsWarningStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleOriginalLinesNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NoObfuscatedLineNumberWithOverrideTest;
@@ -364,6 +368,31 @@
     runRetraceTest(new IdentityMappingStackTrace());
   }
 
+  @Test
+  public void testMapVersionWarningStackTrace() throws Exception {
+    // TODO(b/204289928): Internalize the diagnostics checking.
+    assumeFalse(external);
+    runRetraceTest(new MapVersionWarningStackTrace())
+        .assertOnlyWarnings()
+        .assertWarningsCount(1)
+        .assertAllWarningsMatch(
+            DiagnosticsMatcher.diagnosticType(RetraceUnknownMapVersionDiagnostic.class));
+  }
+
+  @Test
+  public void testMultipleMapVersionWarningStackTrace() throws Exception {
+    // TODO(b/204289928): Internalize the diagnostics checking.
+    assumeFalse(external);
+    runRetraceTest(new MultipleMapVersionsWarningStackTrace())
+        .assertOnlyWarnings()
+        .assertAllWarningsMatch(
+            DiagnosticsMatcher.diagnosticType(RetraceUnknownMapVersionDiagnostic.class))
+        .assertWarningsCount(2)
+        .assertWarningsMatch(
+            DiagnosticsMatcher.diagnosticMessage(containsString("98.0")),
+            DiagnosticsMatcher.diagnosticMessage(containsString("99.0")));
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MapVersionWarningStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MapVersionWarningStackTrace.java
new file mode 100644
index 0000000..094ebd7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MapVersionWarningStackTrace.java
@@ -0,0 +1,43 @@
+// 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class MapVersionWarningStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException", "\tat a.a(:4)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# { id: 'com.android.tools.r8.mapping', version: '99.0' }", "some.Class -> a:");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat some.Class.a(Class.java:4)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat some.Class.a(Class.java:4)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleMapVersionsWarningStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleMapVersionsWarningStackTrace.java
new file mode 100644
index 0000000..63717a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleMapVersionsWarningStackTrace.java
@@ -0,0 +1,48 @@
+// 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class MultipleMapVersionsWarningStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException", "\tat a.a(:4)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# { id: 'com.android.tools.r8.mapping', version: '98.0' }",
+        "some.Class -> a:",
+        "# { id: 'com.android.tools.r8.mapping', version: '99.0' }",
+        "some.other.Class -> b:",
+        "# { id: 'com.android.tools.r8.mapping', version: '1.0' }",
+        "some.third.Class -> c:");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat some.Class.a(Class.java:4)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat some.Class.a(Class.java:4)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 2;
+  }
+}