88fc3bfdff
Reviewed-by: dlong, redestad, neliasso
255 lines
10 KiB
Java
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
|
|
}
|
|
}
|