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 }