1 /* 2 * Copyright (c) 2016, 2025, 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 26 package java.lang.invoke; 27 28 import sun.invoke.util.Wrapper; 29 import sun.util.logging.PlatformLogger; 30 31 import java.lang.classfile.ClassFile; 32 import java.lang.classfile.attribute.SourceFileAttribute; 33 import java.lang.constant.ClassDesc; 34 import java.util.ArrayList; 35 import java.util.HashSet; 36 import java.util.Map; 37 import java.util.Objects; 38 import java.util.Set; 39 import java.util.TreeMap; 40 import java.util.TreeSet; 41 import java.util.stream.Stream; 42 43 import static java.lang.classfile.ClassFile.*; 44 import static java.lang.invoke.LambdaForm.BasicType.*; 45 import static java.lang.invoke.LambdaForm.Kind.*; 46 import static java.lang.invoke.MethodTypeForm.*; 47 48 /** 49 * Helper class to assist the GenerateJLIClassesPlugin to get access to 50 * generate classes ahead of time. 51 */ 52 class GenerateJLIClassesHelper { 53 // Map from DirectMethodHandle method type name to index to LambdaForms 54 static final Map<String, Integer> DMH_METHOD_TYPE_MAP = 55 Map.of( 56 DIRECT_INVOKE_VIRTUAL.methodName, LF_INVVIRTUAL, 57 DIRECT_INVOKE_STATIC.methodName, LF_INVSTATIC, 58 DIRECT_INVOKE_SPECIAL.methodName, LF_INVSPECIAL, 59 DIRECT_NEW_INVOKE_SPECIAL.methodName, LF_NEWINVSPECIAL, 60 DIRECT_INVOKE_INTERFACE.methodName, LF_INVINTERFACE, 61 DIRECT_INVOKE_STATIC_INIT.methodName, LF_INVSTATIC_INIT, 62 DIRECT_INVOKE_SPECIAL_IFC.methodName, LF_INVSPECIAL_IFC 63 ); 64 65 static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder"; 66 static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder"; 67 static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder"; 68 static final String INVOKERS_HOLDER = "java/lang/invoke/Invokers$Holder"; 69 static final String INVOKERS_HOLDER_CLASS_NAME = INVOKERS_HOLDER.replace('/', '.'); 70 static final String BMH_SPECIES_PREFIX = "java.lang.invoke.BoundMethodHandle$Species_"; 71 72 static class HolderClassBuilder { 73 74 75 private final TreeSet<String> speciesTypes = new TreeSet<>(); 76 private final TreeSet<String> invokerTypes = new TreeSet<>(); 77 private final TreeSet<String> callSiteTypes = new TreeSet<>(); 78 private final Map<String, Set<String>> dmhMethods = new TreeMap<>(); 79 80 HolderClassBuilder addSpeciesType(String type) { 81 speciesTypes.add(expandSignature(type)); 82 return this; 83 } 84 85 HolderClassBuilder addInvokerType(String methodType) { 86 validateMethodType(methodType); 87 invokerTypes.add(methodType); 88 return this; 89 } 90 91 HolderClassBuilder addCallSiteType(String csType) { 92 validateMethodType(csType); 93 callSiteTypes.add(csType); 94 return this; 95 } 96 97 Map<String, byte[]> build() { 98 int count = 0; 99 for (Set<String> entry : dmhMethods.values()) { 100 count += entry.size(); 101 } 102 MethodType[] directMethodTypes = new MethodType[count]; 103 int[] dmhTypes = new int[count]; 104 int index = 0; 105 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) { 106 String dmhType = entry.getKey(); 107 for (String type : entry.getValue()) { 108 // The DMH type to actually ask for is retrieved by removing 109 // the first argument, which needs to be of Object.class 110 MethodType mt = asMethodType(type); 111 if (mt.parameterCount() < 1 || 112 mt.parameterType(0) != Object.class) { 113 throw new RuntimeException( 114 "DMH type parameter must start with L: " + dmhType + " " + type); 115 } 116 // Adapt the method type of the LF to retrieve 117 directMethodTypes[index] = mt.dropParameterTypes(0, 1); 118 // invokeVirtual and invokeInterface must have a leading Object 119 // parameter, i.e., the receiver 120 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType); 121 if (dmhTypes[index] == LF_INVINTERFACE || dmhTypes[index] == LF_INVVIRTUAL) { 122 if (mt.parameterCount() < 2 || 123 mt.parameterType(1) != Object.class) { 124 throw new RuntimeException( 125 "DMH type parameter must start with LL: " + dmhType + " " + type); 126 } 127 } 128 index++; 129 } 130 } 131 132 // The invoker type to ask for is retrieved by removing the first 133 // and the last argument, which needs to be of Object.class 134 MethodType[] invokerMethodTypes = new MethodType[invokerTypes.size()]; 135 index = 0; 136 for (String invokerType : invokerTypes) { 137 MethodType mt = asMethodType(invokerType); 138 final int lastParam = mt.parameterCount() - 1; 139 if (!checkInvokerTypeParams(mt)) { 140 throw new RuntimeException( 141 "Invoker type parameter must start and end with Object: " + invokerType); 142 } 143 mt = mt.dropParameterTypes(lastParam, lastParam + 1); 144 invokerMethodTypes[index] = mt.dropParameterTypes(0, 1); 145 index++; 146 } 147 148 // The callSite type to ask for is retrieved by removing the last 149 // argument, which needs to be of Object.class 150 MethodType[] callSiteMethodTypes = new MethodType[callSiteTypes.size()]; 151 index = 0; 152 for (String callSiteType : callSiteTypes) { 153 MethodType mt = asMethodType(callSiteType); 154 final int lastParam = mt.parameterCount() - 1; 155 if (mt.parameterCount() < 1 || 156 mt.parameterType(lastParam) != Object.class) { 157 throw new RuntimeException( 158 "CallSite type parameter must end with Object: " + callSiteType); 159 } 160 callSiteMethodTypes[index] = mt.dropParameterTypes(lastParam, lastParam + 1); 161 index++; 162 } 163 Map<String, byte[]> result = new TreeMap<>(); 164 result.put(DIRECT_HOLDER, 165 generateDirectMethodHandleHolderClassBytes( 166 DIRECT_HOLDER, directMethodTypes, dmhTypes)); 167 result.put(DELEGATING_HOLDER, 168 generateDelegatingMethodHandleHolderClassBytes( 169 DELEGATING_HOLDER, directMethodTypes)); 170 result.put(INVOKERS_HOLDER, 171 generateInvokersHolderClassBytes(INVOKERS_HOLDER, 172 invokerMethodTypes, callSiteMethodTypes)); 173 result.put(BASIC_FORMS_HOLDER, 174 generateBasicFormsClassBytes(BASIC_FORMS_HOLDER)); 175 176 speciesTypes.forEach(types -> { 177 Map.Entry<String, byte[]> entry = generateConcreteBMHClassBytes(types); 178 result.put(entry.getKey(), entry.getValue()); 179 }); 180 // clear builder 181 speciesTypes.clear(); 182 invokerTypes.clear(); 183 callSiteTypes.clear(); 184 dmhMethods.clear(); 185 return result; 186 } 187 188 public static MethodType asMethodType(String basicSignatureString) { 189 String[] parts = basicSignatureString.split("_"); 190 assert (parts.length == 2); 191 assert (parts[1].length() == 1); 192 String parameters = expandSignature(parts[0]); 193 Class<?> rtype = simpleType(parts[1].charAt(0)); 194 if (parameters.isEmpty()) { 195 return MethodType.methodType(rtype); 196 } else { 197 Class<?>[] ptypes = new Class<?>[parameters.length()]; 198 for (int i = 0; i < ptypes.length; i++) { 199 ptypes[i] = simpleType(parameters.charAt(i)); 200 } 201 return MethodType.methodType(rtype, ptypes); 202 } 203 } 204 205 public static boolean checkInvokerTypeParams(MethodType mt) { 206 final int lastParam = mt.parameterCount() - 1; 207 return (mt.parameterCount() >= 2 && 208 mt.parameterType(0) == Object.class && 209 mt.parameterType(lastParam) == Object.class); 210 } 211 212 private void addDMHMethodType(String dmh, String methodType) { 213 validateMethodType(methodType); 214 Set<String> methodTypes = dmhMethods.get(dmh); 215 if (methodTypes == null) { 216 methodTypes = new TreeSet<>(); 217 dmhMethods.put(dmh, methodTypes); 218 } 219 methodTypes.add(methodType); 220 } 221 222 private static void validateMethodType(String type) { 223 String[] typeParts = type.split("_"); 224 // check return type (second part) 225 if (typeParts.length != 2 || typeParts[1].length() != 1 226 || !isBasicTypeChar(typeParts[1].charAt(0))) { 227 throw new RuntimeException( 228 "Method type signature must be of form [LJIFD]*_[LJIFDV]"); 229 } 230 // expand and check arguments (first part) 231 expandSignature(typeParts[0]); 232 } 233 234 // Convert LL -> LL, L3 -> LLL 235 private static String expandSignature(String signature) { 236 StringBuilder sb = new StringBuilder(); 237 char last = 'X'; 238 int count = 0; 239 for (int i = 0; i < signature.length(); i++) { 240 char c = signature.charAt(i); 241 if (c >= '0' && c <= '9') { 242 count *= 10; 243 count += (c - '0'); 244 } else { 245 requireBasicType(c); 246 for (int j = 1; j < count; j++) { 247 sb.append(last); 248 } 249 sb.append(c); 250 last = c; 251 count = 0; 252 } 253 } 254 255 // ended with a number, e.g., "L2": append last char count - 1 times 256 if (count > 1) { 257 requireBasicType(last); 258 for (int j = 1; j < count; j++) { 259 sb.append(last); 260 } 261 } 262 return sb.toString(); 263 } 264 265 private static void requireBasicType(char c) { 266 if (!isArgBasicTypeChar(c)) { 267 throw new RuntimeException( 268 "Character " + c + " must correspond to a basic field type: LIJFD"); 269 } 270 } 271 272 private static Class<?> simpleType(char c) { 273 if (isBasicTypeChar(c)) { 274 return LambdaForm.BasicType.basicType(c).basicTypeClass(); 275 } 276 switch (c) { 277 case 'Z': 278 case 'B': 279 case 'S': 280 case 'C': 281 throw new IllegalArgumentException("Not a valid primitive: " + c + 282 " (use I instead)"); 283 default: 284 throw new IllegalArgumentException("Not a primitive: " + c); 285 } 286 } 287 } 288 289 /* 290 * Returns a map of class name in internal form to the corresponding class bytes 291 * per the given stream of SPECIES_RESOLVE and LF_RESOLVE trace logs. 292 * 293 * Used by GenerateJLIClassesPlugin to pre-generate holder classes during 294 * jlink phase. 295 */ 296 static Map<String, byte[]> generateHolderClasses(Stream<String> traces) { 297 Objects.requireNonNull(traces); 298 HolderClassBuilder builder = new HolderClassBuilder(); 299 traces.map(line -> line.split(" ")) 300 .forEach(parts -> { 301 switch (parts[0]) { 302 case "[SPECIES_RESOLVE]": 303 // Allow for new types of species data classes being resolved here 304 assert parts.length >= 2; 305 if (parts[1].startsWith(BMH_SPECIES_PREFIX)) { 306 String species = parts[1].substring(BMH_SPECIES_PREFIX.length()); 307 if (!"L".equals(species)) { 308 builder.addSpeciesType(species); 309 } 310 } 311 break; 312 case "[LF_RESOLVE]": 313 assert parts.length > 3; 314 String methodType = parts[3]; 315 if (parts[1].equals(INVOKERS_HOLDER_CLASS_NAME)) { 316 if ("linkToTargetMethod".equals(parts[2]) || 317 "linkToCallSite".equals(parts[2])) { 318 builder.addCallSiteType(methodType); 319 } else { 320 MethodType mt = HolderClassBuilder.asMethodType(methodType); 321 // Work around JDK-8327499 322 if (HolderClassBuilder.checkInvokerTypeParams(mt)) { 323 builder.addInvokerType(methodType); 324 } else { 325 PlatformLogger.getLogger("java.lang.invoke") 326 .warning("Invalid LF_RESOLVE " + parts[1] + " " + parts[2] + " " + parts[3]); 327 } 328 } 329 } else if (parts[1].contains("DirectMethodHandle")) { 330 String dmh = parts[2]; 331 // ignore getObject etc for now (generated by default) 332 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) { 333 builder.addDMHMethodType(dmh, methodType); 334 } 335 } 336 break; 337 default: 338 break; // ignore 339 } 340 }); 341 342 return builder.build(); 343 } 344 345 /** 346 * Returns a {@code byte[]} representation of a class implementing 347 * the zero and identity forms of all {@code LambdaForm.BasicType}s. 348 */ 349 static byte[] generateBasicFormsClassBytes(String className) { 350 ArrayList<LambdaForm> forms = new ArrayList<>(); 351 ArrayList<String> names = new ArrayList<>(); 352 HashSet<String> dedupSet = new HashSet<>(); 353 for (LambdaForm.BasicType type : LambdaForm.BasicType.values()) { 354 String name; 355 356 LambdaForm identity = LambdaForm.identityForm(type); 357 name = identity.kind.defaultLambdaName 358 + "_" + identity.returnType().basicTypeChar(); 359 if (dedupSet.add(name)) { 360 names.add(name); 361 forms.add(identity); 362 } 363 364 if (type != V_TYPE) { 365 LambdaForm constant = LambdaForm.constantForm(type); 366 name = constant.kind.defaultLambdaName 367 + "_" + constant.returnType().basicTypeChar(); 368 if (dedupSet.add(name)) { 369 names.add(name); 370 forms.add(constant); 371 } 372 } 373 } 374 return generateCodeBytesForLFs(className, 375 names.toArray(new String[0]), 376 forms.toArray(new LambdaForm[0])); 377 } 378 379 /** 380 * Returns a {@code byte[]} representation of a class implementing 381 * DirectMethodHandle of each pairwise combination of {@code MethodType} and 382 * an {@code int} representing method type. 383 */ 384 static byte[] generateDirectMethodHandleHolderClassBytes(String className, 385 MethodType[] methodTypes, int[] types) { 386 ArrayList<LambdaForm> forms = new ArrayList<>(); 387 ArrayList<String> names = new ArrayList<>(); 388 for (int i = 0; i < methodTypes.length; i++) { 389 // invokeVirtual and invokeInterface must have a leading Object 390 // parameter, i.e., the receiver 391 if (types[i] == LF_INVVIRTUAL || types[i] == LF_INVINTERFACE) { 392 if (methodTypes[i].parameterCount() < 1 || 393 methodTypes[i].parameterType(0) != Object.class) { 394 throw new InternalError("Invalid method type for " + 395 (types[i] == LF_INVVIRTUAL ? "invokeVirtual" : "invokeInterface") + 396 " DMH, needs at least two leading reference arguments: " + 397 methodTypes[i]); 398 } 399 } 400 401 LambdaForm form = DirectMethodHandle.makePreparedLambdaForm(methodTypes[i], types[i]); 402 forms.add(form); 403 names.add(form.kind.defaultLambdaName); 404 } 405 for (Wrapper wrapper : Wrapper.values()) { 406 if (wrapper == Wrapper.VOID) { 407 continue; 408 } 409 for (byte b = DirectMethodHandle.AF_GETFIELD; b < DirectMethodHandle.AF_LIMIT; b++) { 410 int ftype = DirectMethodHandle.ftypeKind(wrapper.primitiveType()); 411 LambdaForm form = DirectMethodHandle 412 .makePreparedFieldLambdaForm(b, /*isVolatile*/false, ftype); 413 if (form.kind != LambdaForm.Kind.GENERIC) { 414 forms.add(form); 415 names.add(form.kind.defaultLambdaName); 416 } 417 // volatile 418 form = DirectMethodHandle 419 .makePreparedFieldLambdaForm(b, /*isVolatile*/true, ftype); 420 if (form.kind != LambdaForm.Kind.GENERIC) { 421 forms.add(form); 422 names.add(form.kind.defaultLambdaName); 423 } 424 } 425 } 426 return generateCodeBytesForLFs(className, 427 names.toArray(new String[0]), 428 forms.toArray(new LambdaForm[0])); 429 } 430 431 /** 432 * Returns a {@code byte[]} representation of a class implementing 433 * DelegatingMethodHandles of each {@code MethodType} kind in the 434 * {@code methodTypes} argument. 435 */ 436 static byte[] generateDelegatingMethodHandleHolderClassBytes(String className, 437 MethodType[] methodTypes) { 438 439 HashSet<MethodType> dedupSet = new HashSet<>(); 440 ArrayList<LambdaForm> forms = new ArrayList<>(); 441 ArrayList<String> names = new ArrayList<>(); 442 for (int i = 0; i < methodTypes.length; i++) { 443 // generate methods representing the DelegatingMethodHandle 444 if (dedupSet.add(methodTypes[i])) { 445 // reinvokers are variant with the associated SpeciesData 446 // and shape of the target LF, but we can easily pregenerate 447 // the basic reinvokers associated with Species_L. Ultimately we 448 // may want to consider pregenerating more of these, which will 449 // require an even more complex naming scheme 450 LambdaForm reinvoker = makeReinvokerFor(methodTypes[i]); 451 forms.add(reinvoker); 452 String speciesSig = BoundMethodHandle.speciesDataFor(reinvoker).key(); 453 assert(speciesSig.equals("L")); 454 names.add(reinvoker.kind.defaultLambdaName + "_" + speciesSig); 455 456 LambdaForm delegate = makeDelegateFor(methodTypes[i]); 457 forms.add(delegate); 458 names.add(delegate.kind.defaultLambdaName); 459 } 460 } 461 return generateCodeBytesForLFs(className, 462 names.toArray(new String[0]), 463 forms.toArray(new LambdaForm[0])); 464 } 465 466 /** 467 * Returns a {@code byte[]} representation of a class implementing 468 * the invoker forms for the set of supplied {@code invokerMethodTypes} 469 * and {@code callSiteMethodTypes}. 470 */ 471 static byte[] generateInvokersHolderClassBytes(String className, 472 MethodType[] invokerMethodTypes, MethodType[] callSiteMethodTypes) { 473 474 HashSet<MethodType> dedupSet = new HashSet<>(); 475 ArrayList<LambdaForm> forms = new ArrayList<>(); 476 ArrayList<String> names = new ArrayList<>(); 477 int[] types = { 478 MethodTypeForm.LF_EX_LINKER, 479 MethodTypeForm.LF_EX_INVOKER, 480 MethodTypeForm.LF_GEN_LINKER, 481 MethodTypeForm.LF_GEN_INVOKER 482 }; 483 484 for (int i = 0; i < invokerMethodTypes.length; i++) { 485 // generate methods representing invokers of the specified type 486 if (dedupSet.add(invokerMethodTypes[i])) { 487 for (int type : types) { 488 LambdaForm invokerForm = Invokers.invokeHandleForm(invokerMethodTypes[i], 489 /*customized*/false, type); 490 forms.add(invokerForm); 491 names.add(invokerForm.kind.defaultLambdaName); 492 } 493 } 494 } 495 496 dedupSet = new HashSet<>(); 497 for (int i = 0; i < callSiteMethodTypes.length; i++) { 498 // generate methods representing invokers of the specified type 499 if (dedupSet.add(callSiteMethodTypes[i])) { 500 LambdaForm callSiteForm = Invokers.callSiteForm(callSiteMethodTypes[i], true); 501 forms.add(callSiteForm); 502 names.add(callSiteForm.kind.defaultLambdaName); 503 504 LambdaForm methodHandleForm = Invokers.callSiteForm(callSiteMethodTypes[i], false); 505 forms.add(methodHandleForm); 506 names.add(methodHandleForm.kind.defaultLambdaName); 507 } 508 } 509 510 return generateCodeBytesForLFs(className, 511 names.toArray(new String[0]), 512 forms.toArray(new LambdaForm[0])); 513 } 514 515 /* 516 * Generate customized code for a set of LambdaForms of specified types into 517 * a class with a specified name. 518 */ 519 private static byte[] generateCodeBytesForLFs(String className, String[] names, LambdaForm[] forms) { 520 return ClassFile.of().build(ClassDesc.ofInternalName(className), clb -> { 521 clb.withFlags(ACC_PRIVATE | ACC_FINAL | ACC_SUPER) 522 .withSuperclass(InvokerBytecodeGenerator.INVOKER_SUPER_DESC) 523 .with(SourceFileAttribute.of(className.substring(className.lastIndexOf('/') + 1))); 524 for (int i = 0; i < forms.length; i++) { 525 new InvokerBytecodeGenerator(className, names[i], forms[i], forms[i].methodType()).addMethod(clb, false); 526 } 527 }); 528 } 529 530 private static LambdaForm makeReinvokerFor(MethodType type) { 531 MethodHandle emptyHandle = MethodHandles.empty(type); 532 return DelegatingMethodHandle.makeReinvokerForm(emptyHandle, 533 MethodTypeForm.LF_REBIND, 534 BoundMethodHandle.speciesData_L(), 535 BoundMethodHandle.speciesData_L().getterFunction(0)); 536 } 537 538 private static LambdaForm makeDelegateFor(MethodType type) { 539 MethodHandle handle = MethodHandles.empty(type); 540 return DelegatingMethodHandle.makeReinvokerForm( 541 handle, 542 MethodTypeForm.LF_DELEGATE, 543 DelegatingMethodHandle.class, 544 DelegatingMethodHandle.NF_getTarget); 545 } 546 547 /** 548 * Returns a {@code byte[]} representation of {@code BoundMethodHandle} 549 * species class implementing the signature defined by {@code types}. 550 */ 551 @SuppressWarnings({"rawtypes", "unchecked"}) 552 static Map.Entry<String, byte[]> generateConcreteBMHClassBytes(final String types) { 553 for (char c : types.toCharArray()) { 554 if (!isArgBasicTypeChar(c)) { 555 throw new IllegalArgumentException("All characters must " 556 + "correspond to a basic field type: LIJFD"); 557 } 558 } 559 final BoundMethodHandle.SpeciesData species = BoundMethodHandle.SPECIALIZER.findSpecies(types); 560 final String className = species.speciesCode().getName(); 561 final ClassSpecializer.Factory factory = BoundMethodHandle.SPECIALIZER.factory(); 562 final byte[] code = factory.generateConcreteSpeciesCodeFile(className, species); 563 return Map.entry(className.replace('.', '/'), code); 564 } 565 566 }