Merge commit '41c2eb659a45e0f909cc6b3906b2454d12ac5513' into dev-release
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 397f2c5..241f740 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -78,7 +78,8 @@
         "java.net.URLDecoder": "j$.net.URLDecoder",
         "java.net.URLEncoder": "j$.net.URLEncoder",
         "java.lang.Desugar": "j$.lang.Desugar",
-        "java.lang.ref.Cleaner": "j$.lang.ref.Cleaner"
+        "java.lang.ref.Cleaner": "j$.lang.ref.Cleaner",
+        "sun.security.action.": "j$.sun.security.action."
       }
     },
     {
@@ -95,7 +96,6 @@
         "java.io.DesugarFile" : "j$.io.DesugarFile",
         "java.util.Desugar": "j$.util.Desugar",
         "sun.misc.Desugar": "j$.sun.misc.Desugar",
-        "sun.security.action.": "j$.sun.security.action.",
         "jdk.internal.": "j$.jdk.internal.",
         "java.nio.Desugar": "j$.nio.Desugar",
         "java.nio.channels.AsynchronousChannel": "j$.nio.channels.AsynchronousChannel",
@@ -204,6 +204,13 @@
   ],
   "program_flags": [
     {
+      "api_level_below_or_equal": 10000,
+      "rewrite_prefix": {
+        "java.net.URLDecoder": "j$.net.URLDecoder",
+        "java.net.URLEncoder": "j$.net.URLEncoder"
+      }
+    },
+    {
       "api_level_below_or_equal": 25,
       "rewrite_prefix": {
         "java.time.": "j$.time.",
@@ -286,6 +293,15 @@
         "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
         "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
       }
+    },
+    {
+      "api_level_below_or_equal": 18,
+      "rewrite_prefix": {
+        "java.nio.charset.StandardCharsets": "j$.nio.charset.StandardCharsets"
+      },
+      "retarget_lib_member": {
+        "java.lang.Character#isBmpCodePoint": "j$.lang.DesugarCharacter"
+      }
     }
   ],
   "shrinker_config": [
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index a13b905..4eaea9f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.SetUtils;
@@ -169,6 +170,22 @@
     getSyntheticItems().addLegacySyntheticClass(clazz, context, featureSplit);
   }
 
+  /** Primitive traversal over all (non-interface) superclasses of a given type. */
+  public TraversalContinuation traverseSuperClasses(
+      DexClass clazz, TriFunction<DexType, DexClass, DexClass, TraversalContinuation> fn) {
+    DexClass currentClass = clazz;
+    while (currentClass != null && currentClass.getSuperType() != null) {
+      DexClass superclass = definitionFor(currentClass.getSuperType());
+      TraversalContinuation stepResult =
+          fn.apply(currentClass.getSuperType(), superclass, currentClass);
+      if (stepResult.shouldBreak()) {
+        return stepResult;
+      }
+      currentClass = superclass;
+    }
+    return CONTINUE;
+  }
+
   /**
    * Primitive traversal over all supertypes of a given type.
    *
@@ -291,6 +308,56 @@
         .shouldBreak();
   }
 
+  public boolean isSubtype(DexClass subclass, DexClass superclass) {
+    return superclass.isInterface()
+        ? isSubtype(subclass.getType(), superclass.getType())
+        : isSubtypeOfClass(subclass, superclass);
+  }
+
+  public boolean isSubtypeOfClass(DexClass subclass, DexClass superclass) {
+    assert subclass != null;
+    assert superclass != null;
+    assert !superclass.isInterface();
+    if (subclass.isInterface()) {
+      return superclass.getType() == dexItemFactory().objectType;
+    }
+    return subclass == superclass || isStrictSubtypeOfClass(subclass, superclass);
+  }
+
+  public boolean isStrictSubtypeOfClass(DexClass subclass, DexClass superclass) {
+    assert subclass != null;
+    assert superclass != null;
+    assert !subclass.isInterface();
+    assert !superclass.isInterface();
+    if (subclass == superclass) {
+      return false;
+    }
+    // Treat object special: it is always the superclass even for broken hierarchies.
+    if (subclass.getType() == dexItemFactory().objectType) {
+      return false;
+    }
+    if (superclass.getType() == dexItemFactory().objectType) {
+      return true;
+    }
+    BooleanBox result = new BooleanBox();
+    traverseSuperClasses(
+        subclass,
+        (currentType, currentClass, immediateSubclass) -> {
+          if (currentType == superclass.getType()) {
+            result.set();
+            return BREAK;
+          }
+          if (currentClass == null) {
+            return BREAK;
+          }
+          if (superclass.isProgramClass() && !currentClass.isProgramClass()) {
+            return BREAK;
+          }
+          return CONTINUE;
+        });
+    return result.isTrue();
+  }
+
   public boolean inSameHierarchy(DexType type, DexType other) {
     assert type.isClassType();
     assert other.isClassType();
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index d276e9b..b3e8f74 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -351,6 +351,10 @@
     return definitionSupplier.definitionFor(this).isInterface();
   }
 
+  public DexProgramClass asProgramClass(DexDefinitionSupplier definitions) {
+    return asProgramClassOrNull(definitions.definitionFor(this));
+  }
+
   public boolean isProgramType(DexDefinitionSupplier definitions) {
     DexClass clazz = definitions.definitionFor(this);
     return clazz != null && clazz.isProgramClass();
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 fb5cfa3..b536669 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoCheckDiscard;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
+import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerCycles;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
 import com.android.tools.r8.horizontalclassmerging.policies.NoConstructorCollisions;
 import com.android.tools.r8.horizontalclassmerging.policies.NoDeadEnumLiteMaps;
@@ -217,7 +218,7 @@
   private static void addMultiClassPoliciesForMergingNonSyntheticClasses(
       AppView<AppInfoWithLiveness> appView,
       ImmutableList.Builder<Policy> builder) {
-    builder.add(new NoDeadLocks(appView));
+    builder.add(new NoClassInitializerCycles(appView), new NoDeadLocks(appView));
   }
 
   private static void addMultiClassPoliciesForInterfaceMerging(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
new file mode 100644
index 0000000..f8faefd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -0,0 +1,506 @@
+// 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 static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.code.CfOrDexInstruction;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Disallows merging of classes when the merging could introduce class initialization deadlocks.
+ *
+ * <p>Example: In the below example, if thread t0 triggers the class initialization of A, and thread
+ * t1 triggers the class initialization of C, then the program will never deadlock. However, if
+ * classes B and C are merged, then the program may all of a sudden deadlock, since thread t0 may
+ * hold the lock for A and wait for BC's lock, meanwhile thread t1 holds the lock for BC while
+ * waiting for A's lock.
+ *
+ * <pre>
+ *   class A {
+ *     static {
+ *       new B();
+ *     }
+ *   }
+ *   class B extends A {}
+ *   class C extends A {}
+ * </pre>
+ *
+ * <p>To identify the above situation, we perform a tracing from {@code A.<clinit>} to check if
+ * there is an execution path that triggers the class initialization of B or C. In that case, the
+ * reached subclass is ineligible for class merging.
+ *
+ * <p>Example: In the below example, if thread t0 triggers the class initialization of A, and thread
+ * t1 triggers the class initialization of B, then the program will never deadlock. However, if
+ * classes B and C are merged, then the program may all of a sudden deadlock, since thread 0 may
+ * hold the lock for A and wait for BC's lock, meanwhile thread t1 holds the lock for BC while
+ * waiting for A's lock.
+ *
+ * <pre>
+ *   class A {
+ *     static {
+ *       new C();
+ *     }
+ *   }
+ *   class B {
+ *     static {
+ *       new A();
+ *     }
+ *   }
+ *   class C {}
+ * </pre>
+ *
+ * <p>To identify the above situation, we perform a tracing for each {@code <clinit>} in the merge
+ * group. If we find an execution path from the class initializer of one class in the merge group to
+ * the class initializer of another class in the merge group, then after merging there is a cycle in
+ * the class initialization that could lead to a deadlock.
+ */
+public class NoClassInitializerCycles extends MultiClassPolicyWithPreprocessing<Void> {
+
+  final AppView<AppInfoWithLiveness> appView;
+
+  // Mapping from each merge candidate to its merge group.
+  final Map<DexProgramClass, MergeGroup> allGroups = new IdentityHashMap<>();
+
+  public NoClassInitializerCycles(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public Collection<MergeGroup> apply(MergeGroup group, Void nothing) {
+    Tracer tracer = new Tracer(group);
+    removeClassesWithPossibleClassInitializerDeadlock(group, tracer);
+
+    List<MergeGroup> newGroups = new LinkedList<>();
+    for (DexProgramClass clazz : group) {
+      MergeGroup newGroup = getOrCreateGroupFor(clazz, newGroups, tracer);
+      if (newGroup != null) {
+        newGroup.add(clazz);
+      } else {
+        // Ineligible for merging.
+      }
+    }
+    return removeTrivialGroups(newGroups);
+  }
+
+  private MergeGroup getOrCreateGroupFor(
+      DexProgramClass clazz, List<MergeGroup> groups, Tracer tracer) {
+    assert !tracer.hasPossibleClassInitializerDeadlock(clazz);
+
+    ProgramMethod classInitializer = clazz.getProgramClassInitializer();
+    if (classInitializer != null) {
+      assert tracer.verifySeenSetIsEmpty();
+      assert tracer.verifyWorklistIsEmpty();
+      tracer.setTracingRoot(clazz);
+      tracer.enqueueMethod(classInitializer);
+      tracer.trace();
+      if (tracer.hasPossibleClassInitializerDeadlock(clazz)) {
+        // Ineligible for merging.
+        return null;
+      }
+    }
+
+    for (MergeGroup group : groups) {
+      if (canMerge(clazz, group, tracer)) {
+        return group;
+      }
+    }
+
+    MergeGroup newGroup = new MergeGroup();
+    groups.add(newGroup);
+    return newGroup;
+  }
+
+  private boolean canMerge(DexProgramClass clazz, MergeGroup group, Tracer tracer) {
+    for (DexProgramClass member : group) {
+      // Check that the class initialization of the given class cannot reach the class initializer
+      // of the current group member.
+      if (tracer.isClassInitializedByClassInitializationOf(member, clazz)) {
+        return false;
+      }
+      // Check that the class initialization of the current group member cannot reach the class
+      // initializer of the given class.
+      if (tracer.isClassInitializedByClassInitializationOf(clazz, member)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Runs the tracer from the parent class initializers, using the entire group as tracing context.
+   * If the class initializer of one of the classes in the merge group is reached, then that class
+   * is not eligible for merging.
+   */
+  private void removeClassesWithPossibleClassInitializerDeadlock(MergeGroup group, Tracer tracer) {
+    tracer.setTracingRoots(group);
+    tracer.enqueueParentClassInitializers(group);
+    tracer.trace();
+    group.removeIf(tracer::hasPossibleClassInitializerDeadlock);
+  }
+
+  @Override
+  public void clear() {
+    allGroups.clear();
+  }
+
+  @Override
+  public String getName() {
+    return "NoClassInitializerCycles";
+  }
+
+  @Override
+  public Void preprocess(Collection<MergeGroup> groups) {
+    for (MergeGroup group : groups) {
+      for (DexProgramClass clazz : group) {
+        allGroups.put(clazz, group);
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public boolean shouldSkipPolicy() {
+    HorizontalClassMergerOptions options = appView.options().horizontalClassMergerOptions();
+    return !options.isClassInitializerDeadlockDetectionEnabled();
+  }
+
+  private class Tracer {
+
+    final Set<DexProgramClass> group;
+
+    private final Set<DexProgramClass> seenClassInitializers = Sets.newIdentityHashSet();
+    private final ProgramMethodSet seenMethods = ProgramMethodSet.create();
+    private final Deque<ProgramMethod> worklist = new ArrayDeque<>();
+
+    // Mapping from each merge grop member to the set of merge group members whose class
+    // initializers may trigger the class initialization of the group member.
+    private final Map<DexProgramClass, Set<DexProgramClass>> classInitializerReachableFromClasses =
+        new IdentityHashMap<>();
+
+    // The current tracing roots (either the entire merge group or one of the classes in the merge
+    // group).
+    private Collection<DexProgramClass> tracingRoots;
+
+    Tracer(MergeGroup group) {
+      this.group = SetUtils.newIdentityHashSet(group);
+    }
+
+    void clearSeen() {
+      seenClassInitializers.clear();
+      seenMethods.clear();
+    }
+
+    boolean markClassInitializerAsSeen(DexProgramClass clazz) {
+      return seenClassInitializers.add(clazz);
+    }
+
+    boolean enqueueMethod(ProgramMethod method) {
+      if (seenMethods.add(method)) {
+        worklist.add(method);
+        return true;
+      }
+      return false;
+    }
+
+    void enqueueParentClassInitializers(MergeGroup group) {
+      DexProgramClass member = group.iterator().next();
+      enqueueParentClassInitializers(member);
+    }
+
+    void enqueueParentClassInitializers(DexProgramClass clazz) {
+      DexProgramClass superClass =
+          asProgramClassOrNull(appView.definitionFor(clazz.getSuperType()));
+      if (superClass == null) {
+        return;
+      }
+      ProgramMethod classInitializer = superClass.getProgramClassInitializer();
+      if (classInitializer != null) {
+        enqueueMethod(classInitializer);
+      }
+      enqueueParentClassInitializers(superClass);
+    }
+
+    void recordClassInitializerReachableFromTracingRoots(DexProgramClass clazz) {
+      assert group.contains(clazz);
+      classInitializerReachableFromClasses
+          .computeIfAbsent(clazz, ignoreKey(Sets::newIdentityHashSet))
+          .addAll(tracingRoots);
+    }
+
+    void recordTracingRootsIneligibleForClassMerging() {
+      for (DexProgramClass tracingRoot : tracingRoots) {
+        classInitializerReachableFromClasses
+            .computeIfAbsent(tracingRoot, ignoreKey(Sets::newIdentityHashSet))
+            .add(tracingRoot);
+      }
+    }
+
+    boolean hasSingleTracingRoot(DexProgramClass clazz) {
+      return tracingRoots.size() == 1 && tracingRoots.contains(clazz);
+    }
+
+    boolean hasPossibleClassInitializerDeadlock(DexProgramClass clazz) {
+      return classInitializerReachableFromClasses
+          .getOrDefault(clazz, Collections.emptySet())
+          .contains(clazz);
+    }
+
+    boolean isClassInitializedByClassInitializationOf(
+        DexProgramClass classToBeInitialized, DexProgramClass classBeingInitialized) {
+      return classInitializerReachableFromClasses
+          .getOrDefault(classToBeInitialized, Collections.emptySet())
+          .contains(classBeingInitialized);
+    }
+
+    void setTracingRoot(DexProgramClass tracingRoot) {
+      setTracingRoots(ImmutableList.of(tracingRoot));
+    }
+
+    void setTracingRoots(Collection<DexProgramClass> tracingRoots) {
+      this.tracingRoots = tracingRoots;
+    }
+
+    void trace() {
+      // TODO(b/205611444): Avoid redundant tracing of the same methods.
+      while (!worklist.isEmpty()) {
+        ProgramMethod method = worklist.removeLast();
+        method.registerCodeReferences(new TracerUseRegistry(method));
+      }
+      clearSeen();
+    }
+
+    boolean verifySeenSetIsEmpty() {
+      assert seenClassInitializers.isEmpty();
+      assert seenMethods.isEmpty();
+      return true;
+    }
+
+    boolean verifyWorklistIsEmpty() {
+      assert worklist.isEmpty();
+      return true;
+    }
+
+    class TracerUseRegistry extends UseRegistry<ProgramMethod> {
+
+      TracerUseRegistry(ProgramMethod context) {
+        super(appView, context);
+      }
+
+      private void fail() {
+        // Ensures that hasPossibleClassInitializerDeadlock() returns true for each tracing root.
+        recordTracingRootsIneligibleForClassMerging();
+        doBreak();
+      }
+
+      private void triggerClassInitializerIfNotAlreadyTriggeredInContext(DexType type) {
+        DexProgramClass clazz = type.asProgramClass(appView);
+        if (clazz != null) {
+          triggerClassInitializerIfNotAlreadyTriggeredInContext(clazz);
+        }
+      }
+
+      private void triggerClassInitializerIfNotAlreadyTriggeredInContext(DexProgramClass clazz) {
+        if (!isClassAlreadyInitializedInCurrentContext(clazz)) {
+          triggerClassInitializer(clazz);
+        }
+      }
+
+      private boolean isClassAlreadyInitializedInCurrentContext(DexProgramClass clazz) {
+        // TODO(b/205611444): There is only a risk of a deadlock if the execution path comes from
+        //  outside the merge group. We could address this by updating this check.
+        return appView.appInfo().isSubtype(getContext().getHolder(), clazz);
+      }
+
+      private void triggerClassInitializer(DexType type) {
+        DexProgramClass clazz = type.asProgramClass(appView);
+        if (clazz != null) {
+          triggerClassInitializer(clazz);
+        }
+      }
+
+      // TODO(b/205611444): This needs to account for pending merging. If the given class is in a
+      //  merge group, then this should trigger the class initializers of all of the classes in the
+      //  merge group.
+      private void triggerClassInitializer(DexProgramClass clazz) {
+        if (!markClassInitializerAsSeen(clazz)) {
+          return;
+        }
+
+        if (group.contains(clazz)) {
+          if (hasSingleTracingRoot(clazz)) {
+            // We found an execution path from the class initializer of the given class back to its
+            // own class initializer. Therefore this class is not eligible for merging.
+            fail();
+          } else {
+            // Record that this class initializer is reachable from the tracing roots.
+            recordClassInitializerReachableFromTracingRoots(clazz);
+          }
+        }
+
+        ProgramMethod classInitializer = clazz.getProgramClassInitializer();
+        if (classInitializer != null) {
+          if (!enqueueMethod(classInitializer)) {
+            // This class initializer is already seen in the current context, thus all of the parent
+            // class initializers are also seen in the current context.
+            return;
+          }
+        }
+
+        triggerClassInitializer(clazz.getSuperType());
+      }
+
+      @Override
+      public void registerInitClass(DexType type) {
+        triggerClassInitializerIfNotAlreadyTriggeredInContext(type);
+      }
+
+      @Override
+      public void registerInvokeDirect(DexMethod method) {
+        DexMethod rewrittenMethod =
+            appView.graphLens().lookupInvokeDirect(method, getContext()).getReference();
+        MethodResolutionResult resolutionResult =
+            appView.appInfo().resolveMethodOnClass(rewrittenMethod);
+        if (resolutionResult.isSingleResolution()
+            && resolutionResult.getResolvedHolder().isProgramClass()) {
+          enqueueMethod(resolutionResult.getResolvedProgramMethod());
+        }
+      }
+
+      @Override
+      public void registerInvokeInterface(DexMethod method) {
+        fail();
+      }
+
+      @Override
+      public void registerInvokeStatic(DexMethod method) {
+        DexMethod rewrittenMethod =
+            appView.graphLens().lookupInvokeStatic(method, getContext()).getReference();
+        ProgramMethod resolvedMethod =
+            appView
+                .appInfo()
+                .unsafeResolveMethodDueToDexFormat(rewrittenMethod)
+                .getResolvedProgramMethod();
+        if (resolvedMethod != null) {
+          triggerClassInitializerIfNotAlreadyTriggeredInContext(resolvedMethod.getHolder());
+          enqueueMethod(resolvedMethod);
+        }
+      }
+
+      @Override
+      public void registerInvokeSuper(DexMethod method) {
+        DexMethod rewrittenMethod =
+            appView.graphLens().lookupInvokeSuper(method, getContext()).getReference();
+        ProgramMethod superTarget =
+            asProgramMethodOrNull(
+                appView.appInfo().lookupSuperTarget(rewrittenMethod, getContext()));
+        if (superTarget != null) {
+          enqueueMethod(superTarget);
+        }
+      }
+
+      @Override
+      public void registerInvokeVirtual(DexMethod method) {
+        fail();
+      }
+
+      @Override
+      public void registerNewInstance(DexType type) {
+        DexType rewrittenType = appView.graphLens().lookupType(type);
+        triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenType);
+      }
+
+      @Override
+      public void registerStaticFieldRead(DexField field) {
+        DexField rewrittenField = appView.graphLens().lookupField(field);
+        triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenField.getHolderType());
+      }
+
+      @Override
+      public void registerStaticFieldWrite(DexField field) {
+        DexField rewrittenField = appView.graphLens().lookupField(field);
+        triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenField.getHolderType());
+      }
+
+      @Override
+      public void registerTypeReference(DexType type) {
+        // Intentionally empty, new-array etc. does not trigger any class initialization.
+      }
+
+      @Override
+      public void registerCallSite(DexCallSite callSite) {
+        LambdaDescriptor descriptor =
+            LambdaDescriptor.tryInfer(callSite, appView.appInfo(), getContext());
+        if (descriptor != null) {
+          // Use of lambda metafactory does not trigger any class initialization.
+        } else {
+          fail();
+        }
+      }
+
+      @Override
+      public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
+        // Intentionally empty, does not trigger any class initialization.
+      }
+
+      @Override
+      public void registerConstClass(
+          DexType type,
+          ListIterator<? extends CfOrDexInstruction> iterator,
+          boolean ignoreCompatRules) {
+        // Intentionally empty, does not trigger any class initialization.
+      }
+
+      @Override
+      public void registerInstanceFieldRead(DexField field) {
+        // Intentionally empty, does not trigger any class initialization.
+      }
+
+      @Override
+      public void registerInstanceFieldWrite(DexField field) {
+        // Intentionally empty, does not trigger any class initialization.
+      }
+
+      @Override
+      public void registerInstanceOf(DexType type) {
+        // Intentionally empty, does not trigger any class initialization.
+      }
+
+      @Override
+      public void registerExceptionGuard(DexType guard) {
+        // Intentionally empty, does not trigger any class initialization.
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 1e69a6b..40c2ce3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -113,7 +113,11 @@
     this.extraNeverInlineMethods =
         appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations
             ? ImmutableSet.of()
-            : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
+            : ImmutableSet.of(
+                intrinsics.throwNpe,
+                intrinsics.throwParameterIsNullException,
+                intrinsics.throwParameterIsNullNPE,
+                intrinsics.throwParameterIsNullIAE);
     this.lensCodeRewriter = lensCodeRewriter;
     this.mainDexInfo = appView.appInfo().getMainDexInfo();
     this.singleInlineCallers =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index b5ced8c..d994add 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -101,6 +101,7 @@
 import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.typechecks.CheckCastAndInstanceOfMethodSpecialization;
 import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.kotlin.Kotlin.Intrinsics;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -744,11 +745,13 @@
     }
     // We need to ignore the holder, since Kotlin adds different versions of null-check machinery,
     // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
-    DexMethod checkParameterIsNotNullMethod =
-        appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull;
+    Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     DexMethod originalInvokedMethod =
         appView.graphLens().getOriginalMethodSignature(invoke.getInvokedMethod());
-    return originalInvokedMethod.match(checkParameterIsNotNullMethod)
+    boolean isCheckNotNullMethod =
+        originalInvokedMethod.match(intrinsics.checkParameterIsNotNull)
+            || originalInvokedMethod.match(intrinsics.checkNotNullParameter);
+    return isCheckNotNullMethod
         && invoke.getFirstArgument() == value
         && originalInvokedMethod.getHolderType().getPackageDescriptor().startsWith(Kotlin.NAME);
   }
@@ -763,11 +766,11 @@
     }
     // We need to ignore the holder, since Kotlin adds different versions of null-check machinery,
     // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
-    DexMethod throwParameterIsNullExceptionMethod =
-        appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException;
+    Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     DexMethod originalInvokedMethod =
         appView.graphLens().getOriginalMethodSignature(invoke.getInvokedMethod());
-    return originalInvokedMethod.match(throwParameterIsNullExceptionMethod)
+    return (originalInvokedMethod.match(intrinsics.throwParameterIsNullException)
+            || originalInvokedMethod.match(intrinsics.throwParameterIsNullNPE))
         && originalInvokedMethod.getHolderType().getPackageDescriptor().startsWith(Kotlin.NAME);
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 9470367..eb3af79 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -164,9 +164,24 @@
     public final DexType type = factory.createType(PACKAGE_PREFIX + "jvm/internal/Intrinsics;");
     public final DexMethod throwParameterIsNullException = factory.createMethod(type,
         factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
+    public final DexMethod throwParameterIsNullNPE =
+        factory.createMethod(
+            type,
+            factory.createProto(factory.voidType, factory.stringType),
+            "throwParameterIsNullNPE");
+    public final DexMethod throwParameterIsNullIAE =
+        factory.createMethod(
+            type,
+            factory.createProto(factory.voidType, factory.stringType),
+            "throwParameterIsNullIAE");
     public final DexMethod checkParameterIsNotNull = factory.createMethod(type,
         factory.createProto(factory.voidType, factory.objectType, factory.stringType),
         "checkParameterIsNotNull");
+    public final DexMethod checkNotNullParameter =
+        factory.createMethod(
+            type,
+            factory.createProto(factory.voidType, factory.objectType, factory.stringType),
+            "checkNotNullParameter");
     public final DexMethod throwNpe = factory.createMethod(
         type, factory.createProto(factory.voidType), "throwNpe");
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 57ce006..72b0295 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -148,7 +148,7 @@
   private boolean isPresentSinceMinApi(LibraryMethod method) {
     AndroidApiLevel apiLevel =
         androidApiLevelCompute.computeApiLevelForLibraryReference(method.getReference());
-    return apiLevel.isLessThanOrEqualTo(options.minApiLevel);
+    return apiLevel != AndroidApiLevel.UNKNOWN && apiLevel.isLessThanOrEqualTo(options.minApiLevel);
   }
 
   public static DexField validMemberRebindingTargetFor(
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 5cd2455..acdf404 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1357,6 +1357,8 @@
     private boolean enable =
         !Version.isDevelopmentVersion()
             || System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
+    // TODO(b/205611444): Enable by default.
+    private boolean enableClassInitializerDeadlockDetection = false;
     private boolean enableInterfaceMerging =
         System.getProperty("com.android.tools.r8.disableHorizontalInterfaceMerging") == null;
     private boolean enableInterfaceMergingInInitial = false;
@@ -1390,6 +1392,10 @@
       return true;
     }
 
+    public boolean isClassInitializerDeadlockDetectionEnabled() {
+      return enableClassInitializerDeadlockDetection;
+    }
+
     public boolean isEnabled(HorizontalClassMerger.Mode mode) {
       if (!enable || debug || intermediate) {
         return false;
@@ -1424,6 +1430,10 @@
       return restrictToSynthetics || !isOptimizing() || !isShrinking();
     }
 
+    public void setEnableClassInitializerDeadlockDetection() {
+      enableClassInitializerDeadlockDetection = true;
+    }
+
     public void setEnableInterfaceMergingInInitial() {
       enableInterfaceMergingInInitial = true;
     }
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index b39af91..b2bf2e8 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -91,10 +91,7 @@
       AndroidApiLevel minApiLevel,
       KeepRuleConsumer keepRuleConsumer,
       StringResource desugaredLibraryConfiguration) {
-    if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      super.enableCoreLibraryDesugaring(
-          minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
-    }
+    super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index dc4a247..cd0036b 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -8,7 +8,7 @@
 import static junit.framework.TestCase.assertTrue;
 
 import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryJDK11Undesugarer;
+import com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidAppConsumers;
diff --git a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
index 111b040..d79f4af 100644
--- a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
+++ b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
@@ -260,14 +260,7 @@
   }
 
   private static KeepRuleConsumer createKeepRuleConsumer(AndroidApiLevel apiLevel) {
-    if (requiresAnyCoreLibDesugaring(apiLevel)) {
       return new PresentKeepRuleConsumer();
-    }
-    return new AbsentKeepRuleConsumer();
-  }
-
-  private static boolean requiresAnyCoreLibDesugaring(AndroidApiLevel apiLevel) {
-    return apiLevel.isLessThan(AndroidApiLevel.O);
   }
 
   public static class PresentKeepRuleConsumer implements KeepRuleConsumer {
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 0f72c5a..6c8ac51 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -654,10 +654,7 @@
       AndroidApiLevel minApiLevel,
       KeepRuleConsumer keepRuleConsumer,
       StringResource desugaredLibraryConfiguration) {
-    if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      super.enableCoreLibraryDesugaring(
-          minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
-    }
+    super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer, desugaredLibraryConfiguration);
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 69bf834..9ab97e7 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -258,9 +258,7 @@
 
   public CR addDesugaredCoreLibraryRunClassPath(
       Function<AndroidApiLevel, Path> classPathSupplier, AndroidApiLevel minAPILevel) {
-    if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      addRunClasspathFiles(classPathSupplier.apply(minAPILevel));
-    }
+    addRunClasspathFiles(classPathSupplier.apply(minAPILevel));
     return self();
   }
 
@@ -269,9 +267,7 @@
       AndroidApiLevel minAPILevel,
       String keepRules,
       boolean shrink) {
-    if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      addRunClasspathFiles(classPathSupplier.apply(minAPILevel, keepRules, shrink));
-    }
+    addRunClasspathFiles(classPathSupplier.apply(minAPILevel, keepRules, shrink));
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 2fe8ae0..c4e86e3 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -484,7 +484,6 @@
       AndroidApiLevel minApiLevel,
       KeepRuleConsumer keepRuleConsumer,
       StringResource desugaredLibraryConfiguration) {
-    assert minApiLevel.getLevel() < AndroidApiLevel.O.getLevel();
     return enableLibraryDesugaring(
         LibraryDesugaringTestConfiguration.builder()
             .setMinApi(minApiLevel)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ImplicitClassInitializationSynchronizationTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ImplicitClassInitializationSynchronizationTest.java
index aa0d663..e1e3c7b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ImplicitClassInitializationSynchronizationTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ImplicitClassInitializationSynchronizationTest.java
@@ -4,14 +4,14 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
-import static org.hamcrest.core.IsEqual.equalTo;
 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 com.google.common.collect.ImmutableList;
 import java.lang.Thread.State;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,25 +34,15 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        // TODO(b/205611444): Should not be merged.
         .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertIsCompleteMergeGroup(B.class, C.class))
+            inspector -> inspector.assertClassesNotMerged(B.class, C.class))
+        .addOptionsModification(
+            options ->
+                options.horizontalClassMergerOptions().setEnableClassInitializerDeadlockDetection())
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/205611444): Should succeed.
-        .assertFailure()
-        .assertStdoutMatches(
-            equalTo(
-                StringUtils.lines(
-                    "Main: fork",
-                    "Main: wait",
-                    "Worker: notify",
-                    "Worker: wait",
-                    "Main: notified",
-                    "Main: lock C",
-                    "Worker: notified",
-                    "Worker: lock B")));
+        .assertSuccessWithOutputLines(getExpectedOutput());
   }
 
   @Test
@@ -61,19 +51,23 @@
     testForJvm()
         .addTestClasspath()
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(
-            "Main: fork",
-            "Main: wait",
-            "Worker: notify",
-            "Worker: wait",
-            "Main: notified",
-            "Main: lock C",
-            "Worker: notified",
-            "Worker: lock B",
-            "B",
-            "Worker: unlock B",
-            "C",
-            "Main: unlock C");
+        .assertSuccessWithOutputLines(getExpectedOutput());
+  }
+
+  private static List<String> getExpectedOutput() {
+    return ImmutableList.of(
+        "Main: fork",
+        "Main: wait",
+        "Worker: notify",
+        "Worker: wait",
+        "Main: notified",
+        "Main: lock C",
+        "Worker: notified",
+        "Worker: lock B",
+        "B",
+        "Worker: unlock B",
+        "C",
+        "Main: unlock C");
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConcurrentHashMapJDK11Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConcurrentHashMapJDK11Test.java
new file mode 100644
index 0000000..434f2b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ConcurrentHashMapJDK11Test.java
@@ -0,0 +1,101 @@
+// 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.desugaredlibrary.jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentHashMap.KeySetView;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConcurrentHashMapJDK11Test extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT = StringUtils.lines("1", "one=ONE, two=TWO");
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public ConcurrentHashMapJDK11Test(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    Assume.assumeFalse(
+        "TODO(b/171682016): reduce methods are backported but rely on non backported ForkJoinPool",
+        parameters.getDexRuntimeVersion().isEqualTo(Version.V4_0_4));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addInnerClasses(ConcurrentHashMapJDK11Test.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    Assume.assumeFalse(
+        "TODO(b/171682016): reduce methods are backported but rely on non backported ForkJoinPool",
+        parameters.getDexRuntimeVersion().isEqualTo(Version.V4_0_4));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(Backend.DEX)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addInnerClasses(ConcurrentHashMapJDK11Test.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      KeySetView<Object, Boolean> keySet = ConcurrentHashMap.newKeySet();
+      keySet.add(new Object());
+      System.out.println(keySet.size());
+
+      ConcurrentHashMap<String, String> chm = new ConcurrentHashMap<>();
+      chm.put("one", "ONE");
+      chm.put("two", "TWO");
+      String reduced = chm.reduce(1, (key, value) -> key + "=" + value, (s1, s2) -> s1 + ", " + s2);
+      System.out.println(reduced);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryJDK11Undesugarer.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryJDK11Undesugarer.java
rename to src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
index f79db70..bdda327 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryJDK11Undesugarer.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
@@ -2,9 +2,10 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.desugaredlibrary;
+package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
 
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.MethodTransformer;
 import com.android.tools.r8.utils.StreamUtils;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java
new file mode 100644
index 0000000..368d169
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StandardCharsetTest.java
@@ -0,0 +1,132 @@
+// 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.desugaredlibrary.jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class StandardCharsetTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("%E3%81%8B", "%82%A0%82%A9%97%43%24%E3%81%8B", "true");
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public StandardCharsetTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addProgramClassFileData(getProgramClassFileData())
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(Backend.DEX)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addProgramClassFileData(getProgramClassFileData())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private Collection<byte[]> getProgramClassFileData() throws IOException {
+    return ImmutableList.of(
+        transformer(TestClass.class)
+            .addMethodTransformer(
+                new MethodTransformer() {
+                  @Override
+                  public void visitMethodInsn(
+                      int opcode,
+                      String owner,
+                      String name,
+                      String descriptor,
+                      boolean isInterface) {
+                    if (opcode == Opcodes.INVOKESTATIC && name.equals("encode")) {
+                      super.visitMethodInsn(
+                          opcode, "java/net/URLEncoder", name, descriptor, isInterface);
+                      return;
+                    }
+                    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                  }
+                })
+            .transform());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(encode("か", StandardCharsets.UTF_8));
+      System.out.println(
+          encode("あ", "Shift_JIS")
+              + encode("か", "Shift_JIS")
+              + encode("佑", "Shift_JIS")
+              + encode("$", "Shift_JIS")
+              + encode("か", StandardCharsets.UTF_8));
+      System.out.println(Character.isBmpCodePoint('か'));
+    }
+
+    // Replaced in the transformer by JDK 11 URLEncoder#encode.
+    public static String encode(String s, Charset set) {
+      return null;
+    }
+
+    // Replaced in the transformer by JDK 11 URLEncoder#encode.
+    public static String encode(String s, String set) {
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index c245f5a..4aaea25 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -256,12 +256,12 @@
                       clazz,
                       "main",
                       String[].class.getCanonicalName()));
+              String kotlinIntrinsics = "void kotlin.jvm.internal.Intrinsics";
               assertEquals(
                   Lists.newArrayList(
                       kotlinc.is(KOTLINC_1_3_72)
-                          ? "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"
-                          : "void kotlin.jvm.internal.Intrinsics.checkNotNullParameter(java.lang.Object,"
-                                + " java.lang.String)"),
+                          ? kotlinIntrinsics + ".throwParameterIsNullException(java.lang.String)"
+                          : kotlinIntrinsics + ".throwParameterIsNullNPE(java.lang.String)"),
                   collectStaticCalls(clazz, "main", String[].class.getCanonicalName()));
             });
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
new file mode 100644
index 0000000..802be75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineChainTest.java
@@ -0,0 +1,89 @@
+// 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.kotlin;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KotlinIntrinsicsInlineChainTest extends KotlinTestBase {
+
+  private static final String FOLDER = "intrinsics";
+  private static final String MAIN = FOLDER + ".InlineChainParameterCheckKt";
+
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
+  }
+
+  private final TestParameters parameters;
+  private final boolean allowAccessModification;
+
+  public KotlinIntrinsicsInlineChainTest(
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(kotlinParameters);
+    this.parameters = parameters;
+    this.allowAccessModification = allowAccessModification;
+  }
+
+  private static final KotlinCompileMemoizer compiledJars =
+      getCompileMemoizer(getKotlinFilesInResource(FOLDER), FOLDER)
+          .configure(kotlinCompilerTool -> kotlinCompilerTool.includeRuntime().noReflect());
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(
+            compiledJars.getForConfiguration(kotlinc, targetVersion),
+            kotlinc.getKotlinAnnotationJar())
+        .addKeepMainRule(MAIN)
+        .allowAccessModification(allowAccessModification)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .run(parameters.getRuntime(), MAIN, "foobar")
+        .assertSuccessWithOutputLines("foobar", "field is foobar")
+        .inspect(
+            inspector -> {
+              ClassSubject mainClass = inspector.clazz(MAIN);
+              assertThat(mainClass, isPresent());
+
+              // Check that we have inlined all methods into main method.
+              assertEquals(1, mainClass.allMethods().size());
+
+              // Count the number of check parameter is not null.
+              MethodSubject main = mainClass.mainMethod();
+              long checkParameterIsNotNull = countCall(main, "checkParameterIsNotNull");
+              long checkNotNullParameter = countCall(main, "checkNotNullParameter");
+              if (kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_3_72)) {
+                assertEquals(allowAccessModification ? 0 : 1, checkParameterIsNotNull);
+                assertEquals(0, checkNotNullParameter);
+              } else {
+                assertEquals(allowAccessModification ? 0 : 1, checkNotNullParameter);
+                assertEquals(0, checkParameterIsNotNull);
+              }
+            });
+  }
+}
diff --git a/src/test/kotlinR8TestResources/intrinsics/inlineChainParameterCheck.kt b/src/test/kotlinR8TestResources/intrinsics/inlineChainParameterCheck.kt
new file mode 100644
index 0000000..e77fab7
--- /dev/null
+++ b/src/test/kotlinR8TestResources/intrinsics/inlineChainParameterCheck.kt
@@ -0,0 +1,17 @@
+// 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 intrinsics
+
+fun foo(field: String) {
+  println("field is " + field)
+}
+
+fun bar(field : String) {
+  println(field)
+  foo(field)
+}
+
+fun main(args : Array<String>) {
+  bar((if (args.size > 0) args.get(0) else null) as String)
+}
\ No newline at end of file