Model array types as subtypes of Cloneable and Serializable

Bug: 214496607
Change-Id: I9dd814a312a3813abfa99ebae25f064a16c8ad0e
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
index 78332d5..9b5ea8e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
@@ -165,6 +165,22 @@
     }
   }
 
+  ReferenceTypeElement join(ClassTypeElement other, AppView<?> appView) {
+    return other.join(this, appView);
+  }
+
+  @Override
+  public ReferenceTypeElement join(ReferenceTypeElement other, AppView<?> appView) {
+    if (other.isArrayType()) {
+      return join(other.asArrayType(), appView);
+    }
+    if (other.isClassType()) {
+      return join(other.asClassType(), appView);
+    }
+    assert other.isNullType();
+    return joinNullability(other.nullability());
+  }
+
   private static ReferenceTypeElement joinMember(
       TypeElement aMember, TypeElement bMember, AppView<?> appView, Nullability nullability) {
     if (aMember.equals(bMember)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index 7e54571..5ff246a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -259,18 +259,39 @@
   }
 
   ClassTypeElement join(ClassTypeElement other, AppView<?> appView) {
-    if (!appView.enableWholeProgramOptimizations()) {
-      assert lazyInterfaces != null;
-      assert lazyInterfaces.isEmpty();
-      assert other.lazyInterfaces != null;
-      assert other.lazyInterfaces.isEmpty();
-      return ClassTypeElement.createForD8(
-          getClassType() == other.getClassType()
-              ? getClassType()
-              : appView.dexItemFactory().objectType,
-          nullability().join(other.nullability()));
+    if (appView.enableWholeProgramOptimizations()) {
+      return joinWithClassHierarchy(other);
+    } else {
+      return joinWithoutClassHierarchy(other.getClassType(), other.nullability(), appView);
     }
-    return joinWithClassHierarchy(other);
+  }
+
+  ReferenceTypeElement join(ArrayTypeElement other, AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    if (appView.enableWholeProgramOptimizations()) {
+      DexType lubType = appView.dexItemFactory().objectType;
+      return joinWithClassHierarchy(
+          lubType,
+          InterfaceCollection.builder()
+              .addKnownInterface(dexItemFactory.cloneableType)
+              .addKnownInterface(dexItemFactory.serializableType)
+              .build(),
+          other.nullability());
+    } else {
+      return joinWithoutClassHierarchy(dexItemFactory.objectType, other.nullability(), appView);
+    }
+  }
+
+  @Override
+  public ReferenceTypeElement join(ReferenceTypeElement other, AppView<?> appView) {
+    if (other.isArrayType()) {
+      return join(other.asArrayType(), appView);
+    }
+    if (other.isClassType()) {
+      return join(other.asClassType(), appView);
+    }
+    assert other.isNullType();
+    return joinNullability(other.nullability());
   }
 
   private ClassTypeElement joinWithClassHierarchy(ClassTypeElement other) {
@@ -278,17 +299,23 @@
     assert appView.enableWholeProgramOptimizations();
     DexType lubType =
         computeLeastUpperBoundOfClasses(appView.appInfo(), getClassType(), other.getClassType());
-    InterfaceCollection c1lubItfs = getInterfaces();
-    InterfaceCollection c2lubItfs = other.getInterfaces();
-    InterfaceCollection lubItfs =
-        c1lubItfs.equals(c2lubItfs)
-            ? c1lubItfs
-            : computeLeastUpperBoundOfInterfaces(appView, c1lubItfs, c2lubItfs);
-    InterfaceCollection lubItfsDefault =
+    return joinWithClassHierarchy(lubType, other.getInterfaces(), other.nullability());
+  }
+
+  private ClassTypeElement joinWithClassHierarchy(
+      DexType lubType, InterfaceCollection otherInterfaces, Nullability nullability) {
+    assert appView != null;
+    assert appView.enableWholeProgramOptimizations();
+    InterfaceCollection interfaces = getInterfaces();
+    InterfaceCollection lubInterfaces =
+        interfaces.equals(otherInterfaces)
+            ? interfaces
+            : computeLeastUpperBoundOfInterfaces(appView, interfaces, otherInterfaces);
+    InterfaceCollection lubInterfacesDefault =
         appView
             .dexItemFactory()
             .getOrComputeLeastUpperBoundOfImplementedInterfaces(lubType, appView);
-    Nullability lubNullability = nullability().join(other.nullability());
+    Nullability lubNullability = nullability().join(nullability);
 
     // If the computed interfaces are identical to the interfaces of `lubType`, then do not include
     // the interfaces in the ClassTypeElement. This canonicalization of interfaces reduces memory,
@@ -296,9 +323,19 @@
     // element does not require any rewriting).
     //
     // From a correctness point of view, both solutions should work.
-    return lubItfs.equals(lubItfsDefault)
+    return lubInterfaces.equals(lubInterfacesDefault)
         ? create(lubType, lubNullability, appView)
-        : create(lubType, lubNullability, appView, lubItfs);
+        : create(lubType, lubNullability, appView, lubInterfaces);
+  }
+
+  ClassTypeElement joinWithoutClassHierarchy(
+      DexType other, Nullability nullability, AppView<?> appView) {
+    assert !appView.enableWholeProgramOptimizations();
+    assert lazyInterfaces != null;
+    assert lazyInterfaces.isEmpty();
+    return createForD8(
+        getClassType() == other ? getClassType() : appView.dexItemFactory().objectType,
+        nullability().join(nullability));
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
index 02c35fd..7f4e28a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
@@ -56,6 +56,10 @@
       return this;
     }
 
+    public Builder addKnownInterface(DexType type) {
+      return addInterface(type, true);
+    }
+
     public InterfaceCollection build() {
       if (interfaces.isEmpty()) {
         return InterfaceCollection.empty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
index 3577c72..ed144be 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 
 public abstract class ReferenceTypeElement extends TypeElement {
@@ -33,6 +34,11 @@
     }
 
     @Override
+    public ReferenceTypeElement join(ReferenceTypeElement other, AppView<?> appView) {
+      return other.joinNullability(nullability());
+    }
+
+    @Override
     public String toString() {
       return nullability.toString() + " " + DexItemFactory.nullValueType.toString();
     }
@@ -90,6 +96,8 @@
     return getOrCreateVariant(Nullability.maybeNull());
   }
 
+  public abstract ReferenceTypeElement join(ReferenceTypeElement other, AppView<?> appView);
+
   public ReferenceTypeElement joinNullability(Nullability nullability) {
     return getOrCreateVariant(nullability().join(nullability));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index 6451a1c..ef0e76e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -5,7 +5,6 @@
 
 import static java.util.Collections.emptySet;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
@@ -119,48 +118,24 @@
    * @return {@link TypeElement}, a least upper bound of {@param this} and {@param other}.
    */
   public TypeElement join(TypeElement other, AppView<?> appView) {
-    if (this == other) {
+    if (this == other || other.isBottom()) {
       return this;
     }
     if (isBottom()) {
       return other;
     }
-    if (other.isBottom()) {
-      return this;
-    }
-    if (isTop() || other.isTop()) {
+    if (isTop() || other.isTop() || isPrimitiveType() != other.isPrimitiveType()) {
       return getTop();
     }
     if (isPrimitiveType()) {
-      return other.isPrimitiveType() ? asPrimitiveType().join(other.asPrimitiveType()) : getTop();
-    }
-    if (other.isPrimitiveType()) {
-      // By the above case, !(isPrimitive())
-      return getTop();
+      return asPrimitiveType().join(other.asPrimitiveType());
     }
     // From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
-    assert isReferenceType() && other.isReferenceType();
-    assert isPreciseType() && other.isPreciseType();
-    Nullability nullabilityJoin = nullability().join(other.nullability());
-    if (isNullType()) {
-      return other.asReferenceType().getOrCreateVariant(nullabilityJoin);
-    }
-    if (other.isNullType()) {
-      return this.asReferenceType().getOrCreateVariant(nullabilityJoin);
-    }
-    if (getClass() != other.getClass()) {
-      return objectClassType(appView, nullabilityJoin);
-    }
-    // From now on, getClass() == other.getClass()
-    if (isArrayType()) {
-      assert other.isArrayType();
-      return asArrayType().join(other.asArrayType(), appView);
-    }
-    if (isClassType()) {
-      assert other.isClassType();
-      return asClassType().join(other.asClassType(), appView);
-    }
-    throw new Unreachable("unless a new type lattice is introduced.");
+    assert isReferenceType();
+    assert isPreciseType();
+    assert other.isReferenceType();
+    assert other.isPreciseType();
+    return asReferenceType().join(other.asReferenceType(), appView);
   }
 
   public static TypeElement join(Iterable<TypeElement> typeLattices, AppView<?> appView) {
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
index c9d943e..4ad8b97 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
@@ -65,9 +65,6 @@
             builder ->
                 builder.addOptionsModification(
                     options -> {
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                       options.testing.processingContextsConsumer =
                           id -> assertNull(idsRoundOne.put(id, id));
                     }));
@@ -80,9 +77,6 @@
             builder ->
                 builder.addOptionsModification(
                     options -> {
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                       options.testing.processingContextsConsumer =
                           id -> {
                             AssertionUtils.assertNotNull(idsRoundOne.get(id));
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
index 34b77bd..a6277d6 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
@@ -63,9 +63,6 @@
             builder ->
                 builder.addOptionsModification(
                     options -> {
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                       options.testing.processingContextsConsumer =
                           id -> assertTrue(idsRoundOne.add(id));
                     }));
@@ -78,9 +75,6 @@
             builder ->
                 builder.addOptionsModification(
                     options -> {
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                       options.testing.processingContextsConsumer =
                           id -> assertTrue(idsRoundTwo.add(id));
                     }));
@@ -104,9 +98,6 @@
                 builder.addOptionsModification(
                     options -> {
                       options.testing.forceJumboStringProcessing = true;
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                     }))
         .runDex2Oat(parameters.getRuntime())
         .assertNoVerificationErrors();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ArrayInstanceOfCloneableAndSerializableTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ArrayInstanceOfCloneableAndSerializableTest.java
index 73f3cfe..fc8ca51 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ArrayInstanceOfCloneableAndSerializableTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ArrayInstanceOfCloneableAndSerializableTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.instanceofremoval;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
@@ -71,12 +72,10 @@
         .inspect(
             inspector -> {
               MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
-              assertEquals(
-                  2,
+              assertTrue(
                   mainMethodSubject
                       .streamInstructions()
-                      .filter(InstructionSubject::isInstanceOf)
-                      .count());
+                      .noneMatch(InstructionSubject::isInstanceOf));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("true", "true");