| /* | |
| * Copyright (c) 2017, 2024, 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 8186211 | |
| * @summary Test basic invocation of multiple ldc's of the same dynamic constant that fail resolution | |
| * @library /java/lang/invoke/common | |
| * @run testng CondyRepeatFailedResolution | |
| * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:UseBootstrapCallInfo=3 CondyRepeatFailedResolution | |
| */ | |
| import java.lang.classfile.ClassFile; | |
| import org.testng.Assert; | |
| import org.testng.annotations.BeforeClass; | |
| import org.testng.annotations.Test; | |
| import java.lang.constant.*; | |
| import java.lang.invoke.MethodHandles; | |
| import java.lang.invoke.MethodType; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.lang.reflect.Method; | |
| @Test | |
| public class CondyRepeatFailedResolution { | |
| // Counter used to determine if a given BSM is invoked more than once | |
| static int bsm_called = 0; | |
| // Generated class with methods containing condy ldc | |
| Class<?> gc; | |
| // Bootstrap method used to represent primitive values | |
| // that cannot be represented directly in the constant pool, | |
| // such as byte, and for completeness of testing primitive values | |
| // that can be represented directly, such as double or long that | |
| // take two slots | |
| public static Object intConversion(MethodHandles.Lookup l, | |
| String constantName, | |
| Class<?> constantType, | |
| int value) throws Throwable { | |
| ++bsm_called; | |
| // replace constantName with a bogus value to trigger failed resolution | |
| constantName = "Foo"; | |
| switch (constantName) { | |
| case "B": | |
| return (byte) value; | |
| case "C": | |
| return (char) value; | |
| case "D": | |
| return (double) value; | |
| case "F": | |
| return (float) value; | |
| case "I": | |
| return value; | |
| case "J": | |
| return (long) value; | |
| case "S": | |
| return (short) value; | |
| case "Z": | |
| return value > 0; | |
| case "nullRef": | |
| return null; | |
| case "string": | |
| return "string"; | |
| case "stringArray": | |
| return new String[]{"string", "string"}; | |
| default: | |
| throw new BootstrapMethodError("Failure to generate a dynamic constant"); | |
| } | |
| } | |
| @BeforeClass | |
| public void generateClass() throws Exception { | |
| String genClassName = CondyRepeatFailedResolution.class.getSimpleName() + "$Code"; | |
| String bsmClassDesc = CondyRepeatFailedResolution.class.descriptorString(); | |
| String bsmMethodName = "intConversion"; | |
| String bsmDescriptor = MethodType.methodType(Object.class, MethodHandles.Lookup.class, | |
| String.class, Class.class, int.class).toMethodDescriptorString(); | |
| DirectMethodHandleDesc bsmMhDesc = MethodHandleDesc.of( | |
| DirectMethodHandleDesc.Kind.STATIC, | |
| ClassDesc.ofDescriptor(bsmClassDesc), | |
| bsmMethodName, | |
| bsmDescriptor | |
| ); | |
| byte[] byteArray = ClassFile.of().build(ClassDesc.of(genClassName), classBuilder -> classBuilder | |
| .withVersion(55, 0) | |
| .withSuperclass(ConstantDescs.CD_Object) | |
| .withMethod(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, | |
| methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .aload(0) | |
| .invokespecial(ConstantDescs.CD_Object, ConstantDescs.INIT_NAME, | |
| ConstantDescs.MTD_void, false) | |
| .return_() | |
| ) | |
| ) | |
| .withMethod("B", MethodTypeDesc.of(ConstantDescs.CD_byte), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "B", | |
| ConstantDescs.CD_byte, | |
| (int) Byte.MAX_VALUE)) | |
| .ireturn() | |
| ) | |
| ) | |
| .withMethod("C", MethodTypeDesc.of(ConstantDescs.CD_char), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "C", | |
| ConstantDescs.CD_char, | |
| (int) Character.MAX_VALUE)) | |
| .ireturn() | |
| ) | |
| ) | |
| .withMethod("D", MethodTypeDesc.of(ConstantDescs.CD_double), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "D", | |
| ConstantDescs.CD_double, | |
| Integer.MAX_VALUE)) | |
| .dreturn() | |
| ) | |
| ) | |
| .withMethod("D_AsType", MethodTypeDesc.of(ConstantDescs.CD_double), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "I", | |
| ConstantDescs.CD_double, | |
| Integer.MAX_VALUE)) | |
| .dreturn() | |
| ) | |
| ) | |
| .withMethod("F", MethodTypeDesc.of(ConstantDescs.CD_float), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "F", | |
| ConstantDescs.CD_float, | |
| Integer.MAX_VALUE)) | |
| .freturn() | |
| ) | |
| ) | |
| .withMethod("F_AsType", MethodTypeDesc.of(ConstantDescs.CD_float), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "I", | |
| ConstantDescs.CD_float, | |
| Integer.MAX_VALUE)) | |
| .freturn() | |
| ) | |
| ) | |
| .withMethod("I", MethodTypeDesc.of(ConstantDescs.CD_int), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "I", | |
| ConstantDescs.CD_int, | |
| Integer.MAX_VALUE)) | |
| .ireturn() | |
| ) | |
| ) | |
| .withMethod("J", MethodTypeDesc.of(ConstantDescs.CD_long), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "J", | |
| ConstantDescs.CD_long, | |
| Integer.MAX_VALUE)) | |
| .lreturn() | |
| ) | |
| ) | |
| .withMethod("J_AsType", MethodTypeDesc.of(ConstantDescs.CD_long), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "I", | |
| ConstantDescs.CD_long, | |
| Integer.MAX_VALUE)) | |
| .lreturn() | |
| ) | |
| ) | |
| .withMethod("S", MethodTypeDesc.of(ConstantDescs.CD_short), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "S", | |
| ConstantDescs.CD_short, | |
| ((int) Short.MAX_VALUE))) | |
| .ireturn() | |
| ) | |
| ) | |
| .withMethod("Z_F", MethodTypeDesc.of(ConstantDescs.CD_boolean), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "Z", | |
| ConstantDescs.CD_boolean, | |
| 0)) | |
| .ireturn() | |
| ) | |
| ) | |
| .withMethod("Z_T", MethodTypeDesc.of(ConstantDescs.CD_boolean), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "Z", | |
| ConstantDescs.CD_boolean, | |
| 1)) | |
| .ireturn() | |
| ) | |
| ) | |
| .withMethod("null", MethodTypeDesc.of(ConstantDescs.CD_Object), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "nullRef", | |
| ConstantDescs.CD_Object, | |
| Integer.MAX_VALUE)) | |
| .areturn() | |
| ) | |
| ) | |
| .withMethod("string", MethodTypeDesc.of(ConstantDescs.CD_String), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "string", | |
| ConstantDescs.CD_String, | |
| Integer.MAX_VALUE)) | |
| .areturn() | |
| ) | |
| ) | |
| .withMethod("stringArray", MethodTypeDesc.of(ConstantDescs.CD_String.arrayType()), | |
| ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, methodBuilder -> methodBuilder | |
| .withCode(codeBuilder -> codeBuilder | |
| .ldc(DynamicConstantDesc.ofNamed( | |
| bsmMhDesc, | |
| "stringArray", | |
| ConstantDescs.CD_String.arrayType(), | |
| Integer.MAX_VALUE)) | |
| .areturn() | |
| ) | |
| ) | |
| ); | |
| gc = MethodHandles.lookup().defineClass(byteArray); | |
| } | |
| @Test | |
| public void testPrimitives() throws Exception { | |
| testConstants(); | |
| } | |
| @Test | |
| public void testRefs() throws Exception { | |
| testConstant("string", "string"); | |
| testConstant("stringArray", new String[]{"string", "string"}); | |
| } | |
| void testConstants() throws Exception { | |
| // Note: for the _asType methods the BSM returns an int which is | |
| // then converted by an asType transformation | |
| testConstant("B", Byte.MAX_VALUE); | |
| testConstant("C", Character.MAX_VALUE); | |
| testConstant("D", (double) Integer.MAX_VALUE); | |
| testConstant("D_AsType", (double) Integer.MAX_VALUE); | |
| testConstant("F", (float) Integer.MAX_VALUE); | |
| testConstant("F_AsType", (float) Integer.MAX_VALUE); | |
| testConstant("I", Integer.MAX_VALUE); | |
| testConstant("J", (long) Integer.MAX_VALUE); | |
| testConstant("J_AsType", (long) Integer.MAX_VALUE); | |
| testConstant("S", Short.MAX_VALUE); | |
| testConstant("Z_F", false); | |
| testConstant("Z_T", true); | |
| testConstant("null", null); | |
| } | |
| void testConstant(String name, Object expected) throws Exception { | |
| Method m = gc.getDeclaredMethod(name); | |
| bsm_called = 0; | |
| try { | |
| Object r1 = m.invoke(null); | |
| Assert.fail("InvocationTargetException expected to be thrown after first invocation"); | |
| } catch (InvocationTargetException e1) { | |
| // bsm_called should have been incremented prior to the exception | |
| Assert.assertEquals(bsm_called, 1); | |
| Assert.assertTrue(e1.getCause() instanceof BootstrapMethodError); | |
| // Try invoking method again to ensure that the bootstrap | |
| // method is not invoked twice and a resolution failure | |
| // results. | |
| try { | |
| Object r2 = m.invoke(null); | |
| Assert.fail("InvocationTargetException expected to be thrown after second invocation"); | |
| } catch (InvocationTargetException e2) { | |
| // bsm_called should remain at 1 since the bootstrap | |
| // method should not have been invoked. | |
| Assert.assertEquals(bsm_called, 1); | |
| Assert.assertTrue(e2.getCause() instanceof BootstrapMethodError); | |
| } catch (Throwable t2) { | |
| Assert.fail("InvocationTargetException expected to be thrown"); | |
| } | |
| } catch (Throwable t1) { | |
| Assert.fail("InvocationTargetException expected to be thrown"); | |
| } | |
| } | |
| } |