Version 1.6.41

Cherry pick: Add test asserting no renaming of interface clinits using
applymapping
CL: https://r8-review.googlesource.com/c/r8/+/44541

Cherry pick: Reserve clinits in proguardmapminifier name strategy
CL: https://r8-review.googlesource.com/c/r8/+/44542

Cherry pick: Do not statically merge classes with synchronized methods
CL: https://r8-review.googlesource.com/c/r8/+/44227

Cherry pick: Classes with synchronized methods should not be merged
vertically
CL: https://r8-review.googlesource.com/c/r8/+/44226

Cherry pick: Update isStrict to isStatic
CL: https://r8-review.googlesource.com/c/r8/+/44360

Cherry pick: Negate assertion regarding synchronized method
CL: https://r8-review.googlesource.com/c/r8/+/44362

Cherry pick: Refactor static class merger merge into a single method
CL: https://r8-review.googlesource.com/c/r8/+/44464

Cherry pick: Reland "Find set of lock-candidates from const-class to disallow in merging"
CL: https://r8-review.googlesource.com/c/r8/+/44742

Cherry pick: Add utility to testbase for testing on current runtime
CL: https://r8-review.googlesource.com/c/r8/+/44182

Bug: 142438687
Bug: 142909857
Change-Id: I9371099fccff6f480711774aca69aeb32dc0f873
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 38f3cb0..d8c530b 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.6.40";
+  public static final String LABEL = "1.6.41";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 6f5df28..7ce2685 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -924,4 +924,13 @@
     assert verifyNoDuplicateMethods();
     return true;
   }
+
+  public boolean hasStaticSynchronizedMethods() {
+    for (DexEncodedMethod encodedMethod : directMethods()) {
+      if (encodedMethod.accessFlags.isStatic() && encodedMethod.accessFlags.isSynchronized()) {
+        return true;
+      }
+    }
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index dc90b8f..47a8d3f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -440,7 +441,20 @@
         DexMethod method,
         InternalNamingState internalState,
         BiPredicate<DexString, DexMethod> isAvailable) {
-      return nextName(method, method.name, method.holder, internalState, isAvailable, super::next);
+      DexEncodedMethod definition = appView.definitionFor(method);
+      DexString nextName =
+          nextName(
+              method,
+              definition,
+              method.name,
+              method.holder,
+              internalState,
+              isAvailable,
+              super::next);
+      assert nextName == method.name || !definition.isClassInitializer();
+      assert nextName == method.name
+          || !appView.definitionFor(method.holder).accessFlags.isAnnotation();
+      return nextName;
     }
 
     @Override
@@ -448,22 +462,32 @@
         DexField field,
         InternalNamingState internalState,
         BiPredicate<DexString, DexField> isAvailable) {
-      return nextName(field, field.name, field.holder, internalState, isAvailable, super::next);
+      return nextName(
+          field,
+          appView.definitionFor(field),
+          field.name,
+          field.holder,
+          internalState,
+          isAvailable,
+          super::next);
     }
 
     private <T extends DexReference> DexString nextName(
         T reference,
+        DexDefinition definition,
         DexString name,
         DexType holderType,
         InternalNamingState internalState,
         BiPredicate<DexString, T> isAvailable,
         TriFunction<T, InternalNamingState, BiPredicate<DexString, T>, DexString> generateName) {
+      assert definition.isDexEncodedMethod() || definition.isDexEncodedField();
+      assert definition.toReference() == reference;
       DexClass holder = appView.definitionFor(holderType);
       assert holder != null;
-      DexString reservedName = getReservedName(reference, name, holder);
+      DexString reservedName = getReservedName(definition, name, holder);
       if (reservedName != null) {
         if (!isAvailable.test(reservedName, reference)) {
-          reportReservationError(reference, reservedName);
+          reportReservationError(definition.asDexReference(), reservedName);
         }
         return reservedName;
       }
@@ -474,26 +498,36 @@
 
     @Override
     public DexString getReservedName(DexEncodedMethod method, DexClass holder) {
-      return getReservedName(method.method, method.method.name, holder);
+      return getReservedName(method, method.method.name, holder);
     }
 
     @Override
     public DexString getReservedName(DexEncodedField field, DexClass holder) {
-      return getReservedName(field.field, field.field.name, holder);
+      return getReservedName(field, field.field.name, holder);
     }
 
-    private DexString getReservedName(DexReference reference, DexString name, DexClass holder) {
+    private DexString getReservedName(DexDefinition definition, DexString name, DexClass holder) {
+      assert definition.isDexEncodedMethod() || definition.isDexEncodedField();
       // Always consult the mapping for renamed members that are not on program path.
-      if (holder.isNotProgramClass() && mappedNames.containsKey(reference)) {
-        return factory.createString(mappedNames.get(reference).getRenamedName());
-      }
-      if (holder.isProgramClass() && appView.rootSet().mayBeMinified(reference, appView)) {
+      DexReference reference = definition.toReference();
+      if (holder.isNotProgramClass()) {
         if (mappedNames.containsKey(reference)) {
           return factory.createString(mappedNames.get(reference).getRenamedName());
         }
-        return null;
+        return name;
       }
-      return name;
+      assert holder.isProgramClass();
+      DexString reservedName =
+          definition.isDexEncodedMethod()
+              ? super.getReservedName(definition.asDexEncodedMethod(), holder)
+              : super.getReservedName(definition.asDexEncodedField(), holder);
+      if (reservedName != null) {
+        return reservedName;
+      }
+      if (mappedNames.containsKey(reference)) {
+        return factory.createString(mappedNames.get(reference).getRenamedName());
+      }
+      return null;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 9676a05..c7776f2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -151,6 +151,8 @@
   public final Set<DexType> neverClassInline;
   /** All types that *must* never be merged due to a configuration directive (testing only). */
   public final Set<DexType> neverMerge;
+  /** Set of const-class references. */
+  public final Set<DexType> constClassReferences;
   /**
    * All methods and fields whose value *must* never be propagated due to a configuration directive.
    * (testing only).
@@ -220,7 +222,8 @@
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps,
-      Set<DexType> instantiatedLambdas) {
+      Set<DexType> instantiatedLambdas,
+      Set<DexType> constClassReferences) {
     super(application);
     this.liveTypes = liveTypes;
     this.instantiatedAnnotationTypes = instantiatedAnnotationTypes;
@@ -260,6 +263,7 @@
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
     this.instantiatedLambdas = instantiatedLambdas;
+    this.constClassReferences = constClassReferences;
   }
 
   public AppInfoWithLiveness(
@@ -299,7 +303,8 @@
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps,
-      Set<DexType> instantiatedLambdas) {
+      Set<DexType> instantiatedLambdas,
+      Set<DexType> constClassReferences) {
     super(appInfoWithSubtyping);
     this.liveTypes = liveTypes;
     this.instantiatedAnnotationTypes = instantiatedAnnotationTypes;
@@ -339,6 +344,7 @@
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
     this.instantiatedLambdas = instantiatedLambdas;
+    this.constClassReferences = constClassReferences;
   }
 
   private AppInfoWithLiveness(AppInfoWithLiveness previous) {
@@ -391,7 +397,8 @@
             : CollectionUtils.mergeSets(previous.prunedTypes, removedClasses),
         previous.switchMaps,
         previous.enumValueInfoMaps,
-        previous.instantiatedLambdas);
+        previous.instantiatedLambdas,
+        previous.constClassReferences);
     copyMetadataFromPrevious(previous);
     assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses);
   }
@@ -476,6 +483,7 @@
             .collect(Collectors.toList()));
     this.switchMaps = rewriteReferenceKeys(previous.switchMaps, lense::lookupField);
     this.enumValueInfoMaps = rewriteReferenceKeys(previous.enumValueInfoMaps, lense::lookupType);
+    this.constClassReferences = previous.constClassReferences;
   }
 
   public AppInfoWithLiveness(
@@ -521,6 +529,7 @@
     this.prunedTypes = previous.prunedTypes;
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
+    this.constClassReferences = previous.constClassReferences;
     previous.markObsolete();
   }
 
@@ -582,6 +591,16 @@
     return interfaces;
   }
 
+  /**
+   * Const-classes is a conservative set of types that may be lock-candidates and cannot be merged.
+   * When using synchronized blocks, we cannot ensure that const-class locks will not flow in. This
+   * can potentially cause incorrect behavior when merging classes. A conservative choice is to not
+   * merge any const-class classes. More info at b/142438687.
+   */
+  public boolean isLockCandidate(DexType type) {
+    return constClassReferences.contains(type);
+  }
+
   public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
     assert checkIfObsolete();
     if (noLongerWrittenFields.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index b38471a..f602541 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -299,6 +299,12 @@
   private final Set<DexReference> pinnedItems = Sets.newIdentityHashSet();
 
   /**
+   * A set of seen const-class references that both serve as an initial lock-candidate set and will
+   * prevent statically merging the classes referenced.
+   */
+  private final Set<DexType> constClassReferences = Sets.newIdentityHashSet();
+
+  /**
    * A map from classes to annotations that need to be processed should the classes ever become
    * live.
    */
@@ -910,6 +916,15 @@
 
     @Override
     public boolean registerConstClass(DexType type) {
+      // We conservatively group T.class and T[].class to ensure that we do not merge T with S if
+      // potential locks on T[].class and S[].class exists.
+      DexType baseType = type.toBaseType(appView.dexItemFactory());
+      if (baseType.isClassType()) {
+        DexProgramClass baseClass = getProgramClassOrNull(baseType);
+        if (baseClass != null) {
+          constClassReferences.add(baseType);
+        }
+      }
       return registerConstClassOrCheckCast(type);
     }
 
@@ -2167,7 +2182,8 @@
             Collections.emptyMap(),
             Collections.emptyMap(),
             SetUtils.mapIdentityHashSet(
-                instantiatedInterfaceTypes.getItems(), DexProgramClass::getType));
+                instantiatedInterfaceTypes.getItems(), DexProgramClass::getType),
+            constClassReferences);
     appInfo.markObsolete();
     return appInfoWithLiveness;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 7f16e07..dc69005 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -157,6 +157,8 @@
     private final HashMultiset<Wrapper<DexField>> fieldBuckets = HashMultiset.create();
     private final HashMultiset<Wrapper<DexMethod>> methodBuckets = HashMultiset.create();
 
+    private boolean hasSynchronizedMethods = false;
+
     public Representative(DexProgramClass clazz) {
       this.clazz = clazz;
       include(clazz);
@@ -168,10 +170,14 @@
         Wrapper<DexField> wrapper = fieldEquivalence.wrap(field.field);
         fieldBuckets.add(wrapper);
       }
+      boolean classHasSynchronizedMethods = false;
       for (DexEncodedMethod method : clazz.methods()) {
+        assert !hasSynchronizedMethods || !method.accessFlags.isSynchronized();
+        classHasSynchronizedMethods |= method.accessFlags.isSynchronized();
         Wrapper<DexMethod> wrapper = methodEquivalence.wrap(method.method);
         methodBuckets.add(wrapper);
       }
+      hasSynchronizedMethods |= classHasSynchronizedMethods;
     }
 
     // Returns true if this representative should no longer be used. The current heuristic is to
@@ -317,83 +323,47 @@
     assert satisfiesMergeCriteria(clazz) == group;
     assert group != MergeGroup.DONT_MERGE;
 
-    String pkg = clazz.type.getPackageDescriptor();
-    return mayMergeAcrossPackageBoundaries(clazz)
-        ? mergeGlobally(clazz, pkg, group)
-        : mergeInsidePackage(clazz, pkg, group);
+    return merge(
+        clazz,
+        mayMergeAcrossPackageBoundaries(clazz)
+            ? group.globalKey()
+            : group.key(clazz.type.getPackageDescriptor()));
   }
 
-  private boolean mergeGlobally(DexProgramClass clazz, String pkg, MergeGroup group) {
-    Representative globalRepresentative = representatives.get(group.globalKey());
-    if (globalRepresentative == null) {
-      if (isValidRepresentative(clazz)) {
-        // Make the current class the global representative.
-        setRepresentative(group.globalKey(), getOrCreateRepresentative(group.key(pkg), clazz));
-      } else {
-        clearRepresentative(group.globalKey());
-      }
-
-      // Do not attempt to merge this class inside its own package, because that could lead to
-      // an increase in the global representative, which is not desirable.
-      return false;
-    } else {
-      // Check if we can merge the current class into the current global representative.
-      globalRepresentative.include(clazz);
-
-      if (globalRepresentative.isFull()) {
-        if (isValidRepresentative(clazz)) {
-          // Make the current class the global representative instead.
-          setRepresentative(group.globalKey(), getOrCreateRepresentative(group.key(pkg), clazz));
-        } else {
-          clearRepresentative(group.globalKey());
-        }
-
-        // Do not attempt to merge this class inside its own package, because that could lead to
-        // an increase in the global representative, which is not desirable.
+  private boolean merge(DexProgramClass clazz, MergeGroup.Key key) {
+    Representative representative = representatives.get(key);
+    if (representative != null) {
+      if (representative.hasSynchronizedMethods && clazz.hasStaticSynchronizedMethods()) {
+        // We are not allowed to merge synchronized classes with synchronized methods.
         return false;
-      } else {
-        // Merge this class into the global representative.
-        moveMembersFromSourceToTarget(clazz, globalRepresentative.clazz);
-        return true;
       }
-    }
-  }
-
-  private boolean mergeInsidePackage(DexProgramClass clazz, String pkg, MergeGroup group) {
-    MergeGroup.Key key = group.key(pkg);
-    Representative packageRepresentative = representatives.get(key);
-    if (packageRepresentative != null) {
+      if (appView.appInfo().constClassReferences.contains(clazz.type)) {
+        // Since the type is const-class referenced (and the static merger does not create a lense
+        // to map the merged type) the class will likely remain and there is no gain from merging.
+        return false;
+      }
+      // Check if current candidate is a better choice depending on visibility. For package private
+      // or protected, the key is parameterized by the package name already, so we just have to
+      // check accessibility-flags. For global this is no-op.
       if (isValidRepresentative(clazz)
-          && clazz.accessFlags.isMoreVisibleThan(
-              packageRepresentative.clazz.accessFlags,
-              clazz.type.getPackageName(),
-              packageRepresentative.clazz.type.getPackageName())) {
-        // Use `clazz` as a representative for this package instead.
+          && !representative.clazz.accessFlags.isAtLeastAsVisibleAs(clazz.accessFlags)) {
+        assert clazz.type.getPackageDescriptor().equals(key.packageOrGlobal);
+        assert representative.clazz.type.getPackageDescriptor().equals(key.packageOrGlobal);
         Representative newRepresentative = getOrCreateRepresentative(key, clazz);
-        newRepresentative.include(packageRepresentative.clazz);
-
+        newRepresentative.include(representative.clazz);
         if (!newRepresentative.isFull()) {
-          setRepresentative(group.key(pkg), newRepresentative);
-          moveMembersFromSourceToTarget(packageRepresentative.clazz, clazz);
+          setRepresentative(key, newRepresentative);
+          moveMembersFromSourceToTarget(representative.clazz, clazz);
           return true;
         }
-
-        // We are not allowed to merge members into a class that is less visible.
-        return false;
-      }
-
-      // Merge current class into the representative of this package if it has room.
-      packageRepresentative.include(clazz);
-
-      // If there is room, then merge, otherwise fall-through to update the representative of this
-      // package.
-      if (!packageRepresentative.isFull()) {
-        moveMembersFromSourceToTarget(clazz, packageRepresentative.clazz);
-        return true;
+      } else {
+        representative.include(clazz);
+        if (!representative.isFull()) {
+          moveMembersFromSourceToTarget(clazz, representative.clazz);
+          return true;
+        }
       }
     }
-
-    // We were unable to use the current representative for this package (if any).
     if (isValidRepresentative(clazz)) {
       setRepresentative(key, getOrCreateRepresentative(key, clazz));
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index ef9a378..53d2179 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -137,6 +137,7 @@
     RESOLUTION_FOR_FIELDS_MAY_CHANGE,
     RESOLUTION_FOR_METHODS_MAY_CHANGE,
     SERVICE_LOADER,
+    SOURCE_AND_TARGET_LOCK_CANDIDATES,
     STATIC_INITIALIZERS,
     UNHANDLED_INVOKE_DIRECT,
     UNHANDLED_INVOKE_SUPER,
@@ -183,6 +184,9 @@
         case SERVICE_LOADER:
           message = "it is used by a service loader";
           break;
+        case SOURCE_AND_TARGET_LOCK_CANDIDATES:
+          message = "source and target are both lock-candidates";
+          break;
         case STATIC_INITIALIZERS:
           message = "merging of static initializers are not supported";
           break;
@@ -463,6 +467,17 @@
       }
       return false;
     }
+    boolean sourceCanBeSynchronizedOn =
+        appView.appInfo().isLockCandidate(clazz.type) || clazz.hasStaticSynchronizedMethods();
+    boolean targetCanBeSynchronizedOn =
+        appView.appInfo().isLockCandidate(targetClass.type)
+            || targetClass.hasStaticSynchronizedMethods();
+    if (sourceCanBeSynchronizedOn && targetCanBeSynchronizedOn) {
+      if (Log.ENABLED) {
+        AbortReason.SOURCE_AND_TARGET_LOCK_CANDIDATES.printLogMessageForClass(clazz);
+      }
+      return false;
+    }
     if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
       // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 9b8abe6..797d185 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -147,6 +147,17 @@
     return testForJvm(temp);
   }
 
+  public TestBuilder<?, ?> testForRuntime(TestRuntime runtime, AndroidApiLevel apiLevel) {
+    if (runtime.isCf()) {
+      return testForJvm();
+    } else {
+      assert runtime.isDex();
+      D8TestBuilder d8TestBuilder = testForD8();
+      d8TestBuilder.setMinApi(apiLevel);
+      return d8TestBuilder;
+    }
+  }
+
   public ProguardTestBuilder testForProguard() {
     return testForProguard(temp);
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java
new file mode 100644
index 0000000..7007821
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/HorizontalClassMergerShouldMergeSynchronizedMethodTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 HorizontalClassMergerShouldMergeSynchronizedMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public HorizontalClassMergerShouldMergeSynchronizedMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNoMergingOfClassUsedInMonitor()
+      throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(HorizontalClassMergerShouldMergeSynchronizedMethodTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1", "2", "3")
+        .inspect(
+            inspector -> {
+              assertFalse(
+                  inspector.clazz(LockOne.class).isPresent()
+                      && inspector.clazz(LockTwo.class).isPresent());
+              assertTrue(
+                  inspector.clazz(LockOne.class).isPresent()
+                      || inspector.clazz(LockTwo.class).isPresent());
+            });
+  }
+
+  // Will be merged with LockTwo.
+  static class LockOne {
+
+    static synchronized void print1() {
+      System.out.println("1");
+    }
+
+    static synchronized void print2() {
+      System.out.println("2");
+    }
+  }
+
+  public static class LockTwo {
+
+    static void print3() {
+      System.out.println("3");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      LockOne.print1();
+      LockOne.print2();
+      LockTwo.print3();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerShouldMergeSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerShouldMergeSynchronizedMethodTest.java
new file mode 100644
index 0000000..5f6e255
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerShouldMergeSynchronizedMethodTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 VerticalClassMergerShouldMergeSynchronizedMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public VerticalClassMergerShouldMergeSynchronizedMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNoMergingOfClassUsedInMonitor()
+      throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(VerticalClassMergerShouldMergeSynchronizedMethodTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("1", "2", "3", "4")
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(LockOne.class), not(isPresent()));
+              assertThat(inspector.clazz(LockTwo.class), isPresent());
+            });
+  }
+
+  // Will be merged with LockTwo.
+  static class LockOne {
+
+    synchronized void print1() {
+      System.out.println("1");
+      print2();
+    }
+
+    private synchronized void print2() {
+      System.out.println("2");
+    }
+  }
+
+  public static class LockTwo extends LockOne {
+
+    synchronized void print3() {
+      System.out.println("3");
+    }
+
+    static void print4() {
+      System.out.println("4");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      LockTwo lockTwo = new LockTwo();
+      lockTwo.print1();
+      lockTwo.print3();
+      LockTwo.print4();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockWithArraysTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockWithArraysTest.java
new file mode 100644
index 0000000..b6039db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockWithArraysTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import java.io.IOException;
+import java.lang.Thread.State;
+import java.util.concurrent.ExecutionException;
+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 VerticalClassMergerSynchronizedBlockWithArraysTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // The 4.0.4 runtime will flakily mark threads as blocking and report DEADLOCKED.
+    return getTestParameters()
+        .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public VerticalClassMergerSynchronizedBlockWithArraysTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testOnRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addInnerClasses(VerticalClassMergerSynchronizedBlockWithArraysTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput("Hello World!");
+  }
+
+  @Test
+  public void testNoMergingOfClassUsedInMonitor()
+      throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(VerticalClassMergerSynchronizedBlockWithArraysTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput("Hello World!")
+        .inspect(inspector -> assertThat(inspector.clazz(LockOne.class), isPresent()));
+  }
+
+  abstract static class LockOne {}
+
+  static class LockTwo extends LockOne {}
+
+  static class LockThree {}
+
+  public static class Main {
+
+    private static volatile boolean inLockThreeCritical = false;
+    private static volatile boolean inLockTwoCritical = false;
+    private static volatile boolean arnoldWillNotBeBack = false;
+
+    private static volatile Thread t1 = new Thread(Main::lockThreeThenOne);
+    private static volatile Thread t2 = new Thread(Main::lockTwoThenThree);
+    private static volatile Thread t3 = new Thread(Main::arnold);
+
+    static void synchronizedAccessThroughLocks(String arg) {
+      System.out.print(arg);
+    }
+
+    public static void main(String[] args) {
+      t1.start();
+      t2.start();
+      // This thread is started to ensure termination in case we are rewriting incorrectly.
+      t3.start();
+
+      while (!arnoldWillNotBeBack) {}
+    }
+
+    static void lockThreeThenOne() {
+      synchronized (LockThree[].class) {
+        inLockThreeCritical = true;
+        while (!inLockTwoCritical) {}
+        synchronized (LockOne[].class) {
+          synchronizedAccessThroughLocks("Hello ");
+        }
+      }
+    }
+
+    static void lockTwoThenThree() {
+      synchronized (LockTwo[].class) {
+        inLockTwoCritical = true;
+        while (!inLockThreeCritical) {}
+        synchronized (LockThree[].class) {
+          synchronizedAccessThroughLocks("World!");
+        }
+      }
+    }
+
+    static void arnold() {
+      while (t1.getState() != State.TERMINATED || t2.getState() != State.TERMINATED) {
+        if (t1.getState() == State.BLOCKED && t2.getState() == State.BLOCKED) {
+          System.err.println("DEADLOCKED!");
+          System.exit(1);
+          break;
+        }
+      }
+      arnoldWillNotBeBack = true;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index a044d3f..f8aa866 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -34,7 +34,10 @@
     final String mainClassName = ex1.getClassName();
     final String extraRules =
         keepMainMethod(mainClassName) + neverInlineMethod(mainClassName, testMethodSignature);
-    runTest(FOLDER, mainClassName, extraRules,
+    runTest(
+        FOLDER,
+        mainClassName,
+        extraRules,
         InternalOptions::enableCallSiteOptimizationInfoPropagation,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
@@ -43,8 +46,7 @@
           MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
           long ifzCount =
               testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count();
-          long paramNullCheckCount =
-              countCall(testMethod, "ArrayIteratorKt", "checkParameterIsNotNull");
+          long paramNullCheckCount = countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
           // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null.
           assertEquals(2, ifzCount);
           assertEquals(allowAccessModification ? 0 : 5, paramNullCheckCount);
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
new file mode 100644
index 0000000..04081f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 ApplyMappingInterfaceClInitTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ApplyMappingInterfaceClInitTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNotRenamingClInitIfNotInMap()
+      throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ApplyMappingInterfaceClInitTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(TestInterface.class)
+        .addApplyMapping("")
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(this::verifyNoRenamingOfClInit);
+  }
+
+  @Test
+  public void testNotRenamingClInitIfInMap()
+      throws ExecutionException, CompilationFailedException, IOException {
+    String interfaceName = TestInterface.class.getTypeName();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ApplyMappingInterfaceClInitTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(TestInterface.class)
+        .addApplyMapping(
+            StringUtils.lines(
+                interfaceName + " -> " + interfaceName + ":", "    void <clinit>() -> a"))
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(this::verifyNoRenamingOfClInit);
+  }
+
+  private void verifyNoRenamingOfClInit(CodeInspector inspector) {
+    ClassSubject interfaceSubject = inspector.clazz(TestInterface.class);
+    assertThat(interfaceSubject, isPresent());
+    interfaceSubject.allMethods().stream()
+        .allMatch(
+            method -> {
+              boolean classInitNotRenamed = !method.isClassInitializer() || !method.isRenamed();
+              assertTrue(classInitNotRenamed);
+              return classInitNotRenamed;
+            });
+  }
+
+  public interface TestInterface {
+    Throwable t = new Throwable();
+
+    void foo();
+  }
+
+  @NeverClassInline
+  public static class Main implements TestInterface {
+
+    public static void main(String[] args) {
+      new Main().foo();
+    }
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+}