< prev index next >

src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java

Print this page




 191         // Poke the privileged block once, taking everything we need:
 192         final Object[] values = new Object[4];
 193         AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
 194             values[0] = System.getProperty("java.lang.invoke.stringConcat");
 195             values[1] = Boolean.getBoolean("java.lang.invoke.stringConcat.cache");
 196             values[2] = Boolean.getBoolean("java.lang.invoke.stringConcat.debug");
 197             values[3] = System.getProperty("java.lang.invoke.stringConcat.dumpClasses");
 198             return null;
 199         });
 200 
 201         final String strategy = (String)  values[0];
 202         CACHE_ENABLE          = (Boolean) values[1];
 203         DEBUG                 = (Boolean) values[2];
 204         final String dumpPath = (String)  values[3];
 205 
 206         STRATEGY = (strategy == null) ? DEFAULT_STRATEGY : Strategy.valueOf(strategy);
 207         CACHE = CACHE_ENABLE ? new ConcurrentHashMap<>() : null;
 208         DUMPER = (dumpPath == null) ? null : ProxyClassesDumper.getInstance(dumpPath);
 209     }
 210 






 211     private static final class Key {

 212         final MethodType mt;
 213         final Recipe recipe;
 214 
 215         public Key(MethodType mt, Recipe recipe) {

 216             this.mt = mt;
 217             this.recipe = recipe;
 218         }
 219 
 220         @Override
 221         public boolean equals(Object o) {
 222             if (this == o) return true;
 223             if (o == null || getClass() != o.getClass()) return false;
 224 
 225             Key key = (Key) o;
 226 

 227             if (!mt.equals(key.mt)) return false;
 228             if (!recipe.equals(key.recipe)) return false;
 229             return true;
 230         }
 231 
 232         @Override
 233         public int hashCode() {
 234             int result = mt.hashCode();

 235             result = 31 * result + recipe.hashCode();
 236             return result;
 237         }
 238     }
 239 
 240     /**
 241      * Parses the recipe string, and produces the traversable collection of
 242      * {@link java.lang.invoke.StringConcatFactory.RecipeElement}-s for generator
 243      * strategies. Notably, this class parses out the constants from the recipe
 244      * and from other static arguments.
 245      */
 246     private static final class Recipe {
 247         private final List<RecipeElement> elements;
 248         private final List<RecipeElement> elementsRev;
 249 
 250         public Recipe(String src, Object[] constants) {
 251             List<RecipeElement> el = new ArrayList<>();
 252 
 253             int constC = 0;
 254             int argC = 0;


 597                     "Mismatched number of concat constants: recipe wants " +
 598                             cCount +
 599                             " constants, but only " +
 600                             constants.length +
 601                             " are passed");
 602         }
 603 
 604         if (!concatType.returnType().isAssignableFrom(String.class)) {
 605             throw new StringConcatException(
 606                     "The return type should be compatible with String, but it is " +
 607                             concatType.returnType());
 608         }
 609 
 610         if (concatType.parameterCount() > MAX_INDY_CONCAT_ARG_SLOTS) {
 611             throw new StringConcatException("Too many concat argument slots: " +
 612                     concatType.parameterCount() +
 613                     ", can only accept " +
 614                     MAX_INDY_CONCAT_ARG_SLOTS);
 615         }
 616 

 617         MethodType mt = adaptType(concatType);
 618 
 619         Recipe rec = new Recipe(recipe, constants);
 620 
 621         MethodHandle mh;
 622         if (CACHE_ENABLE) {
 623             Key key = new Key(mt, rec);
 624             mh = CACHE.get(key);
 625             if (mh == null) {
 626                 mh = generate(lookup, mt, rec);
 627                 CACHE.put(key, mh);
 628             }
 629         } else {
 630             mh = generate(lookup, mt, rec);
 631         }
 632         return new ConstantCallSite(mh.asType(concatType));
 633     }
 634 
 635     /**
 636      * Adapt method type to an API we are going to use.
 637      *
 638      * This strips the concrete classes from the signatures, thus preventing
 639      * class leakage when we cache the concatenation stubs.
 640      *
 641      * @param args actual argument types
 642      * @return argument types the strategy is going to use
 643      */
 644     private static MethodType adaptType(MethodType args) {
 645         Class<?>[] ptypes = args.parameterArray();
 646         boolean changed = false;
 647         for (int i = 0; i < ptypes.length; i++) {
 648             Class<?> ptype = ptypes[i];
 649             if (!ptype.isPrimitive() &&
 650                     ptype != String.class &&
 651                     ptype != Object.class) { // truncate to Object
 652                 ptypes[i] = Object.class;
 653                 changed = true;
 654             }
 655             // else other primitives or String or Object (unchanged)
 656         }
 657         return changed
 658                 ? MethodType.methodType(args.returnType(), ptypes)
 659                 : args;
 660     }
 661 
 662     private static MethodHandle generate(Lookup lookup, MethodType mt, Recipe recipe) throws StringConcatException {












































 663         try {
 664             switch (STRATEGY) {
 665                 case BC_SB:
 666                     return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.DEFAULT);
 667                 case BC_SB_SIZED:
 668                     return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.SIZED);
 669                 case BC_SB_SIZED_EXACT:
 670                     return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.SIZED_EXACT);
 671                 case MH_SB_SIZED:
 672                     return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED);
 673                 case MH_SB_SIZED_EXACT:
 674                     return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT);
 675                 case MH_INLINE_SIZED_EXACT:
 676                     return MethodHandleInlineCopyStrategy.generate(mt, recipe);
 677                 default:
 678                     throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented");
 679             }
 680         } catch (Throwable t) {
 681             throw new StringConcatException("Generator failed", t);
 682         }
 683     }
 684 
 685     private enum Mode {
 686         DEFAULT(false, false),
 687         SIZED(true, false),
 688         SIZED_EXACT(true, true);
 689 
 690         private final boolean sized;


 729      * arguments without resizing. This strategy only makes an educated guess:
 730      * it only guesses the space required for known types (e.g. primitives and
 731      * Strings), but does not otherwise convert arguments. Therefore, the
 732      * capacity estimate may be wrong, and StringBuilder may have to
 733      * transparently resize or trim when doing the actual concatenation. While
 734      * this does not constitute a correctness issue (in the end, that what BC_SB
 735      * has to do anyway), this does pose a potential performance problem.
 736      *
 737      * <p><b>{@link Strategy#BC_SB_SIZED_EXACT}: "bytecode StringBuilder, but
 738      * sized exactly".</b>
 739      *
 740      * <p>This strategy improves on @link Strategy#BC_SB_SIZED}, by first
 741      * converting all arguments to String in order to get the exact capacity
 742      * StringBuilder should have. The conversion is done via the public
 743      * String.valueOf and/or Object.toString methods, and does not touch any
 744      * private String API.
 745      */
 746     private static final class BytecodeStringBuilderStrategy {
 747         static final Unsafe UNSAFE = Unsafe.getUnsafe();
 748         static final int CLASSFILE_VERSION = 52;
 749         static final String NAME_FACTORY = "concat";
 750         static final String CLASS_NAME = "java/lang/String$Concat";
 751 
 752         private BytecodeStringBuilderStrategy() {
 753             // no instantiation
 754         }
 755 
 756         private static MethodHandle generate(MethodHandles.Lookup lookup, MethodType args, Recipe recipe, Mode mode) throws Exception {
 757             ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
 758 
 759             cw.visit(CLASSFILE_VERSION,
 760                     ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,
 761                     CLASS_NAME,
 762                     null,
 763                     "java/lang/Object",
 764                     null
 765             );
 766 
 767             MethodVisitor mv = cw.visitMethod(
 768                     ACC_PUBLIC + ACC_STATIC + ACC_FINAL,
 769                     NAME_FACTORY,
 770                     args.toMethodDescriptorString(),
 771                     null,
 772                     null);
 773 
 774             mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true);
 775             mv.visitCode();
 776 
 777             Class<?>[] arr = args.parameterArray();
 778             boolean[] guaranteedNonNull = new boolean[arr.length];
 779 
 780             if (mode.isExact()) {
 781                 /*
 782                     In exact mode, we need to convert all arguments to their String representations,
 783                     as this allows to compute their String sizes exactly. We cannot use private
 784                     methods for primitives in here, therefore we need to convert those as well.
 785 
 786                     We also record what arguments are guaranteed to be non-null as the result
 787                     of the conversion. String.valueOf does the null checks for us. The only
 788                     corner case to take care of is String.valueOf(Object) returning null itself.
 789 


1028                         false);
1029                 mv.visitInsn(ATHROW);
1030 
1031                 mv.visitLabel(l0);
1032             }
1033 
1034             mv.visitMethodInsn(
1035                     INVOKEVIRTUAL,
1036                     "java/lang/StringBuilder",
1037                     "toString",
1038                     "()Ljava/lang/String;",
1039                     false
1040             );
1041 
1042             mv.visitInsn(ARETURN);
1043 
1044             mv.visitMaxs(-1, -1);
1045             mv.visitEnd();
1046             cw.visitEnd();
1047 
1048             Class<?> targetClass = lookup.lookupClass();
1049             final byte[] classBytes = cw.toByteArray();
1050             final Class<?> innerClass = UNSAFE.defineAnonymousClass(targetClass, classBytes, null);
1051 
1052             if (DUMPER != null) {
1053                 DUMPER.dumpClass(innerClass.getName(), classBytes);
1054             }
1055 
1056             try {


1057                 UNSAFE.ensureClassInitialized(innerClass);
1058                 return lookup.findStatic(innerClass, NAME_FACTORY, args);
1059             } catch (ReflectiveOperationException e) {
1060                 throw new StringConcatException("Exception finding constructor", e);








1061             }
1062         }
1063 
1064         private static String getSBAppendDesc(Class<?> cl) {
1065             if (cl.isPrimitive()) {
1066                 if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) {
1067                     return "(I)Ljava/lang/StringBuilder;";
1068                 } else if (cl == Boolean.TYPE) {
1069                     return "(Z)Ljava/lang/StringBuilder;";
1070                 } else if (cl == Character.TYPE) {
1071                     return "(C)Ljava/lang/StringBuilder;";
1072                 } else if (cl == Double.TYPE) {
1073                     return "(D)Ljava/lang/StringBuilder;";
1074                 } else if (cl == Float.TYPE) {
1075                     return "(F)Ljava/lang/StringBuilder;";
1076                 } else if (cl == Long.TYPE) {
1077                     return "(J)Ljava/lang/StringBuilder;";
1078                 } else {
1079                     throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl);
1080                 }


1670                 CHECK_INDEX = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "checkIndex", int.class, int.class);
1671             } else {
1672                 CHECK_INDEX = null;
1673             }
1674         }
1675     }
1676 
1677     /**
1678      * Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally
1679      * delegate to {@code String.valueOf}, depending on argument's type.
1680      */
1681     private static final class Stringifiers {
1682         private Stringifiers() {
1683             // no instantiation
1684         }
1685 
1686         // This one is deliberately non-lambdified to optimize startup time:
1687         private static final Function<Class<?>, MethodHandle> MOST = new Function<Class<?>, MethodHandle>() {
1688             @Override
1689             public MethodHandle apply(Class<?> cl) {
1690                 MethodHandle mhObject = lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, Object.class);
1691 
1692                 // We need the additional conversion here, because String.valueOf(Object) may return null.
1693                 // String conversion rules in Java state we need to produce "null" String in this case.
1694                 // It can be easily done with applying valueOf the second time.
1695                 MethodHandle mhObjectNoNulls = MethodHandles.filterReturnValue(mhObject,
1696                         mhObject.asType(MethodType.methodType(String.class, String.class)));
1697 
1698                 if (cl == String.class) {
1699                     return mhObject;
1700                 } else if (cl == float.class) {
1701                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, float.class);
1702                 } else if (cl == double.class) {
1703                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, double.class);
1704                 } else if (!cl.isPrimitive()) {
1705                     return mhObjectNoNulls;
1706                 }
1707 
1708                 return null;
1709             }
1710         };
1711 
1712         // This one is deliberately non-lambdified to optimize startup time:
1713         private static final Function<Class<?>, MethodHandle> ANY = new Function<Class<?>, MethodHandle>() {
1714             @Override
1715             public MethodHandle apply(Class<?> cl) {
1716                 MethodHandle mh = MOST.apply(cl);
1717                 if (mh != null) {
1718                     return mh;
1719                 }
1720 
1721                 if (cl == byte.class || cl == short.class || cl == int.class) {
1722                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, int.class);
1723                 } else if (cl == boolean.class) {
1724                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, boolean.class);
1725                 } else if (cl == char.class) {
1726                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, char.class);
1727                 } else if (cl == long.class) {
1728                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, long.class);
1729                 } else {
1730                     throw new IllegalStateException("Unknown class: " + cl);
1731                 }
1732             }
1733         };
1734 
1735         private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_MOST = new ConcurrentHashMap<>();
1736         private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_ANY = new ConcurrentHashMap<>();
1737 
1738         /**
1739          * Returns a stringifier for references and floats/doubles only.
1740          * Always returns null for other primitives.
1741          *
1742          * @param t class to stringify
1743          * @return stringifier; null, if not available
1744          */
1745         static MethodHandle forMost(Class<?> t) {
1746             return STRINGIFIERS_MOST.computeIfAbsent(t, MOST);
1747         }
1748 




 191         // Poke the privileged block once, taking everything we need:
 192         final Object[] values = new Object[4];
 193         AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
 194             values[0] = System.getProperty("java.lang.invoke.stringConcat");
 195             values[1] = Boolean.getBoolean("java.lang.invoke.stringConcat.cache");
 196             values[2] = Boolean.getBoolean("java.lang.invoke.stringConcat.debug");
 197             values[3] = System.getProperty("java.lang.invoke.stringConcat.dumpClasses");
 198             return null;
 199         });
 200 
 201         final String strategy = (String)  values[0];
 202         CACHE_ENABLE          = (Boolean) values[1];
 203         DEBUG                 = (Boolean) values[2];
 204         final String dumpPath = (String)  values[3];
 205 
 206         STRATEGY = (strategy == null) ? DEFAULT_STRATEGY : Strategy.valueOf(strategy);
 207         CACHE = CACHE_ENABLE ? new ConcurrentHashMap<>() : null;
 208         DUMPER = (dumpPath == null) ? null : ProxyClassesDumper.getInstance(dumpPath);
 209     }
 210 
 211     /**
 212      * Cache key is a composite of:
 213      *   - class name, that lets to disambiguate stubs, to avoid excess sharing
 214      *   - method type, describing the dynamic arguments for concatenation
 215      *   - concat recipe, describing the constants and concat shape
 216      */
 217     private static final class Key {
 218         final String className;
 219         final MethodType mt;
 220         final Recipe recipe;
 221 
 222         public Key(String className, MethodType mt, Recipe recipe) {
 223             this.className = className;
 224             this.mt = mt;
 225             this.recipe = recipe;
 226         }
 227 
 228         @Override
 229         public boolean equals(Object o) {
 230             if (this == o) return true;
 231             if (o == null || getClass() != o.getClass()) return false;
 232 
 233             Key key = (Key) o;
 234 
 235             if (!className.equals(key.className)) return false;
 236             if (!mt.equals(key.mt)) return false;
 237             if (!recipe.equals(key.recipe)) return false;
 238             return true;
 239         }
 240 
 241         @Override
 242         public int hashCode() {
 243             int result = className.hashCode();
 244             result = 31 * result + mt.hashCode();
 245             result = 31 * result + recipe.hashCode();
 246             return result;
 247         }
 248     }
 249 
 250     /**
 251      * Parses the recipe string, and produces the traversable collection of
 252      * {@link java.lang.invoke.StringConcatFactory.RecipeElement}-s for generator
 253      * strategies. Notably, this class parses out the constants from the recipe
 254      * and from other static arguments.
 255      */
 256     private static final class Recipe {
 257         private final List<RecipeElement> elements;
 258         private final List<RecipeElement> elementsRev;
 259 
 260         public Recipe(String src, Object[] constants) {
 261             List<RecipeElement> el = new ArrayList<>();
 262 
 263             int constC = 0;
 264             int argC = 0;


 607                     "Mismatched number of concat constants: recipe wants " +
 608                             cCount +
 609                             " constants, but only " +
 610                             constants.length +
 611                             " are passed");
 612         }
 613 
 614         if (!concatType.returnType().isAssignableFrom(String.class)) {
 615             throw new StringConcatException(
 616                     "The return type should be compatible with String, but it is " +
 617                             concatType.returnType());
 618         }
 619 
 620         if (concatType.parameterCount() > MAX_INDY_CONCAT_ARG_SLOTS) {
 621             throw new StringConcatException("Too many concat argument slots: " +
 622                     concatType.parameterCount() +
 623                     ", can only accept " +
 624                     MAX_INDY_CONCAT_ARG_SLOTS);
 625         }
 626 
 627         String className = getClassName(lookup.lookupClass());
 628         MethodType mt = adaptType(concatType);

 629         Recipe rec = new Recipe(recipe, constants);
 630 
 631         MethodHandle mh;
 632         if (CACHE_ENABLE) {
 633             Key key = new Key(className, mt, rec);
 634             mh = CACHE.get(key);
 635             if (mh == null) {
 636                 mh = generate(lookup, className, mt, rec);
 637                 CACHE.put(key, mh);
 638             }
 639         } else {
 640             mh = generate(lookup, className, mt, rec);
 641         }
 642         return new ConstantCallSite(mh.asType(concatType));
 643     }
 644 
 645     /**
 646      * Adapt method type to an API we are going to use.
 647      *
 648      * This strips the concrete classes from the signatures, thus preventing
 649      * class leakage when we cache the concatenation stubs.
 650      *
 651      * @param args actual argument types
 652      * @return argument types the strategy is going to use
 653      */
 654     private static MethodType adaptType(MethodType args) {
 655         Class<?>[] ptypes = args.parameterArray();
 656         boolean changed = false;
 657         for (int i = 0; i < ptypes.length; i++) {
 658             Class<?> ptype = ptypes[i];
 659             if (!ptype.isPrimitive() &&
 660                     ptype != String.class &&
 661                     ptype != Object.class) { // truncate to Object
 662                 ptypes[i] = Object.class;
 663                 changed = true;
 664             }
 665             // else other primitives or String or Object (unchanged)
 666         }
 667         return changed
 668                 ? MethodType.methodType(args.returnType(), ptypes)
 669                 : args;
 670     }
 671 
 672     private static String getClassName(Class<?> hostClass) throws StringConcatException {
 673         /*
 674           When cache is enabled, we want to cache as much as we can.
 675 
 676           However, there are two peculiarities:
 677 
 678            a) The generated class should stay within the same package as the
 679               host class, to allow Unsafe.defineAnonymousClass access controls
 680               to work properly. JDK may choose to fail with IllegalAccessException
 681               when accessing a VM anonymous class with non-privileged callers,
 682               see JDK-8058575.
 683 
 684            b) If we mark the stub with some prefix, say, derived from the package
 685               name because of (a), we can technically use that stub in other packages.
 686               But the call stack traces would be extremely puzzling to unsuspecting users
 687               and profiling tools: whatever stub wins the race, would be linked in all
 688               similar callsites.
 689 
 690            Therefore, we set the class prefix to match the host class package, and use
 691            the prefix as the cache key too. This only affects BC_* strategies, and only when
 692            cache is enabled.
 693          */
 694 
 695         switch (STRATEGY) {
 696             case BC_SB:
 697             case BC_SB_SIZED:
 698             case BC_SB_SIZED_EXACT: {
 699                 if (CACHE_ENABLE) {
 700                     Package pkg = hostClass.getPackage();
 701                     return (pkg != null ? pkg.getName().replace(".", "/") + "/" : "") + "Stubs$$StringConcat";
 702                 } else {
 703                     return hostClass.getName().replace(".", "/") + "$$StringConcat";
 704                 }
 705             }
 706             case MH_SB_SIZED:
 707             case MH_SB_SIZED_EXACT:
 708             case MH_INLINE_SIZED_EXACT:
 709                 // MethodHandle strategies do not need a class name.
 710                 return "";
 711             default:
 712                 throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented");
 713         }
 714     }
 715 
 716     private static MethodHandle generate(Lookup lookup, String className, MethodType mt, Recipe recipe) throws StringConcatException {
 717         try {
 718             switch (STRATEGY) {
 719                 case BC_SB:
 720                     return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.DEFAULT);
 721                 case BC_SB_SIZED:
 722                     return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED);
 723                 case BC_SB_SIZED_EXACT:
 724                     return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED_EXACT);
 725                 case MH_SB_SIZED:
 726                     return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED);
 727                 case MH_SB_SIZED_EXACT:
 728                     return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT);
 729                 case MH_INLINE_SIZED_EXACT:
 730                     return MethodHandleInlineCopyStrategy.generate(mt, recipe);
 731                 default:
 732                     throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented");
 733             }
 734         } catch (Throwable t) {
 735             throw new StringConcatException("Generator failed", t);
 736         }
 737     }
 738 
 739     private enum Mode {
 740         DEFAULT(false, false),
 741         SIZED(true, false),
 742         SIZED_EXACT(true, true);
 743 
 744         private final boolean sized;


 783      * arguments without resizing. This strategy only makes an educated guess:
 784      * it only guesses the space required for known types (e.g. primitives and
 785      * Strings), but does not otherwise convert arguments. Therefore, the
 786      * capacity estimate may be wrong, and StringBuilder may have to
 787      * transparently resize or trim when doing the actual concatenation. While
 788      * this does not constitute a correctness issue (in the end, that what BC_SB
 789      * has to do anyway), this does pose a potential performance problem.
 790      *
 791      * <p><b>{@link Strategy#BC_SB_SIZED_EXACT}: "bytecode StringBuilder, but
 792      * sized exactly".</b>
 793      *
 794      * <p>This strategy improves on @link Strategy#BC_SB_SIZED}, by first
 795      * converting all arguments to String in order to get the exact capacity
 796      * StringBuilder should have. The conversion is done via the public
 797      * String.valueOf and/or Object.toString methods, and does not touch any
 798      * private String API.
 799      */
 800     private static final class BytecodeStringBuilderStrategy {
 801         static final Unsafe UNSAFE = Unsafe.getUnsafe();
 802         static final int CLASSFILE_VERSION = 52;
 803         static final String METHOD_NAME = "concat";

 804 
 805         private BytecodeStringBuilderStrategy() {
 806             // no instantiation
 807         }
 808 
 809         private static MethodHandle generate(Lookup lookup, String className, MethodType args, Recipe recipe, Mode mode) throws Exception {
 810             ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
 811 
 812             cw.visit(CLASSFILE_VERSION,
 813                     ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,
 814                     className,  // Unsafe.defineAnonymousClass would append an unique ID
 815                     null,
 816                     "java/lang/Object",
 817                     null
 818             );
 819 
 820             MethodVisitor mv = cw.visitMethod(
 821                     ACC_PUBLIC + ACC_STATIC + ACC_FINAL,
 822                     METHOD_NAME,
 823                     args.toMethodDescriptorString(),
 824                     null,
 825                     null);
 826 
 827             mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true);
 828             mv.visitCode();
 829 
 830             Class<?>[] arr = args.parameterArray();
 831             boolean[] guaranteedNonNull = new boolean[arr.length];
 832 
 833             if (mode.isExact()) {
 834                 /*
 835                     In exact mode, we need to convert all arguments to their String representations,
 836                     as this allows to compute their String sizes exactly. We cannot use private
 837                     methods for primitives in here, therefore we need to convert those as well.
 838 
 839                     We also record what arguments are guaranteed to be non-null as the result
 840                     of the conversion. String.valueOf does the null checks for us. The only
 841                     corner case to take care of is String.valueOf(Object) returning null itself.
 842 


1081                         false);
1082                 mv.visitInsn(ATHROW);
1083 
1084                 mv.visitLabel(l0);
1085             }
1086 
1087             mv.visitMethodInsn(
1088                     INVOKEVIRTUAL,
1089                     "java/lang/StringBuilder",
1090                     "toString",
1091                     "()Ljava/lang/String;",
1092                     false
1093             );
1094 
1095             mv.visitInsn(ARETURN);
1096 
1097             mv.visitMaxs(-1, -1);
1098             mv.visitEnd();
1099             cw.visitEnd();
1100 
1101             byte[] classBytes = cw.toByteArray();







1102             try {
1103                 Class<?> hostClass = lookup.lookupClass();
1104                 Class<?> innerClass = UNSAFE.defineAnonymousClass(hostClass, classBytes, null);
1105                 UNSAFE.ensureClassInitialized(innerClass);
1106                 dumpIfEnabled(innerClass.getName(), classBytes);
1107                 return lookup.findStatic(innerClass, METHOD_NAME, args);
1108             } catch (Throwable e) {
1109                 dumpIfEnabled(className + "$$FAILED", classBytes);
1110                 throw new StringConcatException("Error while spinning the class", e);
1111             }
1112         }
1113 
1114         private static void dumpIfEnabled(String name, byte[] bytes) {
1115             if (DUMPER != null) {
1116                 DUMPER.dumpClass(name, bytes);
1117             }
1118         }
1119 
1120         private static String getSBAppendDesc(Class<?> cl) {
1121             if (cl.isPrimitive()) {
1122                 if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) {
1123                     return "(I)Ljava/lang/StringBuilder;";
1124                 } else if (cl == Boolean.TYPE) {
1125                     return "(Z)Ljava/lang/StringBuilder;";
1126                 } else if (cl == Character.TYPE) {
1127                     return "(C)Ljava/lang/StringBuilder;";
1128                 } else if (cl == Double.TYPE) {
1129                     return "(D)Ljava/lang/StringBuilder;";
1130                 } else if (cl == Float.TYPE) {
1131                     return "(F)Ljava/lang/StringBuilder;";
1132                 } else if (cl == Long.TYPE) {
1133                     return "(J)Ljava/lang/StringBuilder;";
1134                 } else {
1135                     throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl);
1136                 }


1726                 CHECK_INDEX = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "checkIndex", int.class, int.class);
1727             } else {
1728                 CHECK_INDEX = null;
1729             }
1730         }
1731     }
1732 
1733     /**
1734      * Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally
1735      * delegate to {@code String.valueOf}, depending on argument's type.
1736      */
1737     private static final class Stringifiers {
1738         private Stringifiers() {
1739             // no instantiation
1740         }
1741 
1742         // This one is deliberately non-lambdified to optimize startup time:
1743         private static final Function<Class<?>, MethodHandle> MOST = new Function<Class<?>, MethodHandle>() {
1744             @Override
1745             public MethodHandle apply(Class<?> cl) {
1746                 MethodHandle mhObject = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, Object.class);
1747 
1748                 // We need the additional conversion here, because String.valueOf(Object) may return null.
1749                 // String conversion rules in Java state we need to produce "null" String in this case.
1750                 // It can be easily done with applying valueOf the second time.
1751                 MethodHandle mhObjectNoNulls = MethodHandles.filterReturnValue(mhObject,
1752                         mhObject.asType(MethodType.methodType(String.class, String.class)));
1753 
1754                 if (cl == String.class) {
1755                     return mhObject;
1756                 } else if (cl == float.class) {
1757                     return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, float.class);
1758                 } else if (cl == double.class) {
1759                     return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, double.class);
1760                 } else if (!cl.isPrimitive()) {
1761                     return mhObjectNoNulls;
1762                 }
1763 
1764                 return null;
1765             }
1766         };
1767 
1768         // This one is deliberately non-lambdified to optimize startup time:
1769         private static final Function<Class<?>, MethodHandle> ANY = new Function<Class<?>, MethodHandle>() {
1770             @Override
1771             public MethodHandle apply(Class<?> cl) {
1772                 MethodHandle mh = MOST.apply(cl);
1773                 if (mh != null) {
1774                     return mh;
1775                 }
1776 
1777                 if (cl == byte.class || cl == short.class || cl == int.class) {
1778                     return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, int.class);
1779                 } else if (cl == boolean.class) {
1780                     return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, boolean.class);
1781                 } else if (cl == char.class) {
1782                     return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, char.class);
1783                 } else if (cl == long.class) {
1784                     return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, long.class);
1785                 } else {
1786                     throw new IllegalStateException("Unknown class: " + cl);
1787                 }
1788             }
1789         };
1790 
1791         private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_MOST = new ConcurrentHashMap<>();
1792         private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_ANY = new ConcurrentHashMap<>();
1793 
1794         /**
1795          * Returns a stringifier for references and floats/doubles only.
1796          * Always returns null for other primitives.
1797          *
1798          * @param t class to stringify
1799          * @return stringifier; null, if not available
1800          */
1801         static MethodHandle forMost(Class<?> t) {
1802             return STRINGIFIERS_MOST.computeIfAbsent(t, MOST);
1803         }
1804 


< prev index next >