Remove unused instance-of instructions

Change-Id: I37eb2012d05c27d1ba2a919a7f872f7300d06546
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 0ea0546..0b914a2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -8,7 +8,9 @@
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.code.DexInstanceOf;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -123,6 +125,36 @@
   }
 
   @Override
+  public boolean instructionInstanceCanThrow(
+      AppView<?> appView,
+      ProgramMethod context,
+      AbstractValueSupplier abstractValueSupplier,
+      SideEffectAssumption assumption) {
+    if (appView.options().getTestingOptions().allowTypeErrors
+        && value().isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+      // We conservatively return true here since the object may be uninitialized.
+      // However, in this case the original code is invalid, since it does not verify.
+      return true;
+    }
+    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    if (baseType.isIdenticalTo(context.getHolderType()) || baseType.isPrimitiveType()) {
+      return false;
+    }
+    assert baseType.isClassType();
+    if (!appView.enableWholeProgramOptimizations()) {
+      return true;
+    }
+    DexClass definition = appView.definitionFor(baseType);
+    if (definition != null
+        && definition.isResolvable(appView)
+        && AccessControl.isClassAccessible(definition, context, appView.withClassHierarchy())
+            .isTrue()) {
+      return false;
+    }
+    return true;
+  }
+
+  @Override
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
diff --git a/src/test/bootstrap/com/android/tools/r8/bootstrap/JavaR8CompilationTest.java b/src/test/bootstrap/com/android/tools/r8/bootstrap/JavaR8CompilationTest.java
index d1ba47f..0204e8f 100644
--- a/src/test/bootstrap/com/android/tools/r8/bootstrap/JavaR8CompilationTest.java
+++ b/src/test/bootstrap/com/android/tools/r8/bootstrap/JavaR8CompilationTest.java
@@ -60,6 +60,8 @@
         .setMinApi(parameters)
         .addProgramFiles(r8WithRelocatedDeps)
         .addKeepRuleFiles(KEEP_RULE_FILES)
+        .allowUnusedDontWarnPatterns()
+        .allowUnusedProguardConfigurationRules()
         .compile()
         .inspect(this::assertNotEmpty)
         .inspect(JavaR8CompilationTest::assertNoNests);
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
index 3cfa21e..6e12153 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
@@ -52,12 +52,13 @@
         .compileWithExpectedDiagnostics(this::inspect);
   }
 
-  @Test()
+  @Test
   public void testD8Dex() throws Exception {
     parameters.assumeDexRuntime();
     boolean expectFailure = parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0);
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
+        .addOptionsModification(options -> options.getTestingOptions().allowTypeErrors = true)
         .setMinApi(parameters)
         .compileWithExpectedDiagnostics(this::inspect)
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UnusedInstanceOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UnusedInstanceOfTest.java
new file mode 100644
index 0000000..be27a56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UnusedInstanceOfTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2025, 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.ir.optimize.instanceofremoval;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class UnusedInstanceOfTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().withPartialCompilation().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters)
+        .addProgramClasses(Main.class)
+        .release()
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject testMethod =
+                  inspector.clazz(Main.class).uniqueMethodWithOriginalName("test");
+              assertFalse(hasInstanceOf(testMethod, Main.class));
+              assertTrue(hasInstanceOf(testMethod, Missing.class));
+              assertTrue(hasInstanceOf(testMethod, String.class));
+            });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters)
+        .addProgramClasses(Main.class)
+        .addKeepAllClassesRule()
+        .addDontWarn(Missing.class)
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject testMethod =
+                  inspector.clazz(Main.class).uniqueMethodWithOriginalName("test");
+              assertThat(testMethod, isPresent());
+              assertFalse(hasInstanceOf(testMethod, Main.class));
+              assertTrue(hasInstanceOf(testMethod, Missing.class));
+              assertTrue(
+                  !hasInstanceOf(testMethod, String.class)
+                      || parameters.getPartialCompilationTestParameters().isRandom());
+            });
+  }
+
+  private boolean hasInstanceOf(MethodSubject method, Class<?> clazz) {
+    return method.streamInstructions().anyMatch(i -> i.isInstanceOf(clazz.getTypeName()));
+  }
+
+  static class Main {
+
+    static void test(Object o) {
+      boolean unused1 = o instanceof Main;
+      boolean unused2 = o instanceof Missing;
+      boolean unused3 = o instanceof String;
+    }
+  }
+
+  static class Missing {}
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java
index ca48bf6..ae4cdc9 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -249,7 +249,8 @@
                 assert parameters.isRandom();
                 builder.randomizeForTesting();
               }
-            });
+            })
+        .applyIf(parameters.isRandom(), R8PartialTestBuilder::allowUnusedDontWarnPatterns);
   }
 
   public R8PartialTestBuilder testForR8Partial(Backend backend) {