cbe329b90a
Reviewed-by: lkorinth, lmesnik
286 lines
12 KiB
Java
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());
|
|
}
|
|
}
|