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 }