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 }