1 /*
   2  * Copyright (c) 2016, 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 package jdk.tools.jlink.internal.plugins;
  26 
  27 import java.io.File;
  28 import java.io.IOException;
  29 import java.lang.invoke.MethodType;
  30 import java.nio.file.Files;
  31 import java.util.ArrayList;
  32 import java.util.Arrays;
  33 import java.util.EnumSet;
  34 import java.util.HashMap;
  35 import java.util.List;
  36 import java.util.Map;
  37 import java.util.Set;
  38 import java.util.stream.Collectors;
  39 import java.util.stream.Stream;
  40 import jdk.internal.misc.SharedSecrets;
  41 import jdk.internal.misc.JavaLangInvokeAccess;
  42 import jdk.tools.jlink.plugin.ResourcePoolEntry;
  43 import jdk.tools.jlink.plugin.PluginException;
  44 import jdk.tools.jlink.plugin.ResourcePool;
  45 import jdk.tools.jlink.plugin.ResourcePoolBuilder;
  46 import jdk.tools.jlink.plugin.Plugin;
  47 
  48 /**
  49  * Plugin to generate java.lang.invoke classes.
  50  */
  51 public final class GenerateJLIClassesPlugin implements Plugin {
  52 
  53     private static final String NAME = "generate-jli-classes";
  54 
  55     private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
  56 
  57     private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder";
  58     private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
  59     private static final String DMH_INVOKE_STATIC = "invokeStatic";
  60     private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
  61     private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
  62     private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
  63     private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
  64 
  65     private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder";
  66     private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder";
  67     private static final String INVOKERS_HOLDER = "java/lang/invoke/Invokers$Holder";
  68 
  69     private static final JavaLangInvokeAccess JLIA
  70             = SharedSecrets.getJavaLangInvokeAccess();
  71 
  72     List<String> speciesTypes;
  73 
  74     List<String> invokerTypes;
  75 
  76     Map<String, List<String>> dmhMethods;
  77 
  78     public GenerateJLIClassesPlugin() {
  79     }
  80 
  81     @Override
  82     public String getName() {
  83         return NAME;
  84     }
  85 
  86     @Override
  87     public String getDescription() {
  88         return DESCRIPTION;
  89     }
  90 
  91     @Override
  92     public Set<State> getState() {
  93         return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL);
  94     }
  95 
  96     @Override
  97     public boolean hasArguments() {
  98         return true;
  99     }
 100 
 101     @Override
 102     public String getArgumentsDescription() {
 103        return PluginsResourceBundle.getArgument(NAME);
 104     }
 105 
 106     /**
 107      * @return the default Species forms to generate.
 108      *
 109      * This list was derived from running a small startup benchmark.
 110      * A better long-term solution is to define and run a set of quick
 111      * generators and extracting this list as a step in the build process.
 112      */
 113     private static List<String> defaultSpecies() {
 114         return List.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I",
 115                 "L7II", "L7IIL", "L8", "L9", "L10", "L10I", "L10II", "L10IIL",
 116                 "L11", "L12", "L13", "LI", "D", "L3I", "LIL", "LLI", "LLIL",
 117                 "LILL", "I", "LLILL");
 118     }
 119 
 120     /**
 121      * @return the default invoker forms to generate.
 122      */
 123     private static List<String> defaultInvokers() {
 124         return List.of("LL_L", "LL_I", "LILL_I", "L6_L");
 125     }
 126 
 127     /**
 128      * @return the list of default DirectMethodHandle methods to generate.
 129      */
 130     private static Map<String, List<String>> defaultDMHMethods() {
 131         return Map.of(
 132             DMH_INVOKE_VIRTUAL, List.of("L_L", "LL_L", "LLI_I", "L3_V"),
 133             DMH_INVOKE_SPECIAL, List.of("LL_I", "LL_L", "LLF_L", "LLD_L", "L3_L",
 134                 "L4_L", "L5_L", "L6_L", "L7_L", "L8_L", "LLI_I", "LLI_L",
 135                 "LLIL_I", "LLII_I", "LLII_L", "L3I_L", "L3I_I", "LLILI_I",
 136                 "LLIIL_L", "LLIILL_L", "LLIILL_I", "LLIIL_I", "LLILIL_I",
 137                 "LLILILL_I", "LLILII_I", "LLI3_I", "LLI3L_I", "LLI3LL_I",
 138                 "LLI3_L", "LLI4_I"),
 139             DMH_INVOKE_STATIC, List.of("LII_I", "LIL_I", "LILIL_I", "LILII_I",
 140                 "L_I", "L_L", "L_V", "LD_L", "LF_L", "LI_I", "LII_L", "LLI_L",
 141                 "LL_V", "LL_L", "L3_L", "L4_L", "L5_L", "L6_L", "L7_L",
 142                 "L8_L", "L9_L", "L10_L", "L10I_L", "L10II_L", "L10IIL_L",
 143                 "L11_L", "L12_L", "L13_L", "L14_L", "L14I_L", "L14II_L")
 144         );
 145     }
 146 
 147     // Map from DirectMethodHandle method type to internal ID
 148     private static final Map<String, Integer> DMH_METHOD_TYPE_MAP =
 149             Map.of(
 150                 DMH_INVOKE_VIRTUAL,     0,
 151                 DMH_INVOKE_STATIC,      1,
 152                 DMH_INVOKE_SPECIAL,     2,
 153                 DMH_NEW_INVOKE_SPECIAL, 3,
 154                 DMH_INVOKE_INTERFACE,   4,
 155                 DMH_INVOKE_STATIC_INIT, 5
 156             );
 157 
 158     @Override
 159     public void configure(Map<String, String> config) {
 160         String mainArgument = config.get(NAME);
 161 
 162         if (mainArgument != null && mainArgument.startsWith("@")) {
 163             File file = new File(mainArgument.substring(1));
 164             if (file.exists()) {
 165                 speciesTypes = new ArrayList<>();
 166                 invokerTypes = new ArrayList<>();
 167                 dmhMethods = new HashMap<>();
 168                 Stream<String> lines = fileLines(file);
 169 
 170                 lines.map(line -> line.split(" "))
 171                     .forEach(parts -> {
 172                         switch (parts[0]) {
 173                             case "[BMH_RESOLVE]":
 174                                 speciesTypes.add(expandSignature(parts[1]));
 175                                 break;
 176                             case "[LF_RESOLVE]":
 177                                 String methodType = parts[3];
 178                                 validateMethodType(methodType);
 179                                 if (parts[1].contains("Invokers")) {
 180                                     invokerTypes.add(methodType);
 181                                 } else if (parts[1].contains("DirectMethodHandle")) {
 182                                     String dmh = parts[2];
 183                                     // ignore getObject etc for now (generated
 184                                     // by default)
 185                                     if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
 186                                         addDMHMethodType(dmh, methodType);
 187                                     }
 188                                 }
 189                                 break;
 190                             default: break; // ignore
 191                         }
 192                 });
 193             }
 194         } else {
 195             List<String> bmhSpecies = defaultSpecies();
 196             // Expand BMH species signatures
 197             speciesTypes = bmhSpecies.stream()
 198                     .map(type -> expandSignature(type))
 199                     .collect(Collectors.toList());
 200 
 201             invokerTypes = defaultInvokers();
 202             validateMethodTypes(invokerTypes);
 203 
 204             dmhMethods = defaultDMHMethods();
 205             for (List<String> dmhMethodTypes : dmhMethods.values()) {
 206                 validateMethodTypes(dmhMethodTypes);
 207             }
 208         }
 209     }
 210 
 211     private void addDMHMethodType(String dmh, String methodType) {
 212         validateMethodType(methodType);
 213         List<String> methodTypes = dmhMethods.get(dmh);
 214         if (methodTypes == null) {
 215             methodTypes = new ArrayList<>();
 216             dmhMethods.put(dmh, methodTypes);
 217         }
 218         methodTypes.add(methodType);
 219     }
 220 
 221     private Stream<String> fileLines(File file) {
 222         try {
 223             return Files.lines(file.toPath());
 224         } catch (IOException io) {
 225             throw new PluginException("Couldn't read file");
 226         }
 227     }
 228 
 229     private void validateMethodTypes(List<String> dmhMethodTypes) {
 230         for (String type : dmhMethodTypes) {
 231             validateMethodType(type);
 232         }
 233     }
 234 
 235     private void validateMethodType(String type) {
 236         String[] typeParts = type.split("_");
 237         // check return type (second part)
 238         if (typeParts.length != 2 || typeParts[1].length() != 1
 239                 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) {
 240             throw new PluginException(
 241                     "Method type signature must be of form [LJIFD]*_[LJIFDV]");
 242         }
 243         // expand and check arguments (first part)
 244         expandSignature(typeParts[0]);
 245     }
 246 
 247     private static void requireBasicType(char c) {
 248         if ("LIJFD".indexOf(c) < 0) {
 249             throw new PluginException(
 250                     "Character " + c + " must correspond to a basic field type: LIJFD");
 251         }
 252     }
 253 
 254     @Override
 255     public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
 256         // Copy all but DMH_ENTRY to out
 257         in.transformAndCopy(entry -> {
 258                 // filter out placeholder entries
 259                 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) ||
 260                     entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) ||
 261                     entry.path().equals(INVOKERS_HOLDER_ENTRY) ||
 262                     entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) {
 263                     return null;
 264                 } else {
 265                     return entry;
 266                 }
 267             }, out);
 268         speciesTypes.forEach(types -> generateBMHClass(types, out));
 269         generateHolderClasses(out);
 270         return out.build();
 271     }
 272 
 273     @SuppressWarnings("unchecked")
 274     private void generateBMHClass(String types, ResourcePoolBuilder out) {
 275         try {
 276             // Generate class
 277             Map.Entry<String, byte[]> result =
 278                     JLIA.generateConcreteBMHClassBytes(types);
 279             String className = result.getKey();
 280             byte[] bytes = result.getValue();
 281 
 282             // Add class to pool
 283             ResourcePoolEntry ndata = ResourcePoolEntry.create(
 284                     "/java.base/" + className + ".class",
 285                     bytes);
 286             out.add(ndata);
 287         } catch (Exception ex) {
 288             throw new PluginException(ex);
 289         }
 290     }
 291 
 292     private void generateHolderClasses(ResourcePoolBuilder out) {
 293         int count = 0;
 294         for (List<String> entry : dmhMethods.values()) {
 295             count += entry.size();
 296         }
 297         MethodType[] directMethodTypes = new MethodType[count];
 298         int[] dmhTypes = new int[count];
 299         int index = 0;
 300         for (Map.Entry<String, List<String>> entry : dmhMethods.entrySet()) {
 301             String dmhType = entry.getKey();
 302             for (String type : entry.getValue()) {
 303                 // The DMH type to actually ask for is retrieved by removing
 304                 // the first argument, which needs to be of Object.class
 305                 MethodType mt = asMethodType(type);
 306                 if (mt.parameterCount() < 1 ||
 307                     mt.parameterType(0) != Object.class) {
 308                     throw new PluginException(
 309                             "DMH type parameter must start with L");
 310                 }
 311                 directMethodTypes[index] = mt.dropParameterTypes(0, 1);
 312                 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType);
 313                 index++;
 314             }
 315         }
 316         MethodType[] invokerMethodTypes = new MethodType[this.invokerTypes.size()];
 317         for (int i = 0; i < invokerTypes.size(); i++) {
 318             // The invoker type to ask for is retrieved by removing the first
 319             // and the last argument, which needs to be of Object.class
 320             MethodType mt = asMethodType(invokerTypes.get(i));
 321             final int lastParam = mt.parameterCount() - 1;
 322             if (mt.parameterCount() < 2 ||
 323                     mt.parameterType(0) != Object.class ||
 324                     mt.parameterType(lastParam) != Object.class) {
 325                 throw new PluginException(
 326                         "Invoker type parameter must start and end with L");
 327             }
 328             mt = mt.dropParameterTypes(lastParam, lastParam + 1);
 329             invokerMethodTypes[i] = mt.dropParameterTypes(0, 1);
 330         }
 331         try {
 332             byte[] bytes = JLIA.generateDirectMethodHandleHolderClassBytes(
 333                     DIRECT_HOLDER, directMethodTypes, dmhTypes);
 334             ResourcePoolEntry ndata = ResourcePoolEntry
 335                     .create(DIRECT_METHOD_HOLDER_ENTRY, bytes);
 336             out.add(ndata);
 337 
 338             bytes = JLIA.generateDelegatingMethodHandleHolderClassBytes(
 339                     DELEGATING_HOLDER, directMethodTypes);
 340             ndata = ResourcePoolEntry.create(DELEGATING_METHOD_HOLDER_ENTRY, bytes);
 341             out.add(ndata);
 342 
 343             bytes = JLIA.generateInvokersHolderClassBytes(INVOKERS_HOLDER,
 344                     invokerMethodTypes);
 345             ndata = ResourcePoolEntry.create(INVOKERS_HOLDER_ENTRY, bytes);
 346             out.add(ndata);
 347 
 348             bytes = JLIA.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER);
 349             ndata = ResourcePoolEntry.create(BASIC_FORMS_HOLDER_ENTRY, bytes);
 350             out.add(ndata);
 351         } catch (Exception ex) {
 352             throw new PluginException(ex);
 353         }
 354     }
 355     private static final String DIRECT_METHOD_HOLDER_ENTRY =
 356             "/java.base/" + DIRECT_HOLDER + ".class";
 357     private static final String DELEGATING_METHOD_HOLDER_ENTRY =
 358             "/java.base/" + DELEGATING_HOLDER + ".class";
 359     private static final String BASIC_FORMS_HOLDER_ENTRY =
 360             "/java.base/" + BASIC_FORMS_HOLDER + ".class";
 361     private static final String INVOKERS_HOLDER_ENTRY =
 362             "/java.base/" + INVOKERS_HOLDER + ".class";
 363 
 364     // Convert LL -> LL, L3 -> LLL
 365     private static String expandSignature(String signature) {
 366         StringBuilder sb = new StringBuilder();
 367         char last = 'X';
 368         int count = 0;
 369         for (int i = 0; i < signature.length(); i++) {
 370             char c = signature.charAt(i);
 371             if (c >= '0' && c <= '9') {
 372                 count *= 10;
 373                 count += (c - '0');
 374             } else {
 375                 requireBasicType(c);
 376                 for (int j = 1; j < count; j++) {
 377                     sb.append(last);
 378                 }
 379                 sb.append(c);
 380                 last = c;
 381                 count = 0;
 382             }
 383         }
 384 
 385         // ended with a number, e.g., "L2": append last char count - 1 times
 386         if (count > 1) {
 387             requireBasicType(last);
 388             for (int j = 1; j < count; j++) {
 389                 sb.append(last);
 390             }
 391         }
 392         return sb.toString();
 393     }
 394 
 395     private static MethodType asMethodType(String basicSignatureString) {
 396         String[] parts = basicSignatureString.split("_");
 397         assert(parts.length == 2);
 398         assert(parts[1].length() == 1);
 399         String parameters = expandSignature(parts[0]);
 400         Class<?> rtype = simpleType(parts[1].charAt(0));
 401         if (parameters.isEmpty()) {
 402             return MethodType.methodType(rtype);
 403         } else {
 404             Class<?>[] ptypes = new Class<?>[parameters.length()];
 405             for (int i = 0; i < ptypes.length; i++) {
 406                 ptypes[i] = simpleType(parameters.charAt(i));
 407             }
 408             return MethodType.methodType(rtype, ptypes);
 409         }
 410     }
 411 
 412     private static Class<?> simpleType(char c) {
 413         switch (c) {
 414             case 'F':
 415                 return float.class;
 416             case 'D':
 417                 return double.class;
 418             case 'I':
 419                 return int.class;
 420             case 'L':
 421                 return Object.class;
 422             case 'J':
 423                 return long.class;
 424             case 'V':
 425                 return void.class;
 426             case 'Z':
 427             case 'B':
 428             case 'S':
 429             case 'C':
 430                 throw new IllegalArgumentException("Not a valid primitive: " + c +
 431                         " (use I instead)");
 432             default:
 433                 throw new IllegalArgumentException("Not a primitive: " + c);
 434         }
 435     }
 436 }