Disable removal of append with substring when unused
Bug: b/380182105
Change-Id: I039bf72226358b4b7ceb3157d0e1339d47148625
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
index 5cdcc6b..89469c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
@@ -385,13 +385,16 @@
boolean canRemoveIfLastAndNoLoop =
!isLoopingOnPath(root, currentNode, munchingState)
&& currentNode.getSuccessors().isEmpty();
+ Instruction instruction = currentNode.asAppendNode().getInstruction();
boolean hasKnownArgumentOrCannotBeObserved =
appendNode.hasConstantOrNonConstantArgument()
- || !munchingState.oracle.canObserveStringBuilderCall(
- currentNode.asAppendNode().getInstruction());
+ || !munchingState.oracle.canObserveStringBuilderCall(instruction);
+ // R8 would need to check for range overflow if removing append with sub arrays.
+ boolean canRemoveNonSub = !munchingState.oracle.isAppendWithSubArray(instruction);
if (canRemoveIfNoInspectionOrMaterializing
&& canRemoveIfLastAndNoLoop
- && hasKnownArgumentOrCannotBeObserved) {
+ && hasKnownArgumentOrCannotBeObserved
+ && canRemoveNonSub) {
removeNode = true;
}
} else if (currentNode.isInitNode()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
index 2c43b60..d9428ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
import java.util.List;
@@ -39,6 +38,8 @@
boolean isAppend(Instruction instruction);
+ boolean isAppendWithSubArray(Instruction instruction);
+
boolean canObserveStringBuilderCall(Instruction instruction);
boolean isInit(Instruction instruction);
@@ -182,7 +183,11 @@
|| factory.stringBufferMethods.isAppendMethod(invokedMethod);
}
- public boolean isAppendWithSubArray(InvokeMethodWithReceiver instruction) {
+ @Override
+ public boolean isAppendWithSubArray(Instruction instruction) {
+ if (!instruction.isInvokeMethodWithReceiver()) {
+ return false;
+ }
DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
return factory.stringBuilderMethods.isAppendSubArrayMethod(invokedMethod)
|| factory.stringBufferMethods.isAppendSubArrayMethod(invokedMethod);
diff --git a/src/test/examplesJava17/string/StringBuilderWithAppendOutOfBoundsTest.java b/src/test/examplesJava17/string/StringBuilderWithAppendOutOfBoundsTest.java
new file mode 100644
index 0000000..cb3e379
--- /dev/null
+++ b/src/test/examplesJava17/string/StringBuilderWithAppendOutOfBoundsTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2024, 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 string;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+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 StringBuilderWithAppendOutOfBoundsTest extends TestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("class java.lang.IndexOutOfBoundsException");
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addKeepMainRule(Main.class)
+ .addLibraryFiles(ToolHelper.getAndroidJar(35))
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ static class Main {
+
+ public static void main(String[] strArr) {
+ String text = "sss";
+ int l = 7;
+
+ StringBuilder sb = new StringBuilder();
+ try {
+ sb.append(text, 0, l);
+ System.out.println("not out of bounds");
+ } catch (Exception e) {
+ System.out.println(e.getClass());
+ }
+ }
+ }
+}