1 /*
  2  * Copyright (c) 2016, 2024, 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 java.lang.invoke;
 27 
 28 import sun.invoke.util.Wrapper;
 29 import sun.util.logging.PlatformLogger;
 30 
 31 import java.lang.classfile.ClassFile;
 32 import java.lang.classfile.attribute.SourceFileAttribute;
 33 import java.lang.constant.ClassDesc;
 34 import java.util.ArrayList;
 35 import java.util.HashSet;
 36 import java.util.Map;
 37 import java.util.Objects;
 38 import java.util.Set;
 39 import java.util.TreeMap;
 40 import java.util.TreeSet;
 41 import java.util.stream.Stream;
 42 
 43 import static java.lang.classfile.ClassFile.*;
 44 import static java.lang.invoke.LambdaForm.BasicType.*;
 45 import static java.lang.invoke.LambdaForm.Kind.*;
 46 import static java.lang.invoke.MethodTypeForm.*;
 47 
 48 /**
 49  * Helper class to assist the GenerateJLIClassesPlugin to get access to
 50  * generate classes ahead of time.
 51  */
 52 class GenerateJLIClassesHelper {
 53     // Map from DirectMethodHandle method type name to index to LambdaForms
 54     static final Map<String, Integer> DMH_METHOD_TYPE_MAP =
 55             Map.of(
 56                     DIRECT_INVOKE_VIRTUAL.methodName,     LF_INVVIRTUAL,
 57                     DIRECT_INVOKE_STATIC.methodName,      LF_INVSTATIC,
 58                     DIRECT_INVOKE_SPECIAL.methodName,     LF_INVSPECIAL,
 59                     DIRECT_NEW_INVOKE_SPECIAL.methodName, LF_NEWINVSPECIAL,
 60                     DIRECT_INVOKE_INTERFACE.methodName,   LF_INVINTERFACE,
 61                     DIRECT_INVOKE_STATIC_INIT.methodName, LF_INVSTATIC_INIT,
 62                     DIRECT_INVOKE_SPECIAL_IFC.methodName, LF_INVSPECIAL_IFC
 63             );
 64 
 65     static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder";
 66     static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder";
 67     static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder";
 68     static final String INVOKERS_HOLDER = "java/lang/invoke/Invokers$Holder";
 69     static final String INVOKERS_HOLDER_CLASS_NAME = INVOKERS_HOLDER.replace('/', '.');
 70     static final String BMH_SPECIES_PREFIX = "java.lang.invoke.BoundMethodHandle$Species_";
 71 
 72     static class HolderClassBuilder {
 73 
 74 
 75         private final TreeSet<String> speciesTypes = new TreeSet<>();
 76         private final TreeSet<String> invokerTypes = new TreeSet<>();
 77         private final TreeSet<String> callSiteTypes = new TreeSet<>();
 78         private final Map<String, Set<String>> dmhMethods = new TreeMap<>();
 79 
 80         HolderClassBuilder addSpeciesType(String type) {
 81             speciesTypes.add(expandSignature(type));
 82             return this;
 83         }
 84 
 85         HolderClassBuilder addInvokerType(String methodType) {
 86             validateMethodType(methodType);
 87             invokerTypes.add(methodType);
 88             return this;
 89         }
 90 
 91         HolderClassBuilder addCallSiteType(String csType) {
 92             validateMethodType(csType);
 93             callSiteTypes.add(csType);
 94             return this;
 95         }
 96 
 97         Map<String, byte[]> build() {
 98             int count = 0;
 99             for (Set<String> entry : dmhMethods.values()) {
100                 count += entry.size();
101             }
102             MethodType[] directMethodTypes = new MethodType[count];
103             int[] dmhTypes = new int[count];
104             int index = 0;
105             for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) {
106                 String dmhType = entry.getKey();
107                 for (String type : entry.getValue()) {
108                     // The DMH type to actually ask for is retrieved by removing
109                     // the first argument, which needs to be of Object.class
110                     MethodType mt = asMethodType(type);
111                     if (mt.parameterCount() < 1 ||
112                             mt.parameterType(0) != Object.class) {
113                         throw new RuntimeException(
114                                 "DMH type parameter must start with L: " + dmhType + " " + type);
115                     }
116                     // Adapt the method type of the LF to retrieve
117                     directMethodTypes[index] = mt.dropParameterTypes(0, 1);
118                     // invokeVirtual and invokeInterface must have a leading Object
119                     // parameter, i.e., the receiver
120                     dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType);
121                     if (dmhTypes[index] == LF_INVINTERFACE || dmhTypes[index] == LF_INVVIRTUAL) {
122                         if (mt.parameterCount() < 2 ||
123                                 mt.parameterType(1) != Object.class) {
124                             throw new RuntimeException(
125                                     "DMH type parameter must start with LL: " + dmhType + " " + type);
126                         }
127                     }
128                     index++;
129                 }
130             }
131 
132             // The invoker type to ask for is retrieved by removing the first
133             // and the last argument, which needs to be of Object.class
134             MethodType[] invokerMethodTypes = new MethodType[invokerTypes.size()];
135             index = 0;
136             for (String invokerType : invokerTypes) {
137                 MethodType mt = asMethodType(invokerType);
138                 final int lastParam = mt.parameterCount() - 1;
139                 if (!checkInvokerTypeParams(mt)) {
140                     throw new RuntimeException(
141                             "Invoker type parameter must start and end with Object: " + invokerType);
142                 }
143                 mt = mt.dropParameterTypes(lastParam, lastParam + 1);
144                 invokerMethodTypes[index] = mt.dropParameterTypes(0, 1);
145                 index++;
146             }
147 
148             // The callSite type to ask for is retrieved by removing the last
149             // argument, which needs to be of Object.class
150             MethodType[] callSiteMethodTypes = new MethodType[callSiteTypes.size()];
151             index = 0;
152             for (String callSiteType : callSiteTypes) {
153                 MethodType mt = asMethodType(callSiteType);
154                 final int lastParam = mt.parameterCount() - 1;
155                 if (mt.parameterCount() < 1 ||
156                         mt.parameterType(lastParam) != Object.class) {
157                     throw new RuntimeException(
158                             "CallSite type parameter must end with Object: " + callSiteType);
159                 }
160                 callSiteMethodTypes[index] = mt.dropParameterTypes(lastParam, lastParam + 1);
161                 index++;
162             }
163             Map<String, byte[]> result = new TreeMap<>();
164             result.put(DIRECT_HOLDER,
165                        generateDirectMethodHandleHolderClassBytes(
166                             DIRECT_HOLDER, directMethodTypes, dmhTypes));
167             result.put(DELEGATING_HOLDER,
168                        generateDelegatingMethodHandleHolderClassBytes(
169                             DELEGATING_HOLDER, directMethodTypes));
170             result.put(INVOKERS_HOLDER,
171                        generateInvokersHolderClassBytes(INVOKERS_HOLDER,
172                             invokerMethodTypes, callSiteMethodTypes));
173             result.put(BASIC_FORMS_HOLDER,
174                        generateBasicFormsClassBytes(BASIC_FORMS_HOLDER));
175 
176             speciesTypes.forEach(types -> {
177                 Map.Entry<String, byte[]> entry = generateConcreteBMHClassBytes(types);
178                 result.put(entry.getKey(), entry.getValue());
179             });
180             // clear builder
181             speciesTypes.clear();
182             invokerTypes.clear();
183             callSiteTypes.clear();
184             dmhMethods.clear();
185             return result;
186         }
187 
188         public static MethodType asMethodType(String basicSignatureString) {
189             String[] parts = basicSignatureString.split("_");
190             assert (parts.length == 2);
191             assert (parts[1].length() == 1);
192             String parameters = expandSignature(parts[0]);
193             Class<?> rtype = simpleType(parts[1].charAt(0));
194             if (parameters.isEmpty()) {
195                 return MethodType.methodType(rtype);
196             } else {
197                 Class<?>[] ptypes = new Class<?>[parameters.length()];
198                 for (int i = 0; i < ptypes.length; i++) {
199                     ptypes[i] = simpleType(parameters.charAt(i));
200                 }
201                 return MethodType.methodType(rtype, ptypes);
202             }
203         }
204 
205         public static boolean checkInvokerTypeParams(MethodType mt) {
206             final int lastParam = mt.parameterCount() - 1;
207             return (mt.parameterCount() >= 2 &&
208                     mt.parameterType(0) == Object.class &&
209                     mt.parameterType(lastParam) == Object.class);
210         }
211 
212         private void addDMHMethodType(String dmh, String methodType) {
213             validateMethodType(methodType);
214             Set<String> methodTypes = dmhMethods.get(dmh);
215             if (methodTypes == null) {
216                 methodTypes = new TreeSet<>();
217                 dmhMethods.put(dmh, methodTypes);
218             }
219             methodTypes.add(methodType);
220         }
221 
222         private static void validateMethodType(String type) {
223             String[] typeParts = type.split("_");
224             // check return type (second part)
225             if (typeParts.length != 2 || typeParts[1].length() != 1
226                     || !isBasicTypeChar(typeParts[1].charAt(0))) {
227                 throw new RuntimeException(
228                         "Method type signature must be of form [LJIFD]*_[LJIFDV]");
229             }
230             // expand and check arguments (first part)
231             expandSignature(typeParts[0]);
232         }
233 
234         // Convert LL -> LL, L3 -> LLL
235         private static String expandSignature(String signature) {
236             StringBuilder sb = new StringBuilder();
237             char last = 'X';
238             int count = 0;
239             for (int i = 0; i < signature.length(); i++) {
240                 char c = signature.charAt(i);
241                 if (c >= '0' && c <= '9') {
242                     count *= 10;
243                     count += (c - '0');
244                 } else {
245                     requireBasicType(c);
246                     for (int j = 1; j < count; j++) {
247                         sb.append(last);
248                     }
249                     sb.append(c);
250                     last = c;
251                     count = 0;
252                 }
253             }
254 
255             // ended with a number, e.g., "L2": append last char count - 1 times
256             if (count > 1) {
257                 requireBasicType(last);
258                 for (int j = 1; j < count; j++) {
259                     sb.append(last);
260                 }
261             }
262             return sb.toString();
263         }
264 
265         private static void requireBasicType(char c) {
266             if (!isArgBasicTypeChar(c)) {
267                 throw new RuntimeException(
268                         "Character " + c + " must correspond to a basic field type: LIJFD");
269             }
270         }
271 
272         private static Class<?> simpleType(char c) {
273             if (isBasicTypeChar(c)) {
274                 return LambdaForm.BasicType.basicType(c).basicTypeClass();
275             }
276             switch (c) {
277                 case 'Z':
278                 case 'B':
279                 case 'S':
280                 case 'C':
281                     throw new IllegalArgumentException("Not a valid primitive: " + c +
282                             " (use I instead)");
283                 default:
284                     throw new IllegalArgumentException("Not a primitive: " + c);
285             }
286         }
287     }
288 
289     /*
290      * Returns a map of class name in internal form to the corresponding class bytes
291      * per the given stream of SPECIES_RESOLVE and LF_RESOLVE trace logs.
292      *
293      * Used by GenerateJLIClassesPlugin to pre-generate holder classes during
294      * jlink phase.
295      */
296     static Map<String, byte[]> generateHolderClasses(Stream<String> traces)  {
297         Objects.requireNonNull(traces);
298         HolderClassBuilder builder = new HolderClassBuilder();
299         traces.map(line -> line.split(" "))
300                 .forEach(parts -> {
301                     switch (parts[0]) {
302                         case "[SPECIES_RESOLVE]":
303                             // Allow for new types of species data classes being resolved here
304                             assert parts.length >= 2;
305                             if (parts[1].startsWith(BMH_SPECIES_PREFIX)) {
306                                 String species = parts[1].substring(BMH_SPECIES_PREFIX.length());
307                                 if (!"L".equals(species)) {
308                                     builder.addSpeciesType(species);
309                                 }
310                             }
311                             break;
312                         case "[LF_RESOLVE]":
313                             assert parts.length > 3;
314                             String methodType = parts[3];
315                             if (parts[1].equals(INVOKERS_HOLDER_CLASS_NAME)) {
316                                 if ("linkToTargetMethod".equals(parts[2]) ||
317                                         "linkToCallSite".equals(parts[2])) {
318                                     builder.addCallSiteType(methodType);
319                                 } else {
320                                     MethodType mt = HolderClassBuilder.asMethodType(methodType);
321                                     // Work around JDK-8327499
322                                     if (HolderClassBuilder.checkInvokerTypeParams(mt)) {
323                                         builder.addInvokerType(methodType);
324                                     } else {
325                                         PlatformLogger.getLogger("java.lang.invoke")
326                                                 .warning("Invalid LF_RESOLVE " + parts[1] + " " + parts[2] + " " + parts[3]);
327                                     }
328                                 }
329                             } else if (parts[1].contains("DirectMethodHandle")) {
330                                 String dmh = parts[2];
331                                 // ignore getObject etc for now (generated by default)
332                                 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
333                                     builder.addDMHMethodType(dmh, methodType);
334                                 }
335                             }
336                             break;
337                         default:
338                             break; // ignore
339                     }
340                 });
341 
342         return builder.build();
343     }
344 
345     /**
346      * Returns a {@code byte[]} representation of a class implementing
347      * the zero and identity forms of all {@code LambdaForm.BasicType}s.
348      */
349     static byte[] generateBasicFormsClassBytes(String className) {
350         ArrayList<LambdaForm> forms = new ArrayList<>();
351         ArrayList<String> names = new ArrayList<>();
352         HashSet<String> dedupSet = new HashSet<>();
353         for (LambdaForm.BasicType type : LambdaForm.BasicType.values()) {
354             LambdaForm zero = LambdaForm.zeroForm(type);
355             String name = zero.kind.defaultLambdaName
356                    + "_" + zero.returnType().basicTypeChar();
357             if (dedupSet.add(name)) {
358                 names.add(name);
359                 forms.add(zero);
360             }
361 
362             LambdaForm identity = LambdaForm.identityForm(type);
363             name = identity.kind.defaultLambdaName
364                    + "_" + identity.returnType().basicTypeChar();
365             if (dedupSet.add(name)) {
366                 names.add(name);
367                 forms.add(identity);
368             }
369         }
370         return generateCodeBytesForLFs(className,
371                 names.toArray(new String[0]),
372                 forms.toArray(new LambdaForm[0]));
373     }
374 
375     /**
376      * Returns a {@code byte[]} representation of a class implementing
377      * DirectMethodHandle of each pairwise combination of {@code MethodType} and
378      * an {@code int} representing method type.
379      */
380     static byte[] generateDirectMethodHandleHolderClassBytes(String className,
381             MethodType[] methodTypes, int[] types) {
382         ArrayList<LambdaForm> forms = new ArrayList<>();
383         ArrayList<String> names = new ArrayList<>();
384         for (int i = 0; i < methodTypes.length; i++) {
385             // invokeVirtual and invokeInterface must have a leading Object
386             // parameter, i.e., the receiver
387             if (types[i] == LF_INVVIRTUAL || types[i] == LF_INVINTERFACE) {
388                 if (methodTypes[i].parameterCount() < 1 ||
389                         methodTypes[i].parameterType(0) != Object.class) {
390                     throw new InternalError("Invalid method type for " +
391                             (types[i] == LF_INVVIRTUAL ? "invokeVirtual" : "invokeInterface") +
392                             " DMH, needs at least two leading reference arguments: " +
393                             methodTypes[i]);
394                 }
395             }
396 
397             LambdaForm form = DirectMethodHandle.makePreparedLambdaForm(methodTypes[i], types[i]);
398             forms.add(form);
399             names.add(form.kind.defaultLambdaName);
400         }
401         for (Wrapper wrapper : Wrapper.values()) {
402             if (wrapper == Wrapper.VOID) {
403                 continue;
404             }
405             for (byte b = DirectMethodHandle.AF_GETFIELD; b < DirectMethodHandle.AF_LIMIT; b++) {
406                 int ftype = DirectMethodHandle.ftypeKind(wrapper.primitiveType());
407                 LambdaForm form = DirectMethodHandle
408                         .makePreparedFieldLambdaForm(b, /*isVolatile*/false, ftype);
409                 if (form.kind != LambdaForm.Kind.GENERIC) {
410                     forms.add(form);
411                     names.add(form.kind.defaultLambdaName);
412                 }
413                 // volatile
414                 form = DirectMethodHandle
415                         .makePreparedFieldLambdaForm(b, /*isVolatile*/true, ftype);
416                 if (form.kind != LambdaForm.Kind.GENERIC) {
417                     forms.add(form);
418                     names.add(form.kind.defaultLambdaName);
419                 }
420             }
421         }
422         return generateCodeBytesForLFs(className,
423                 names.toArray(new String[0]),
424                 forms.toArray(new LambdaForm[0]));
425     }
426 
427     /**
428      * Returns a {@code byte[]} representation of a class implementing
429      * DelegatingMethodHandles of each {@code MethodType} kind in the
430      * {@code methodTypes} argument.
431      */
432     static byte[] generateDelegatingMethodHandleHolderClassBytes(String className,
433             MethodType[] methodTypes) {
434 
435         HashSet<MethodType> dedupSet = new HashSet<>();
436         ArrayList<LambdaForm> forms = new ArrayList<>();
437         ArrayList<String> names = new ArrayList<>();
438         for (int i = 0; i < methodTypes.length; i++) {
439             // generate methods representing the DelegatingMethodHandle
440             if (dedupSet.add(methodTypes[i])) {
441                 // reinvokers are variant with the associated SpeciesData
442                 // and shape of the target LF, but we can easily pregenerate
443                 // the basic reinvokers associated with Species_L. Ultimately we
444                 // may want to consider pregenerating more of these, which will
445                 // require an even more complex naming scheme
446                 LambdaForm reinvoker = makeReinvokerFor(methodTypes[i]);
447                 forms.add(reinvoker);
448                 String speciesSig = BoundMethodHandle.speciesDataFor(reinvoker).key();
449                 assert(speciesSig.equals("L"));
450                 names.add(reinvoker.kind.defaultLambdaName + "_" + speciesSig);
451 
452                 LambdaForm delegate = makeDelegateFor(methodTypes[i]);
453                 forms.add(delegate);
454                 names.add(delegate.kind.defaultLambdaName);
455             }
456         }
457         return generateCodeBytesForLFs(className,
458                 names.toArray(new String[0]),
459                 forms.toArray(new LambdaForm[0]));
460     }
461 
462     /**
463      * Returns a {@code byte[]} representation of a class implementing
464      * the invoker forms for the set of supplied {@code invokerMethodTypes}
465      * and {@code callSiteMethodTypes}.
466      */
467     static byte[] generateInvokersHolderClassBytes(String className,
468             MethodType[] invokerMethodTypes, MethodType[] callSiteMethodTypes) {
469 
470         HashSet<MethodType> dedupSet = new HashSet<>();
471         ArrayList<LambdaForm> forms = new ArrayList<>();
472         ArrayList<String> names = new ArrayList<>();
473         int[] types = {
474             MethodTypeForm.LF_EX_LINKER,
475             MethodTypeForm.LF_EX_INVOKER,
476             MethodTypeForm.LF_GEN_LINKER,
477             MethodTypeForm.LF_GEN_INVOKER
478         };
479 
480         for (int i = 0; i < invokerMethodTypes.length; i++) {
481             // generate methods representing invokers of the specified type
482             if (dedupSet.add(invokerMethodTypes[i])) {
483                 for (int type : types) {
484                     LambdaForm invokerForm = Invokers.invokeHandleForm(invokerMethodTypes[i],
485                             /*customized*/false, type);
486                     forms.add(invokerForm);
487                     names.add(invokerForm.kind.defaultLambdaName);
488                 }
489             }
490         }
491 
492         dedupSet = new HashSet<>();
493         for (int i = 0; i < callSiteMethodTypes.length; i++) {
494             // generate methods representing invokers of the specified type
495             if (dedupSet.add(callSiteMethodTypes[i])) {
496                 LambdaForm callSiteForm = Invokers.callSiteForm(callSiteMethodTypes[i], true);
497                 forms.add(callSiteForm);
498                 names.add(callSiteForm.kind.defaultLambdaName);
499 
500                 LambdaForm methodHandleForm = Invokers.callSiteForm(callSiteMethodTypes[i], false);
501                 forms.add(methodHandleForm);
502                 names.add(methodHandleForm.kind.defaultLambdaName);
503             }
504         }
505 
506         return generateCodeBytesForLFs(className,
507                 names.toArray(new String[0]),
508                 forms.toArray(new LambdaForm[0]));
509     }
510 
511     /*
512      * Generate customized code for a set of LambdaForms of specified types into
513      * a class with a specified name.
514      */
515     private static byte[] generateCodeBytesForLFs(String className, String[] names, LambdaForm[] forms) {
516         return ClassFile.of().build(ClassDesc.ofInternalName(className), clb -> {
517             clb.withFlags(ACC_PRIVATE | ACC_FINAL | ACC_SUPER)
518                .withSuperclass(InvokerBytecodeGenerator.INVOKER_SUPER_DESC)
519                .with(SourceFileAttribute.of(className.substring(className.lastIndexOf('/') + 1)));
520             for (int i = 0; i < forms.length; i++) {
521                 new InvokerBytecodeGenerator(className, names[i], forms[i], forms[i].methodType()).addMethod(clb);
522             }
523         });
524     }
525 
526     private static LambdaForm makeReinvokerFor(MethodType type) {
527         MethodHandle emptyHandle = MethodHandles.empty(type);
528         return DelegatingMethodHandle.makeReinvokerForm(emptyHandle,
529                 MethodTypeForm.LF_REBIND,
530                 BoundMethodHandle.speciesData_L(),
531                 BoundMethodHandle.speciesData_L().getterFunction(0));
532     }
533 
534     private static LambdaForm makeDelegateFor(MethodType type) {
535         MethodHandle handle = MethodHandles.empty(type);
536         return DelegatingMethodHandle.makeReinvokerForm(
537                 handle,
538                 MethodTypeForm.LF_DELEGATE,
539                 DelegatingMethodHandle.class,
540                 DelegatingMethodHandle.NF_getTarget);
541     }
542 
543     /**
544      * Returns a {@code byte[]} representation of {@code BoundMethodHandle}
545      * species class implementing the signature defined by {@code types}.
546      */
547     @SuppressWarnings({"rawtypes", "unchecked"})
548     static Map.Entry<String, byte[]> generateConcreteBMHClassBytes(final String types) {
549         for (char c : types.toCharArray()) {
550             if (!isArgBasicTypeChar(c)) {
551                 throw new IllegalArgumentException("All characters must "
552                         + "correspond to a basic field type: LIJFD");
553             }
554         }
555         final BoundMethodHandle.SpeciesData species = BoundMethodHandle.SPECIALIZER.findSpecies(types);
556         final String className = species.speciesCode().getName();
557         final ClassSpecializer.Factory factory = BoundMethodHandle.SPECIALIZER.factory();
558         final byte[] code = factory.generateConcreteSpeciesCodeFile(className, species);
559         return Map.entry(className.replace('.', '/'), code);
560     }
561 
562 }