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.EnumSet;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.Set;
  36 import java.util.TreeMap;
  37 import java.util.TreeSet;
  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     Set<String> speciesTypes;
  73 
  74     Set<String> invokerTypes;
  75 
  76     Map<String, Set<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     public static Set<String> defaultSpecies() {
 114         return Set.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 Set<String> defaultInvokers() {
 124         return Set.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, Set<String>> defaultDMHMethods() {
 131         return Map.of(
 132             DMH_INVOKE_VIRTUAL, Set.of("L_L", "LL_L", "LLI_I", "L3_V"),
 133             DMH_INVOKE_SPECIAL, Set.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, Set.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 ("none".equals(mainArgument)) {
 163             speciesTypes = Set.of();
 164             invokerTypes = Set.of();
 165             dmhMethods = Map.of();
 166             return;
 167         }
 168 
 169         // Start with the default configuration
 170         Set<String> defaultBMHSpecies = defaultSpecies();
 171         // Expand BMH species signatures
 172         defaultBMHSpecies = defaultBMHSpecies.stream()
 173                 .map(type -> expandSignature(type))
 174                 .collect(Collectors.toSet());
 175 
 176         Set<String> defaultInvokerTypes = defaultInvokers();
 177         validateMethodTypes(defaultInvokerTypes);
 178 
 179         Map<String, Set<String>> defaultDmhMethods = defaultDMHMethods();
 180         for (Set<String> dmhMethodTypes : defaultDmhMethods.values()) {
 181             validateMethodTypes(dmhMethodTypes);
 182         }
 183 
 184         // Extend the default configuration with the contents in the supplied
 185         // input file
 186         if (mainArgument == null || !mainArgument.startsWith("@")) {
 187             speciesTypes = defaultBMHSpecies;
 188             invokerTypes = defaultInvokerTypes;
 189             dmhMethods = defaultDmhMethods;
 190         } else {
 191             File file = new File(mainArgument.substring(1));
 192             if (file.exists()) {
 193                 // Use TreeSet/TreeMap to keep things sorted in a deterministic
 194                 // order to avoid scrambling the layout on small changes and to
 195                 // ease finding methods in the generated code
 196                 speciesTypes = new TreeSet<>(defaultBMHSpecies);
 197                 invokerTypes = new TreeSet<>(defaultInvokerTypes);
 198                 dmhMethods = new TreeMap<>();
 199                 for (Map.Entry<String, Set<String>> entry : defaultDmhMethods.entrySet()) {
 200                     dmhMethods.put(entry.getKey(), new TreeSet<>(entry.getValue()));
 201                 }
 202                 fileLines(file)
 203                     .map(line -> line.split(" "))
 204                     .forEach(parts -> {
 205                         switch (parts[0]) {
 206                             case "[BMH_RESOLVE]":
 207                                 speciesTypes.add(expandSignature(parts[1]));
 208                                 break;
 209                             case "[LF_RESOLVE]":
 210                                 String methodType = parts[3];
 211                                 validateMethodType(methodType);
 212                                 if (parts[1].contains("Invokers")) {
 213                                     invokerTypes.add(methodType);
 214                                 } else if (parts[1].contains("DirectMethodHandle")) {
 215                                     String dmh = parts[2];
 216                                     // ignore getObject etc for now (generated
 217                                     // by default)
 218                                     if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
 219                                         addDMHMethodType(dmh, methodType);
 220                                     }
 221                                 }
 222                                 break;
 223                             default: break; // ignore
 224                         }
 225                 });
 226             }
 227         }
 228     }
 229 
 230     private void addDMHMethodType(String dmh, String methodType) {
 231         validateMethodType(methodType);
 232         Set<String> methodTypes = dmhMethods.get(dmh);
 233         if (methodTypes == null) {
 234             methodTypes = new TreeSet<>();
 235             dmhMethods.put(dmh, methodTypes);
 236         }
 237         methodTypes.add(methodType);
 238     }
 239 
 240     private Stream<String> fileLines(File file) {
 241         try {
 242             return Files.lines(file.toPath());
 243         } catch (IOException io) {
 244             throw new PluginException("Couldn't read file");
 245         }
 246     }
 247 
 248     private void validateMethodTypes(Set<String> dmhMethodTypes) {
 249         for (String type : dmhMethodTypes) {
 250             validateMethodType(type);
 251         }
 252     }
 253 
 254     private void validateMethodType(String type) {
 255         String[] typeParts = type.split("_");
 256         // check return type (second part)
 257         if (typeParts.length != 2 || typeParts[1].length() != 1
 258                 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) {
 259             throw new PluginException(
 260                     "Method type signature must be of form [LJIFD]*_[LJIFDV]");
 261         }
 262         // expand and check arguments (first part)
 263         expandSignature(typeParts[0]);
 264     }
 265 
 266     private static void requireBasicType(char c) {
 267         if ("LIJFD".indexOf(c) < 0) {
 268             throw new PluginException(
 269                     "Character " + c + " must correspond to a basic field type: LIJFD");
 270         }
 271     }
 272 
 273     @Override
 274     public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
 275         // Copy all but DMH_ENTRY to out
 276         in.transformAndCopy(entry -> {
 277                 // filter out placeholder entries
 278                 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) ||
 279                     entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) ||
 280                     entry.path().equals(INVOKERS_HOLDER_ENTRY) ||
 281                     entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) {
 282                     return null;
 283                 } else {
 284                     return entry;
 285                 }
 286             }, out);
 287         speciesTypes.forEach(types -> generateBMHClass(types, out));
 288         generateHolderClasses(out);
 289         return out.build();
 290     }
 291 
 292     @SuppressWarnings("unchecked")
 293     private void generateBMHClass(String types, ResourcePoolBuilder out) {
 294         try {
 295             // Generate class
 296             Map.Entry<String, byte[]> result =
 297                     JLIA.generateConcreteBMHClassBytes(types);
 298             String className = result.getKey();
 299             byte[] bytes = result.getValue();
 300 
 301             // Add class to pool
 302             ResourcePoolEntry ndata = ResourcePoolEntry.create(
 303                     "/java.base/" + className + ".class",
 304                     bytes);
 305             out.add(ndata);
 306         } catch (Exception ex) {
 307             throw new PluginException(ex);
 308         }
 309     }
 310 
 311     private void generateHolderClasses(ResourcePoolBuilder out) {
 312         int count = 0;
 313         for (Set<String> entry : dmhMethods.values()) {
 314             count += entry.size();
 315         }
 316         MethodType[] directMethodTypes = new MethodType[count];
 317         int[] dmhTypes = new int[count];
 318         int index = 0;
 319         for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) {
 320             String dmhType = entry.getKey();
 321             for (String type : entry.getValue()) {
 322                 // The DMH type to actually ask for is retrieved by removing
 323                 // the first argument, which needs to be of Object.class
 324                 MethodType mt = asMethodType(type);
 325                 if (mt.parameterCount() < 1 ||
 326                     mt.parameterType(0) != Object.class) {
 327                     throw new PluginException(
 328                             "DMH type parameter must start with L");
 329                 }
 330                 directMethodTypes[index] = mt.dropParameterTypes(0, 1);
 331                 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType);
 332                 index++;
 333             }
 334         }
 335         MethodType[] invokerMethodTypes = new MethodType[this.invokerTypes.size()];
 336         int i = 0;
 337         for (String invokerType : invokerTypes) {
 338             // The invoker type to ask for is retrieved by removing the first
 339             // and the last argument, which needs to be of Object.class
 340             MethodType mt = asMethodType(invokerType);
 341             final int lastParam = mt.parameterCount() - 1;
 342             if (mt.parameterCount() < 2 ||
 343                     mt.parameterType(0) != Object.class ||
 344                     mt.parameterType(lastParam) != Object.class) {
 345                 throw new PluginException(
 346                         "Invoker type parameter must start and end with L");
 347             }
 348             mt = mt.dropParameterTypes(lastParam, lastParam + 1);
 349             invokerMethodTypes[i] = mt.dropParameterTypes(0, 1);
 350             i++;
 351         }
 352         try {
 353             byte[] bytes = JLIA.generateDirectMethodHandleHolderClassBytes(
 354                     DIRECT_HOLDER, directMethodTypes, dmhTypes);
 355             ResourcePoolEntry ndata = ResourcePoolEntry
 356                     .create(DIRECT_METHOD_HOLDER_ENTRY, bytes);
 357             out.add(ndata);
 358 
 359             bytes = JLIA.generateDelegatingMethodHandleHolderClassBytes(
 360                     DELEGATING_HOLDER, directMethodTypes);
 361             ndata = ResourcePoolEntry.create(DELEGATING_METHOD_HOLDER_ENTRY, bytes);
 362             out.add(ndata);
 363 
 364             bytes = JLIA.generateInvokersHolderClassBytes(INVOKERS_HOLDER,
 365                     invokerMethodTypes);
 366             ndata = ResourcePoolEntry.create(INVOKERS_HOLDER_ENTRY, bytes);
 367             out.add(ndata);
 368 
 369             bytes = JLIA.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER);
 370             ndata = ResourcePoolEntry.create(BASIC_FORMS_HOLDER_ENTRY, bytes);
 371             out.add(ndata);
 372         } catch (Exception ex) {
 373             throw new PluginException(ex);
 374         }
 375     }
 376     private static final String DIRECT_METHOD_HOLDER_ENTRY =
 377             "/java.base/" + DIRECT_HOLDER + ".class";
 378     private static final String DELEGATING_METHOD_HOLDER_ENTRY =
 379             "/java.base/" + DELEGATING_HOLDER + ".class";
 380     private static final String BASIC_FORMS_HOLDER_ENTRY =
 381             "/java.base/" + BASIC_FORMS_HOLDER + ".class";
 382     private static final String INVOKERS_HOLDER_ENTRY =
 383             "/java.base/" + INVOKERS_HOLDER + ".class";
 384 
 385     // Convert LL -> LL, L3 -> LLL
 386     private static String expandSignature(String signature) {
 387         StringBuilder sb = new StringBuilder();
 388         char last = 'X';
 389         int count = 0;
 390         for (int i = 0; i < signature.length(); i++) {
 391             char c = signature.charAt(i);
 392             if (c >= '0' && c <= '9') {
 393                 count *= 10;
 394                 count += (c - '0');
 395             } else {
 396                 requireBasicType(c);
 397                 for (int j = 1; j < count; j++) {
 398                     sb.append(last);
 399                 }
 400                 sb.append(c);
 401                 last = c;
 402                 count = 0;
 403             }
 404         }
 405 
 406         // ended with a number, e.g., "L2": append last char count - 1 times
 407         if (count > 1) {
 408             requireBasicType(last);
 409             for (int j = 1; j < count; j++) {
 410                 sb.append(last);
 411             }
 412         }
 413         return sb.toString();
 414     }
 415 
 416     private static MethodType asMethodType(String basicSignatureString) {
 417         String[] parts = basicSignatureString.split("_");
 418         assert(parts.length == 2);
 419         assert(parts[1].length() == 1);
 420         String parameters = expandSignature(parts[0]);
 421         Class<?> rtype = simpleType(parts[1].charAt(0));
 422         if (parameters.isEmpty()) {
 423             return MethodType.methodType(rtype);
 424         } else {
 425             Class<?>[] ptypes = new Class<?>[parameters.length()];
 426             for (int i = 0; i < ptypes.length; i++) {
 427                 ptypes[i] = simpleType(parameters.charAt(i));
 428             }
 429             return MethodType.methodType(rtype, ptypes);
 430         }
 431     }
 432 
 433     private static Class<?> simpleType(char c) {
 434         switch (c) {
 435             case 'F':
 436                 return float.class;
 437             case 'D':
 438                 return double.class;
 439             case 'I':
 440                 return int.class;
 441             case 'L':
 442                 return Object.class;
 443             case 'J':
 444                 return long.class;
 445             case 'V':
 446                 return void.class;
 447             case 'Z':
 448             case 'B':
 449             case 'S':
 450             case 'C':
 451                 throw new IllegalArgumentException("Not a valid primitive: " + c +
 452                         " (use I instead)");
 453             default:
 454                 throw new IllegalArgumentException("Not a primitive: " + c);
 455         }
 456     }
 457 }