1 /*
2 * Copyright (c) 2025, 2026, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.incubator.code.runtime;
27
28 import java.lang.classfile.ClassBuilder;
29 import java.lang.classfile.CodeBuilder;
30 import java.lang.classfile.Opcode;
31 import java.lang.classfile.TypeKind;
32 import java.lang.classfile.constantpool.ConstantPoolBuilder;
33 import java.lang.classfile.constantpool.MethodRefEntry;
34 import java.lang.constant.ClassDesc;
35 import java.lang.constant.MethodTypeDesc;
36 import java.lang.invoke.CallSite;
37 import java.lang.invoke.LambdaConversionException;
38 import java.lang.invoke.LambdaMetafactory;
39 import java.lang.invoke.MethodHandle;
40 import java.lang.invoke.MethodHandles;
41 import java.lang.invoke.MethodHandles.Lookup;
42 import java.lang.invoke.MethodType;
43 import java.util.List;
44
45 import jdk.incubator.code.Op;
46 import jdk.incubator.code.Quoted;
47 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
48 import jdk.internal.access.JavaLangInvokeAccess;
49 import jdk.internal.access.SharedSecrets;
50
51 import static java.lang.classfile.ClassFile.ACC_PRIVATE;
52 import static java.lang.classfile.ClassFile.ACC_STATIC;
53 import static java.lang.classfile.ClassFile.ACC_SYNCHRONIZED;
54 import static java.lang.constant.ConstantDescs.*;
55 import java.lang.constant.DynamicConstantDesc;
56 import java.util.function.Function;
57
58 /**
59 * Provides runtime support for creating reflectable lambdas. A reflectable lambda is a lambda whose
60 * code model can be inspected using {@link Op#ofLambda(Object)}.
61 * @see LambdaMetafactory
62 * @see Op#ofLambda(Object)
63 */
64 public class ReflectableLambdaMetafactory {
65
66 private static final String NAME_METHOD_QUOTED = "__internal_quoted";
67 private static final String QUOTED_FIELD_NAME = "quoted";
68 private static final String MODEL_FIELD_NAME = "model";
69
70 static final ClassDesc CD_Quoted = Quoted.class.describeConstable().get();
71 static final ClassDesc CD_FuncOp = FuncOp.class.describeConstable().get();
72 static final ClassDesc CD_Op = Op.class.describeConstable().get();
73 static final MethodTypeDesc MTD_extractOp = MethodTypeDesc.of(CD_Quoted, CD_FuncOp, CD_Object.arrayType());
74 static final DynamicConstantDesc<?> DCD_CLASS_DATA = DynamicConstantDesc.ofNamed(BSM_CLASS_DATA, DEFAULT_NAME, CD_List);
75
76 private ReflectableLambdaMetafactory() {
77 // nope
78 }
79
80 /**
81 * Metafactory used to create a reflectable lambda.
82 * <p>
83 * The functionality provided by this metafactory is identical to that in
84 * {@link LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)}
85 * with one important difference: this metafactory expects the provided name to be encoded in the following form:
86 * <code>
87 * lambdaName=opMethodName
88 * </code>
89 * The {@code lambdaName} part of the name is passed to the regular metafactory method, along with all the other
90 * parameters unchanged. The {@code opMethod} part of the name is used to locate a method in the
91 * {@linkplain Lookup#lookupClass() lookup class} associated with the provided lookup. This method is expected
92 * to accept no parameters and return a {@link Op}, namely the model of the reflectable lambda.
93 * <p>
94 * This means the clients can pass the lambda returned by this factory to the {@link Op#ofLambda(Object)} method,
95 * to access the code model of the lambda expression dynamically.
96 *
97 * @param caller The lookup
98 * @param interfaceMethodName The name of the method to implement.
99 * This is encoded in the format described above.
100 * @param factoryType The expected signature of the {@code CallSite}.
101 * @param interfaceMethodType Signature and return type of method to be
102 * implemented by the function object.
103 * @param implementation A direct method handle describing the implementation
104 * method which should be called at invocation time.
105 * @param dynamicMethodType The signature and return type that should
106 * be enforced dynamically at invocation time.
107 * @return a CallSite whose target can be used to perform capture, generating
108 * a reflectable lambda instance implementing the interface named by {@code factoryType}.
109 * The code model for such instance can be inspected using {@link Op#ofLambda(Object)}.
110 *
111 * @throws LambdaConversionException If, after the lambda name is decoded,
112 * the parameters of the call are invalid for
113 * {@link LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)}
114 * @throws NullPointerException If any argument is {@code null}.
115 *
116 * @see LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)
117 * @see Op#ofLambda(Object)
118 */
119 public static CallSite metafactory(MethodHandles.Lookup caller,
120 String interfaceMethodName,
121 MethodType factoryType,
122 MethodType interfaceMethodType,
123 MethodHandle implementation,
124 MethodType dynamicMethodType)
125 throws LambdaConversionException {
126 DecodedName decodedName = findReflectableOpGetter(caller, interfaceMethodName);
127 LambdaFinisher finisher = new LambdaFinisher(caller.lookupClass(), factoryType.parameterList(), decodedName.opHandle);
128 return JLI_ACCESS.metafactoryInternal(caller, decodedName.name, factoryType, interfaceMethodType,
129 implementation, dynamicMethodType, finisher);
130 }
131
132 /**
133 * Metafactory used to create a reflectable lambda.
134 * <p>
135 * The functionality provided by this metafactory is identical to that in
136 * {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
137 * with one important difference: this metafactory expects the provided name to be encoded in the following form:
138 * <code>
139 * lambdaName=opMethodName
140 * </code>
141 * The {@code lambdaName} part of the name is passed to the regular metafactory method, along with all the other
142 * parameters unchanged. The {@code opMethod} part of the name is used to locate a method in the
143 * {@linkplain Lookup#lookupClass() lookup class} associated with the provided lookup. This method is expected
144 * to accept no parameters and return a {@link Op}, namely the model of the reflectable lambda.
145 * <p>
146 * This means the clients can pass the lambda returned by this factory to the {@link Op#ofLambda(Object)} method,
147 * to access the code model of the lambda expression dynamically.
148 *
149 * @param caller The lookup
150 * @param interfaceMethodName The name of the method to implement.
151 * This is encoded in the format described above.
152 * @param factoryType The expected signature of the {@code CallSite}.
153 * @param args An array of {@code Object} containing the required
154 * arguments {@code interfaceMethodType}, {@code implementation},
155 * {@code dynamicMethodType}, {@code flags}, and any
156 * optional arguments, as required by {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
157 * @return a CallSite whose target can be used to perform capture, generating
158 * a reflectable lambda instance implementing the interface named by {@code factoryType}.
159 * The code model for such instance can be inspected using {@link Op#ofLambda(Object)}.
160 *
161 * @throws LambdaConversionException If, after the lambda name is decoded,
162 * the parameters of the call are invalid for
163 * {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
164 * @throws NullPointerException If any argument, or any component of {@code args},
165 * is {@code null}.
166 * @throws IllegalArgumentException If {@code args} are invalid for
167 * {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
168 *
169 * @see LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)
170 * @see Op#ofLambda(Object)
171 */
172 public static CallSite altMetafactory(MethodHandles.Lookup caller,
173 String interfaceMethodName,
174 MethodType factoryType,
175 Object... args)
176 throws LambdaConversionException {
177 DecodedName decodedName = findReflectableOpGetter(caller, interfaceMethodName);
178 LambdaFinisher finisher = new LambdaFinisher(caller.lookupClass(), factoryType.parameterList(), decodedName.opHandle);
179 return JLI_ACCESS.altMetafactoryInternal(caller, decodedName.name, factoryType, finisher, args);
180 }
181
182 static final JavaLangInvokeAccess JLI_ACCESS = SharedSecrets.getJavaLangInvokeAccess();
183
184 record DecodedName(String name, MethodHandle opHandle) { }
185
186 private static DecodedName findReflectableOpGetter(MethodHandles.Lookup lookup, String interfaceMethodName) throws LambdaConversionException {
187 String[] implNameParts = interfaceMethodName.split("=");
188 if (implNameParts.length != 2) {
189 throw new LambdaConversionException("Bad method name: " + interfaceMethodName);
190 }
191 try {
192 return new DecodedName(
193 implNameParts[0],
194 lookup.findStatic(lookup.lookupClass(), implNameParts[1], MethodType.methodType(Op.class)));
195 } catch (ReflectiveOperationException ex) {
196 throw new LambdaConversionException(ex);
197 }
198 }
199
200 static class LambdaFinisher implements Function<ClassBuilder, Object> {
201
202 final ClassDesc lambdaClassSymbol;
203 final ClassDesc[] argDescs;
204 final MethodHandle opHandle;
205
206 public LambdaFinisher(Class<?> callerClass, List<Class<?>> parameterTypes, MethodHandle opHandle) throws LambdaConversionException {
207 this.lambdaClassSymbol = ClassDesc.ofInternalName(sanitizedTargetClassName(callerClass).concat("$$Lambda"));
208 this.argDescs = parameterTypes.stream().map(cls -> cls.describeConstable().get()).toArray(ClassDesc[]::new);
209 this.opHandle = opHandle;
210 }
211
212 @Override
213 public Object apply(ClassBuilder clb) {
214 // the field that will hold the quoted instance
215 clb.withField(QUOTED_FIELD_NAME, CD_Quoted, ACC_PRIVATE);
216 // the field that will hold the model
217 clb.withField(MODEL_FIELD_NAME, CD_FuncOp,
218 ACC_PRIVATE | ACC_STATIC);
219 // Generate method #__internal_quoted()
220 clb.withMethodBody(NAME_METHOD_QUOTED, MethodTypeDesc.of(CD_Quoted), ACC_PRIVATE, (cob) ->
221 cob.aload(0)
222 .invokevirtual(lambdaClassSymbol, "getQuoted", MethodTypeDesc.of(CD_Quoted))
223 .areturn());
224 // generate helper methods
225 /*
226 synchronized Quoted getQuoted() {
227 Quoted v = quoted;
228 if (v == null) {
229 v = quoted = Quoted.extractOp(getModel(), captures);
230 }
231 return v;
232 }
233 */
234 clb.withMethodBody("getQuoted", MethodTypeDesc.of(CD_Quoted),
235 ACC_PRIVATE + ACC_SYNCHRONIZED, cob ->
236 cob.aload(0)
237 .getfield(lambdaClassSymbol, QUOTED_FIELD_NAME, CD_Quoted)
238 .astore(1)
239 .aload(1)
240 .ifThen(Opcode.IFNULL, bcb -> {
241 bcb.aload(0) // will be used by putfield
242 .invokestatic(lambdaClassSymbol, "getModel", MethodTypeDesc.of(CD_FuncOp))
243 // load captured args in array
244 .loadConstant(argDescs.length)
245 .anewarray(CD_Object);
246 for (int i = 0; i < argDescs.length; i++) {
247 bcb.dup()
248 .loadConstant(i)
249 .aload(0)
250 .getfield(lambdaClassSymbol, "arg$" + (i + 1), argDescs[i]);
251 boxIfTypePrimitive(bcb, TypeKind.from(argDescs[i]));
252 bcb.aastore();
253 }
254 // invoke Quoted.extractOp
255 bcb.invokestatic(CD_Quoted, "extractOp", MTD_extractOp)
256 .dup_x1()
257 .putfield(lambdaClassSymbol, QUOTED_FIELD_NAME, CD_Quoted)
258 .astore(1);
259 })
260 .aload(1)
261 .areturn());
262 /*
263 private static synchronized CoreOp.FuncOp getModel() {
264 FuncOp v = model;
265 if (v == null) {
266 v = model = ...invoke lambda op building method...
267 }
268 return v;
269 }
270 */
271 clb.withMethodBody("getModel", MethodTypeDesc.of(CD_FuncOp),
272 ACC_PRIVATE + ACC_STATIC + ACC_SYNCHRONIZED, cob ->
273 cob.getstatic(lambdaClassSymbol, MODEL_FIELD_NAME, CD_FuncOp)
274 .astore(0)
275 .aload(0)
276 .ifThen(Opcode.IFNULL, bcb ->
277 // last item in the class data list is a method handle to get the op
278 bcb.ldc(DCD_CLASS_DATA)
279 .invokeinterface(CD_List, "getLast", MethodTypeDesc.of(CD_Object))
280 .checkcast(CD_MethodHandle)
281 .invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDesc.of(CD_Op))
282 .checkcast(CD_FuncOp)
283 .dup()
284 .putstatic(lambdaClassSymbol, MODEL_FIELD_NAME, CD_FuncOp)
285 .astore(0))
286 .aload(0)
287 .areturn());
288 // return opHandle as additional class data
289 return opHandle;
290 }
291
292 static void boxIfTypePrimitive(CodeBuilder cob, TypeKind tk) {
293 var cp = cob.constantPool();
294 switch (tk) {
295 case BOOLEAN -> cob.invokestatic(box(cp, CD_boolean, CD_Boolean));
296 case BYTE -> cob.invokestatic(box(cp, CD_byte, CD_Byte));
297 case CHAR -> cob.invokestatic(box(cp, CD_char, CD_Character));
298 case DOUBLE -> cob.invokestatic(box(cp, CD_double, CD_Double));
299 case FLOAT -> cob.invokestatic(box(cp, CD_float, CD_Float));
300 case INT -> cob.invokestatic(box(cp, CD_int, CD_Integer));
301 case LONG -> cob.invokestatic(box(cp, CD_long, CD_Long));
302 case SHORT -> cob.invokestatic(box(cp, CD_short, CD_Short));
303 }
304 }
305
306 private static MethodRefEntry box(ConstantPoolBuilder cp, ClassDesc primitive, ClassDesc target) {
307 return cp.methodRefEntry(target, "valueOf", MethodTypeDesc.of(target, primitive));
308 }
309
310 private static String sanitizedTargetClassName(Class<?> targetClass) {
311 String name = targetClass.getName();
312 if (targetClass.isHidden()) {
313 // use the original class name
314 name = name.replace('/', '_');
315 }
316 return name.replace('.', '/');
317 }
318 }
319 }