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