jdk-24/test/hotspot/jtreg/compiler/runtime/TestConstantsInError.java
2024-01-03 08:53:01 +00:00

286 lines
12 KiB
Java

/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8279822
* @requires vm.flagless
* @library /test/lib
* @modules java.base/jdk.internal.org.objectweb.asm
*
* @run main compiler.runtime.TestConstantsInError
*/
package compiler.runtime;
import jdk.internal.org.objectweb.asm.*;
import jdk.test.lib.Platform;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleProxies;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.List;
import static jdk.internal.org.objectweb.asm.ClassWriter.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
interface OutputProcessor {
default void process(OutputAnalyzer output, boolean isC1) {}
}
public abstract class TestConstantsInError implements OutputProcessor {
static final String TEST_PREFIX = class2desc(TestConstantsInError.class) + "$Test";
public interface Test extends Runnable {}
interface Generator {
void generate(MethodVisitor mv);
}
static String class2desc(Class<?> cls) {
return cls.getName().replace('.', '/');
}
public static final String PATH = System.getProperty("test.classes", ".") + java.io.File.separator;
static byte[] generateClassFile(String suffix, Generator g) throws IOException {
var cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
String name = TEST_PREFIX + "_" + suffix;
cw.visit(V19, ACC_PUBLIC | ACC_SUPER, name, null, "java/lang/Object", null);
{
var mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "()V", null, null);
mv.visitCode();
g.generate(mv);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
}
byte[] classFile = cw.toByteArray();
try (FileOutputStream fos = new FileOutputStream(PATH + name + ".class")) {
fos.write(classFile);
}
return classFile;
}
static Test generate(String suffix, Class<? extends LinkageError> expectedError, Generator g) {
try {
byte[] classFile = generateClassFile(suffix, g);
MethodHandles.Lookup testLookup = MethodHandles.lookup().defineHiddenClass(classFile, true);
MethodHandle testMH = testLookup.findStatic(testLookup.lookupClass(), "test", MethodType.methodType(void.class));
testMH = MethodHandles.filterReturnValue(testMH,
MethodHandles.insertArguments(
MethodHandles.throwException(void.class, AssertionError.class),
0, new AssertionError("no exception thrown")));
// Install empty handler for linkage exceptions.
testMH = MethodHandles.catchException(testMH, expectedError,
MethodHandles.empty(MethodType.methodType(void.class, expectedError)));
return MethodHandleProxies.asInterfaceInstance(Test.class, testMH);
} catch (Throwable e) {
throw new InternalError(e);
}
}
static void run(String name, Class<? extends LinkageError> expectedError, Generator g) {
Test test = generate(name, expectedError, g);
for (int i = 0; i < 1000; i++) {
test.run();
}
}
static class TestConstantClass extends TestConstantsInError {
public static void main(String[] args) {
run("C1", NoClassDefFoundError.class, mv -> mv.visitLdcInsn(Type.getType("LUnknownClass;"))); // non-existent class
run("C2", IllegalAccessError.class, mv -> mv.visitLdcInsn(Type.getType("Ljava/lang/invoke/LambdaForm;"))); // inaccessible
// class loader constraints?
}
public void process(OutputAnalyzer results, boolean isC1) {
results.shouldMatch("Test_C1/.*::test \\(3 bytes\\)$")
.shouldMatch("Test_C2/.*::test \\(3 bytes\\)$");
if (isC1 && (Platform.isAArch64() || Platform.isRISCV64())) { // no code patching
results.shouldMatch("Test_C1/.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_C2/.*::test \\(3 bytes\\) made not entrant");
} else {
results.shouldNotContain("made not entrant");
}
}
public void processC2(OutputAnalyzer results) {
results.shouldNotContain("made not entrant");
}
}
static class TestConstantMethodHandle extends TestConstantsInError {
public static void main(String[] args) {
// Non-existent holder class
run("MH1", NoClassDefFoundError.class,
mv -> mv.visitLdcInsn(new Handle(H_INVOKESTATIC, "UnknownClass", "ignored", "()V", false)));
// Inaccessible holder class
run("MH2", IllegalAccessError.class,
mv -> mv.visitLdcInsn(new Handle(H_INVOKESTATIC, "java/lang/invoke/LambdaForm", "ignored", "()V", false)));
// Method vs InterfaceMethod mismatch
run("MH3", IncompatibleClassChangeError.class,
mv -> mv.visitLdcInsn(new Handle(H_INVOKESTATIC, "java/lang/Object", "ignored", "()V", true)));
// Non-existent method
run("MH4", NoSuchMethodError.class,
mv -> mv.visitLdcInsn(new Handle(H_INVOKESTATIC, "java/lang/Object", "cast", "()V", false)));
}
public void process(OutputAnalyzer results, boolean isC1) {
results.shouldMatch("Test_MH1/.*::test \\(3 bytes\\)$")
.shouldMatch("Test_MH2/.*::test \\(3 bytes\\)$")
.shouldMatch("Test_MH3/.*::test \\(3 bytes\\)$")
.shouldMatch("Test_MH4/.*::test \\(3 bytes\\)$");
if (isC1 && (Platform.isAArch64() || Platform.isRISCV64())) { // no code patching
results.shouldMatch("Test_MH1/.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_MH2/.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_MH3/.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_MH4/.*::test \\(3 bytes\\) made not entrant");
} else {
results.shouldNotContain("made not entrant");
}
}
}
static class TestConstantMethodType extends TestConstantsInError {
public static void main(String[] args) {
run("MT1", NoClassDefFoundError.class,
mv -> mv.visitLdcInsn(Type.getMethodType("(LUnknownClass;)V")));
run("MT2", NoClassDefFoundError.class,
mv -> mv.visitLdcInsn(Type.getMethodType("()LUnknownClass;")));
}
public void process(OutputAnalyzer results, boolean isC1) {
results.shouldMatch("Test_MT1/.*::test \\(3 bytes\\)$")
.shouldMatch("Test_MT2/.*::test \\(3 bytes\\)$");
if (isC1 && (Platform.isAArch64() || Platform.isRISCV64())) { // no code patching
results.shouldMatch("Test_MT1/.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_MT2/.*::test \\(3 bytes\\) made not entrant");
} else {
results.shouldNotContain("made not entrant");
}
}
}
static class TestConstantDynamic extends TestConstantsInError {
static int bsm1() throws Exception {
throw new AssertionError("should not be invoked");
}
static int bsm2(MethodHandles.Lookup lookup, String name, Class c) throws Exception {
throw new Exception("expected");
}
static final Handle BSM1 = new Handle(H_INVOKESTATIC, class2desc(TestConstantDynamic.class), "bsm1", "()I", false);
static final Handle BSM2 = new Handle(H_INVOKESTATIC, class2desc(TestConstantDynamic.class), "bsm2",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)I",
false);
public static void main(String[] args) {
run("CD1", NoClassDefFoundError.class,
mv -> {
Handle bsm = new Handle(H_INVOKESTATIC, "UnknownClass", "unknown", "()LUnknownClass;", false);
mv.visitLdcInsn(new ConstantDynamic("tmp", "LUnknownClass;", bsm));
});
run("CD2", NoSuchMethodError.class,
mv -> {
Handle bsm = new Handle(H_INVOKESTATIC, class2desc(TestConstantDynamic.class), "unknown", "()I", false);
mv.visitLdcInsn(new ConstantDynamic("tmp", "LUnknownClass;", bsm));
});
run("CD3", BootstrapMethodError.class, mv -> mv.visitLdcInsn(new ConstantDynamic("tmp", "I", BSM1)));
run("CD4", BootstrapMethodError.class, mv -> mv.visitLdcInsn(new ConstantDynamic("tmp", "I", BSM2)));
}
public void process(OutputAnalyzer results, boolean isC1) {
results.shouldMatch("Test_CD1.*::test \\(3 bytes\\)$")
.shouldMatch("Test_CD2.*::test \\(3 bytes\\)$")
.shouldMatch("Test_CD3.*::test \\(3 bytes\\)$")
.shouldMatch("Test_CD4.*::test \\(3 bytes\\)$");
if (isC1 && (Platform.isAArch64() || Platform.isRISCV64())) { // no code patching
results.shouldMatch("Test_CD1.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_CD2.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_CD3.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_CD4.*::test \\(3 bytes\\) made not entrant");
} else {
results.shouldNotContain("made not entrant");
}
}
}
static void run(TestConstantsInError test) throws Exception {
List<String> commonArgs = List.of(
"--add-exports", "java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED",
"-Xbatch", "-XX:CompileThreshold=100",
"-XX:CompileCommand=quiet", "-XX:CompileCommand=compileonly,*::test",
"-XX:+PrintCompilation",
"-XX:CompileCommand=print,*::test",
"-Dtest.classes=" + System.getProperty("test.classes", "."),
"-XX:+IgnoreUnrecognizedVMOptions",
test.getClass().getName());
ArrayList<String> c1Args = new ArrayList<>();
c1Args.addAll(List.of("-XX:+TieredCompilation", "-XX:TieredStopAtLevel=1", "-XX:+TracePatching"));
c1Args.addAll(commonArgs);
OutputAnalyzer outputC1 = ProcessTools.executeTestJava(c1Args)
.shouldHaveExitValue(0);
test.process(outputC1, true);
ArrayList<String> c2Args = new ArrayList<>();
c2Args.add("-XX:-TieredCompilation");
c2Args.addAll(commonArgs);
OutputAnalyzer outputC2 = ProcessTools.executeTestJava(c2Args)
.shouldHaveExitValue(0);
test.process(outputC2, false);
}
public static void main(String[] args) throws Exception {
run(new TestConstantClass());
run(new TestConstantMethodType());
run(new TestConstantMethodHandle());
run(new TestConstantDynamic());
}
}