Make isInterface a conservative method on app-view.

This CL also ensures that we never put null in interfaces.

Bug: 130296309

Change-Id: If5b73f85f87ab1143e8f52c344a072c349fe95b3
diff --git a/src/main/java/com/android/tools/r8/OptionalBool.java b/src/main/java/com/android/tools/r8/OptionalBool.java
index db49872..92f5d07 100644
--- a/src/main/java/com/android/tools/r8/OptionalBool.java
+++ b/src/main/java/com/android/tools/r8/OptionalBool.java
@@ -12,6 +12,11 @@
         public boolean isTrue() {
           return true;
         }
+
+        @Override
+        public String toString() {
+          return "true";
+        }
       };
 
   private static final OptionalBool FALSE =
@@ -20,6 +25,11 @@
         public boolean isFalse() {
           return true;
         }
+
+        @Override
+        public String toString() {
+          return "false";
+        }
       };
 
   private static final OptionalBool UNKNOWN =
@@ -28,6 +38,11 @@
         public boolean isUnknown() {
           return true;
         }
+
+        @Override
+        public String toString() {
+          return "unknown";
+        }
       };
 
   public static OptionalBool of(boolean bool) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 35df2ef..0c8196d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -466,7 +466,7 @@
     return getTypeInfo(type).isUnknown();
   }
 
-  public boolean isInterface(DexType type) {
+  public boolean isMarkedAsInterface(DexType type) {
     return getTypeInfo(type).isInterface();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 5798d1a..95e8ab3 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -87,6 +87,21 @@
     return appInfo().definitionFor(type);
   }
 
+  public OptionalBool isInterface(DexType type) {
+    // Without whole program information we should not assume anything about any other class than
+    // the current holder in a given context.
+    if (enableWholeProgramOptimizations()) {
+      assert appInfo().hasSubtyping();
+      if (appInfo().hasSubtyping()) {
+        AppInfoWithSubtyping appInfo = appInfo().withSubtyping();
+        return appInfo.isUnknown(type)
+            ? OptionalBool.unknown()
+            : OptionalBool.of(appInfo.isMarkedAsInterface(type));
+      }
+    }
+    return OptionalBool.unknown();
+  }
+
   @Override
   public DexItemFactory dexItemFactory() {
     return dexItemFactory;
@@ -132,6 +147,13 @@
     this.verticallyMergedClasses = verticallyMergedClasses;
   }
 
+  @SuppressWarnings("unchecked")
+  public AppView<AppInfoWithSubtyping> withSubtyping() {
+    return appInfo.hasSubtyping()
+        ? (AppView<AppInfoWithSubtyping>) this
+        : null;
+  }
+
   public AppView<AppInfoWithLiveness> withLiveness() {
     @SuppressWarnings("unchecked")
     AppView<AppInfoWithLiveness> appViewWithLiveness = (AppView<AppInfoWithLiveness>) this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 2e85ad1..7c3a73f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -36,6 +36,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -1214,23 +1215,21 @@
             type,
             t -> {
               if (type.isClassType()) {
-                if (!appView.appInfo().hasSubtyping()) {
-                  return ClassTypeLatticeElement.create(type, nullability, (Set<DexType>) null);
+                if (!appView.enableWholeProgramOptimizations()) {
+                  // Don't reason at the level of interfaces in D8.
+                  return ClassTypeLatticeElement.create(type, nullability, Collections.emptySet());
                 }
-                AppView<? extends AppInfoWithSubtyping> appViewWithSubtyping =
-                    appView.withLiveness();
-                // TODO(zerny): It should never be the case that we have unknown at this point!
-                if (!appViewWithSubtyping.appInfo().isUnknown(type)
-                    && appViewWithSubtyping.appInfo().isInterface(type)) {
+                assert appView.appInfo().hasSubtyping();
+                if (appView.isInterface(type).isTrue()) {
                   return ClassTypeLatticeElement.create(
-                      objectType, nullability, ImmutableSet.of(type));
+                      objectType, nullability, Collections.singleton(type));
                 }
                 // In theory, `interfaces` is the least upper bound of implemented interfaces.
                 // It is expensive to walk through type hierarchy; collect implemented interfaces;
                 // and compute the least upper bound of two interface sets. Hence, lazy
                 // computations. Most likely during lattice join. See {@link
                 // ClassTypeLatticeElement#getInterfaces}.
-                return ClassTypeLatticeElement.create(type, nullability, appViewWithSubtyping);
+                return ClassTypeLatticeElement.create(type, nullability, appView.withSubtyping());
               }
               assert type.isArrayType();
               return ArrayTypeLatticeElement.create(finalMemberType, nullability);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 298251d..84b79c2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayDeque;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -28,6 +29,7 @@
 
   public static ClassTypeLatticeElement create(
       DexType classType, Nullability nullability, Set<DexType> interfaces) {
+    assert interfaces != null;
     return NullabilityVariants.create(
         nullability,
         (variants) ->
@@ -36,6 +38,7 @@
 
   public static ClassTypeLatticeElement create(
       DexType classType, Nullability nullability, AppView<? extends AppInfoWithSubtyping> appView) {
+    assert appView != null;
     return NullabilityVariants.create(
         nullability,
         (variants) -> new ClassTypeLatticeElement(classType, nullability, null, variants, appView));
@@ -133,14 +136,14 @@
   ClassTypeLatticeElement join(ClassTypeLatticeElement other, AppView<?> appView) {
     Nullability nullability = nullability().join(other.nullability());
     if (!appView.appInfo().hasSubtyping()) {
-      assert getInterfaces() == null;
-      assert other.getInterfaces() == null;
+      assert getInterfaces().isEmpty();
+      assert other.getInterfaces().isEmpty();
       return ClassTypeLatticeElement.create(
           getClassType() == other.getClassType()
               ? getClassType()
               : appView.dexItemFactory().objectType,
           nullability,
-          (Set<DexType>) null);
+          Collections.emptySet());
     }
     DexType lubType =
         appView
@@ -176,6 +179,9 @@
 
   static Set<DexType> computeLeastUpperBoundOfInterfaces(
       AppView<? extends AppInfoWithSubtyping> appView, Set<DexType> s1, Set<DexType> s2) {
+    if (s1.isEmpty() || s2.isEmpty()) {
+      return Collections.emptySet();
+    }
     Set<DexType> cached = appView.dexItemFactory().leastUpperBoundOfInterfacesTable.get(s1, s2);
     if (cached != null) {
       return cached;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 13d9035..5f340d7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -598,7 +598,7 @@
   }
 
   private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
-    return target.isVirtualMethod() && !appView.appInfo().isInterface(target.method.holder);
+    return target.isVirtualMethod() && appView.isInterface(target.method.holder).isFalse();
   }
 
   private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 54ab3eb..d26a3d9 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -208,7 +208,6 @@
 
   private void reserveNamesInClasses(
       DexType type, DexType libraryFrontier, NamingState<DexProto, ?> parent) {
-    assert !appView.appInfo().isInterface(type);
     NamingState<DexProto, ?> state =
         frontierState.allocateNamingStateAndReserve(type, libraryFrontier, parent);
 
@@ -216,7 +215,6 @@
     // frontier forward.
     DexClass holder = appView.definitionFor(type);
     for (DexType subtype : appView.appInfo().allExtendsSubtypes(type)) {
-      assert !appView.appInfo().isInterface(subtype);
       reserveNamesInClasses(
           subtype, holder == null || holder.isNotProgramClass() ? subtype : libraryFrontier, state);
     }
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 98cd095..71c5bcd 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1428,7 +1428,7 @@
   }
 
   private void fillWorkList(Deque<DexType> worklist, DexType type) {
-    if (appInfo.isInterface(type)) {
+    if (appInfo.isMarkedAsInterface(type)) {
       // We need to check if the method is shadowed by a class that directly implements
       // the interface and go recursively down to the sub interfaces to reach class
       // implementing the interface
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index c027f4a..d4cec12 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -66,7 +66,8 @@
 
   private void testInterfaceLookup(DexProgramClass clazz, DexEncodedMethod method) {
     Set<DexEncodedMethod> targets = appInfo.lookupInterfaceTargets(method.method);
-    if (appInfo.subtypes(method.method.holder).stream().allMatch(appInfo::isInterface)) {
+    if (appInfo.subtypes(method.method.holder).stream()
+        .allMatch(t -> appInfo.definitionFor(t).isInterface())) {
       assertTrue(targets.isEmpty());
     } else {
       assertFalse(targets.isEmpty());
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index 5ab424a..d604130 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -120,7 +121,7 @@
         new ApplicationReader(app, TEST_OPTIONS, new Timing("TypeAnalysisTest.appReader"))
             .read().toDirect();
     inspection.accept(
-        AppView.createForR8(new AppInfo(dexApplication), TEST_OPTIONS),
+        AppView.createForR8(new AppInfoWithSubtyping(dexApplication), TEST_OPTIONS),
         new CodeInspector(dexApplication));
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index cf21b50..d63a3d2 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -508,6 +508,7 @@
   @Test
   public void testSelfOrderWithoutSubtypingInfo() {
     DexType type = factory.createType("Lmy/Type;");
+    appView.withSubtyping().appInfo().registerNewType(type, factory.objectType);
     TypeLatticeElement nonNullType = fromDexType(type, Nullability.definitelyNotNull(), appView);
     ReferenceTypeLatticeElement nullableType =
         nonNullType.asReferenceTypeLatticeElement().getOrCreateVariant(Nullability.maybeNull());