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 }