| // Copyright (c) 2023, 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.dex; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.fail; |
| |
| import com.android.tools.r8.D8TestBuilder; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.graph.DebugLocalInfo; |
| import com.android.tools.r8.graph.DexCode.Try; |
| import com.android.tools.r8.ir.analysis.type.TypeElement; |
| import com.android.tools.r8.ir.code.Add; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.InstructionListIterator; |
| import com.android.tools.r8.ir.code.NumericType; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import java.util.Arrays; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| // Regression test for b/297320921 |
| @RunWith(Parameterized.class) |
| public class TryCatchRangeOverflowTest extends TestBase { |
| |
| private final TestParameters parameters; |
| |
| @Parameterized.Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build(); |
| } |
| |
| public TryCatchRangeOverflowTest(TestParameters parameters) { |
| this.parameters = parameters; |
| } |
| |
| // Each add/2addr instruction has size 1, so we add have as many instruction minus some padding |
| // to make room for the instructions before and after but still in the same block. |
| // Notice that this value may change if the generated code by the compiler changes. It must then |
| // be updated to the precise limit again so that the test for jumbo-string exactly hits the |
| // crossing point. |
| private final int PADDING = 33; |
| private final int UNSPLIT_LIMIT = 0xFFFF - PADDING; |
| private final int SPLIT_2_LIMIT = 0xFFFF * 2 - PADDING; |
| |
| @Test |
| public void testWithinU2() throws Exception { |
| parameters.assumeDexRuntime(); |
| int addCount = UNSPLIT_LIMIT; |
| compile(addCount) |
| .run(parameters.getRuntime(), TestClass.class) |
| .assertSuccessWithOutputLines("" + addCount) |
| .inspect(inspector -> checkTryCatchHandlers(1, inspector)); |
| } |
| |
| @Test |
| public void testJumboExceedsU2() throws Exception { |
| parameters.assumeDexRuntime(); |
| int addCount = UNSPLIT_LIMIT; |
| compile(addCount) |
| .addOptionsModification(o -> o.testing.forceJumboStringProcessing = true) |
| .run(parameters.getRuntime(), TestClass.class) |
| .assertSuccessWithOutputLines("" + addCount) |
| .inspect(inspector -> checkTryCatchHandlers(2, inspector)); |
| } |
| |
| @Test |
| public void testExceedsU2() throws Exception { |
| parameters.assumeDexRuntime(); |
| // Test with a few values above the limit. |
| for (int addCount : Arrays.asList(UNSPLIT_LIMIT + 1, UNSPLIT_LIMIT + 2, UNSPLIT_LIMIT + 100)) { |
| compile(addCount) |
| .run(parameters.getRuntime(), TestClass.class) |
| .assertSuccessWithOutputLines("" + addCount) |
| .inspect(inspector -> checkTryCatchHandlers(2, inspector)); |
| } |
| } |
| |
| @Test |
| public void testWithinU2x2() throws Exception { |
| parameters.assumeDexRuntime(); |
| int addCount = SPLIT_2_LIMIT; |
| compile(addCount) |
| .run(parameters.getRuntime(), TestClass.class) |
| .assertSuccessWithOutputLines("" + addCount) |
| .inspect(inspector -> checkTryCatchHandlers(2, inspector)); |
| } |
| |
| @Test |
| public void testJumboExceedsU2x2() throws Exception { |
| parameters.assumeDexRuntime(); |
| int addCount = SPLIT_2_LIMIT; |
| compile(addCount) |
| .addOptionsModification(o -> o.testing.forceJumboStringProcessing = true) |
| .run(parameters.getRuntime(), TestClass.class) |
| .assertSuccessWithOutputLines("" + addCount) |
| .inspect(inspector -> checkTryCatchHandlers(3, inspector)); |
| } |
| |
| @Test |
| public void testExceedsU2x2() throws Exception { |
| parameters.assumeDexRuntime(); |
| int addCount = SPLIT_2_LIMIT + 1; |
| compile(addCount) |
| .run(parameters.getRuntime(), TestClass.class) |
| .assertSuccessWithOutputLines("" + addCount) |
| .inspect(inspector -> checkTryCatchHandlers(3, inspector)); |
| } |
| |
| private D8TestBuilder compile(int addCount) throws Exception { |
| return testForD8(Backend.DEX) |
| .addProgramClasses(TestClass.class) |
| .addOptionsModification( |
| o -> |
| o.testing.irModifier = |
| (code, appView) -> amendCodeWithAddInstructions(addCount, code)) |
| .setMinApi(parameters); |
| } |
| |
| private static void amendCodeWithAddInstructions(int addCount, IRCode code) { |
| if (!code.context().getReference().qualifiedName().endsWith("main")) { |
| return; |
| } |
| InstructionListIterator it = code.instructionListIterator(); |
| while (it.hasNext()) { |
| Instruction instruction = it.next(); |
| if (instruction.isAdd()) { |
| TypeElement outType = instruction.getOutType(); |
| DebugLocalInfo localInfo = instruction.getLocalInfo(); |
| // Create the last value which will replace the users of the original value in the |
| // continuations. |
| Value newLastValue = code.createValue(outType, localInfo); |
| instruction.outValue().replaceUsers(newLastValue); |
| |
| Add add = instruction.asAdd(); |
| NumericType numericType = add.getNumericType(); |
| assert add.rightValue().isConstNumber(); |
| for (int i = 1; i < addCount; i++) { |
| Value dest = i == addCount - 1 ? newLastValue : code.createValue(outType, localInfo); |
| Add newAdd = new Add(numericType, dest, add.outValue(), add.rightValue()); |
| add.outValue().addDebugLocalEnd(newAdd); |
| newAdd.setPosition(add.getPosition()); |
| it.add(newAdd); |
| add = newAdd; |
| } |
| return; |
| } |
| } |
| fail("Expected to find an Add instruction."); |
| } |
| |
| private static void checkTryCatchHandlers(int tryCount, CodeInspector inspector) |
| throws NoSuchMethodException { |
| |
| MethodSubject main = inspector.method(TestClass.class.getMethod("main", String[].class)); |
| Try[] tries = main.getMethod().getCode().asDexCode().tries; |
| assertEquals(Arrays.toString(tries), tryCount, tries.length); |
| } |
| |
| static class TestClass { |
| |
| public static void main(String[] args) { |
| int i = 0; |
| try { |
| String str; |
| int len = args.length; |
| if (len == 0) { |
| str = ""; |
| } else if (len == 1 /* Using a constant 1 here causes the add to be an add/2addr */) { |
| str = "Strings might become jumbos"; |
| } else if (len % 2 == 0) { |
| str = "We need 4"; |
| } else { |
| str = "to ensure overflow."; |
| } |
| i = str.length(); |
| ++i; // repeated count number of times. |
| i += args[0].length(); |
| } catch (Throwable e) { |
| System.out.println(i); |
| return; |
| } |
| System.out.println("unexpected i " + i); |
| } |
| } |
| } |