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.lang.invoke.MethodHandleInfo;
57 import java.util.function.Function;
58
59 /**
60 * Provides runtime support for creating reflectable lambdas. A reflectable lambda is a lambda whose
61 * code model can be inspected using {@link Op#ofLambda(Object)}.
62 * @see LambdaMetafactory
63 * @see Op#ofLambda(Object)
64 */
65 public class ReflectableLambdaMetafactory {
66
67 private static final String NAME_METHOD_QUOTED = "__internal_quoted";
68 private static final String QUOTED_FIELD_NAME = "quoted";
69 private static final String MODEL_FIELD_NAME = "model";
70
71 static final ClassDesc CD_Quoted = Quoted.class.describeConstable().get();
72 static final ClassDesc CD_FuncOp = FuncOp.class.describeConstable().get();
73 static final ClassDesc CD_Op = Op.class.describeConstable().get();
74 static final MethodTypeDesc MTD_extractOp = MethodTypeDesc.of(CD_Quoted, CD_FuncOp, CD_Object.arrayType());
75 static final DynamicConstantDesc<?> DCD_CLASS_DATA = DynamicConstantDesc.ofNamed(BSM_CLASS_DATA, DEFAULT_NAME, CD_List);
76
77 private ReflectableLambdaMetafactory() {
78 // nope
79 }
80
81 /**
82 * Metafactory used to create a reflectable lambda.
83 * <p>
84 * Clients can pass the lambda returned by this factory to the {@link Op#ofLambda(Object)} method,
85 * to access the code model of the lambda expression dynamically.
86 * <p>
87 * The functionality provided by this metafactory is identical to that in
88 * {@link LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)}.
89 *
90 * @param caller The lookup
91 * @param interfaceMethodName The name of the method to implement.
92 * @param factoryType The expected signature of the {@code CallSite}.
93 * @param interfaceMethodType Signature and return type of method to be
94 * implemented by the function object.
95 * @param implementation A direct method handle describing the implementation
96 * method which should be called at invocation time.
97 * @param dynamicMethodType The signature and return type that should
98 * be enforced dynamically at invocation time.
99 * @return a CallSite whose target can be used to perform capture, generating
100 * a reflectable lambda instance implementing the interface named by {@code factoryType}.
101 * The code model for such instance can be inspected using {@link Op#ofLambda(Object)}.
102 *
103 * @throws LambdaConversionException If the lambda's code model cannot be located,
104 * or the parameters of the call are invalid for
105 * {@link LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)}
106 * @throws NullPointerException If any argument is {@code null}.
107 *
108 * @see LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)
109 * @see Op#ofLambda(Object)
110 */
111 public static CallSite metafactory(MethodHandles.Lookup caller,
112 String interfaceMethodName,
113 MethodType factoryType,
114 MethodType interfaceMethodType,
115 MethodHandle implementation,
116 MethodType dynamicMethodType)
117 throws LambdaConversionException {
118 MethodHandle opHandle = findReflectableOpGetter(caller, implementation);
119 LambdaFinisher finisher = new LambdaFinisher(caller.lookupClass(), factoryType.parameterList(), opHandle);
120 return JLI_ACCESS.metafactoryInternal(caller, interfaceMethodName, factoryType, interfaceMethodType,
121 implementation, dynamicMethodType, finisher);
122 }
123
124 /**
125 * Metafactory used to create a reflectable lambda.
126 * <p>
127 * Clients can pass the lambda returned by this factory to the {@link Op#ofLambda(Object)} method,
128 * to access the code model of the lambda expression dynamically.
129 * <p>
130 * The functionality provided by this metafactory is identical to that in
131 * {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}.
132 *
133 * @param caller The lookup
134 * @param interfaceMethodName The name of the method to implement.
135 * @param factoryType The expected signature of the {@code CallSite}.
136 * @param args An array of {@code Object} containing the required
137 * arguments {@code interfaceMethodType}, {@code implementation},
138 * {@code dynamicMethodType}, {@code flags}, and any
139 * optional arguments, as required by {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
140 * @return a CallSite whose target can be used to perform capture, generating
141 * a reflectable lambda instance implementing the interface named by {@code factoryType}.
142 * The code model for such instance can be inspected using {@link Op#ofLambda(Object)}.
143 *
144 * @throws LambdaConversionException If the lambda's code model cannot be located,
145 * or the parameters of the call are invalid for
146 * {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
147 * @throws NullPointerException If any argument, or any component of {@code args},
148 * is {@code null}.
149 * @throws IllegalArgumentException If {@code args} are invalid for
150 * {@link LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)}
151 *
152 * @see LambdaMetafactory#altMetafactory(Lookup, String, MethodType, Object...)
153 * @see Op#ofLambda(Object)
154 */
155 public static CallSite altMetafactory(MethodHandles.Lookup caller,
156 String interfaceMethodName,
157 MethodType factoryType,
158 Object... args)
159 throws LambdaConversionException {
160 LambdaFinisher finisher = (args.length > 1 && args[1] instanceof MethodHandle impl)
161 ? new LambdaFinisher(caller.lookupClass(), factoryType.parameterList(), findReflectableOpGetter(caller, impl))
162 : null;
163 return JLI_ACCESS.altMetafactoryInternal(caller, interfaceMethodName, factoryType, finisher, args);
164 }
165
166 static final JavaLangInvokeAccess JLI_ACCESS = SharedSecrets.getJavaLangInvokeAccess();
167
168 static MethodHandle findReflectableOpGetter(MethodHandles.Lookup lookup, MethodHandle implementation)
169 throws LambdaConversionException {
170 try {
171 MethodHandleInfo info = lookup.revealDirect(implementation);
172 return lookup.findStatic(info.getDeclaringClass(), "op$" + info.getName(), MethodType.methodType(Op.class));
173 } catch (ReflectiveOperationException | IllegalArgumentException ex) {
174 throw new LambdaConversionException(ex);
175 }
176 }
177
178 static class LambdaFinisher implements Function<ClassBuilder, Object> {
179
180 final ClassDesc lambdaClassSymbol;
181 final ClassDesc[] argDescs;
182 final MethodHandle opHandle;
183
184 public LambdaFinisher(Class<?> callerClass, List<Class<?>> parameterTypes, MethodHandle opHandle)
185 throws LambdaConversionException {
186 this.lambdaClassSymbol = ClassDesc.ofInternalName(sanitizedTargetClassName(callerClass).concat("$$Lambda"));
187 this.argDescs = parameterTypes.stream().map(cls -> cls.describeConstable().get()).toArray(ClassDesc[]::new);
188 this.opHandle = opHandle;
189 }
190
191 @Override
192 public Object apply(ClassBuilder clb) {
193 // the field that will hold the quoted instance
194 clb.withField(QUOTED_FIELD_NAME, CD_Quoted, ACC_PRIVATE);
195 // the field that will hold the model
196 clb.withField(MODEL_FIELD_NAME, CD_FuncOp,
197 ACC_PRIVATE | ACC_STATIC);
198 // Generate method #__internal_quoted()
199 clb.withMethodBody(NAME_METHOD_QUOTED, MethodTypeDesc.of(CD_Quoted), ACC_PRIVATE, (cob) ->
200 cob.aload(0)
201 .invokevirtual(lambdaClassSymbol, "getQuoted", MethodTypeDesc.of(CD_Quoted))
202 .areturn());
203 // generate helper methods
204 /*
205 synchronized Quoted getQuoted() {
206 Quoted v = quoted;
207 if (v == null) {
208 v = quoted = Quoted.extractOp(getModel(), captures);
209 }
210 return v;
211 }
212 */
213 clb.withMethodBody("getQuoted", MethodTypeDesc.of(CD_Quoted),
214 ACC_PRIVATE + ACC_SYNCHRONIZED, cob ->
215 cob.aload(0)
216 .getfield(lambdaClassSymbol, QUOTED_FIELD_NAME, CD_Quoted)
217 .astore(1)
218 .aload(1)
219 .ifThen(Opcode.IFNULL, bcb -> {
220 bcb.aload(0) // will be used by putfield
221 .invokestatic(lambdaClassSymbol, "getModel", MethodTypeDesc.of(CD_FuncOp))
222 // load captured args in array
223 .loadConstant(argDescs.length)
224 .anewarray(CD_Object);
225 for (int i = 0; i < argDescs.length; i++) {
226 bcb.dup()
227 .loadConstant(i)
228 .aload(0)
229 .getfield(lambdaClassSymbol, "arg$" + (i + 1), argDescs[i]);
230 boxIfTypePrimitive(bcb, TypeKind.from(argDescs[i]));
231 bcb.aastore();
232 }
233 // invoke Quoted.extractOp
234 bcb.invokestatic(CD_Quoted, "extractOp", MTD_extractOp)
235 .dup_x1()
236 .putfield(lambdaClassSymbol, QUOTED_FIELD_NAME, CD_Quoted)
237 .astore(1);
238 })
239 .aload(1)
240 .areturn());
241 /*
242 private static synchronized CoreOp.FuncOp getModel() {
243 FuncOp v = model;
244 if (v == null) {
245 v = model = ...invoke lambda op building method...
246 }
247 return v;
248 }
249 */
250 clb.withMethodBody("getModel", MethodTypeDesc.of(CD_FuncOp),
251 ACC_PRIVATE + ACC_STATIC + ACC_SYNCHRONIZED, cob ->
252 cob.getstatic(lambdaClassSymbol, MODEL_FIELD_NAME, CD_FuncOp)
253 .astore(0)
254 .aload(0)
255 .ifThen(Opcode.IFNULL, bcb ->
256 // last item in the class data list is a method handle to get the op
257 bcb.ldc(DCD_CLASS_DATA)
258 .invokeinterface(CD_List, "getLast", MethodTypeDesc.of(CD_Object))
259 .checkcast(CD_MethodHandle)
260 .invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDesc.of(CD_Op))
261 .checkcast(CD_FuncOp)
262 .dup()
263 .putstatic(lambdaClassSymbol, MODEL_FIELD_NAME, CD_FuncOp)
264 .astore(0))
265 .aload(0)
266 .areturn());
267 // return opHandle as additional class data
268 return opHandle;
269 }
270
271 static void boxIfTypePrimitive(CodeBuilder cob, TypeKind tk) {
272 var cp = cob.constantPool();
273 switch (tk) {
274 case BOOLEAN -> cob.invokestatic(box(cp, CD_boolean, CD_Boolean));
275 case BYTE -> cob.invokestatic(box(cp, CD_byte, CD_Byte));
276 case CHAR -> cob.invokestatic(box(cp, CD_char, CD_Character));
277 case DOUBLE -> cob.invokestatic(box(cp, CD_double, CD_Double));
278 case FLOAT -> cob.invokestatic(box(cp, CD_float, CD_Float));
279 case INT -> cob.invokestatic(box(cp, CD_int, CD_Integer));
280 case LONG -> cob.invokestatic(box(cp, CD_long, CD_Long));
281 case SHORT -> cob.invokestatic(box(cp, CD_short, CD_Short));
282 }
283 }
284
285 private static MethodRefEntry box(ConstantPoolBuilder cp, ClassDesc primitive, ClassDesc target) {
286 return cp.methodRefEntry(target, "valueOf", MethodTypeDesc.of(target, primitive));
287 }
288
289 private static String sanitizedTargetClassName(Class<?> targetClass) {
290 String name = targetClass.getName();
291 if (targetClass.isHidden()) {
292 // use the original class name
293 name = name.replace('/', '_');
294 }
295 return name.replace('.', '/');
296 }
297 }
298 }