Fix joining of parameter types in instance initializer merger

Change-Id: I6f319b30689673001f06469cbda8cf2c406a1737
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
index b6d9757..bff5528 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
@@ -4,86 +4,39 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
-import com.android.tools.r8.utils.ObjectUtils;
-import java.util.Iterator;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.google.common.collect.Iterables;
 
 public class DexTypeUtils {
 
   public static DexType computeLeastUpperBound(
       AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-
-    Iterator<DexType> iterator = types.iterator();
-    assert iterator.hasNext();
-
-    DexType result = iterator.next();
-    while (iterator.hasNext()) {
-      result = computeLeastUpperBound(appView, result, iterator.next());
-    }
-    return result;
+    TypeElement join =
+        TypeElement.join(Iterables.transform(types, type -> type.toTypeElement(appView)), appView);
+    return toDexType(appView, join);
   }
 
-  public static DexType computeLeastUpperBound(
-      AppView<? extends AppInfoWithClassHierarchy> appView, DexType type, DexType other) {
-    if (type == other) {
-      return type;
-    }
-
+  private static DexType toDexType(
+      AppView<? extends AppInfoWithClassHierarchy> appView, TypeElement type) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    if (type == dexItemFactory.objectType
-        || other == dexItemFactory.objectType
-        || type.isArrayType() != other.isArrayType()) {
-      return dexItemFactory.objectType;
+    if (type.isPrimitiveType()) {
+      return type.asPrimitiveType().toDexType(dexItemFactory);
     }
-
     if (type.isArrayType()) {
-      assert other.isArrayType();
-      int arrayDimension = type.getNumberOfLeadingSquareBrackets();
-      if (other.getNumberOfLeadingSquareBrackets() != arrayDimension) {
-        return dexItemFactory.objectType;
-      }
-
-      DexType baseType = type.toBaseType(dexItemFactory);
-      DexType otherBaseType = other.toBaseType(dexItemFactory);
-      if (baseType.isPrimitiveType() || otherBaseType.isPrimitiveType()) {
-        assert baseType != otherBaseType;
-        return dexItemFactory.objectType;
-      }
-
-      return dexItemFactory.createArrayType(
-          arrayDimension, computeLeastUpperBound(appView, baseType, otherBaseType));
+      ArrayTypeElement arrayType = type.asArrayType();
+      DexType baseType = toDexType(appView, arrayType.getBaseType());
+      return baseType.toArrayType(arrayType.getNesting(), dexItemFactory);
     }
-
-    assert !type.isArrayType();
-    assert !other.isArrayType();
-
-    boolean isInterface =
-        type.isClassType()
-            && ObjectUtils.getBooleanOrElse(
-                appView.definitionFor(type), DexClass::isInterface, false);
-    boolean otherIsInterface =
-        other.isClassType()
-            && ObjectUtils.getBooleanOrElse(
-                appView.definitionFor(other), DexClass::isInterface, false);
-    if (isInterface != otherIsInterface) {
-      return dexItemFactory.objectType;
+    assert type.isClassType();
+    ClassTypeElement classType = type.asClassType();
+    if (classType.getClassType() != dexItemFactory.objectType) {
+      return classType.getClassType();
     }
-
-    if (isInterface) {
-      assert otherIsInterface;
-      InterfaceCollection interfaceCollection =
-          ClassTypeElement.computeLeastUpperBoundOfInterfaces(
-              appView, InterfaceCollection.singleton(type), InterfaceCollection.singleton(other));
-      return interfaceCollection.hasSingleKnownInterface()
-          ? interfaceCollection.getSingleKnownInterface()
-          : dexItemFactory.objectType;
+    if (classType.getInterfaces().hasSingleKnownInterface()) {
+      return classType.getInterfaces().getSingleKnownInterface();
     }
-
-    assert !isInterface;
-    assert !otherIsInterface;
-
-    return ClassTypeElement.computeLeastUpperBoundOfClasses(appView.appInfo(), type, other);
+    return dexItemFactory.objectType;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 4b79710..70d534e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -279,6 +279,7 @@
       classMergers.add(
           new ClassMerger.Builder(appView, codeProvider, group, mode).build(lensBuilder));
     }
+    appView.dexItemFactory().clearTypeElementsCache();
     return classMergers;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
index f60f332..329d43a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfSafeCheckCast;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -115,7 +116,13 @@
 
     // Assign each field.
     addCfInstructionsForInstanceFieldAssignments(
-        instructionBuilder, instanceFieldAssignmentsPre, argumentToLocalIndex, maxStack, lens);
+        appView,
+        method,
+        instructionBuilder,
+        instanceFieldAssignmentsPre,
+        argumentToLocalIndex,
+        maxStack,
+        lens);
 
     // Load receiver for parent constructor call.
     int stackHeightForParentConstructorCall = 1;
@@ -155,7 +162,13 @@
 
     // Assign each field.
     addCfInstructionsForInstanceFieldAssignments(
-        instructionBuilder, instanceFieldAssignmentsPost, argumentToLocalIndex, maxStack, lens);
+        appView,
+        method,
+        instructionBuilder,
+        instanceFieldAssignmentsPost,
+        argumentToLocalIndex,
+        maxStack,
+        lens);
 
     // Return.
     instructionBuilder.add(new CfReturnVoid());
@@ -174,6 +187,8 @@
   }
 
   private static void addCfInstructionsForInstanceFieldAssignments(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ProgramMethod method,
       ImmutableList.Builder<CfInstruction> instructionBuilder,
       Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments,
       int[] argumentToLocalIndex,
@@ -186,8 +201,28 @@
           int stackSizeForInitializationInfo =
               addCfInstructionsForInitializationInfo(
                   instructionBuilder, initializationInfo, argumentToLocalIndex, field.getType());
-          instructionBuilder.add(
-              new CfInstanceFieldWrite(lens.getRenamedFieldSignature(field, lens.getPrevious())));
+          DexField rewrittenField = lens.getRenamedFieldSignature(field, lens.getPrevious());
+
+          // Insert a check to ensure the program continues to type check according to Java type
+          // checking. Otherwise, instance initializer merging may cause open interfaces. If
+          // <init>(A) and <init>(B) both have the behavior `this.i = arg; this.j = arg` where the
+          // type of `i` is I and the type of `j` is J, and both A and B implements I and J, then
+          // the constructors are merged into a single constructor <init>(java.lang.Object), which
+          // is no longer strictly type checking. Note that no choice of parameter type would solve
+          // this.
+          if (initializationInfo.isArgumentInitializationInfo()) {
+            int argumentIndex =
+                initializationInfo.asArgumentInitializationInfo().getArgumentIndex();
+            if (argumentIndex > 0) {
+              DexType argumentType = method.getArgumentType(argumentIndex);
+              if (argumentType.isClassType()
+                  && !appView.appInfo().isSubtype(argumentType, rewrittenField.getType())) {
+                instructionBuilder.add(new CfSafeCheckCast(rewrittenField.getType()));
+              }
+            }
+          }
+
+          instructionBuilder.add(new CfInstanceFieldWrite(rewrittenField));
           maxStack.setMax(stackSizeForInitializationInfo + 1);
         });
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 1c3443b..f669473 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -27,14 +27,15 @@
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 public class InstanceInitializerMerger {
 
@@ -101,10 +102,6 @@
     return classFileVersion;
   }
 
-  private DexMethod getNewMethodReference(ProgramMethod representative) {
-    return getNewMethodReference(representative, false);
-  }
-
   private DexMethod getNewMethodReference(ProgramMethod representative, boolean needsClassId) {
     DexType[] oldParameters = representative.getParameters().values;
     DexType[] newParameters =
@@ -112,12 +109,15 @@
     System.arraycopy(oldParameters, 0, newParameters, 0, oldParameters.length);
     for (int i = 0; i < oldParameters.length; i++) {
       final int parameterIndex = i;
-      newParameters[i] =
-          DexTypeUtils.computeLeastUpperBound(
-              appView,
-              Iterables.transform(
-                  instanceInitializers,
-                  instanceInitializer -> instanceInitializer.getParameter(parameterIndex)));
+      Set<DexType> parameterTypes =
+          SetUtils.newIdentityHashSet(
+              builder ->
+                  instanceInitializers.forEach(
+                      instanceInitializer ->
+                          builder.accept(instanceInitializer.getParameter(parameterIndex))));
+      if (parameterTypes.size() > 1) {
+        newParameters[i] = DexTypeUtils.computeLeastUpperBound(appView, parameterTypes);
+      }
     }
     if (needsClassId) {
       assert ArrayUtils.last(newParameters) == null;