1 /* 2 * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @summary Test ObjectMethods::bootstrap call via condy 27 * @modules java.base/jdk.internal.value 28 * @enablePreview 29 * @run testng/othervm ObjectMethodsViaCondy 30 */ 31 32 import java.io.IOException; 33 import java.io.OutputStream; 34 import java.io.UncheckedIOException; 35 import java.lang.classfile.ClassFile; 36 import java.lang.constant.ClassDesc; 37 import java.lang.constant.ConstantDesc; 38 import java.lang.constant.DirectMethodHandleDesc; 39 import java.lang.constant.DynamicConstantDesc; 40 import java.lang.constant.MethodHandleDesc; 41 import java.lang.constant.MethodTypeDesc; 42 import java.lang.invoke.MethodHandle; 43 import java.lang.invoke.MethodHandles; 44 import java.lang.invoke.MethodHandles.Lookup.ClassOption; 45 import java.lang.invoke.MethodType; 46 import java.lang.runtime.ObjectMethods; 47 import java.nio.file.Files; 48 import java.nio.file.Path; 49 import java.nio.file.Paths; 50 import java.util.List; 51 import java.util.stream.Stream; 52 53 import org.testng.annotations.Test; 54 55 import static java.lang.classfile.ClassFile.*; 56 import static java.lang.constant.ConstantDescs.*; 57 import static java.lang.invoke.MethodType.methodType; 58 import static org.testng.Assert.assertEquals; 59 import static org.testng.Assert.assertTrue; 60 import static org.testng.Assert.assertFalse; 61 62 public class ObjectMethodsViaCondy { 63 public static value record ValueRecord(int i, String name) { 64 static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 65 static final MethodType TO_STRING_DESC = methodType(String.class, ValueRecord.class); 66 static final String NAME_LIST = "i;name"; 67 private static final ClassDesc CD_ValueRecord = ValueRecord.class.describeConstable().orElseThrow(); 68 private static final ClassDesc CD_ObjectMethods = ObjectMethods.class.describeConstable().orElseThrow(); 69 private static final MethodTypeDesc MTD_ObjectMethods_bootstrap = MethodTypeDesc.of(CD_Object, CD_MethodHandles_Lookup, CD_String, 70 ClassDesc.ofInternalName("java/lang/invoke/TypeDescriptor"), CD_Class, CD_String, CD_MethodHandle.arrayType()); 71 static final List<DirectMethodHandleDesc> ACCESSORS = accessors(); 72 73 private static List<DirectMethodHandleDesc> accessors() { 74 try { 75 return List.of( 76 MethodHandleDesc.ofField(DirectMethodHandleDesc.Kind.GETTER, CD_ValueRecord, "i", CD_int), 77 MethodHandleDesc.ofField(DirectMethodHandleDesc.Kind.GETTER, CD_ValueRecord, "name", CD_String) 78 ); 79 } catch (Exception e) { 80 throw new AssertionError(e); 81 } 82 } 83 84 /** 85 * Returns the method handle for the given method for this ValueRecord class. 86 * This method defines a hidden class to invoke the ObjectMethods::bootstrap method 87 * via condy. 88 * 89 * @param methodName the name of the method to generate, which must be one of 90 * {@code "equals"}, {@code "hashCode"}, or {@code "toString"} 91 */ 92 static MethodHandle makeBootstrapMethod(String methodName) throws Throwable { 93 String className = "Test-" + methodName; 94 ClassDesc testClass = ClassDesc.of(className); 95 byte[] bytes = ClassFile.of().build(testClass, clb -> clb 96 .withVersion(JAVA_19_VERSION, 0) 97 .withFlags(ACC_FINAL | ACC_SUPER) 98 .withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> cob 99 .aload(0) 100 .invokespecial(CD_Object, INIT_NAME, MTD_void) 101 .return_()) 102 .withMethodBody("bootstrap", MethodTypeDesc.of(CD_Object), ACC_PUBLIC | ACC_STATIC, cob -> cob 103 .loadConstant(DynamicConstantDesc.ofNamed( 104 MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC, CD_ObjectMethods, 105 "bootstrap", MTD_ObjectMethods_bootstrap), 106 methodName, 107 CD_MethodHandle, 108 Stream.concat(Stream.of(CD_ValueRecord, NAME_LIST), ACCESSORS.stream()).toArray(ConstantDesc[]::new))) 109 .areturn()) 110 ); 111 112 Path p = Paths.get(className + ".class"); 113 try (OutputStream os = Files.newOutputStream(p)) { 114 os.write(bytes); 115 } catch (IOException e) { 116 throw new UncheckedIOException(e); 117 } 118 119 MethodHandles.Lookup lookup = LOOKUP.defineHiddenClass(bytes, true, ClassOption.NESTMATE); 120 MethodType mtype = MethodType.methodType(Object.class); 121 MethodHandle mh = lookup.findStatic(lookup.lookupClass(), "bootstrap", mtype); 122 return (MethodHandle) mh.invoke(); 123 } 124 } 125 126 @Test 127 public void testToString() throws Throwable { 128 MethodHandle handle = ValueRecord.makeBootstrapMethod("toString"); 129 assertEquals((String)handle.invokeExact(new ValueRecord(10, "ten")), "ValueRecord[i=10, name=ten]"); 130 assertEquals((String)handle.invokeExact(new ValueRecord(40, "forty")), "ValueRecord[i=40, name=forty]"); 131 } 132 133 @Test 134 public void testToEquals() throws Throwable { 135 MethodHandle handle = ValueRecord.makeBootstrapMethod("equals"); 136 assertTrue((boolean)handle.invoke(new ValueRecord(10, "ten"), new ValueRecord(10, "ten"))); 137 assertFalse((boolean)handle.invoke(new ValueRecord(11, "eleven"), new ValueRecord(10, "ten"))); 138 } 139 }