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 }