1 /* 2 * Copyright (c) 2015, 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 26 package java.lang.invoke; 27 28 import jdk.internal.org.objectweb.asm.ClassWriter; 29 import jdk.internal.org.objectweb.asm.Label; 30 import jdk.internal.org.objectweb.asm.MethodVisitor; 31 import jdk.internal.org.objectweb.asm.Opcodes; 32 import jdk.internal.vm.annotation.ForceInline; 33 import jdk.internal.misc.Unsafe; 34 35 import java.lang.invoke.MethodHandles.Lookup; 36 import java.util.*; 37 import java.util.concurrent.ConcurrentHashMap; 38 import java.util.concurrent.ConcurrentMap; 39 import java.util.function.Function; 40 import sun.security.action.GetPropertyAction; 41 42 import static jdk.internal.org.objectweb.asm.Opcodes.*; 43 44 /** 45 * <p>Methods to facilitate the creation of String concatenation methods, that 46 * can be used to efficiently concatenate a known number of arguments of known 47 * types, possibly after type adaptation and partial evaluation of arguments. 48 * These methods are typically used as <em>bootstrap methods</em> for {@code 49 * invokedynamic} call sites, to support the <em>string concatenation</em> 50 * feature of the Java Programming Language. 51 * 52 * <p>Indirect access to the behavior specified by the provided {@code 53 * MethodHandle} proceeds in order through two phases: 54 * 55 * <ol> 56 * <li><em>Linkage</em> occurs when the methods in this class are invoked. 57 * They take as arguments a method type describing the concatenated arguments 58 * count and types, and optionally the String <em>recipe</em>, plus the 59 * constants that participate in the String concatenation. The details on 60 * accepted recipe shapes are described further below. Linkage may involve 61 * dynamically loading a new class that implements the expected concatenation 62 * behavior. The {@code CallSite} holds the {@code MethodHandle} pointing to the 63 * exact concatenation method. The concatenation methods may be shared among 64 * different {@code CallSite}s, e.g. if linkage methods produce them as pure 65 * functions.</li> 66 * 67 * <li><em>Invocation</em> occurs when a generated concatenation method is 68 * invoked with the exact dynamic arguments. This may occur many times for a 69 * single concatenation method. The method referenced by the behavior {@code 70 * MethodHandle} is invoked with the static arguments and any additional dynamic 71 * arguments provided on invocation, as if by {@link MethodHandle#invoke(Object...)}.</li> 72 * </ol> 73 * 74 * <p> This class provides two forms of linkage methods: a simple version 75 * ({@link #makeConcat(java.lang.invoke.MethodHandles.Lookup, String, 76 * MethodType)}) using only the dynamic arguments, and an advanced version 77 * ({@link #makeConcatWithConstants(java.lang.invoke.MethodHandles.Lookup, 78 * String, MethodType, String, Object...)} using the advanced forms of capturing 79 * the constant arguments. The advanced strategy can produce marginally better 80 * invocation bytecode, at the expense of exploding the number of shapes of 81 * string concatenation methods present at runtime, because those shapes would 82 * include constant static arguments as well. 83 * 84 * @author Aleksey Shipilev 85 * @author Remi Forax 86 * @author Peter Levart 87 * 88 * @apiNote 89 * <p>There is a JVM limit (classfile structural constraint): no method 90 * can call with more than 255 slots. This limits the number of static and 91 * dynamic arguments one can pass to bootstrap method. Since there are potential 92 * concatenation strategies that use {@code MethodHandle} combinators, we need 93 * to reserve a few empty slots on the parameter lists to capture the 94 * temporal results. This is why bootstrap methods in this factory do not accept 95 * more than 200 argument slots. Users requiring more than 200 argument slots in 96 * concatenation are expected to split the large concatenation in smaller 97 * expressions. 98 * 99 * @since 9 100 */ 101 public final class StringConcatFactory { 102 103 /** 104 * Tag used to demarcate an ordinary argument. 105 */ 106 private static final char TAG_ARG = '\u0001'; 107 108 /** 109 * Tag used to demarcate a constant. 110 */ 111 private static final char TAG_CONST = '\u0002'; 112 113 /** 114 * Maximum number of argument slots in String Concat call. 115 * 116 * While the maximum number of argument slots that indy call can handle is 253, 117 * we do not use all those slots, to let the strategies with MethodHandle 118 * combinators to use some arguments. 119 */ 120 private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200; 121 122 /** 123 * Concatenation strategy to use. See {@link Strategy} for possible options. 124 * This option is controllable with -Djava.lang.invoke.stringConcat JDK option. 125 */ 126 private static Strategy STRATEGY; 127 128 /** 129 * Default strategy to use for concatenation. 130 */ 131 private static final Strategy DEFAULT_STRATEGY = Strategy.BC_SB; 132 133 private enum Strategy { 134 /** 135 * Bytecode generator, calling into {@link java.lang.StringBuilder}. 136 */ 137 BC_SB, 138 139 /** 140 * Bytecode generator, calling into {@link java.lang.StringBuilder}; 141 * but trying to estimate the required storage. 142 */ 143 BC_SB_SIZED, 144 145 /** 146 * Bytecode generator, calling into {@link java.lang.StringBuilder}; 147 * but computing the required storage exactly. 148 */ 149 BC_SB_SIZED_EXACT, 150 151 /** 152 * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. 153 * This strategy also tries to estimate the required storage. 154 */ 155 MH_SB_SIZED, 156 157 /** 158 * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. 159 * This strategy also estimate the required storage exactly. 160 */ 161 MH_SB_SIZED_EXACT, 162 163 /** 164 * MethodHandle-based generator, that constructs its own byte[] array from 165 * the arguments. It computes the required storage exactly. 166 */ 167 MH_INLINE_SIZED_EXACT 168 } 169 170 /** 171 * Enables debugging: this may print debugging messages, perform additional (non-neutral for performance) 172 * checks, etc. 173 */ 174 private static final boolean DEBUG; 175 176 /** 177 * Enables caching of strategy stubs. This may improve the linkage time by reusing the generated 178 * code, at the expense of contaminating the profiles. 179 */ 180 private static final boolean CACHE_ENABLE; 181 182 private static final ConcurrentMap<Key, MethodHandle> CACHE; 183 184 /** 185 * Dump generated classes to disk, for debugging purposes. 186 */ 187 private static final ProxyClassesDumper DUMPER; 188 189 static { 190 // In case we need to double-back onto the StringConcatFactory during this 191 // static initialization, make sure we have the reasonable defaults to complete 192 // the static initialization properly. After that, actual users would use the 193 // the proper values we have read from the the properties. 194 STRATEGY = DEFAULT_STRATEGY; 195 // CACHE_ENABLE = false; // implied 196 // CACHE = null; // implied 197 // DEBUG = false; // implied 198 // DUMPER = null; // implied 199 200 Properties props = GetPropertyAction.getProperties(); 201 final String strategy = 202 props.getProperty("java.lang.invoke.stringConcat"); 203 CACHE_ENABLE = Boolean.parseBoolean( 204 props.getProperty("java.lang.invoke.stringConcat.cache")); 205 DEBUG = Boolean.parseBoolean( 206 props.getProperty("java.lang.invoke.stringConcat.debug")); 207 final String dumpPath = 208 props.getProperty("java.lang.invoke.stringConcat.dumpClasses"); 209 210 STRATEGY = (strategy == null) ? DEFAULT_STRATEGY : Strategy.valueOf(strategy); 211 CACHE = CACHE_ENABLE ? new ConcurrentHashMap<>() : null; 212 DUMPER = (dumpPath == null) ? null : ProxyClassesDumper.getInstance(dumpPath); 213 } 214 215 /** 216 * Cache key is a composite of: 217 * - class name, that lets to disambiguate stubs, to avoid excess sharing 218 * - method type, describing the dynamic arguments for concatenation 219 * - concat recipe, describing the constants and concat shape 220 */ 221 private static final class Key { 222 final String className; 223 final MethodType mt; 224 final Recipe recipe; 225 226 public Key(String className, MethodType mt, Recipe recipe) { 227 this.className = className; 228 this.mt = mt; 229 this.recipe = recipe; 230 } 231 232 @Override 233 public boolean equals(Object o) { 234 if (this == o) return true; 235 if (o == null || getClass() != o.getClass()) return false; 236 237 Key key = (Key) o; 238 239 if (!className.equals(key.className)) return false; 240 if (!mt.equals(key.mt)) return false; 241 if (!recipe.equals(key.recipe)) return false; 242 return true; 243 } 244 245 @Override 246 public int hashCode() { 247 int result = className.hashCode(); 248 result = 31 * result + mt.hashCode(); 249 result = 31 * result + recipe.hashCode(); 250 return result; 251 } 252 } 253 254 /** 255 * Parses the recipe string, and produces the traversable collection of 256 * {@link java.lang.invoke.StringConcatFactory.RecipeElement}-s for generator 257 * strategies. Notably, this class parses out the constants from the recipe 258 * and from other static arguments. 259 */ 260 private static final class Recipe { 261 private final List<RecipeElement> elements; 262 263 public Recipe(String src, Object[] constants) { 264 List<RecipeElement> el = new ArrayList<>(); 265 266 int constC = 0; 267 int argC = 0; 268 269 StringBuilder acc = new StringBuilder(); 270 271 for (int i = 0; i < src.length(); i++) { 272 char c = src.charAt(i); 273 274 if (c == TAG_CONST || c == TAG_ARG) { 275 // Detected a special tag, flush all accumulated characters 276 // as a constant first: 277 if (acc.length() > 0) { 278 el.add(new RecipeElement(acc.toString())); 279 acc.setLength(0); 280 } 281 if (c == TAG_CONST) { 282 Object cnst = constants[constC++]; 283 el.add(new RecipeElement(cnst)); 284 } 285 if (c == TAG_ARG) { 286 el.add(new RecipeElement(argC++)); 287 } 288 } else { 289 // Not a special characters, this is a constant embedded into 290 // the recipe itself. 291 acc.append(c); 292 } 293 } 294 295 // Flush the remaining characters as constant: 296 if (acc.length() > 0) { 297 el.add(new RecipeElement(acc.toString())); 298 } 299 300 elements = el; 301 } 302 303 public List<RecipeElement> getElements() { 304 return elements; 305 } 306 307 @Override 308 public boolean equals(Object o) { 309 if (this == o) return true; 310 if (o == null || getClass() != o.getClass()) return false; 311 312 Recipe recipe = (Recipe) o; 313 return elements.equals(recipe.elements); 314 } 315 316 @Override 317 public int hashCode() { 318 return elements.hashCode(); 319 } 320 } 321 322 private static final class RecipeElement { 323 private final Object value; 324 private final int argPos; 325 private final Tag tag; 326 327 public RecipeElement(Object cnst) { 328 this.value = Objects.requireNonNull(cnst); 329 this.argPos = -1; 330 this.tag = Tag.CONST; 331 } 332 333 public RecipeElement(int arg) { 334 this.value = null; 335 this.argPos = arg; 336 this.tag = Tag.ARG; 337 } 338 339 public Object getValue() { 340 assert (tag == Tag.CONST); 341 return value; 342 } 343 344 public int getArgPos() { 345 assert (tag == Tag.ARG); 346 return argPos; 347 } 348 349 public Tag getTag() { 350 return tag; 351 } 352 353 @Override 354 public boolean equals(Object o) { 355 if (this == o) return true; 356 if (o == null || getClass() != o.getClass()) return false; 357 358 RecipeElement that = (RecipeElement) o; 359 360 if (tag != that.tag) return false; 361 if (tag == Tag.CONST && (!value.equals(that.value))) return false; 362 if (tag == Tag.ARG && (argPos != that.argPos)) return false; 363 return true; 364 } 365 366 @Override 367 public int hashCode() { 368 return tag.hashCode(); 369 } 370 } 371 372 private enum Tag { 373 CONST, ARG 374 } 375 376 /** 377 * Facilitates the creation of optimized String concatenation methods, that 378 * can be used to efficiently concatenate a known number of arguments of 379 * known types, possibly after type adaptation and partial evaluation of 380 * arguments. Typically used as a <em>bootstrap method</em> for {@code 381 * invokedynamic} call sites, to support the <em>string concatenation</em> 382 * feature of the Java Programming Language. 383 * 384 * <p>When the target of the {@code CallSite} returned from this method is 385 * invoked, it returns the result of String concatenation, taking all 386 * function arguments passed to the linkage method as inputs for 387 * concatenation. The target signature is given by {@code concatType}. 388 * The arguments are concatenated as per requirements stated in JLS 15.18.1 389 * "String Concatenation Operator +". Notably, the inputs are converted as 390 * per JLS 5.1.11 "String Conversion", and combined from left to right. 391 * 392 * <p>Assume the linkage arguments are as follows: 393 * 394 * <ul> 395 * <li>{@code concatType}, describing the {@code CallSite} signature</li> 396 * </ul> 397 * 398 * <p>Then the following linkage invariants must hold: 399 * 400 * <ul> 401 * <li>The parameter count in {@code concatType} is less than or equal to 200</li> 402 * 403 * <li>The return type in {@code concatType} is assignable from {@link java.lang.String}</li> 404 * </ul> 405 * 406 * @param lookup Represents a lookup context with the accessibility 407 * privileges of the caller. When used with {@code 408 * invokedynamic}, this is stacked automatically by the VM. 409 * @param name The name of the method to implement. This name is 410 * arbitrary, and has no meaning for this linkage method. 411 * When used with {@code invokedynamic}, this is provided by 412 * the {@code NameAndType} of the {@code InvokeDynamic} 413 * structure and is stacked automatically by the VM. 414 * @param concatType The expected signature of the {@code CallSite}. The 415 * parameter types represent the types of concatenation 416 * arguments; the return type is always assignable from {@link 417 * java.lang.String}. When used with {@code invokedynamic}, 418 * this is provided by the {@code NameAndType} of the {@code 419 * InvokeDynamic} structure and is stacked automatically by 420 * the VM. 421 * @return a CallSite whose target can be used to perform String 422 * concatenation, with dynamic concatenation arguments described by the given 423 * {@code concatType}. 424 * @throws StringConcatException If any of the linkage invariants described 425 * here are violated. 426 * @throws NullPointerException If any of the incoming arguments is null. 427 * This will never happen when a bootstrap method 428 * is called with invokedynamic. 429 * 430 * @jls 5.1.11 String Conversion 431 * @jls 15.18.1 String Concatenation Operator + 432 */ 433 public static CallSite makeConcat(MethodHandles.Lookup lookup, 434 String name, 435 MethodType concatType) throws StringConcatException { 436 if (DEBUG) { 437 System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType); 438 } 439 440 return doStringConcat(lookup, name, concatType, true, null); 441 } 442 443 /** 444 * Facilitates the creation of optimized String concatenation methods, that 445 * can be used to efficiently concatenate a known number of arguments of 446 * known types, possibly after type adaptation and partial evaluation of 447 * arguments. Typically used as a <em>bootstrap method</em> for {@code 448 * invokedynamic} call sites, to support the <em>string concatenation</em> 449 * feature of the Java Programming Language. 450 * 451 * <p>When the target of the {@code CallSite} returned from this method is 452 * invoked, it returns the result of String concatenation, taking all 453 * function arguments and constants passed to the linkage method as inputs for 454 * concatenation. The target signature is given by {@code concatType}, and 455 * does not include constants. The arguments are concatenated as per requirements 456 * stated in JLS 15.18.1 "String Concatenation Operator +". Notably, the inputs 457 * are converted as per JLS 5.1.11 "String Conversion", and combined from left 458 * to right. 459 * 460 * <p>The concatenation <em>recipe</em> is a String description for the way to 461 * construct a concatenated String from the arguments and constants. The 462 * recipe is processed from left to right, and each character represents an 463 * input to concatenation. Recipe characters mean: 464 * 465 * <ul> 466 * 467 * <li><em>\1 (Unicode point 0001)</em>: an ordinary argument. This 468 * input is passed through dynamic argument, and is provided during the 469 * concatenation method invocation. This input can be null.</li> 470 * 471 * <li><em>\2 (Unicode point 0002):</em> a constant. This input passed 472 * through static bootstrap argument. This constant can be any value 473 * representable in constant pool. If necessary, the factory would call 474 * {@code toString} to perform a one-time String conversion.</li> 475 * 476 * <li><em>Any other char value:</em> a single character constant.</li> 477 * </ul> 478 * 479 * <p>Assume the linkage arguments are as follows: 480 * 481 * <ul> 482 * <li>{@code concatType}, describing the {@code CallSite} signature</li> 483 * <li>{@code recipe}, describing the String recipe</li> 484 * <li>{@code constants}, the vararg array of constants</li> 485 * </ul> 486 * 487 * <p>Then the following linkage invariants must hold: 488 * 489 * <ul> 490 * <li>The parameter count in {@code concatType} is less than or equal to 491 * 200</li> 492 * 493 * <li>The parameter count in {@code concatType} equals to number of \1 tags 494 * in {@code recipe}</li> 495 * 496 * <li>The return type in {@code concatType} is assignable 497 * from {@link java.lang.String}, and matches the return type of the 498 * returned {@link MethodHandle}</li> 499 * 500 * <li>The number of elements in {@code constants} equals to number of \2 501 * tags in {@code recipe}</li> 502 * </ul> 503 * 504 * @param lookup Represents a lookup context with the accessibility 505 * privileges of the caller. When used with {@code 506 * invokedynamic}, this is stacked automatically by the 507 * VM. 508 * @param name The name of the method to implement. This name is 509 * arbitrary, and has no meaning for this linkage method. 510 * When used with {@code invokedynamic}, this is provided 511 * by the {@code NameAndType} of the {@code InvokeDynamic} 512 * structure and is stacked automatically by the VM. 513 * @param concatType The expected signature of the {@code CallSite}. The 514 * parameter types represent the types of dynamic concatenation 515 * arguments; the return type is always assignable from {@link 516 * java.lang.String}. When used with {@code 517 * invokedynamic}, this is provided by the {@code 518 * NameAndType} of the {@code InvokeDynamic} structure and 519 * is stacked automatically by the VM. 520 * @param recipe Concatenation recipe, described above. 521 * @param constants A vararg parameter representing the constants passed to 522 * the linkage method. 523 * @return a CallSite whose target can be used to perform String 524 * concatenation, with dynamic concatenation arguments described by the given 525 * {@code concatType}. 526 * @throws StringConcatException If any of the linkage invariants described 527 * here are violated. 528 * @throws NullPointerException If any of the incoming arguments is null, or 529 * any constant in {@code recipe} is null. 530 * This will never happen when a bootstrap method 531 * is called with invokedynamic. 532 * @apiNote Code generators have three distinct ways to process a constant 533 * string operand S in a string concatenation expression. First, S can be 534 * materialized as a reference (using ldc) and passed as an ordinary argument 535 * (recipe '\1'). Or, S can be stored in the constant pool and passed as a 536 * constant (recipe '\2') . Finally, if S contains neither of the recipe 537 * tag characters ('\1', '\2') then S can be interpolated into the recipe 538 * itself, causing its characters to be inserted into the result. 539 * 540 * @jls 5.1.11 String Conversion 541 * @jls 15.18.1 String Concatenation Operator + 542 */ 543 public static CallSite makeConcatWithConstants(MethodHandles.Lookup lookup, 544 String name, 545 MethodType concatType, 546 String recipe, 547 Object... constants) throws StringConcatException { 548 if (DEBUG) { 549 System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType + ", {" + recipe + "}, " + Arrays.toString(constants)); 550 } 551 552 return doStringConcat(lookup, name, concatType, false, recipe, constants); 553 } 554 555 private static CallSite doStringConcat(MethodHandles.Lookup lookup, 556 String name, 557 MethodType concatType, 558 boolean generateRecipe, 559 String recipe, 560 Object... constants) throws StringConcatException { 561 Objects.requireNonNull(lookup, "Lookup is null"); 562 Objects.requireNonNull(name, "Name is null"); 563 Objects.requireNonNull(concatType, "Concat type is null"); 564 Objects.requireNonNull(constants, "Constants are null"); 565 566 for (Object o : constants) { 567 Objects.requireNonNull(o, "Cannot accept null constants"); 568 } 569 570 if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) { 571 throw new StringConcatException(String.format( 572 "Invalid caller: %s", 573 lookup.lookupClass().getName())); 574 } 575 576 int cCount = 0; 577 int oCount = 0; 578 if (generateRecipe) { 579 // Mock the recipe to reuse the concat generator code 580 char[] value = new char[concatType.parameterCount()]; 581 Arrays.fill(value, TAG_ARG); 582 recipe = new String(value); 583 oCount = concatType.parameterCount(); 584 } else { 585 Objects.requireNonNull(recipe, "Recipe is null"); 586 587 for (int i = 0; i < recipe.length(); i++) { 588 char c = recipe.charAt(i); 589 if (c == TAG_CONST) cCount++; 590 if (c == TAG_ARG) oCount++; 591 } 592 } 593 594 if (oCount != concatType.parameterCount()) { 595 throw new StringConcatException( 596 "Mismatched number of concat arguments: recipe wants " + 597 oCount + 598 " arguments, but signature provides " + 599 concatType.parameterCount()); 600 } 601 602 if (cCount != constants.length) { 603 throw new StringConcatException( 604 "Mismatched number of concat constants: recipe wants " + 605 cCount + 606 " constants, but only " + 607 constants.length + 608 " are passed"); 609 } 610 611 if (!concatType.returnType().isAssignableFrom(String.class)) { 612 throw new StringConcatException( 613 "The return type should be compatible with String, but it is " + 614 concatType.returnType()); 615 } 616 617 if (concatType.parameterCount() > MAX_INDY_CONCAT_ARG_SLOTS) { 618 throw new StringConcatException("Too many concat argument slots: " + 619 concatType.parameterCount() + 620 ", can only accept " + 621 MAX_INDY_CONCAT_ARG_SLOTS); 622 } 623 624 String className = getClassName(lookup.lookupClass()); 625 MethodType mt = adaptType(concatType); 626 Recipe rec = new Recipe(recipe, constants); 627 628 MethodHandle mh; 629 if (CACHE_ENABLE) { 630 Key key = new Key(className, mt, rec); 631 mh = CACHE.get(key); 632 if (mh == null) { 633 mh = generate(lookup, className, mt, rec); 634 CACHE.put(key, mh); 635 } 636 } else { 637 mh = generate(lookup, className, mt, rec); 638 } 639 return new ConstantCallSite(mh.asType(concatType)); 640 } 641 642 /** 643 * Adapt method type to an API we are going to use. 644 * 645 * This strips the concrete classes from the signatures, thus preventing 646 * class leakage when we cache the concatenation stubs. 647 * 648 * @param args actual argument types 649 * @return argument types the strategy is going to use 650 */ 651 private static MethodType adaptType(MethodType args) { 652 Class<?>[] ptypes = args.parameterArray(); 653 boolean changed = false; 654 for (int i = 0; i < ptypes.length; i++) { 655 Class<?> ptype = ptypes[i]; 656 if (!ptype.isPrimitive() && 657 ptype != String.class && 658 ptype != Object.class) { // truncate to Object 659 ptypes[i] = Object.class; 660 changed = true; 661 } 662 // else other primitives or String or Object (unchanged) 663 } 664 return changed 665 ? MethodType.methodType(args.returnType(), ptypes) 666 : args; 667 } 668 669 private static String getClassName(Class<?> hostClass) throws StringConcatException { 670 /* 671 When cache is enabled, we want to cache as much as we can. 672 673 However, there are two peculiarities: 674 675 a) The generated class should stay within the same package as the 676 host class, to allow Unsafe.defineAnonymousClass access controls 677 to work properly. JDK may choose to fail with IllegalAccessException 678 when accessing a VM anonymous class with non-privileged callers, 679 see JDK-8058575. 680 681 b) If we mark the stub with some prefix, say, derived from the package 682 name because of (a), we can technically use that stub in other packages. 683 But the call stack traces would be extremely puzzling to unsuspecting users 684 and profiling tools: whatever stub wins the race, would be linked in all 685 similar callsites. 686 687 Therefore, we set the class prefix to match the host class package, and use 688 the prefix as the cache key too. This only affects BC_* strategies, and only when 689 cache is enabled. 690 */ 691 692 switch (STRATEGY) { 693 case BC_SB: 694 case BC_SB_SIZED: 695 case BC_SB_SIZED_EXACT: { 696 if (CACHE_ENABLE) { 697 String pkgName = hostClass.getPackageName(); 698 return (pkgName != null && !pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat"; 699 } else { 700 return hostClass.getName().replace('.', '/') + "$$StringConcat"; 701 } 702 } 703 case MH_SB_SIZED: 704 case MH_SB_SIZED_EXACT: 705 case MH_INLINE_SIZED_EXACT: 706 // MethodHandle strategies do not need a class name. 707 return ""; 708 default: 709 throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented"); 710 } 711 } 712 713 private static MethodHandle generate(Lookup lookup, String className, MethodType mt, Recipe recipe) throws StringConcatException { 714 try { 715 switch (STRATEGY) { 716 case BC_SB: 717 return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.DEFAULT); 718 case BC_SB_SIZED: 719 return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED); 720 case BC_SB_SIZED_EXACT: 721 return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED_EXACT); 722 case MH_SB_SIZED: 723 return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED); 724 case MH_SB_SIZED_EXACT: 725 return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT); 726 case MH_INLINE_SIZED_EXACT: 727 return MethodHandleInlineCopyStrategy.generate(mt, recipe); 728 default: 729 throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented"); 730 } 731 } catch (Throwable t) { 732 throw new StringConcatException("Generator failed", t); 733 } 734 } 735 736 private enum Mode { 737 DEFAULT(false, false), 738 SIZED(true, false), 739 SIZED_EXACT(true, true); 740 741 private final boolean sized; 742 private final boolean exact; 743 744 Mode(boolean sized, boolean exact) { 745 this.sized = sized; 746 this.exact = exact; 747 } 748 749 boolean isSized() { 750 return sized; 751 } 752 753 boolean isExact() { 754 return exact; 755 } 756 } 757 758 /** 759 * Bytecode StringBuilder strategy. 760 * 761 * <p>This strategy operates in three modes, gated by {@link Mode}. 762 * 763 * <p><b>{@link Strategy#BC_SB}: "bytecode StringBuilder".</b> 764 * 765 * <p>This strategy spins up the bytecode that has the same StringBuilder 766 * chain javac would otherwise emit. This strategy uses only the public API, 767 * and comes as the baseline for the current JDK behavior. On other words, 768 * this strategy moves the javac generated bytecode to runtime. The 769 * generated bytecode is loaded via Unsafe.defineAnonymousClass, but with 770 * the caller class coming from the BSM -- in other words, the protection 771 * guarantees are inherited from the method where invokedynamic was 772 * originally called. This means, among other things, that the bytecode is 773 * verified for all non-JDK uses. 774 * 775 * <p><b>{@link Strategy#BC_SB_SIZED}: "bytecode StringBuilder, but 776 * sized".</b> 777 * 778 * <p>This strategy acts similarly to {@link Strategy#BC_SB}, but it also 779 * tries to guess the capacity required for StringBuilder to accept all 780 * arguments without resizing. This strategy only makes an educated guess: 781 * it only guesses the space required for known types (e.g. primitives and 782 * Strings), but does not otherwise convert arguments. Therefore, the 783 * capacity estimate may be wrong, and StringBuilder may have to 784 * transparently resize or trim when doing the actual concatenation. While 785 * this does not constitute a correctness issue (in the end, that what BC_SB 786 * has to do anyway), this does pose a potential performance problem. 787 * 788 * <p><b>{@link Strategy#BC_SB_SIZED_EXACT}: "bytecode StringBuilder, but 789 * sized exactly".</b> 790 * 791 * <p>This strategy improves on @link Strategy#BC_SB_SIZED}, by first 792 * converting all arguments to String in order to get the exact capacity 793 * StringBuilder should have. The conversion is done via the public 794 * String.valueOf and/or Object.toString methods, and does not touch any 795 * private String API. 796 */ 797 private static final class BytecodeStringBuilderStrategy { 798 static final Unsafe UNSAFE = Unsafe.getUnsafe(); 799 static final int CLASSFILE_VERSION = 52; 800 static final String METHOD_NAME = "concat"; 801 802 private BytecodeStringBuilderStrategy() { 803 // no instantiation 804 } 805 806 private static MethodHandle generate(Lookup lookup, String className, MethodType args, Recipe recipe, Mode mode) throws Exception { 807 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); 808 809 cw.visit(CLASSFILE_VERSION, 810 ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC, 811 className, // Unsafe.defineAnonymousClass would append an unique ID 812 null, 813 "java/lang/Object", 814 null 815 ); 816 817 MethodVisitor mv = cw.visitMethod( 818 ACC_PUBLIC + ACC_STATIC + ACC_FINAL, 819 METHOD_NAME, 820 args.toMethodDescriptorString(), 821 null, 822 null); 823 824 mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true); 825 mv.visitCode(); 826 827 Class<?>[] arr = args.parameterArray(); 828 boolean[] guaranteedNonNull = new boolean[arr.length]; 829 830 if (mode.isExact()) { 831 /* 832 In exact mode, we need to convert all arguments to their String representations, 833 as this allows to compute their String sizes exactly. We cannot use private 834 methods for primitives in here, therefore we need to convert those as well. 835 836 We also record what arguments are guaranteed to be non-null as the result 837 of the conversion. String.valueOf does the null checks for us. The only 838 corner case to take care of is String.valueOf(Object) returning null itself. 839 840 Also, if any conversion happened, then the slot indices in the incoming 841 arguments are not equal to the final local maps. The only case this may break 842 is when converting 2-slot long/double argument to 1-slot String. Therefore, 843 we get away with tracking modified offset, since no conversion can overwrite 844 the upcoming the argument. 845 */ 846 847 int off = 0; 848 int modOff = 0; 849 for (int c = 0; c < arr.length; c++) { 850 Class<?> cl = arr[c]; 851 if (cl == String.class) { 852 if (off != modOff) { 853 mv.visitIntInsn(getLoadOpcode(cl), off); 854 mv.visitIntInsn(ASTORE, modOff); 855 } 856 } else { 857 mv.visitIntInsn(getLoadOpcode(cl), off); 858 mv.visitMethodInsn( 859 INVOKESTATIC, 860 "java/lang/String", 861 "valueOf", 862 getStringValueOfDesc(cl), 863 false 864 ); 865 mv.visitIntInsn(ASTORE, modOff); 866 arr[c] = String.class; 867 guaranteedNonNull[c] = cl.isPrimitive(); 868 } 869 off += getParameterSize(cl); 870 modOff += getParameterSize(String.class); 871 } 872 } 873 874 if (mode.isSized()) { 875 /* 876 When operating in sized mode (this includes exact mode), it makes sense to make 877 StringBuilder append chains look familiar to OptimizeStringConcat. For that, we 878 need to do null-checks early, not make the append chain shape simpler. 879 */ 880 881 int off = 0; 882 for (RecipeElement el : recipe.getElements()) { 883 switch (el.getTag()) { 884 case CONST: { 885 // Guaranteed non-null, no null check required. 886 break; 887 } 888 case ARG: { 889 // Null-checks are needed only for String arguments, and when a previous stage 890 // did not do implicit null-checks. If a String is null, we eagerly replace it 891 // with "null" constant. Note, we omit Objects here, because we don't call 892 // .length() on them down below. 893 int ac = el.getArgPos(); 894 Class<?> cl = arr[ac]; 895 if (cl == String.class && !guaranteedNonNull[ac]) { 896 Label l0 = new Label(); 897 mv.visitIntInsn(ALOAD, off); 898 mv.visitJumpInsn(IFNONNULL, l0); 899 mv.visitLdcInsn("null"); 900 mv.visitIntInsn(ASTORE, off); 901 mv.visitLabel(l0); 902 } 903 off += getParameterSize(cl); 904 break; 905 } 906 default: 907 throw new StringConcatException("Unhandled tag: " + el.getTag()); 908 } 909 } 910 } 911 912 // Prepare StringBuilder instance 913 mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); 914 mv.visitInsn(DUP); 915 916 if (mode.isSized()) { 917 /* 918 Sized mode requires us to walk through the arguments, and estimate the final length. 919 In exact mode, this will operate on Strings only. This code would accumulate the 920 final length on stack. 921 */ 922 int len = 0; 923 int off = 0; 924 925 mv.visitInsn(ICONST_0); 926 927 for (RecipeElement el : recipe.getElements()) { 928 switch (el.getTag()) { 929 case CONST: { 930 Object cnst = el.getValue(); 931 len += cnst.toString().length(); 932 break; 933 } 934 case ARG: { 935 /* 936 If an argument is String, then we can call .length() on it. Sized/Exact modes have 937 converted arguments for us. If an argument is primitive, we can provide a guess 938 for its String representation size. 939 */ 940 Class<?> cl = arr[el.getArgPos()]; 941 if (cl == String.class) { 942 mv.visitIntInsn(ALOAD, off); 943 mv.visitMethodInsn( 944 INVOKEVIRTUAL, 945 "java/lang/String", 946 "length", 947 "()I", 948 false 949 ); 950 mv.visitInsn(IADD); 951 } else if (cl.isPrimitive()) { 952 len += estimateSize(cl); 953 } 954 off += getParameterSize(cl); 955 break; 956 } 957 default: 958 throw new StringConcatException("Unhandled tag: " + el.getTag()); 959 } 960 } 961 962 // Constants have non-zero length, mix in 963 if (len > 0) { 964 iconst(mv, len); 965 mv.visitInsn(IADD); 966 } 967 968 mv.visitMethodInsn( 969 INVOKESPECIAL, 970 "java/lang/StringBuilder", 971 "<init>", 972 "(I)V", 973 false 974 ); 975 } else { 976 mv.visitMethodInsn( 977 INVOKESPECIAL, 978 "java/lang/StringBuilder", 979 "<init>", 980 "()V", 981 false 982 ); 983 } 984 985 // At this point, we have a blank StringBuilder on stack, fill it in with .append calls. 986 { 987 int off = 0; 988 for (RecipeElement el : recipe.getElements()) { 989 String desc; 990 switch (el.getTag()) { 991 case CONST: { 992 Object cnst = el.getValue(); 993 mv.visitLdcInsn(cnst); 994 desc = getSBAppendDesc(cnst.getClass()); 995 break; 996 } 997 case ARG: { 998 Class<?> cl = arr[el.getArgPos()]; 999 mv.visitVarInsn(getLoadOpcode(cl), off); 1000 off += getParameterSize(cl); 1001 desc = getSBAppendDesc(cl); 1002 break; 1003 } 1004 default: 1005 throw new StringConcatException("Unhandled tag: " + el.getTag()); 1006 } 1007 mv.visitMethodInsn( 1008 INVOKEVIRTUAL, 1009 "java/lang/StringBuilder", 1010 "append", 1011 desc, 1012 false 1013 ); 1014 } 1015 } 1016 1017 if (DEBUG && mode.isExact()) { 1018 /* 1019 Exactness checks compare the final StringBuilder.capacity() with a resulting 1020 String.length(). If these values disagree, that means StringBuilder had to perform 1021 storage trimming, which defeats the purpose of exact strategies. 1022 */ 1023 1024 /* 1025 The logic for this check is as follows: 1026 1027 Stack before: Op: 1028 (SB) dup, dup 1029 (SB, SB, SB) capacity() 1030 (int, SB, SB) swap 1031 (SB, int, SB) toString() 1032 (S, int, SB) length() 1033 (int, int, SB) if_icmpeq 1034 (SB) <end> 1035 1036 Note that it leaves the same StringBuilder on exit, like the one on enter. 1037 */ 1038 1039 mv.visitInsn(DUP); 1040 mv.visitInsn(DUP); 1041 1042 mv.visitMethodInsn( 1043 INVOKEVIRTUAL, 1044 "java/lang/StringBuilder", 1045 "capacity", 1046 "()I", 1047 false 1048 ); 1049 1050 mv.visitInsn(SWAP); 1051 1052 mv.visitMethodInsn( 1053 INVOKEVIRTUAL, 1054 "java/lang/StringBuilder", 1055 "toString", 1056 "()Ljava/lang/String;", 1057 false 1058 ); 1059 1060 mv.visitMethodInsn( 1061 INVOKEVIRTUAL, 1062 "java/lang/String", 1063 "length", 1064 "()I", 1065 false 1066 ); 1067 1068 Label l0 = new Label(); 1069 mv.visitJumpInsn(IF_ICMPEQ, l0); 1070 1071 mv.visitTypeInsn(NEW, "java/lang/AssertionError"); 1072 mv.visitInsn(DUP); 1073 mv.visitLdcInsn("Failed exactness check"); 1074 mv.visitMethodInsn(INVOKESPECIAL, 1075 "java/lang/AssertionError", 1076 "<init>", 1077 "(Ljava/lang/Object;)V", 1078 false); 1079 mv.visitInsn(ATHROW); 1080 1081 mv.visitLabel(l0); 1082 } 1083 1084 mv.visitMethodInsn( 1085 INVOKEVIRTUAL, 1086 "java/lang/StringBuilder", 1087 "toString", 1088 "()Ljava/lang/String;", 1089 false 1090 ); 1091 1092 mv.visitInsn(ARETURN); 1093 1094 mv.visitMaxs(-1, -1); 1095 mv.visitEnd(); 1096 cw.visitEnd(); 1097 1098 byte[] classBytes = cw.toByteArray(); 1099 try { 1100 Class<?> hostClass = lookup.lookupClass(); 1101 Class<?> innerClass = UNSAFE.defineAnonymousClass(hostClass, classBytes, null); 1102 UNSAFE.ensureClassInitialized(innerClass); 1103 dumpIfEnabled(innerClass.getName(), classBytes); 1104 return Lookup.IMPL_LOOKUP.findStatic(innerClass, METHOD_NAME, args); 1105 } catch (Throwable e) { 1106 dumpIfEnabled(className + "$$FAILED", classBytes); 1107 throw new StringConcatException("Error while spinning the class", e); 1108 } 1109 } 1110 1111 private static void dumpIfEnabled(String name, byte[] bytes) { 1112 if (DUMPER != null) { 1113 DUMPER.dumpClass(name, bytes); 1114 } 1115 } 1116 1117 private static String getSBAppendDesc(Class<?> cl) { 1118 if (cl.isPrimitive()) { 1119 if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { 1120 return "(I)Ljava/lang/StringBuilder;"; 1121 } else if (cl == Boolean.TYPE) { 1122 return "(Z)Ljava/lang/StringBuilder;"; 1123 } else if (cl == Character.TYPE) { 1124 return "(C)Ljava/lang/StringBuilder;"; 1125 } else if (cl == Double.TYPE) { 1126 return "(D)Ljava/lang/StringBuilder;"; 1127 } else if (cl == Float.TYPE) { 1128 return "(F)Ljava/lang/StringBuilder;"; 1129 } else if (cl == Long.TYPE) { 1130 return "(J)Ljava/lang/StringBuilder;"; 1131 } else { 1132 throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl); 1133 } 1134 } else if (cl == String.class) { 1135 return "(Ljava/lang/String;)Ljava/lang/StringBuilder;"; 1136 } else { 1137 return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;"; 1138 } 1139 } 1140 1141 private static String getStringValueOfDesc(Class<?> cl) { 1142 if (cl.isPrimitive()) { 1143 if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { 1144 return "(I)Ljava/lang/String;"; 1145 } else if (cl == Boolean.TYPE) { 1146 return "(Z)Ljava/lang/String;"; 1147 } else if (cl == Character.TYPE) { 1148 return "(C)Ljava/lang/String;"; 1149 } else if (cl == Double.TYPE) { 1150 return "(D)Ljava/lang/String;"; 1151 } else if (cl == Float.TYPE) { 1152 return "(F)Ljava/lang/String;"; 1153 } else if (cl == Long.TYPE) { 1154 return "(J)Ljava/lang/String;"; 1155 } else { 1156 throw new IllegalStateException("Unhandled String.valueOf: " + cl); 1157 } 1158 } else if (cl == String.class) { 1159 return "(Ljava/lang/String;)Ljava/lang/String;"; 1160 } else { 1161 return "(Ljava/lang/Object;)Ljava/lang/String;"; 1162 } 1163 } 1164 1165 /** 1166 * The following method is copied from 1167 * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small 1168 * and fast Java bytecode manipulation framework. 1169 * Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved. 1170 */ 1171 private static void iconst(MethodVisitor mv, final int cst) { 1172 if (cst >= -1 && cst <= 5) { 1173 mv.visitInsn(Opcodes.ICONST_0 + cst); 1174 } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { 1175 mv.visitIntInsn(Opcodes.BIPUSH, cst); 1176 } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { 1177 mv.visitIntInsn(Opcodes.SIPUSH, cst); 1178 } else { 1179 mv.visitLdcInsn(cst); 1180 } 1181 } 1182 1183 private static int getLoadOpcode(Class<?> c) { 1184 if (c == Void.TYPE) { 1185 throw new InternalError("Unexpected void type of load opcode"); 1186 } 1187 return ILOAD + getOpcodeOffset(c); 1188 } 1189 1190 private static int getOpcodeOffset(Class<?> c) { 1191 if (c.isPrimitive()) { 1192 if (c == Long.TYPE) { 1193 return 1; 1194 } else if (c == Float.TYPE) { 1195 return 2; 1196 } else if (c == Double.TYPE) { 1197 return 3; 1198 } 1199 return 0; 1200 } else { 1201 return 4; 1202 } 1203 } 1204 1205 private static int getParameterSize(Class<?> c) { 1206 if (c == Void.TYPE) { 1207 return 0; 1208 } else if (c == Long.TYPE || c == Double.TYPE) { 1209 return 2; 1210 } 1211 return 1; 1212 } 1213 } 1214 1215 /** 1216 * MethodHandle StringBuilder strategy. 1217 * 1218 * <p>This strategy operates in two modes, gated by {@link Mode}. 1219 * 1220 * <p><b>{@link Strategy#MH_SB_SIZED}: "MethodHandles StringBuilder, 1221 * sized".</b> 1222 * 1223 * <p>This strategy avoids spinning up the bytecode by building the 1224 * computation on MethodHandle combinators. The computation is built with 1225 * public MethodHandle APIs, resolved from a public Lookup sequence, and 1226 * ends up calling the public StringBuilder API. Therefore, this strategy 1227 * does not use any private API at all, even the Unsafe.defineAnonymousClass, 1228 * since everything is handled under cover by java.lang.invoke APIs. 1229 * 1230 * <p><b>{@link Strategy#MH_SB_SIZED_EXACT}: "MethodHandles StringBuilder, 1231 * sized exactly".</b> 1232 * 1233 * <p>This strategy improves on @link Strategy#MH_SB_SIZED}, by first 1234 * converting all arguments to String in order to get the exact capacity 1235 * StringBuilder should have. The conversion is done via the public 1236 * String.valueOf and/or Object.toString methods, and does not touch any 1237 * private String API. 1238 */ 1239 private static final class MethodHandleStringBuilderStrategy { 1240 1241 private MethodHandleStringBuilderStrategy() { 1242 // no instantiation 1243 } 1244 1245 private static MethodHandle generate(MethodType mt, Recipe recipe, Mode mode) throws Exception { 1246 int pc = mt.parameterCount(); 1247 1248 Class<?>[] ptypes = mt.parameterArray(); 1249 MethodHandle[] filters = new MethodHandle[ptypes.length]; 1250 for (int i = 0; i < ptypes.length; i++) { 1251 MethodHandle filter; 1252 switch (mode) { 1253 case SIZED: 1254 // In sized mode, we convert all references and floats/doubles 1255 // to String: there is no specialization for different 1256 // classes in StringBuilder API, and it will convert to 1257 // String internally anyhow. 1258 filter = Stringifiers.forMost(ptypes[i]); 1259 break; 1260 case SIZED_EXACT: 1261 // In exact mode, we convert everything to String: 1262 // this helps to compute the storage exactly. 1263 filter = Stringifiers.forAny(ptypes[i]); 1264 break; 1265 default: 1266 throw new StringConcatException("Not supported"); 1267 } 1268 if (filter != null) { 1269 filters[i] = filter; 1270 ptypes[i] = filter.type().returnType(); 1271 } 1272 } 1273 1274 List<Class<?>> ptypesList = Arrays.asList(ptypes); 1275 MethodHandle[] lengthers = new MethodHandle[pc]; 1276 1277 // Figure out lengths: constants' lengths can be deduced on the spot. 1278 // All reference arguments were filtered to String in the combinators below, so we can 1279 // call the usual String.length(). Primitive values string sizes can be estimated. 1280 int initial = 0; 1281 for (RecipeElement el : recipe.getElements()) { 1282 switch (el.getTag()) { 1283 case CONST: { 1284 Object cnst = el.getValue(); 1285 initial += cnst.toString().length(); 1286 break; 1287 } 1288 case ARG: { 1289 final int i = el.getArgPos(); 1290 Class<?> type = ptypesList.get(i); 1291 if (type.isPrimitive()) { 1292 MethodHandle est = MethodHandles.constant(int.class, estimateSize(type)); 1293 est = MethodHandles.dropArguments(est, 0, type); 1294 lengthers[i] = est; 1295 } else { 1296 lengthers[i] = STRING_LENGTH; 1297 } 1298 break; 1299 } 1300 default: 1301 throw new StringConcatException("Unhandled tag: " + el.getTag()); 1302 } 1303 } 1304 1305 // Create (StringBuilder, <args>) shape for appending: 1306 MethodHandle builder = MethodHandles.dropArguments(MethodHandles.identity(StringBuilder.class), 1, ptypesList); 1307 1308 // Compose append calls. This is done in reverse because the application order is 1309 // reverse as well. 1310 List<RecipeElement> elements = recipe.getElements(); 1311 for (int i = elements.size() - 1; i >= 0; i--) { 1312 RecipeElement el = elements.get(i); 1313 MethodHandle appender; 1314 switch (el.getTag()) { 1315 case CONST: { 1316 Object constant = el.getValue(); 1317 MethodHandle mh = appender(adaptToStringBuilder(constant.getClass())); 1318 appender = MethodHandles.insertArguments(mh, 1, constant); 1319 break; 1320 } 1321 case ARG: { 1322 int ac = el.getArgPos(); 1323 appender = appender(ptypesList.get(ac)); 1324 1325 // Insert dummy arguments to match the prefix in the signature. 1326 // The actual appender argument will be the ac-ith argument. 1327 if (ac != 0) { 1328 appender = MethodHandles.dropArguments(appender, 1, ptypesList.subList(0, ac)); 1329 } 1330 break; 1331 } 1332 default: 1333 throw new StringConcatException("Unhandled tag: " + el.getTag()); 1334 } 1335 builder = MethodHandles.foldArguments(builder, appender); 1336 } 1337 1338 // Build the sub-tree that adds the sizes and produces a StringBuilder: 1339 1340 // a) Start with the reducer that accepts all arguments, plus one 1341 // slot for the initial value. Inject the initial value right away. 1342 // This produces (<ints>)int shape: 1343 MethodHandle sum = getReducerFor(pc + 1); 1344 MethodHandle adder = MethodHandles.insertArguments(sum, 0, initial); 1345 1346 // b) Apply lengthers to transform arguments to lengths, producing (<args>)int 1347 adder = MethodHandles.filterArguments(adder, 0, lengthers); 1348 1349 // c) Instantiate StringBuilder (<args>)int -> (<args>)StringBuilder 1350 MethodHandle newBuilder = MethodHandles.filterReturnValue(adder, NEW_STRING_BUILDER); 1351 1352 // d) Fold in StringBuilder constructor, this produces (<args>)StringBuilder 1353 MethodHandle mh = MethodHandles.foldArguments(builder, newBuilder); 1354 1355 // Convert non-primitive arguments to Strings 1356 mh = MethodHandles.filterArguments(mh, 0, filters); 1357 1358 // Convert (<args>)StringBuilder to (<args>)String 1359 if (DEBUG && mode.isExact()) { 1360 mh = MethodHandles.filterReturnValue(mh, BUILDER_TO_STRING_CHECKED); 1361 } else { 1362 mh = MethodHandles.filterReturnValue(mh, BUILDER_TO_STRING); 1363 } 1364 1365 return mh; 1366 } 1367 1368 private static MethodHandle getReducerFor(int cnt) { 1369 return SUMMERS.computeIfAbsent(cnt, SUMMER); 1370 } 1371 1372 private static MethodHandle appender(Class<?> appendType) { 1373 MethodHandle appender = lookupVirtual(MethodHandles.publicLookup(), StringBuilder.class, "append", 1374 StringBuilder.class, adaptToStringBuilder(appendType)); 1375 1376 // appenders should return void, this would not modify the target signature during folding 1377 MethodType nt = MethodType.methodType(void.class, StringBuilder.class, appendType); 1378 return appender.asType(nt); 1379 } 1380 1381 private static String toStringChecked(StringBuilder sb) { 1382 String s = sb.toString(); 1383 if (s.length() != sb.capacity()) { 1384 throw new AssertionError("Exactness check failed: result length = " + s.length() + ", buffer capacity = " + sb.capacity()); 1385 } 1386 return s; 1387 } 1388 1389 private static int sum(int v1, int v2) { 1390 return v1 + v2; 1391 } 1392 1393 private static int sum(int v1, int v2, int v3) { 1394 return v1 + v2 + v3; 1395 } 1396 1397 private static int sum(int v1, int v2, int v3, int v4) { 1398 return v1 + v2 + v3 + v4; 1399 } 1400 1401 private static int sum(int v1, int v2, int v3, int v4, int v5) { 1402 return v1 + v2 + v3 + v4 + v5; 1403 } 1404 1405 private static int sum(int v1, int v2, int v3, int v4, int v5, int v6) { 1406 return v1 + v2 + v3 + v4 + v5 + v6; 1407 } 1408 1409 private static int sum(int v1, int v2, int v3, int v4, int v5, int v6, int v7) { 1410 return v1 + v2 + v3 + v4 + v5 + v6 + v7; 1411 } 1412 1413 private static int sum(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8) { 1414 return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8; 1415 } 1416 1417 private static int sum(int initial, int[] vs) { 1418 int sum = initial; 1419 for (int v : vs) { 1420 sum += v; 1421 } 1422 return sum; 1423 } 1424 1425 private static final ConcurrentMap<Integer, MethodHandle> SUMMERS; 1426 1427 // This one is deliberately non-lambdified to optimize startup time: 1428 private static final Function<Integer, MethodHandle> SUMMER = new Function<Integer, MethodHandle>() { 1429 @Override 1430 public MethodHandle apply(Integer cnt) { 1431 if (cnt == 1) { 1432 return MethodHandles.identity(int.class); 1433 } else if (cnt <= 8) { 1434 // Variable-arity collectors are not as efficient as small-count methods, 1435 // unroll some initial sizes. 1436 Class<?>[] cls = new Class<?>[cnt]; 1437 Arrays.fill(cls, int.class); 1438 return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls); 1439 } else { 1440 return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class) 1441 .asCollector(int[].class, cnt - 1); 1442 } 1443 } 1444 }; 1445 1446 private static final MethodHandle NEW_STRING_BUILDER, STRING_LENGTH, BUILDER_TO_STRING, BUILDER_TO_STRING_CHECKED; 1447 1448 static { 1449 SUMMERS = new ConcurrentHashMap<>(); 1450 Lookup publicLookup = MethodHandles.publicLookup(); 1451 NEW_STRING_BUILDER = lookupConstructor(publicLookup, StringBuilder.class, int.class); 1452 STRING_LENGTH = lookupVirtual(publicLookup, String.class, "length", int.class); 1453 BUILDER_TO_STRING = lookupVirtual(publicLookup, StringBuilder.class, "toString", String.class); 1454 if (DEBUG) { 1455 BUILDER_TO_STRING_CHECKED = lookupStatic(MethodHandles.Lookup.IMPL_LOOKUP, 1456 MethodHandleStringBuilderStrategy.class, "toStringChecked", String.class, StringBuilder.class); 1457 } else { 1458 BUILDER_TO_STRING_CHECKED = null; 1459 } 1460 } 1461 1462 } 1463 1464 1465 /** 1466 * <p><b>{@link Strategy#MH_INLINE_SIZED_EXACT}: "MethodHandles inline, 1467 * sized exactly".</b> 1468 * 1469 * <p>This strategy replicates what StringBuilders are doing: it builds the 1470 * byte[] array on its own and passes that byte[] array to String 1471 * constructor. This strategy requires access to some private APIs in JDK, 1472 * most notably, the read-only Integer/Long.stringSize methods that measure 1473 * the character length of the integers, and the private String constructor 1474 * that accepts byte[] arrays without copying. While this strategy assumes a 1475 * particular implementation details for String, this opens the door for 1476 * building a very optimal concatenation sequence. This is the only strategy 1477 * that requires porting if there are private JDK changes occur. 1478 */ 1479 private static final class MethodHandleInlineCopyStrategy { 1480 static final Unsafe UNSAFE = Unsafe.getUnsafe(); 1481 1482 private MethodHandleInlineCopyStrategy() { 1483 // no instantiation 1484 } 1485 1486 static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable { 1487 1488 // Create filters and obtain filtered parameter types. Filters would be used in the beginning 1489 // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings). 1490 // The filtered argument type list is used all over in the combinators below. 1491 Class<?>[] ptypes = mt.parameterArray(); 1492 MethodHandle[] filters = null; 1493 for (int i = 0; i < ptypes.length; i++) { 1494 MethodHandle filter = Stringifiers.forMost(ptypes[i]); 1495 if (filter != null) { 1496 if (filters == null) { 1497 filters = new MethodHandle[ptypes.length]; 1498 } 1499 filters[i] = filter; 1500 ptypes[i] = filter.type().returnType(); 1501 } 1502 } 1503 List<Class<?>> ptypesList = Arrays.asList(ptypes); 1504 1505 // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes" 1506 // with the (int, byte[], byte)String in String helper. The combinators are assembled bottom-up, 1507 // which makes the code arguably hard to read. 1508 1509 // Drop all remaining parameter types, leave only helper arguments: 1510 MethodHandle mh; 1511 1512 mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes); 1513 mh = MethodHandles.dropArguments(mh, 0, int.class); 1514 1515 // Safety: check that remaining index is zero -- that would mean the storage is completely 1516 // overwritten, and no leakage of uninitialized data occurred. 1517 mh = MethodHandles.filterArgument(mh, 0, CHECK_INDEX); 1518 1519 // Mix in prependers. This happens when (int, byte[], byte) = (index, storage, coder) is already 1520 // known from the combinators below. We are assembling the string backwards, so "index" is the 1521 // *ending* index. 1522 for (RecipeElement el : recipe.getElements()) { 1523 MethodHandle prepender; 1524 switch (el.getTag()) { 1525 case CONST: { 1526 Object cnst = el.getValue(); 1527 prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst); 1528 break; 1529 } 1530 case ARG: { 1531 int pos = el.getArgPos(); 1532 prepender = selectArgument(prepender(ptypesList.get(pos)), 3, ptypesList, pos); 1533 break; 1534 } 1535 default: 1536 throw new StringConcatException("Unhandled tag: " + el.getTag()); 1537 } 1538 1539 // Remove "old" index from arguments 1540 mh = MethodHandles.dropArguments(mh, 1, int.class); 1541 1542 // Do the prepend, and put "new" index at index 0 1543 mh = MethodHandles.foldArguments(mh, prepender); 1544 } 1545 1546 // Prepare the argument list for prepending. The tree below would instantiate 1547 // the storage byte[] into argument 0, so we need to swap "storage" and "index". 1548 // The index at this point equals to "size", and resides at argument 1. 1549 { 1550 MethodType nmt = mh.type() 1551 .changeParameterType(0, byte[].class) 1552 .changeParameterType(1, int.class); 1553 mh = MethodHandles.permuteArguments(mh, nmt, swap10(nmt.parameterCount())); 1554 } 1555 1556 // Fold in byte[] instantiation at argument 0. 1557 MethodHandle combiner = MethodHandles.dropArguments(NEW_ARRAY, 2, ptypesList); 1558 mh = MethodHandles.foldArguments(mh, combiner); 1559 1560 // Start combining length and coder mixers. 1561 // 1562 // Length is easy: constant lengths can be computed on the spot, and all non-constant 1563 // shapes have been either converted to Strings, or explicit methods for getting the 1564 // string length out of primitives are provided. 1565 // 1566 // Coders are more interesting. Only Object, String and char arguments (and constants) 1567 // can have non-Latin1 encoding. It is easier to blindly convert constants to String, 1568 // and deduce the coder from there. Arguments would be either converted to Strings 1569 // during the initial filtering, or handled by primitive specializations in CODER_MIXERS. 1570 // 1571 // The method handle shape after all length and coder mixers is: 1572 // (int, byte, <args>)String = ("index", "coder", <args>) 1573 byte initialCoder = INITIAL_CODER; 1574 int initialLen = 0; // initial length, in characters 1575 for (RecipeElement el : recipe.getElements()) { 1576 switch (el.getTag()) { 1577 case CONST: { 1578 Object constant = el.getValue(); 1579 String s = constant.toString(); 1580 initialCoder = (byte) coderMixer(String.class).invoke(initialCoder, s); 1581 initialLen += s.length(); 1582 break; 1583 } 1584 case ARG: { 1585 int ac = el.getArgPos(); 1586 1587 Class<?> argClass = ptypesList.get(ac); 1588 MethodHandle lm = selectArgument(lengthMixer(argClass), 1, ptypesList, ac); 1589 lm = MethodHandles.dropArguments(lm, 0, byte.class); // (*) 1590 lm = MethodHandles.dropArguments(lm, 2, byte.class); 1591 1592 MethodHandle cm = selectArgument(coderMixer(argClass), 1, ptypesList, ac); 1593 cm = MethodHandles.dropArguments(cm, 0, int.class); // (**) 1594 1595 // Read this bottom up: 1596 1597 // 4. Drop old index and coder, producing ("new-index", "new-coder", <args>) 1598 mh = MethodHandles.dropArguments(mh, 2, int.class, byte.class); 1599 1600 // 3. Compute "new-index", producing ("new-index", "new-coder", "old-index", "old-coder", <args>) 1601 // Length mixer ignores both "new-coder" and "old-coder" due to dropArguments above (*) 1602 mh = MethodHandles.foldArguments(mh, lm); 1603 1604 // 2. Compute "new-coder", producing ("new-coder", "old-index", "old-coder", <args>) 1605 // Coder mixer ignores the "old-index" arg due to dropArguments above (**) 1606 mh = MethodHandles.foldArguments(mh, cm); 1607 1608 // 1. The mh shape here is ("old-index", "old-coder", <args>) 1609 break; 1610 } 1611 default: 1612 throw new StringConcatException("Unhandled tag: " + el.getTag()); 1613 } 1614 } 1615 1616 // Insert initial lengths and coders here. 1617 // The method handle shape here is (<args>). 1618 mh = MethodHandles.insertArguments(mh, 0, initialLen, initialCoder); 1619 1620 // Apply filters, converting the arguments: 1621 if (filters != null) { 1622 mh = MethodHandles.filterArguments(mh, 0, filters); 1623 } 1624 1625 return mh; 1626 } 1627 1628 private static int[] swap10(int count) { 1629 int[] perm = new int[count]; 1630 perm[0] = 1; 1631 perm[1] = 0; 1632 for (int i = 2; i < count; i++) { 1633 perm[i] = i; 1634 } 1635 return perm; 1636 } 1637 1638 // Adapts: (...prefix..., parameter[pos])R -> (...prefix..., ...parameters...)R 1639 private static MethodHandle selectArgument(MethodHandle mh, int prefix, List<Class<?>> ptypes, int pos) { 1640 if (pos == 0) { 1641 return MethodHandles.dropArguments(mh, prefix + 1, ptypes.subList(1, ptypes.size())); 1642 } else if (pos == ptypes.size() - 1) { 1643 return MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, ptypes.size() - 1)); 1644 } else { // 0 < pos < ptypes.size() - 1 1645 MethodHandle t = MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, pos)); 1646 return MethodHandles.dropArguments(t, prefix + 1 + pos, ptypes.subList(pos + 1, ptypes.size())); 1647 } 1648 } 1649 1650 @ForceInline 1651 private static byte[] newArray(int length, byte coder) { 1652 return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder); 1653 } 1654 1655 @ForceInline 1656 private static int checkIndex(int index) { 1657 if (index != 0) { 1658 throw new IllegalStateException("Storage is not completely initialized, " + index + " bytes left"); 1659 } 1660 return index; 1661 } 1662 1663 private static MethodHandle prepender(Class<?> cl) { 1664 return PREPENDERS.computeIfAbsent(cl, PREPEND); 1665 } 1666 1667 private static MethodHandle coderMixer(Class<?> cl) { 1668 return CODER_MIXERS.computeIfAbsent(cl, CODER_MIX); 1669 } 1670 1671 private static MethodHandle lengthMixer(Class<?> cl) { 1672 return LENGTH_MIXERS.computeIfAbsent(cl, LENGTH_MIX); 1673 } 1674 1675 // This one is deliberately non-lambdified to optimize startup time: 1676 private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() { 1677 @Override 1678 public MethodHandle apply(Class<?> c) { 1679 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", int.class, int.class, byte[].class, byte.class, c); 1680 } 1681 }; 1682 1683 // This one is deliberately non-lambdified to optimize startup time: 1684 private static final Function<Class<?>, MethodHandle> CODER_MIX = new Function<Class<?>, MethodHandle>() { 1685 @Override 1686 public MethodHandle apply(Class<?> c) { 1687 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mixCoder", byte.class, byte.class, c); 1688 } 1689 }; 1690 1691 // This one is deliberately non-lambdified to optimize startup time: 1692 private static final Function<Class<?>, MethodHandle> LENGTH_MIX = new Function<Class<?>, MethodHandle>() { 1693 @Override 1694 public MethodHandle apply(Class<?> c) { 1695 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mixLen", int.class, int.class, c); 1696 } 1697 }; 1698 1699 private static final MethodHandle NEW_STRING; 1700 private static final MethodHandle CHECK_INDEX; 1701 private static final MethodHandle NEW_ARRAY; 1702 private static final ConcurrentMap<Class<?>, MethodHandle> PREPENDERS; 1703 private static final ConcurrentMap<Class<?>, MethodHandle> LENGTH_MIXERS; 1704 private static final ConcurrentMap<Class<?>, MethodHandle> CODER_MIXERS; 1705 private static final Class<?> STRING_HELPER; 1706 private static final byte INITIAL_CODER; 1707 1708 static { 1709 try { 1710 STRING_HELPER = Class.forName("java.lang.StringConcatHelper"); 1711 MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", byte.class); 1712 INITIAL_CODER = (byte) initCoder.invoke(); 1713 } catch (Throwable e) { 1714 throw new AssertionError(e); 1715 } 1716 1717 PREPENDERS = new ConcurrentHashMap<>(); 1718 LENGTH_MIXERS = new ConcurrentHashMap<>(); 1719 CODER_MIXERS = new ConcurrentHashMap<>(); 1720 1721 NEW_STRING = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newString", String.class, byte[].class, byte.class); 1722 NEW_ARRAY = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "newArray", byte[].class, int.class, byte.class); 1723 CHECK_INDEX = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "checkIndex", int.class, int.class); 1724 } 1725 } 1726 1727 /** 1728 * Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally 1729 * delegate to {@code String.valueOf}, depending on argument's type. 1730 */ 1731 private static final class Stringifiers { 1732 private Stringifiers() { 1733 // no instantiation 1734 } 1735 1736 // This one is deliberately non-lambdified to optimize startup time: 1737 private static final Function<Class<?>, MethodHandle> MOST = new Function<Class<?>, MethodHandle>() { 1738 @Override 1739 public MethodHandle apply(Class<?> cl) { 1740 MethodHandle mhObject = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, Object.class); 1741 1742 // We need the additional conversion here, because String.valueOf(Object) may return null. 1743 // String conversion rules in Java state we need to produce "null" String in this case. 1744 // It can be easily done with applying valueOf the second time. 1745 MethodHandle mhObjectNoNulls = MethodHandles.filterReturnValue(mhObject, 1746 mhObject.asType(MethodType.methodType(String.class, String.class))); 1747 1748 if (cl == String.class) { 1749 return mhObject; 1750 } else if (cl == float.class) { 1751 return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, float.class); 1752 } else if (cl == double.class) { 1753 return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, double.class); 1754 } else if (!cl.isPrimitive()) { 1755 return mhObjectNoNulls; 1756 } 1757 1758 return null; 1759 } 1760 }; 1761 1762 // This one is deliberately non-lambdified to optimize startup time: 1763 private static final Function<Class<?>, MethodHandle> ANY = new Function<Class<?>, MethodHandle>() { 1764 @Override 1765 public MethodHandle apply(Class<?> cl) { 1766 MethodHandle mh = MOST.apply(cl); 1767 if (mh != null) { 1768 return mh; 1769 } 1770 1771 if (cl == byte.class || cl == short.class || cl == int.class) { 1772 return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, int.class); 1773 } else if (cl == boolean.class) { 1774 return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, boolean.class); 1775 } else if (cl == char.class) { 1776 return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, char.class); 1777 } else if (cl == long.class) { 1778 return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, long.class); 1779 } else { 1780 throw new IllegalStateException("Unknown class: " + cl); 1781 } 1782 } 1783 }; 1784 1785 private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_MOST = new ConcurrentHashMap<>(); 1786 private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_ANY = new ConcurrentHashMap<>(); 1787 1788 /** 1789 * Returns a stringifier for references and floats/doubles only. 1790 * Always returns null for other primitives. 1791 * 1792 * @param t class to stringify 1793 * @return stringifier; null, if not available 1794 */ 1795 static MethodHandle forMost(Class<?> t) { 1796 return STRINGIFIERS_MOST.computeIfAbsent(t, MOST); 1797 } 1798 1799 /** 1800 * Returns a stringifier for any type. Never returns null. 1801 * 1802 * @param t class to stringify 1803 * @return stringifier 1804 */ 1805 static MethodHandle forAny(Class<?> t) { 1806 return STRINGIFIERS_ANY.computeIfAbsent(t, ANY); 1807 } 1808 } 1809 1810 /* ------------------------------- Common utilities ------------------------------------ */ 1811 1812 private static MethodHandle lookupStatic(Lookup lookup, Class<?> refc, String name, Class<?> rtype, Class<?>... ptypes) { 1813 try { 1814 return lookup.findStatic(refc, name, MethodType.methodType(rtype, ptypes)); 1815 } catch (NoSuchMethodException | IllegalAccessException e) { 1816 throw new AssertionError(e); 1817 } 1818 } 1819 1820 private static MethodHandle lookupVirtual(Lookup lookup, Class<?> refc, String name, Class<?> rtype, Class<?>... ptypes) { 1821 try { 1822 return lookup.findVirtual(refc, name, MethodType.methodType(rtype, ptypes)); 1823 } catch (NoSuchMethodException | IllegalAccessException e) { 1824 throw new AssertionError(e); 1825 } 1826 } 1827 1828 private static MethodHandle lookupConstructor(Lookup lookup, Class<?> refc, Class<?> ptypes) { 1829 try { 1830 return lookup.findConstructor(refc, MethodType.methodType(void.class, ptypes)); 1831 } catch (NoSuchMethodException | IllegalAccessException e) { 1832 throw new AssertionError(e); 1833 } 1834 } 1835 1836 private static int estimateSize(Class<?> cl) { 1837 if (cl == Integer.TYPE) { 1838 return 11; // "-2147483648" 1839 } else if (cl == Boolean.TYPE) { 1840 return 5; // "false" 1841 } else if (cl == Byte.TYPE) { 1842 return 4; // "-128" 1843 } else if (cl == Character.TYPE) { 1844 return 1; // duh 1845 } else if (cl == Short.TYPE) { 1846 return 6; // "-32768" 1847 } else if (cl == Double.TYPE) { 1848 return 26; // apparently, no larger than this, see FloatingDecimal.BinaryToASCIIBuffer.buffer 1849 } else if (cl == Float.TYPE) { 1850 return 26; // apparently, no larger than this, see FloatingDecimal.BinaryToASCIIBuffer.buffer 1851 } else if (cl == Long.TYPE) { 1852 return 20; // "-9223372036854775808" 1853 } else { 1854 throw new IllegalArgumentException("Cannot estimate the size for " + cl); 1855 } 1856 } 1857 1858 private static Class<?> adaptToStringBuilder(Class<?> c) { 1859 if (c.isPrimitive()) { 1860 if (c == Byte.TYPE || c == Short.TYPE) { 1861 return int.class; 1862 } 1863 } else { 1864 if (c != String.class) { 1865 return Object.class; 1866 } 1867 } 1868 return c; 1869 } 1870 1871 private StringConcatFactory() { 1872 // no instantiation 1873 } 1874 1875 }