blob: c18c21095e7af482fca61d89edfba894c5863695 [file] [log] [blame]
// 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.cf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
public class NormalAndExceptionFlowTest extends TestBase {
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public NormalAndExceptionFlowTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void testJvm() throws Exception {
// This test documents that the Java C1 compiler will bail out when it finds an exception
// handler that is also targeted by a normal control-flow jump. See b/296916426.
parameters.assumeJvmTestParameters();
testForJvm(parameters)
.addVmArguments("-XX:+PrintCompilation", "-Xcomp")
.addProgramClassFileData(getTransformedTestClass())
.run(parameters.getRuntime(), TestClass.class, "foo")
.assertFailureWithErrorThatThrows(ArrayIndexOutOfBoundsException.class)
.apply(
r ->
assertThat(
r.getStdOut(),
containsString(
"compilation bailout: "
+ "Exception handler can be reached by both "
+ "normal and exceptional control flow")));
}
private static byte[] getTransformedTestClass() throws IOException {
return transformer(TestClass.class)
.transformMethodInsnInMethod(
"main",
(opcode, owner, name, descriptor, isInterface, visitor) -> {
Label handler1 = new Label();
Label handler2 = new Label();
Label exit = new Label();
// Read an array index and pop result within a handler range.
visitor.visitVarInsn(Opcodes.ALOAD, 0);
visitor.visitLdcInsn(0);
Label start1 = new Label();
visitor.visitLabel(start1);
visitor.visitInsn(Opcodes.AALOAD);
Label end1 = new Label();
visitor.visitLabel(end1);
visitor.visitInsn(Opcodes.POP);
// Read an array index and pop result within another handler range.
visitor.visitVarInsn(Opcodes.ALOAD, 0);
visitor.visitLdcInsn(1);
Label start2 = new Label();
visitor.visitLabel(start2);
visitor.visitInsn(Opcodes.AALOAD);
Label end2 = new Label();
visitor.visitLabel(end2);
visitor.visitInsn(Opcodes.POP);
// Normal exit.
visitor.visitJumpInsn(Opcodes.GOTO, exit);
// First handler just jumps to the next handler leaving the exception on the stack.
visitor.visitLabel(handler1);
visitor.visitFrame(
Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"java/lang/Throwable"});
visitor.visitJumpInsn(Opcodes.GOTO, handler2);
// Second handler is either hit directly via exception table or jumped to above.
visitor.visitLabel(handler2);
visitor.visitFrame(
Opcodes.F_FULL, 0, new Object[] {}, 1, new Object[] {"java/lang/Throwable"});
visitor.visitInsn(Opcodes.ATHROW);
// Exit epilog - the method template inserts the "return" so it is not added here.
visitor.visitLabel(exit);
visitor.visitFrame(Opcodes.F_FULL, 0, new Object[] {}, 0, new Object[] {});
visitor.visitTryCatchBlock(start1, end1, handler1, null);
visitor.visitTryCatchBlock(start2, end2, handler2, null);
})
.setMaxs(MethodPredicate.onName("main"), 100, 100)
.transform();
}
static class TestClass {
public static void placeholder() {}
public static void main(String[] args) {
placeholder(); // replaced by transformation.
}
}
}