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) {