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 |