jdk-24/test/hotspot/jtreg/compiler/runtime/TestConstantDynamic.java
Vladimir Ivanov 88fc3bfdff 8280473: CI: Support unresolved JVM_CONSTANT_Dynamic constant pool entries
Reviewed-by: dlong, redestad, neliasso
2022-02-14 18:46:46 +00:00

255 lines
10 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 8280473
* @library /test/lib
* @modules java.base/jdk.internal.org.objectweb.asm
*
* @run main/othervm -XX:+TieredCompilation -XX:TieredStopAtLevel=1
* -Xbatch -XX:CompileThreshold=100 -XX:CompileCommand=compileonly,*::test
* -XX:CompileCommand=quiet -XX:+PrintCompilation
* compiler.runtime.TestConstantDynamic
* @run main/othervm -XX:-TieredCompilation
* -Xbatch -XX:CompileThreshold=100 -XX:CompileCommand=compileonly,*::test
* -XX:CompileCommand=quiet -XX:+PrintCompilation
* compiler.runtime.TestConstantDynamic
*/
package compiler.runtime;
import jdk.internal.org.objectweb.asm.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.constant.ConstantDescs;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleProxies;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import static jdk.internal.org.objectweb.asm.ClassWriter.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
public class TestConstantDynamic {
static final Class<TestConstantDynamic> THIS_CLASS = TestConstantDynamic.class;
static final String THIS_CLASS_NAME = THIS_CLASS.getName().replace('.', '/');
static final String CLASS_NAME = THIS_CLASS_NAME + "$Test";
public interface Test {
Object run(boolean b);
}
public static final String PATH = System.getProperty("test.classes", ".") + java.io.File.separator;
private static int ID = 0;
/* =================================================================================================== */
static final String BSM_DESC = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;";
static final Handle BSM = new Handle(H_INVOKESTATIC, THIS_CLASS_NAME, "bsm", BSM_DESC, false);
static Object bsm(MethodHandles.Lookup lookup, String name, Class c) throws IllegalAccessException {
Object[] classData = MethodHandles.classData(lookup, ConstantDescs.DEFAULT_NAME, Object[].class);
Object value = classData[0];
System.out.printf("BSM: lookup=%s name=\"%s\" class=%s => \"%s\"\n", lookup, name, c, classData[0]);
return value;
}
static final Handle THROWING_BSM = new Handle(H_INVOKESTATIC, THIS_CLASS_NAME, "throwingBSM", BSM_DESC, false);
static Object throwingBSM(MethodHandles.Lookup lookup, String name, Class c) throws IllegalAccessException {
Object[] classData = (Object[])MethodHandles.classData(lookup, ConstantDescs.DEFAULT_NAME, Object[].class);
Object value = classData[0];
System.out.printf("BSM: lookup=%s name=\"%s\" class=%s value=\"%s\" => Exception\n", lookup, name, c, value);
throw new IllegalArgumentException(lookup.lookupClass().getName() + ": " + c.getName() + " " + name + " " + value);
}
/* =================================================================================================== */
static byte[] generateClassFile(String suffix, String desc, int retOpcode, Handle bsm) throws IOException {
var cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
String name = CLASS_NAME + "_" + suffix + "_" + (++ID);
cw.visit(V19, ACC_PUBLIC | ACC_SUPER, name, null, "java/lang/Object", null);
Handle localBSM = new Handle(H_INVOKESTATIC, name, "bsm", BSM_DESC, false);
{
var mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "bsm", BSM_DESC, null, null);
mv.visitLdcInsn(bsm);
mv.visitIntInsn(ALOAD, 0);
mv.visitIntInsn(ALOAD, 1);
mv.visitIntInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", BSM_DESC, false);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
}
{
var mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "(Z)" + desc, null, null);
mv.visitCode();
Label endL = new Label();
Label falseL = new Label();
mv.visitIntInsn(ILOAD, 0);
mv.visitJumpInsn(Opcodes.IFNE, falseL);
mv.visitLdcInsn(new ConstantDynamic("first", desc, localBSM)); // is resolved on b = false
mv.visitJumpInsn(GOTO, endL);
mv.visitLabel(falseL);
mv.visitLdcInsn(new ConstantDynamic("second", desc, localBSM)); // is resolved on b = true
mv.visitLabel(endL);
mv.visitInsn(retOpcode);
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 desc, int retOpcode, Object value, Handle bsm, boolean shouldThrow) {
try {
byte[] classFile = generateClassFile("CD", desc, retOpcode, bsm);
Object[] classData = new Object[] { value };
MethodHandles.Lookup testLookup = MethodHandles.lookup().defineHiddenClassWithClassData(classFile, classData, true);
Method testMethod = testLookup.lookupClass().getDeclaredMethod("test", boolean.class);
MethodHandle testMH = testLookup.unreflect(testMethod);
if (shouldThrow) {
// Install empty handler for linkage errors, but throw an error on successful invocation.
// try { Test.test(b); throw AssertionError(); } catch (LinkageError e) { /* expected */ }
testMH = MethodHandles.filterReturnValue(testMH,
MethodHandles.dropArguments(
MethodHandles.insertArguments(
MethodHandles.throwException(testMH.type().returnType(), AssertionError.class),
0, new AssertionError("no exception thrown")),
0, testMH.type().returnType()));
testMH = MethodHandles.catchException(testMH, LinkageError.class,
MethodHandles.empty(MethodType.methodType(testMH.type().returnType(), LinkageError.class)));
} else {
Class<?> type = testMH.type().returnType();
testMH = MethodHandles.filterReturnValue(testMH,
MethodHandles.insertArguments(VALIDATE_MH, 0, value)
.asType(MethodType.methodType(type, type)));
}
return MethodHandleProxies.asInterfaceInstance(Test.class, testMH);
} catch (Throwable e) {
throw new InternalError(e);
}
}
static final MethodHandle VALIDATE_MH;
static {
try {
VALIDATE_MH = MethodHandles.lookup().findStatic(THIS_CLASS, "validateResult",
MethodType.methodType(Object.class, Object.class, Object.class));
} catch (ReflectiveOperationException e) {
throw new InternalError(e);
}
}
static Object validateResult(Object expected, Object actual) {
if ((expected == null && actual != null) ||
(expected != null && !expected.equals(actual))) {
throw new AssertionError(String.format("expected=%s != actual=%s", expected.toString(), actual.toString()));
}
return actual;
}
private Handle bsm;
private boolean shouldThrow;
TestConstantDynamic(Handle bsm, boolean shouldThrow) {
this.bsm = bsm;
this.shouldThrow = shouldThrow;
}
static TestConstantDynamic shouldNotThrow() {
return new TestConstantDynamic(BSM, false);
}
static TestConstantDynamic shouldThrow() {
return new TestConstantDynamic(THROWING_BSM, true);
}
static void shouldThrow(Handle bsm, String desc, int retOpcode, Object value) {
(new TestConstantDynamic(bsm, true)).test(desc, retOpcode, value);
}
void test(String desc, int retOpcode, Object value) {
Test test = generate(desc, retOpcode, value, bsm, shouldThrow);
for (int i = 0; i < 200; i++) {
test.run(false);
}
for (int i = 0; i < 200; i++) {
test.run(true);
}
}
static void run(TestConstantDynamic t) {
t.test("Z", IRETURN, Boolean.TRUE);
t.test("B", IRETURN, Byte.MAX_VALUE);
t.test("S", IRETURN, Short.MAX_VALUE);
t.test("C", IRETURN, Character.MAX_VALUE);
t.test("I", IRETURN, Integer.MAX_VALUE);
t.test("J", LRETURN, Long.MAX_VALUE);
t.test("F", FRETURN, Float.MAX_VALUE);
t.test("D", DRETURN, Double.MAX_VALUE);
t.test("Ljava/lang/Object;", ARETURN, new Object());
t.test("Ljava/lang/Object;", ARETURN, null);
t.test("[Ljava/lang/Object;", ARETURN, new Object[0]);
t.test("[Ljava/lang/Object;", ARETURN, null);
t.test("[I", ARETURN, new int[0]);
t.test("[I", ARETURN, null);
t.test("Ljava/lang/Runnable;", ARETURN, (Runnable)(() -> {}));
t.test("Ljava/lang/Runnable;", ARETURN, null);
}
public static void main(String[] args) {
run(shouldNotThrow());
run(shouldThrow()); // use error-throwing BSM
shouldThrow(BSM, "Ljava/lang/Runnable;", ARETURN, new Object()); // not a Runnable
}
}