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