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 }