165 * MethodHandle-based generator, that constructs its own byte[] array from
166 * the arguments. It computes the required storage exactly.
167 */
168 MH_INLINE_SIZED_EXACT
169 }
170
171 /**
172 * Enables debugging: this may print debugging messages, perform additional (non-neutral for performance)
173 * checks, etc.
174 */
175 private static final boolean DEBUG;
176
177 /**
178 * Enables caching of strategy stubs. This may improve the linkage time by reusing the generated
179 * code, at the expense of contaminating the profiles.
180 */
181 private static final boolean CACHE_ENABLE;
182
183 private static final ConcurrentMap<Key, MethodHandle> CACHE;
184
185 static {
186 // Poke the privileged block once, taking everything we need:
187 final Object[] values = new Object[3];
188 AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
189 values[0] = System.getProperty("java.lang.invoke.stringConcat");
190 values[1] = Boolean.getBoolean("java.lang.invoke.stringConcat.cache");
191 values[2] = Boolean.getBoolean("java.lang.invoke.stringConcat.debug");
192 return null;
193 });
194
195 final String strategy = (String) values[0];
196 CACHE_ENABLE = (Boolean) values[1];
197 DEBUG = (Boolean) values[2];
198
199 STRATEGY = (strategy == null) ? DEFAULT_STRATEGY : Strategy.valueOf(strategy);
200 CACHE = CACHE_ENABLE ? new ConcurrentHashMap<>() : null;
201 }
202
203 private static final class Key {
204 final MethodType mt;
205 final Recipe recipe;
206
207 public Key(MethodType mt, Recipe recipe) {
208 this.mt = mt;
209 this.recipe = recipe;
210 }
211
212 @Override
213 public boolean equals(Object o) {
214 if (this == o) return true;
215 if (o == null || getClass() != o.getClass()) return false;
216
217 Key key = (Key) o;
218
219 if (!mt.equals(key.mt)) return false;
220 if (!recipe.equals(key.recipe)) return false;
535 }
536
537 return doStringConcat(lookup, name, concatType, false, recipe, constants);
538 }
539
540 private static CallSite doStringConcat(MethodHandles.Lookup lookup,
541 String name,
542 MethodType concatType,
543 boolean generateRecipe,
544 String recipe,
545 Object... constants) throws StringConcatException {
546 Objects.requireNonNull(lookup, "Lookup is null");
547 Objects.requireNonNull(name, "Name is null");
548 Objects.requireNonNull(concatType, "Concat type is null");
549 Objects.requireNonNull(constants, "Constants are null");
550
551 for (Object o : constants) {
552 Objects.requireNonNull(o, "Cannot accept null constants");
553 }
554
555 int cCount = 0;
556 int oCount = 0;
557 if (generateRecipe) {
558 // Mock the recipe to reuse the concat generator code
559 char[] value = new char[concatType.parameterCount()];
560 Arrays.fill(value, TAG_ARG);
561 recipe = new String(value);
562 oCount = concatType.parameterCount();
563 } else {
564 Objects.requireNonNull(recipe, "Recipe is null");
565
566 for (int i = 0; i < recipe.length(); i++) {
567 char c = recipe.charAt(i);
568 if (c == TAG_CONST) cCount++;
569 if (c == TAG_ARG) oCount++;
570 }
571 }
572
573 if (oCount != concatType.parameterCount()) {
574 throw new StringConcatException(
1017 mv.visitLabel(l0);
1018 }
1019
1020 mv.visitMethodInsn(
1021 INVOKEVIRTUAL,
1022 "java/lang/StringBuilder",
1023 "toString",
1024 "()Ljava/lang/String;",
1025 false
1026 );
1027
1028 mv.visitInsn(ARETURN);
1029
1030 mv.visitMaxs(-1, -1);
1031 mv.visitEnd();
1032 cw.visitEnd();
1033
1034 Class<?> targetClass = lookup.lookupClass();
1035 final byte[] classBytes = cw.toByteArray();
1036 final Class<?> innerClass = UNSAFE.defineAnonymousClass(targetClass, classBytes, null);
1037
1038 try {
1039 UNSAFE.ensureClassInitialized(innerClass);
1040 return lookup.findStatic(innerClass, NAME_FACTORY, args);
1041 } catch (ReflectiveOperationException e) {
1042 throw new StringConcatException("Exception finding constructor", e);
1043 }
1044 }
1045
1046 private static String getSBAppendDesc(Class<?> cl) {
1047 if (cl.isPrimitive()) {
1048 if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) {
1049 return "(I)Ljava/lang/StringBuilder;";
1050 } else if (cl == Boolean.TYPE) {
1051 return "(Z)Ljava/lang/StringBuilder;";
1052 } else if (cl == Character.TYPE) {
1053 return "(C)Ljava/lang/StringBuilder;";
1054 } else if (cl == Double.TYPE) {
1055 return "(D)Ljava/lang/StringBuilder;";
1056 } else if (cl == Float.TYPE) {
|
165 * MethodHandle-based generator, that constructs its own byte[] array from
166 * the arguments. It computes the required storage exactly.
167 */
168 MH_INLINE_SIZED_EXACT
169 }
170
171 /**
172 * Enables debugging: this may print debugging messages, perform additional (non-neutral for performance)
173 * checks, etc.
174 */
175 private static final boolean DEBUG;
176
177 /**
178 * Enables caching of strategy stubs. This may improve the linkage time by reusing the generated
179 * code, at the expense of contaminating the profiles.
180 */
181 private static final boolean CACHE_ENABLE;
182
183 private static final ConcurrentMap<Key, MethodHandle> CACHE;
184
185 /**
186 * Dump generated classes to disk, for debugging purposes.
187 */
188 private static final ProxyClassesDumper DUMPER;
189
190 static {
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;
543 }
544
545 return doStringConcat(lookup, name, concatType, false, recipe, constants);
546 }
547
548 private static CallSite doStringConcat(MethodHandles.Lookup lookup,
549 String name,
550 MethodType concatType,
551 boolean generateRecipe,
552 String recipe,
553 Object... constants) throws StringConcatException {
554 Objects.requireNonNull(lookup, "Lookup is null");
555 Objects.requireNonNull(name, "Name is null");
556 Objects.requireNonNull(concatType, "Concat type is null");
557 Objects.requireNonNull(constants, "Constants are null");
558
559 for (Object o : constants) {
560 Objects.requireNonNull(o, "Cannot accept null constants");
561 }
562
563 if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) {
564 throw new StringConcatException(String.format(
565 "Invalid caller: %s",
566 lookup.lookupClass().getName()));
567 }
568
569 int cCount = 0;
570 int oCount = 0;
571 if (generateRecipe) {
572 // Mock the recipe to reuse the concat generator code
573 char[] value = new char[concatType.parameterCount()];
574 Arrays.fill(value, TAG_ARG);
575 recipe = new String(value);
576 oCount = concatType.parameterCount();
577 } else {
578 Objects.requireNonNull(recipe, "Recipe is null");
579
580 for (int i = 0; i < recipe.length(); i++) {
581 char c = recipe.charAt(i);
582 if (c == TAG_CONST) cCount++;
583 if (c == TAG_ARG) oCount++;
584 }
585 }
586
587 if (oCount != concatType.parameterCount()) {
588 throw new StringConcatException(
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) {
|