1 /*
   2  * Copyright (c) 2020, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  *
  23  */
  24 package java.lang.invoke;
  25 
  26 import jdk.internal.loader.BuiltinClassLoader;
  27 import jdk.internal.misc.VM;
  28 import jdk.internal.access.JavaLangInvokeAccess;
  29 import jdk.internal.access.SharedSecrets;
  30 import java.io.BufferedReader;
  31 import java.io.File;
  32 import java.io.IOException;
  33 import java.io.InputStream;
  34 import java.io.InputStreamReader;
  35 import java.lang.invoke.MethodType;
  36 import java.nio.file.Files;
  37 import java.util.Arrays;
  38 import java.util.EnumSet;
  39 import java.util.Map;
  40 import java.util.HashMap;
  41 import java.util.Set;
  42 import java.util.TreeMap;
  43 import java.util.TreeSet;
  44 import java.util.stream.Stream;
  45 /**
  46  * Helper to generate java.lang.invoke classes.
  47  *
  48  * This class takes stream of strings generated by running any application with
  49  * {@code -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true}. This is done
  50  * automatically during build, see make/GenerateLinkOptData.gmk. See
  51  * build/tools/classlist/HelloClasslist.java for the training application.
  52  *
  53  * HelloClasslist tries to reflect common use of java.lang.invoke during early
  54  * startup and warmup in various applications. To ensure a good default
  55  * trade-off between static footprint and startup the application should be
  56  * relatively conservative.
  57  *
  58  * help to improve startup time.
  59  */
  60  
  61 class InvokerBytecodeGeneratorHelper {
  62     // @overide Can be accessed in children
  63     private static int DMH_INVOKE_VIRTUAL_TYPE = 0;
  64     private static int DMH_INVOKE_INTERFACE_TYPE = 4;
  65 
  66     private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder";
  67     private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
  68     private static final String DMH_INVOKE_STATIC = "invokeStatic";
  69     private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
  70     private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
  71     private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
  72     private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
  73     private static final String DMH_INVOKE_SPECIAL_IFC = "invokeSpecialIFC";
  74 
  75     private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder";
  76     private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder";
  77 
  78     private static final String INVOKERS_HOLDER_NAME = "java.lang.invoke.Invokers$Holder";
  79     private static final String INVOKERS_HOLDER_INTERNAL_NAME = INVOKERS_HOLDER_NAME.replace('.', '/');
  80 
  81     // Map from DirectMethodHandle method type to internal ID, matching values
  82     // of the corresponding constants in java.lang.invoke.MethodTypeForm
  83     private static final Map<String, Integer> DMH_METHOD_TYPE_MAP =
  84             Map.of(
  85                 DMH_INVOKE_VIRTUAL,     DMH_INVOKE_VIRTUAL_TYPE,
  86                 DMH_INVOKE_STATIC,      1,
  87                 DMH_INVOKE_SPECIAL,     2,
  88                 DMH_NEW_INVOKE_SPECIAL, 3,
  89                 DMH_INVOKE_INTERFACE,   DMH_INVOKE_INTERFACE_TYPE,
  90                 DMH_INVOKE_STATIC_INIT, 5,
  91                 DMH_INVOKE_SPECIAL_IFC, 20
  92             );
  93 
  94     /**
  95      * Output to DumpLoadedClassList, format as LF_RESOLVE 
  96      * @see InvokerBytecodeGenerator
  97      * @param line the line to output.
  98      */
  99     static native void cdsTraceResolve(String line);
 100 
 101     private static TreeSet<String> getSpeciesTyes() { return speciesTypes; }
 102     private static TreeSet<String> speciesTypes = new TreeSet<>();
 103     private static TreeSet<String> invokerTypes = new TreeSet<>();
 104     private static TreeSet<String> callSiteTypes = new TreeSet<>();
 105     private static Map<String, Set<String>> dmhMethods = new TreeMap<>();
 106 
 107     private static void clear() {
 108         speciesTypes.clear();
 109         invokerTypes.clear();
 110         callSiteTypes.clear();
 111         dmhMethods.clear();
 112     }
 113 
 114     private static void addSpeciesType(String type) {
 115         speciesTypes.add(expandSignature(type));
 116     }
 117 
 118     private static void addInvokerType(String methodType) {
 119         validateMethodType(methodType);
 120         invokerTypes.add(methodType);
 121     }
 122 
 123     private static void addCallSiteType(String csType) {
 124         validateMethodType(csType);
 125         callSiteTypes.add(csType);
 126     }
 127 
 128     private static void readTraceConfig(Stream<String> lines) {
 129         lines.map(line -> line.split(" "))
 130             .forEach(parts -> {
 131                 switch (parts[0]) {
 132                     case "[SPECIES_RESOLVE]":
 133                         // Allow for new types of species data classes being resolved here
 134                         if (parts.length == 3 && parts[1].startsWith("java.lang.invoke.BoundMethodHandle$Species_")) {
 135                             String species = parts[1].substring("java.lang.invoke.BoundMethodHandle$Species_".length());
 136                             if (!"L".equals(species)) {
 137                                 addSpeciesType(species);
 138                             }
 139                         }
 140                         break;
 141                     case "[LF_RESOLVE]":
 142                         String methodType = parts[3];
 143                         if (parts[1].equals(INVOKERS_HOLDER_NAME)) {
 144                             if ("linkToTargetMethod".equals(parts[2]) ||
 145                                     "linkToCallSite".equals(parts[2])) {
 146                                 addCallSiteType(methodType);
 147                             } else {
 148                                 addInvokerType(methodType);
 149                             }
 150                         } else if (parts[1].contains("DirectMethodHandle")) {
 151                             String dmh = parts[2];
 152                             // ignore getObject etc for now (generated
 153                             // by default)
 154                             if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
 155                                 addDMHMethodType(dmh, methodType);
 156                             }
 157                         }
 158                         break;
 159                     default: break; // ignore
 160                 }
 161             });
 162     }
 163 
 164     /**
 165      * called from vm to create generated lambda-form class
 166      * The input is as TRACE_RESOLVE
 167      * @return @code { Object[] } if holder classes can be generated.
 168      * @param lines the output line string from @cdsTraceResolve
 169      */
 170     static Object[] generateMethodHandleHolderClasses(String[] lines) {
 171         try { 
 172             Map<String, byte[]> result = generateMHHolderClasses(lines);
 173             clear();
 174             if (result == null) {
 175                 return null;
 176             }
 177             int size = result.size();
 178             Object[] ret_array = new Object[size * 2];
 179             int i = 0;
 180             for (Map.Entry<String, byte[]> entry : result.entrySet()) {
 181                 ret_array[i++] = entry.getKey();
 182                 ret_array[i++] = entry.getValue();
 183             };
 184             return ret_array;
 185         } catch (Exception e) {
 186             return null;
 187         }
 188     }
 189     
 190     /* return a map of <class with module pkg, class bytes? */
 191     static Map<String, byte[]> generateMHHolderClasses(String[] lines) throws InvokerGenerateBytesException {
 192         if (lines == null || lines.length == 0) {
 193             return null;
 194         }
 195         readTraceConfig(Arrays.stream(lines));
 196         int count = 0;
 197         for (Set<String> entry : dmhMethods.values()) {
 198             count += entry.size();
 199         }
 200         MethodType[] directMethodTypes = new MethodType[count];
 201         int[] dmhTypes = new int[count];
 202         int index = 0;
 203         for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) {
 204             String dmhType = entry.getKey();
 205             for (String type : entry.getValue()) {
 206                 // The DMH type to actually ask for is retrieved by removing
 207                 // the first argument, which needs to be of Object.class
 208                 MethodType mt = asMethodType(type);
 209                 if (mt.parameterCount() < 1 ||
 210                     mt.parameterType(0) != Object.class) {
 211                     throw new InvokerGenerateBytesException(
 212                             "DMH type parameter must start with L: " + dmhType + " " + type);
 213                 }
 214 
 215                 // Adapt the method type of the LF to retrieve
 216                 directMethodTypes[index] = mt.dropParameterTypes(0, 1);
 217 
 218                 // invokeVirtual and invokeInterface must have a leading Object
 219                 // parameter, i.e., the receiver
 220                 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType);
 221                 if (dmhTypes[index] == DMH_INVOKE_INTERFACE_TYPE ||
 222                     dmhTypes[index] == DMH_INVOKE_VIRTUAL_TYPE) {
 223                     if (mt.parameterCount() < 2 ||
 224                         mt.parameterType(1) != Object.class) {
 225                         throw new InvokerGenerateBytesException(
 226                                 "DMH type parameter must start with LL: " + dmhType + " " + type);
 227                     }
 228                 }
 229                 index++;
 230             }
 231         }
 232         
 233         // The invoker type to ask for is retrieved by removing the first
 234         // and the last argument, which needs to be of Object.class
 235         MethodType[] invokerMethodTypes = new MethodType[invokerTypes.size()];
 236         int i = 0;
 237         for (String invokerType : invokerTypes) {
 238             MethodType mt = asMethodType(invokerType);
 239             final int lastParam = mt.parameterCount() - 1;
 240             if (mt.parameterCount() < 2 ||
 241                     mt.parameterType(0) != Object.class ||
 242                     mt.parameterType(lastParam) != Object.class) {
 243                 throw new InvokerGenerateBytesException(
 244                         "Invoker type parameter must start and end with Object: " + invokerType);
 245             }
 246             mt = mt.dropParameterTypes(lastParam, lastParam + 1);
 247             invokerMethodTypes[i] = mt.dropParameterTypes(0, 1);
 248             i++;
 249         }
 250 
 251         // The callSite type to ask for is retrieved by removing the last
 252         // argument, which needs to be of Object.class
 253         MethodType[] callSiteMethodTypes = new MethodType[callSiteTypes.size()];
 254         i = 0;
 255         for (String callSiteType : callSiteTypes) {
 256             MethodType mt = asMethodType(callSiteType);
 257             final int lastParam = mt.parameterCount() - 1;
 258             if (mt.parameterCount() < 1 ||
 259                     mt.parameterType(lastParam) != Object.class) {
 260                 throw new InvokerGenerateBytesException(
 261                         "CallSite type parameter must end with Object: " + callSiteType);
 262             }
 263             callSiteMethodTypes[i] = mt.dropParameterTypes(lastParam, lastParam + 1);
 264             i++;
 265         }
 266         Map<String, byte[]> result = new HashMap<String, byte[]>();
 267 
 268         byte[] res = GenerateJLIClassesHelper.generateDirectMethodHandleHolderClassBytes(
 269                          DIRECT_HOLDER, directMethodTypes, dmhTypes);
 270         result.put(DIRECT_METHOD_HOLDER_ENTRY, res);
 271 
 272         res = GenerateJLIClassesHelper.generateDelegatingMethodHandleHolderClassBytes(
 273                   DELEGATING_HOLDER, directMethodTypes);
 274         result.put(DELEGATING_METHOD_HOLDER_ENTRY, res);
 275  
 276 
 277         res = GenerateJLIClassesHelper.generateInvokersHolderClassBytes(INVOKERS_HOLDER_INTERNAL_NAME,
 278                   invokerMethodTypes, callSiteMethodTypes);
 279         result.put(INVOKERS_HOLDER_ENTRY, res);       
 280 
 281         res  = GenerateJLIClassesHelper.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER);
 282         result.put(BASIC_FORMS_HOLDER_ENTRY, res);
 283 
 284         speciesTypes.forEach(types -> {
 285             Map.Entry<String, byte[]> entry = GenerateJLIClassesHelper.generateConcreteBMHClassBytes(types);
 286             String className = entry.getKey();
 287             String key = "/java.base/" + className + ".class"; 
 288             byte[] value = entry.getValue();
 289             result.put(key, value);
 290         });
 291 
 292         return result; 
 293     }
 294 
 295     private static final String DIRECT_METHOD_HOLDER_ENTRY =
 296             "/java.base/" + DIRECT_HOLDER + ".class";
 297     private static final String DELEGATING_METHOD_HOLDER_ENTRY =
 298             "/java.base/" + DELEGATING_HOLDER + ".class";
 299     private static final String BASIC_FORMS_HOLDER_ENTRY =
 300             "/java.base/" + BASIC_FORMS_HOLDER + ".class";
 301     private static final String INVOKERS_HOLDER_ENTRY =
 302             "/java.base/" + INVOKERS_HOLDER_INTERNAL_NAME + ".class";
 303 
 304     private static MethodType asMethodType(String basicSignatureString) {
 305         String[] parts = basicSignatureString.split("_");
 306         assert(parts.length == 2);
 307         assert(parts[1].length() == 1);
 308         String parameters = expandSignature(parts[0]);
 309         Class<?> rtype = simpleType(parts[1].charAt(0));
 310         if (parameters.isEmpty()) {
 311             return MethodType.methodType(rtype);
 312         } else {
 313             Class<?>[] ptypes = new Class<?>[parameters.length()];
 314             for (int i = 0; i < ptypes.length; i++) {
 315                 ptypes[i] = simpleType(parameters.charAt(i));
 316             }
 317             return MethodType.methodType(rtype, ptypes);
 318         }
 319     }
 320 
 321     private static void addDMHMethodType(String dmh, String methodType) {
 322         validateMethodType(methodType);
 323         Set<String> methodTypes = dmhMethods.get(dmh);
 324         if (methodTypes == null) {
 325             methodTypes = new TreeSet<>();
 326             dmhMethods.put(dmh, methodTypes);
 327         }
 328         methodTypes.add(methodType);
 329     }
 330 
 331     private static void validateMethodType(String type) {
 332         String[] typeParts = type.split("_");
 333         // check return type (second part)
 334         if (typeParts.length != 2 || typeParts[1].length() != 1
 335                 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) {
 336             throw new InvokerGenerateBytesException(
 337                     "Method type signature must be of form [LJIFD]*_[LJIFDV]");
 338         }
 339         // expand and check arguments (first part)
 340         expandSignature(typeParts[0]);
 341     }
 342 
 343     // Convert LL -> LL, L3 -> LLL
 344     private static String expandSignature(String signature) {
 345         StringBuilder sb = new StringBuilder();
 346         char last = 'X';
 347         int count = 0;
 348         for (int i = 0; i < signature.length(); i++) {
 349             char c = signature.charAt(i);
 350             if (c >= '0' && c <= '9') {
 351                 count *= 10;
 352                 count += (c - '0');
 353             } else {
 354                 requireBasicType(c);
 355                 for (int j = 1; j < count; j++) {
 356                     sb.append(last);
 357                 }
 358                 sb.append(c);
 359                 last = c;
 360                 count = 0;
 361             }
 362         }
 363 
 364         // ended with a number, e.g., "L2": append last char count - 1 times
 365         if (count > 1) {
 366             requireBasicType(last);
 367             for (int j = 1; j < count; j++) {
 368                 sb.append(last);
 369             }
 370         }
 371         return sb.toString();
 372     }
 373 
 374     private static void requireBasicType(char c) {
 375         if ("LIJFD".indexOf(c) < 0) {
 376             throw new InvokerGenerateBytesException(
 377                     "Character " + c + " must correspond to a basic field type: LIJFD");
 378         }
 379     }
 380 
 381     private static Class<?> simpleType(char c) {
 382         switch (c) {
 383             case 'F':
 384                 return float.class;
 385             case 'D':
 386                 return double.class;
 387             case 'I':
 388                 return int.class;
 389             case 'L':
 390                 return Object.class;
 391             case 'J':
 392                 return long.class;
 393             case 'V':
 394                 return void.class;
 395             case 'Z':
 396             case 'B':
 397             case 'S':
 398             case 'C':
 399                 throw new IllegalArgumentException("Not a valid primitive: " + c +
 400                         " (use I instead)");
 401             default:
 402                 throw new IllegalArgumentException("Not a primitive: " + c);
 403         }
 404     }
 405 }