| /* |
| * 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 |
| } |
| } |