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.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.lang.module.ModuleDescriptor; 34 import java.nio.file.Files; 35 import java.util.EnumSet; 36 import java.util.Map; 37 import java.util.Optional; 38 import java.util.Set; 39 import java.util.TreeMap; 40 import java.util.TreeSet; 41 import java.util.stream.Collectors; 42 import java.util.stream.Stream; 43 import jdk.internal.misc.SharedSecrets; 44 import jdk.internal.misc.JavaLangInvokeAccess; 45 import jdk.tools.jlink.plugin.ResourcePoolEntry; 46 import jdk.tools.jlink.plugin.PluginException; 47 import jdk.tools.jlink.plugin.ResourcePool; 48 import jdk.tools.jlink.plugin.ResourcePoolBuilder; 49 import jdk.tools.jlink.plugin.Plugin; 50 51 /** 52 * Plugin to generate java.lang.invoke classes. 53 */ 54 public final class GenerateJLIClassesPlugin implements Plugin { 55 56 private static final String NAME = "generate-jli-classes"; 57 private static final String IGNORE_VERSION = "ignore-version"; 58 59 private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME); 60 private static final String IGNORE_VERSION_WARNING = NAME + ".ignore.version.warn"; 61 private static final String VERSION_MISMATCH_WARNING = NAME + ".version.mismatch.warn"; 62 63 private static final String DEFAULT_TRACE_FILE = "default_jli_trace.txt"; 64 65 private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder"; 66 private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual"; 67 private static final String DMH_INVOKE_STATIC = "invokeStatic"; 68 private static final String DMH_INVOKE_SPECIAL = "invokeSpecial"; 69 private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial"; 70 private static final String DMH_INVOKE_INTERFACE = "invokeInterface"; 71 private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit"; 72 73 private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder"; 74 private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder"; 75 private static final String INVOKERS_HOLDER = "java/lang/invoke/Invokers$Holder"; 76 77 private static final JavaLangInvokeAccess JLIA 78 = SharedSecrets.getJavaLangInvokeAccess(); 79 80 Set<String> speciesTypes = Set.of(); 81 82 Set<String> invokerTypes = Set.of(); 83 84 Map<String, Set<String>> dmhMethods = Map.of(); 85 86 String mainArgument; 87 88 boolean ignoreVersion; 89 90 public GenerateJLIClassesPlugin() { 91 } 92 93 @Override 94 public String getName() { 95 return NAME; 96 } 97 98 @Override 99 public String getDescription() { 100 return DESCRIPTION; 101 } 102 103 @Override 104 public Set<State> getState() { 105 return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL); 106 } 107 108 @Override 109 public boolean hasArguments() { 110 return true; 111 } 112 113 @Override 114 public String getArgumentsDescription() { 115 return PluginsResourceBundle.getArgument(NAME); 116 } 117 118 /** 119 * @return the default Species forms to generate. 120 * 121 * This list was derived from running a small startup benchmark. 122 * A better long-term solution is to define and run a set of quick 123 * generators and extracting this list as a step in the build process. 124 */ 125 public static Set<String> defaultSpecies() { 126 return Set.of("LL", "L3", "L4", "L5", "L6", "L7", "L7I", 127 "L7II", "L7IIL", "L8", "L9", "L10", "L10I", "L10II", "L10IIL", 128 "L11", "L12", "L13", "LI", "D", "L3I", "LIL", "LLI", "LLIL", 129 "LILL", "I", "LLILL"); 130 } 131 132 /** 133 * @return the default invoker forms to generate. 134 */ 135 private static Set<String> defaultInvokers() { 136 return Set.of("LL_L", "LL_I", "LILL_I", "L6_L"); 137 } 138 139 /** 140 * @return the list of default DirectMethodHandle methods to generate. 141 */ 142 private static Map<String, Set<String>> defaultDMHMethods() { 143 return Map.of( 144 DMH_INVOKE_VIRTUAL, Set.of("L_L", "LL_L", "LLI_I", "L3_V"), 145 DMH_INVOKE_SPECIAL, Set.of("LL_I", "LL_L", "LLF_L", "LLD_L", "L3_L", 146 "L4_L", "L5_L", "L6_L", "L7_L", "L8_L", "LLI_I", "LLI_L", 147 "LLIL_I", "LLII_I", "LLII_L", "L3I_L", "L3I_I", "LLILI_I", 148 "LLIIL_L", "LLIILL_L", "LLIILL_I", "LLIIL_I", "LLILIL_I", 149 "LLILILL_I", "LLILII_I", "LLI3_I", "LLI3L_I", "LLI3LL_I", 150 "LLI3_L", "LLI4_I"), 151 DMH_INVOKE_STATIC, Set.of("LII_I", "LIL_I", "LILIL_I", "LILII_I", 152 "L_I", "L_L", "L_V", "LD_L", "LF_L", "LI_I", "LII_L", "LLI_L", 153 "LL_V", "LL_L", "L3_L", "L4_L", "L5_L", "L6_L", "L7_L", 154 "L8_L", "L9_L", "L10_L", "L10I_L", "L10II_L", "L10IIL_L", 155 "L11_L", "L12_L", "L13_L", "L14_L", "L14I_L", "L14II_L") 156 ); 157 } 158 159 // Map from DirectMethodHandle method type to internal ID 160 private static final Map<String, Integer> DMH_METHOD_TYPE_MAP = 161 Map.of( 162 DMH_INVOKE_VIRTUAL, 0, 163 DMH_INVOKE_STATIC, 1, 164 DMH_INVOKE_SPECIAL, 2, 165 DMH_NEW_INVOKE_SPECIAL, 3, 166 DMH_INVOKE_INTERFACE, 4, 167 DMH_INVOKE_STATIC_INIT, 5 168 ); 169 170 @Override 171 public void configure(Map<String, String> config) { 172 mainArgument = config.get(NAME); 173 ignoreVersion = Boolean.parseBoolean(config.get(IGNORE_VERSION)); 174 } 175 176 public void initialize(ResourcePool in) { 177 // Start with the default configuration 178 speciesTypes = defaultSpecies().stream() 179 .map(type -> expandSignature(type)) 180 .collect(Collectors.toSet()); 181 182 invokerTypes = defaultInvokers(); 183 validateMethodTypes(invokerTypes); 184 185 dmhMethods = defaultDMHMethods(); 186 for (Set<String> dmhMethodTypes : dmhMethods.values()) { 187 validateMethodTypes(dmhMethodTypes); 188 } 189 190 // Extend the default configuration with the contents in the supplied 191 // input file - if none was supplied we look for the default file 192 if (mainArgument == null || !mainArgument.startsWith("@")) { 193 try (InputStream traceFile = 194 this.getClass().getResourceAsStream(DEFAULT_TRACE_FILE)) { 195 if (traceFile != null) { 196 readTraceConfig( 197 new BufferedReader( 198 new InputStreamReader(traceFile)).lines()); 199 } 200 } catch (Exception e) { 201 throw new PluginException("Couldn't read " + DEFAULT_TRACE_FILE, e); 202 } 203 } else { 204 File file = new File(mainArgument.substring(1)); 205 if (file.exists()) { 206 readTraceConfig(fileLines(file)); 207 } 208 } 209 } 210 211 private boolean checkVersion(Runtime.Version linkedVersion) { 212 Runtime.Version baseVersion = Runtime.version(); 213 if (baseVersion.major() != linkedVersion.major() || 214 baseVersion.minor() != linkedVersion.minor()) { 215 return false; 216 } 217 return true; 218 } 219 220 private Runtime.Version getLinkedVersion(ResourcePool in) { 221 ModuleDescriptor.Version version = in.moduleView() 222 .findModule("java.base") 223 .get() 224 .descriptor() 225 .version() 226 .orElseThrow(() -> new PluginException("No version defined in " 227 + "the java.base being linked")); 228 return Runtime.Version.parse(version.toString()); 229 } 230 231 private void readTraceConfig(Stream<String> lines) { 232 // Use TreeSet/TreeMap to keep things sorted in a deterministic 233 // order to avoid scrambling the layout on small changes and to 234 // ease finding methods in the generated code 235 speciesTypes = new TreeSet<>(speciesTypes); 236 invokerTypes = new TreeSet<>(invokerTypes); 237 TreeMap<String, Set<String>> newDMHMethods = new TreeMap<>(); 238 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) { 239 newDMHMethods.put(entry.getKey(), new TreeSet<>(entry.getValue())); 240 } 241 dmhMethods = newDMHMethods; 242 lines.map(line -> line.split(" ")) 243 .forEach(parts -> { 244 switch (parts[0]) { 245 case "[SPECIES_RESOLVE]": 246 // Allow for new types of species data classes being resolved here 247 if (parts.length == 3 && parts[1].startsWith("java.lang.invoke.BoundMethodHandle$Species_")) { 248 String species = parts[1].substring("java.lang.invoke.BoundMethodHandle$Species_".length()); 249 if (!"L".equals(species)) { 250 speciesTypes.add(expandSignature(species)); 251 } 252 } 253 break; 254 case "[LF_RESOLVE]": 255 String methodType = parts[3]; 256 validateMethodType(methodType); 257 if (parts[1].contains("Invokers")) { 258 invokerTypes.add(methodType); 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 if (ignoreVersion) { 319 System.out.println( 320 PluginsResourceBundle 321 .getMessage(IGNORE_VERSION_WARNING)); 322 } else if (!checkVersion(getLinkedVersion(in))) { 323 // The linked images are not version compatible 324 if (mainArgument != null) { 325 // Log a mismatch warning if an argument was specified 326 System.out.println( 327 PluginsResourceBundle 328 .getMessage(VERSION_MISMATCH_WARNING, 329 getLinkedVersion(in), 330 Runtime.version())); 331 } 332 in.transformAndCopy(entry -> entry, out); 333 return out.build(); 334 } 335 336 initialize(in); 337 // Copy all but DMH_ENTRY to out 338 in.transformAndCopy(entry -> { 339 // filter out placeholder entries 340 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) || 341 entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) || 342 entry.path().equals(INVOKERS_HOLDER_ENTRY) || 343 entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) { 344 return null; 345 } else { 346 return entry; 347 } 348 }, out); 349 350 // Generate BMH Species classes 351 speciesTypes.forEach(types -> generateBMHClass(types, out)); 352 353 // Generate LambdaForm Holder classes 354 generateHolderClasses(out); 355 356 // Let it go 357 speciesTypes = null; 358 invokerTypes = null; 359 dmhMethods = null; 360 361 return out.build(); 362 } 363 364 @SuppressWarnings("unchecked") 365 private void generateBMHClass(String types, ResourcePoolBuilder out) { 366 try { 367 // Generate class 368 Map.Entry<String, byte[]> result = 369 JLIA.generateConcreteBMHClassBytes(types); 370 String className = result.getKey(); 371 byte[] bytes = result.getValue(); 372 373 // Add class to pool 374 ResourcePoolEntry ndata = ResourcePoolEntry.create( 375 "/java.base/" + className + ".class", 376 bytes); 377 out.add(ndata); 378 } catch (Exception ex) { 379 throw new PluginException(ex); 380 } 381 } 382 383 private void generateHolderClasses(ResourcePoolBuilder out) { 384 int count = 0; 385 for (Set<String> entry : dmhMethods.values()) { 386 count += entry.size(); 387 } 388 MethodType[] directMethodTypes = new MethodType[count]; 389 int[] dmhTypes = new int[count]; 390 int index = 0; 391 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) { 392 String dmhType = entry.getKey(); 393 for (String type : entry.getValue()) { 394 // The DMH type to actually ask for is retrieved by removing 395 // the first argument, which needs to be of Object.class 396 MethodType mt = asMethodType(type); 397 if (mt.parameterCount() < 1 || 398 mt.parameterType(0) != Object.class) { 399 throw new PluginException( 400 "DMH type parameter must start with L"); 401 } 402 directMethodTypes[index] = mt.dropParameterTypes(0, 1); 403 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType); 404 index++; 405 } 406 } 407 MethodType[] invokerMethodTypes = new MethodType[this.invokerTypes.size()]; 408 int i = 0; 409 for (String invokerType : invokerTypes) { 410 // The invoker type to ask for is retrieved by removing the first 411 // and the last argument, which needs to be of Object.class 412 MethodType mt = asMethodType(invokerType); 413 final int lastParam = mt.parameterCount() - 1; 414 if (mt.parameterCount() < 2 || 415 mt.parameterType(0) != Object.class || 416 mt.parameterType(lastParam) != Object.class) { 417 throw new PluginException( 418 "Invoker type parameter must start and end with L"); 419 } 420 mt = mt.dropParameterTypes(lastParam, lastParam + 1); 421 invokerMethodTypes[i] = mt.dropParameterTypes(0, 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, 437 invokerMethodTypes); 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 + ".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 }