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 "[BMH_RESOLVE]": 246 speciesTypes.add(expandSignature(parts[1])); 247 break; 248 case "[LF_RESOLVE]": 249 String methodType = parts[3]; 250 validateMethodType(methodType); 251 if (parts[1].contains("Invokers")) { 252 invokerTypes.add(methodType); 253 } else if (parts[1].contains("DirectMethodHandle")) { 254 String dmh = parts[2]; 255 // ignore getObject etc for now (generated 256 // by default) 257 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) { 258 addDMHMethodType(dmh, methodType); 259 } 260 } 261 break; 262 default: break; // ignore 263 } 264 }); 265 } 266 267 private void addDMHMethodType(String dmh, String methodType) { 268 validateMethodType(methodType); 269 Set<String> methodTypes = dmhMethods.get(dmh); 270 if (methodTypes == null) { 271 methodTypes = new TreeSet<>(); 272 dmhMethods.put(dmh, methodTypes); 273 } 274 methodTypes.add(methodType); 275 } 276 277 private Stream<String> fileLines(File file) { 278 try { 279 return Files.lines(file.toPath()); 280 } catch (IOException io) { 281 throw new PluginException("Couldn't read file"); 282 } 283 } 284 285 private void validateMethodTypes(Set<String> dmhMethodTypes) { 286 for (String type : dmhMethodTypes) { 287 validateMethodType(type); 288 } 289 } 290 291 private void validateMethodType(String type) { 292 String[] typeParts = type.split("_"); 293 // check return type (second part) 294 if (typeParts.length != 2 || typeParts[1].length() != 1 295 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) { 296 throw new PluginException( 297 "Method type signature must be of form [LJIFD]*_[LJIFDV]"); 298 } 299 // expand and check arguments (first part) 300 expandSignature(typeParts[0]); 301 } 302 303 private static void requireBasicType(char c) { 304 if ("LIJFD".indexOf(c) < 0) { 305 throw new PluginException( 306 "Character " + c + " must correspond to a basic field type: LIJFD"); 307 } 308 } 309 310 @Override 311 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { 312 if (ignoreVersion) { 313 System.out.println( 314 PluginsResourceBundle 315 .getMessage(IGNORE_VERSION_WARNING)); 316 } else if (!checkVersion(getLinkedVersion(in))) { 317 // The linked images are not version compatible 318 if (mainArgument != null) { 319 // Log a mismatch warning if an argument was specified 320 System.out.println( 321 PluginsResourceBundle 322 .getMessage(VERSION_MISMATCH_WARNING, 323 getLinkedVersion(in), 324 Runtime.version())); 325 } 326 in.transformAndCopy(entry -> entry, out); 327 return out.build(); 328 } 329 330 initialize(in); 331 // Copy all but DMH_ENTRY to out 332 in.transformAndCopy(entry -> { 333 // filter out placeholder entries 334 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) || 335 entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) || 336 entry.path().equals(INVOKERS_HOLDER_ENTRY) || 337 entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) { 338 return null; 339 } else { 340 return entry; 341 } 342 }, out); 343 344 // Generate BMH Species classes 345 speciesTypes.forEach(types -> generateBMHClass(types, out)); 346 347 // Generate LambdaForm Holder classes 348 generateHolderClasses(out); 349 350 // Let it go 351 speciesTypes = null; 352 invokerTypes = null; 353 dmhMethods = null; 354 355 return out.build(); 356 } 357 358 @SuppressWarnings("unchecked") 359 private void generateBMHClass(String types, ResourcePoolBuilder out) { 360 try { 361 // Generate class 362 Map.Entry<String, byte[]> result = 363 JLIA.generateConcreteBMHClassBytes(types); 364 String className = result.getKey(); 365 byte[] bytes = result.getValue(); 366 367 // Add class to pool 368 ResourcePoolEntry ndata = ResourcePoolEntry.create( 369 "/java.base/" + className + ".class", 370 bytes); 371 out.add(ndata); 372 } catch (Exception ex) { 373 throw new PluginException(ex); 374 } 375 } 376 377 private void generateHolderClasses(ResourcePoolBuilder out) { 378 int count = 0; 379 for (Set<String> entry : dmhMethods.values()) { 380 count += entry.size(); 381 } 382 MethodType[] directMethodTypes = new MethodType[count]; 383 int[] dmhTypes = new int[count]; 384 int index = 0; 385 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) { 386 String dmhType = entry.getKey(); 387 for (String type : entry.getValue()) { 388 // The DMH type to actually ask for is retrieved by removing 389 // the first argument, which needs to be of Object.class 390 MethodType mt = asMethodType(type); 391 if (mt.parameterCount() < 1 || 392 mt.parameterType(0) != Object.class) { 393 throw new PluginException( 394 "DMH type parameter must start with L"); 395 } 396 directMethodTypes[index] = mt.dropParameterTypes(0, 1); 397 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType); 398 index++; 399 } 400 } 401 MethodType[] invokerMethodTypes = new MethodType[this.invokerTypes.size()]; 402 int i = 0; 403 for (String invokerType : invokerTypes) { 404 // The invoker type to ask for is retrieved by removing the first 405 // and the last argument, which needs to be of Object.class 406 MethodType mt = asMethodType(invokerType); 407 final int lastParam = mt.parameterCount() - 1; 408 if (mt.parameterCount() < 2 || 409 mt.parameterType(0) != Object.class || 410 mt.parameterType(lastParam) != Object.class) { 411 throw new PluginException( 412 "Invoker type parameter must start and end with L"); 413 } 414 mt = mt.dropParameterTypes(lastParam, lastParam + 1); 415 invokerMethodTypes[i] = mt.dropParameterTypes(0, 1); 416 i++; 417 } 418 try { 419 byte[] bytes = JLIA.generateDirectMethodHandleHolderClassBytes( 420 DIRECT_HOLDER, directMethodTypes, dmhTypes); 421 ResourcePoolEntry ndata = ResourcePoolEntry 422 .create(DIRECT_METHOD_HOLDER_ENTRY, bytes); 423 out.add(ndata); 424 425 bytes = JLIA.generateDelegatingMethodHandleHolderClassBytes( 426 DELEGATING_HOLDER, directMethodTypes); 427 ndata = ResourcePoolEntry.create(DELEGATING_METHOD_HOLDER_ENTRY, bytes); 428 out.add(ndata); 429 430 bytes = JLIA.generateInvokersHolderClassBytes(INVOKERS_HOLDER, 431 invokerMethodTypes); 432 ndata = ResourcePoolEntry.create(INVOKERS_HOLDER_ENTRY, bytes); 433 out.add(ndata); 434 435 bytes = JLIA.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER); 436 ndata = ResourcePoolEntry.create(BASIC_FORMS_HOLDER_ENTRY, bytes); 437 out.add(ndata); 438 } catch (Exception ex) { 439 throw new PluginException(ex); 440 } 441 } 442 private static final String DIRECT_METHOD_HOLDER_ENTRY = 443 "/java.base/" + DIRECT_HOLDER + ".class"; 444 private static final String DELEGATING_METHOD_HOLDER_ENTRY = 445 "/java.base/" + DELEGATING_HOLDER + ".class"; 446 private static final String BASIC_FORMS_HOLDER_ENTRY = 447 "/java.base/" + BASIC_FORMS_HOLDER + ".class"; 448 private static final String INVOKERS_HOLDER_ENTRY = 449 "/java.base/" + INVOKERS_HOLDER + ".class"; 450 451 // Convert LL -> LL, L3 -> LLL 452 private static String expandSignature(String signature) { 453 StringBuilder sb = new StringBuilder(); 454 char last = 'X'; 455 int count = 0; 456 for (int i = 0; i < signature.length(); i++) { 457 char c = signature.charAt(i); 458 if (c >= '0' && c <= '9') { 459 count *= 10; 460 count += (c - '0'); 461 } else { 462 requireBasicType(c); 463 for (int j = 1; j < count; j++) { 464 sb.append(last); 465 } 466 sb.append(c); 467 last = c; 468 count = 0; 469 } 470 } 471 472 // ended with a number, e.g., "L2": append last char count - 1 times 473 if (count > 1) { 474 requireBasicType(last); 475 for (int j = 1; j < count; j++) { 476 sb.append(last); 477 } 478 } 479 return sb.toString(); 480 } 481 482 private static MethodType asMethodType(String basicSignatureString) { 483 String[] parts = basicSignatureString.split("_"); 484 assert(parts.length == 2); 485 assert(parts[1].length() == 1); 486 String parameters = expandSignature(parts[0]); 487 Class<?> rtype = simpleType(parts[1].charAt(0)); 488 if (parameters.isEmpty()) { 489 return MethodType.methodType(rtype); 490 } else { 491 Class<?>[] ptypes = new Class<?>[parameters.length()]; 492 for (int i = 0; i < ptypes.length; i++) { 493 ptypes[i] = simpleType(parameters.charAt(i)); 494 } 495 return MethodType.methodType(rtype, ptypes); 496 } 497 } 498 499 private static Class<?> simpleType(char c) { 500 switch (c) { 501 case 'F': 502 return float.class; 503 case 'D': 504 return double.class; 505 case 'I': 506 return int.class; 507 case 'L': 508 return Object.class; 509 case 'J': 510 return long.class; 511 case 'V': 512 return void.class; 513 case 'Z': 514 case 'B': 515 case 'S': 516 case 'C': 517 throw new IllegalArgumentException("Not a valid primitive: " + c + 518 " (use I instead)"); 519 default: 520 throw new IllegalArgumentException("Not a primitive: " + c); 521 } 522 } 523 }