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