Fix imprecise join in ArrayTypeElement causing nondeterminism

Bug: 227138351
Change-Id: I084d200f776ddd839c2e18ee1f7bfff4a80275fe
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index bbee604..d05ea55 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -203,12 +203,10 @@
             }
             assert fieldType.isClassType();
             DynamicType dynamicType =
-                fieldType.isArrayType()
-                    ? DynamicType.unknown()
-                    : WideningUtils.widenDynamicNonReceiverType(
-                        appView,
-                        value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
-                        field.getType());
+                WideningUtils.widenDynamicNonReceiverType(
+                    appView,
+                    value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
+                    field.getType());
             return ConcreteClassTypeFieldState.create(abstractValue, dynamicType);
           }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
index 7cd6f2e..3f51c25 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
@@ -46,13 +46,12 @@
       AbstractValue abstractValue,
       DynamicType dynamicType,
       ProgramField field) {
+    assert field.getType().isClassType();
     this.abstractValue =
         this.abstractValue.joinReference(abstractValue, appView.abstractValueFactory());
     this.dynamicType =
-        field.getType().isArrayType()
-            ? DynamicType.unknown()
-            : WideningUtils.widenDynamicNonReceiverType(
-                appView, this.dynamicType.join(appView, dynamicType), field.getType());
+        WideningUtils.widenDynamicNonReceiverType(
+            appView, this.dynamicType.join(appView, dynamicType), field.getType());
     return isEffectivelyUnknown() ? unknown() : this;
   }
 
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 9b5ea8e..72cbe7d 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
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -85,12 +83,10 @@
   }
 
   @Override
-  public ReferenceTypeElement getOrCreateVariant(Nullability nullability) {
-    ArrayTypeElement variant = variants.get(nullability);
-    if (variant != null) {
-      return variant;
-    }
-    return variants.getOrCreateElement(nullability, this::createVariant);
+  public ArrayTypeElement getOrCreateVariant(Nullability nullability) {
+    return nullability.equals(nullability())
+        ? this
+        : variants.getOrCreateElement(nullability, this::createVariant);
   }
 
   @Override
@@ -151,16 +147,16 @@
   ReferenceTypeElement join(ArrayTypeElement other, AppView<?> appView) {
     Nullability nullability = nullability().join(other.nullability());
     ReferenceTypeElement join =
-        joinMember(this.memberTypeLattice, other.memberTypeLattice, appView, nullability);
+        joinMember(getMemberType(), other.getMemberType(), appView, nullability);
     if (join == null) {
       // Check if other has the right nullability before creating it.
-      if (other.nullability == nullability) {
+      if (other.nullability() == nullability) {
         return other;
       } else {
         return getOrCreateVariant(nullability);
       }
     } else {
-      assert join.nullability == nullability;
+      assert join.nullability() == nullability;
       return join;
     }
   }
@@ -188,12 +184,14 @@
       return null;
     }
     if (aMember.isArrayType() && bMember.isArrayType()) {
+      TypeElement aMemberMember = aMember.asArrayType().getMemberType();
+      TypeElement bMemberMember = bMember.asArrayType().getMemberType();
       TypeElement join =
           joinMember(
-              aMember.asArrayType().memberTypeLattice,
-              bMember.asArrayType().memberTypeLattice,
+              aMemberMember,
+              bMemberMember,
               appView,
-              maybeNull());
+              aMemberMember.nullability().join(bMemberMember.nullability()));
       return join == null ? null : ArrayTypeElement.create(join, nullability);
     }
     if (aMember.isClassType() && bMember.isClassType()) {
@@ -201,6 +199,20 @@
       return ArrayTypeElement.create(join, nullability);
     }
     if (aMember.isPrimitiveType() || bMember.isPrimitiveType()) {
+      if (appView.enableWholeProgramOptimizations()) {
+        assert appView.hasClassHierarchy();
+        DexItemFactory dexItemFactory = appView.dexItemFactory();
+        InterfaceCollection interfaceCollection =
+            InterfaceCollection.builder()
+                .addKnownInterface(dexItemFactory.cloneableType)
+                .addKnownInterface(dexItemFactory.serializableType)
+                .build();
+        return ClassTypeElement.create(
+            dexItemFactory.objectType,
+            nullability,
+            appView.withClassHierarchy(),
+            interfaceCollection);
+      }
       return objectClassType(appView, nullability);
     }
     return objectArrayType(appView, nullability);
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 5ff246a..3298b8e 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
@@ -111,11 +111,9 @@
 
   @Override
   public ClassTypeElement getOrCreateVariant(Nullability nullability) {
-    ClassTypeElement variant = variants.get(nullability);
-    if (variant != null) {
-      return variant;
-    }
-    return variants.getOrCreateElement(nullability, this::createVariant);
+    return nullability.equals(nullability())
+        ? this
+        : variants.getOrCreateElement(nullability, this::createVariant);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
index 9448ebb..1b1a557 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
@@ -65,6 +65,15 @@
   }
 
   @Override
+  public String toString() {
+    return "DynamicTypeWithLowerBound(upperBound="
+        + getDynamicUpperBoundType()
+        + ", lowerBound="
+        + getDynamicLowerBoundType()
+        + ")";
+  }
+
+  @Override
   public DynamicTypeWithLowerBound withNullability(Nullability nullability) {
     if (getDynamicUpperBoundType().nullability() == nullability) {
       return this;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
index 754fb43..5784e9e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
@@ -266,6 +266,11 @@
     return dynamicUpperBoundType.hashCode();
   }
 
+  @Override
+  public String toString() {
+    return "DynamicTypeWithUpperBound(upperBound=" + getDynamicUpperBoundType() + ")";
+  }
+
   private static boolean verifyNotEffectivelyFinalClassType(
       AppView<AppInfoWithLiveness> appView, TypeElement type) {
     if (type.isClassType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
index 8d2db28..0cd29f2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
@@ -73,4 +73,9 @@
   public int hashCode() {
     return getExactClassType().hashCode();
   }
+
+  @Override
+  public String toString() {
+    return "ExactDynamicType(type=" + getExactClassType() + ")";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java
index 37ed14a..a9830ff 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java
@@ -70,4 +70,9 @@
   public int hashCode() {
     return System.identityHashCode(this);
   }
+
+  @Override
+  public String toString() {
+    return "NotNullDynamicType";
+  }
 }
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 ed144be..4aa2116 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
@@ -16,7 +16,7 @@
     }
 
     @Override
-    public ReferenceTypeElement getOrCreateVariant(Nullability nullability) {
+    public NullElement getOrCreateVariant(Nullability nullability) {
       return nullability.isNullable() ? NULL_INSTANCE : NULL_BOTTOM_INSTANCE;
     }
 
@@ -60,8 +60,8 @@
     }
   }
 
-  private static final ReferenceTypeElement NULL_INSTANCE = NullElement.create();
-  private static final ReferenceTypeElement NULL_BOTTOM_INSTANCE = NullElement.createBottom();
+  private static final NullElement NULL_INSTANCE = NullElement.create();
+  private static final NullElement NULL_BOTTOM_INSTANCE = NullElement.createBottom();
 
   final Nullability nullability;