1 /* 2 * Copyright (c) 2021, 2024, 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 * java.base/jdk.internal.org.objectweb.asm 29 * @enablePreview 30 * @run testng/othervm ObjectMethodsViaCondy 31 */ 32 33 import java.io.IOException; 34 import java.io.OutputStream; 35 import java.io.UncheckedIOException; 36 import java.lang.invoke.MethodHandle; 37 import java.lang.invoke.MethodHandles; 38 import java.lang.invoke.MethodHandles.Lookup.ClassOption; 39 import java.lang.invoke.MethodType; 40 import java.lang.invoke.TypeDescriptor; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.util.Arrays; 45 import java.util.stream.Stream; 46 47 import jdk.internal.org.objectweb.asm.ClassWriter; 48 import jdk.internal.org.objectweb.asm.ConstantDynamic; 49 import jdk.internal.org.objectweb.asm.Handle; 50 import jdk.internal.org.objectweb.asm.MethodVisitor; 51 import jdk.internal.org.objectweb.asm.Type; 52 import org.testng.annotations.Test; 53 import static java.lang.invoke.MethodType.methodType; 54 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL; 55 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_IDENTITY; 56 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; 57 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; 58 import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; 59 import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN; 60 import static jdk.internal.org.objectweb.asm.Opcodes.H_GETFIELD; 61 import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC; 62 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL; 63 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; 64 import static jdk.internal.org.objectweb.asm.Opcodes.V19; 65 import static org.testng.Assert.assertEquals; 66 import static org.testng.Assert.assertTrue; 67 import static org.testng.Assert.assertFalse; 68 69 public class ObjectMethodsViaCondy { 70 public static value record ValueRecord(int i, String name) { 71 static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 72 static final MethodType TO_STRING_DESC = methodType(String.class, ValueRecord.class); 73 static final Handle[] ACCESSORS = accessors(); 74 static final String NAME_LIST = "i;name"; 75 private static Handle[] accessors() { 76 try { 77 return new Handle[]{ 78 new Handle(H_GETFIELD, Type.getInternalName(ValueRecord.class), "i", "I", false), 79 new Handle(H_GETFIELD, Type.getInternalName(ValueRecord.class), "name", String.class.descriptorString(), false) 80 }; 81 } catch (Exception e) { 82 throw new AssertionError(e); 83 } 84 } 85 86 /** 87 * Returns the method handle for the given method for this ValueRecord class. 88 * This method defines a hidden class to invoke the ObjectMethods::bootstrap method 89 * via condy. 90 * 91 * @param methodName the name of the method to generate, which must be one of 92 * {@code "equals"}, {@code "hashCode"}, or {@code "toString"} 93 */ 94 static MethodHandle makeBootstrapMethod(String methodName) throws Throwable { 95 ClassFileBuilder builder = new ClassFileBuilder("Test-" + methodName); 96 builder.bootstrapMethod(methodName, TO_STRING_DESC, ValueRecord.class, NAME_LIST, ACCESSORS); 97 byte[] bytes = builder.build(); 98 MethodHandles.Lookup lookup = LOOKUP.defineHiddenClass(bytes, true, ClassOption.NESTMATE); 99 MethodType mtype = MethodType.methodType(Object.class); 100 MethodHandle mh = lookup.findStatic(lookup.lookupClass(), "bootstrap", mtype); 101 return (MethodHandle) mh.invoke(); 102 } 103 } 104 105 @Test 106 public void testToString() throws Throwable { 107 MethodHandle handle = ValueRecord.makeBootstrapMethod("toString"); 108 assertEquals((String)handle.invokeExact(new ValueRecord(10, "ten")), "ValueRecord[i=10, name=ten]"); 109 assertEquals((String)handle.invokeExact(new ValueRecord(40, "forty")), "ValueRecord[i=40, name=forty]"); 110 } 111 112 @Test 113 public void testToEquals() throws Throwable { 114 MethodHandle handle = ValueRecord.makeBootstrapMethod("equals"); 115 assertTrue((boolean)handle.invoke(new ValueRecord(10, "ten"), new ValueRecord(10, "ten"))); 116 assertFalse((boolean)handle.invoke(new ValueRecord(11, "eleven"), new ValueRecord(10, "ten"))); 117 } 118 119 static class ClassFileBuilder { 120 private static final String OBJECT_CLS = "java/lang/Object"; 121 private static final String OBJ_METHODS_CLS = "java/lang/runtime/ObjectMethods"; 122 private static final String BSM_DESCR = 123 MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, 124 TypeDescriptor.class, Class.class, String.class, MethodHandle[].class) 125 .descriptorString(); 126 private final ClassWriter cw; 127 private final String classname; 128 129 /** 130 * A builder to generate a class file to access class data 131 * 132 * @param classname 133 */ 134 ClassFileBuilder(String classname) { 135 this.classname = classname; 136 this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 137 cw.visit(V19, ACC_FINAL | ACC_IDENTITY, classname, null, OBJECT_CLS, null); 138 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 139 mv.visitCode(); 140 mv.visitVarInsn(ALOAD, 0); 141 mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLS, "<init>", "()V", false); 142 mv.visitInsn(RETURN); 143 mv.visitMaxs(0, 0); 144 mv.visitEnd(); 145 } 146 147 byte[] build() { 148 cw.visitEnd(); 149 byte[] bytes = cw.toByteArray(); 150 Path p = Paths.get(classname + ".class"); 151 try (OutputStream os = Files.newOutputStream(p)) { 152 os.write(bytes); 153 } catch (IOException e) { 154 throw new UncheckedIOException(e); 155 } 156 return bytes; 157 } 158 159 /* 160 * Generate the bootstrap method that invokes ObjectMethods::bootstrap via condy 161 */ 162 void bootstrapMethod(String name, TypeDescriptor descriptor, Class<?> recordClass, String names, Handle[] getters) { 163 MethodType mtype = MethodType.methodType(Object.class); 164 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC|ACC_STATIC, 165 "bootstrap", mtype.descriptorString(), null, null); 166 mv.visitCode(); 167 Handle bsm = new Handle(H_INVOKESTATIC, OBJ_METHODS_CLS, "bootstrap", 168 BSM_DESCR, false); 169 Object[] args = Stream.concat(Stream.of(Type.getType(recordClass), names), Arrays.stream(getters)).toArray(); 170 ConstantDynamic dynamic = new ConstantDynamic(name, MethodHandle.class.descriptorString(), bsm, args); 171 mv.visitLdcInsn(dynamic); 172 mv.visitInsn(ARETURN); 173 mv.visitMaxs(0, 0); 174 mv.visitEnd(); 175 } 176 } 177 }