|
1 /* |
|
2 * Copyright (c) 2017, 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.loader.BootLoader; |
|
29 import jdk.internal.org.objectweb.asm.ClassWriter; |
|
30 import jdk.internal.org.objectweb.asm.FieldVisitor; |
|
31 import jdk.internal.org.objectweb.asm.MethodVisitor; |
|
32 import jdk.internal.vm.annotation.Stable; |
|
33 import sun.invoke.util.BytecodeName; |
|
34 |
|
35 import java.lang.reflect.*; |
|
36 import java.security.AccessController; |
|
37 import java.security.PrivilegedAction; |
|
38 import java.security.ProtectionDomain; |
|
39 import java.util.*; |
|
40 import java.util.concurrent.ConcurrentHashMap; |
|
41 import java.util.concurrent.ConcurrentMap; |
|
42 import java.util.function.Function; |
|
43 |
|
44 import static java.lang.invoke.LambdaForm.*; |
|
45 import static java.lang.invoke.MethodHandleNatives.Constants.REF_getStatic; |
|
46 import static java.lang.invoke.MethodHandleNatives.Constants.REF_putStatic; |
|
47 import static java.lang.invoke.MethodHandleStatics.*; |
|
48 import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; |
|
49 import static jdk.internal.org.objectweb.asm.Opcodes.*; |
|
50 |
|
51 /** |
|
52 * Class specialization code. |
|
53 * @param <T> top class under which species classes are created. |
|
54 * @param <K> key which identifies individual specializations. |
|
55 * @param <S> species data type. |
|
56 */ |
|
57 /*non-public*/ |
|
58 abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesData> { |
|
59 private final Class<T> topClass; |
|
60 private final Class<K> keyType; |
|
61 private final Class<S> metaType; |
|
62 private final MemberName sdAccessor; |
|
63 private final String sdFieldName; |
|
64 private final List<MemberName> transformMethods; |
|
65 private final MethodType baseConstructorType; |
|
66 private final S topSpecies; |
|
67 private final ConcurrentMap<K, S> cache = new ConcurrentHashMap<>(); |
|
68 private final Factory factory; |
|
69 private @Stable boolean topClassIsSuper; |
|
70 |
|
71 /** Return the top type mirror, for type {@code T} */ |
|
72 public final Class<T> topClass() { return topClass; } |
|
73 |
|
74 /** Return the key type mirror, for type {@code K} */ |
|
75 public final Class<K> keyType() { return keyType; } |
|
76 |
|
77 /** Return the species metadata type mirror, for type {@code S} */ |
|
78 public final Class<S> metaType() { return metaType; } |
|
79 |
|
80 /** Report the leading arguments (if any) required by every species factory. |
|
81 * Every species factory adds its own field types as additional arguments, |
|
82 * but these arguments always come first, in every factory method. |
|
83 */ |
|
84 protected MethodType baseConstructorType() { return baseConstructorType; } |
|
85 |
|
86 /** Return the trivial species for the null sequence of arguments. */ |
|
87 protected final S topSpecies() { return topSpecies; } |
|
88 |
|
89 /** Return the list of transform methods originally given at creation of this specializer. */ |
|
90 protected final List<MemberName> transformMethods() { return transformMethods; } |
|
91 |
|
92 /** Return the factory object used to build and load concrete species code. */ |
|
93 protected final Factory factory() { return factory; } |
|
94 |
|
95 /** |
|
96 * Constructor for this class specializer. |
|
97 * @param topClass type mirror for T |
|
98 * @param keyType type mirror for K |
|
99 * @param metaType type mirror for S |
|
100 * @param baseConstructorType principal constructor type |
|
101 * @param sdAccessor the method used to get the speciesData |
|
102 * @param sdFieldName the name of the species data field, inject the speciesData object |
|
103 * @param transformMethods optional list of transformMethods |
|
104 */ |
|
105 protected ClassSpecializer(Class<T> topClass, |
|
106 Class<K> keyType, |
|
107 Class<S> metaType, |
|
108 MethodType baseConstructorType, |
|
109 MemberName sdAccessor, |
|
110 String sdFieldName, |
|
111 List<MemberName> transformMethods) { |
|
112 this.topClass = topClass; |
|
113 this.keyType = keyType; |
|
114 this.metaType = metaType; |
|
115 this.sdAccessor = sdAccessor; |
|
116 // FIXME: use List.copyOf once 8177290 is in |
|
117 this.transformMethods = List.of(transformMethods.toArray(new MemberName[transformMethods.size()])); |
|
118 this.sdFieldName = sdFieldName; |
|
119 this.baseConstructorType = baseConstructorType.changeReturnType(void.class); |
|
120 this.factory = makeFactory(); |
|
121 K tsk = topSpeciesKey(); |
|
122 S topSpecies = null; |
|
123 if (tsk != null && topSpecies == null) { |
|
124 // if there is a key, build the top species if needed: |
|
125 topSpecies = findSpecies(tsk); |
|
126 } |
|
127 this.topSpecies = topSpecies; |
|
128 } |
|
129 |
|
130 // Utilities for subclass constructors: |
|
131 protected static <T> Constructor<T> reflectConstructor(Class<T> defc, Class<?>... ptypes) { |
|
132 try { |
|
133 return defc.getDeclaredConstructor(ptypes); |
|
134 } catch (NoSuchMethodException ex) { |
|
135 throw newIAE(defc.getName()+"("+MethodType.methodType(void.class, ptypes)+")", ex); |
|
136 } |
|
137 } |
|
138 |
|
139 protected static Field reflectField(Class<?> defc, String name) { |
|
140 try { |
|
141 return defc.getDeclaredField(name); |
|
142 } catch (NoSuchFieldException ex) { |
|
143 throw newIAE(defc.getName()+"."+name, ex); |
|
144 } |
|
145 } |
|
146 |
|
147 private static RuntimeException newIAE(String message, Throwable cause) { |
|
148 return new IllegalArgumentException(message, cause); |
|
149 } |
|
150 |
|
151 public final S findSpecies(K key) { |
|
152 S speciesData = cache.computeIfAbsent(key, new Function<>() { |
|
153 @Override |
|
154 public S apply(K key1) { |
|
155 return factory.loadSpecies(newSpeciesData(key1)); |
|
156 } |
|
157 }); |
|
158 // Note: Species instantiation may throw VirtualMachineError because of |
|
159 // code cache overflow. If this happens the species bytecode may be |
|
160 // loaded but not linked to its species metadata (with MH's etc). |
|
161 // That will cause a throw out of CHM.computeIfAbsent, |
|
162 // which will shut down the caller thread. |
|
163 // |
|
164 // In a latter attempt to get the same species, the already-loaded |
|
165 // class will be present in the system dictionary, causing an |
|
166 // error when the species generator tries to reload it. |
|
167 // We try to detect this case and link the pre-existing code. |
|
168 // |
|
169 // Although it would be better to start fresh by loading a new |
|
170 // copy, we have to salvage the previously loaded but broken code. |
|
171 // (As an alternative, we might spin a new class with a new name, |
|
172 // or use the anonymous class mechanism.) |
|
173 // |
|
174 // In the end, as long as everybody goes through the same CHM, |
|
175 // CHM.computeIfAbsent will ensure only one SpeciesData will be set |
|
176 // successfully on a concrete class if ever. |
|
177 // The concrete class is published via SpeciesData instance |
|
178 // returned here only after the class and species data are linked together. |
|
179 assert(speciesData != null); |
|
180 return speciesData; |
|
181 } |
|
182 |
|
183 /** |
|
184 * Meta-data wrapper for concrete subtypes of the top class. |
|
185 * Each concrete subtype corresponds to a given sequence of basic field types (LIJFD). |
|
186 * The fields are immutable; their values are fully specified at object construction. |
|
187 * Each species supplies an array of getter functions which may be used in lambda forms. |
|
188 * A concrete value is always constructed from the full tuple of its field values, |
|
189 * accompanied by the required constructor parameters. |
|
190 * There *may* also be transforms which cloning a species instance and |
|
191 * either replace a constructor parameter or add one or more new field values. |
|
192 * The shortest possible species has zero fields. |
|
193 * Subtypes are not interrelated among themselves by subtyping, even though |
|
194 * it would appear that a shorter species could serve as a supertype of a |
|
195 * longer one which extends it. |
|
196 */ |
|
197 public abstract class SpeciesData { |
|
198 // Bootstrapping requires circular relations Class -> SpeciesData -> Class |
|
199 // Therefore, we need non-final links in the chain. Use @Stable fields. |
|
200 private final K key; |
|
201 private final List<Class<?>> fieldTypes; |
|
202 @Stable private Class<? extends T> speciesCode; |
|
203 @Stable private List<MethodHandle> factories; |
|
204 @Stable private List<MethodHandle> getters; |
|
205 @Stable private List<LambdaForm.NamedFunction> nominalGetters; |
|
206 @Stable private final MethodHandle[] transformHelpers = new MethodHandle[transformMethods.size()]; |
|
207 |
|
208 protected SpeciesData(K key) { |
|
209 this.key = keyType.cast(Objects.requireNonNull(key)); |
|
210 List<Class<?>> types = deriveFieldTypes(key); |
|
211 // TODO: List.copyOf |
|
212 int arity = types.size(); |
|
213 this.fieldTypes = List.of(types.toArray(new Class<?>[arity])); |
|
214 } |
|
215 |
|
216 public final K key() { |
|
217 return key; |
|
218 } |
|
219 |
|
220 protected final List<Class<?>> fieldTypes() { |
|
221 return fieldTypes; |
|
222 } |
|
223 |
|
224 protected final int fieldCount() { |
|
225 return fieldTypes.size(); |
|
226 } |
|
227 |
|
228 protected ClassSpecializer<T,K,S> outer() { |
|
229 return ClassSpecializer.this; |
|
230 } |
|
231 |
|
232 protected final boolean isResolved() { |
|
233 return speciesCode != null && factories != null && !factories.isEmpty(); |
|
234 } |
|
235 |
|
236 @Override public String toString() { |
|
237 return metaType.getSimpleName() + "[" + key.toString() + " => " + (isResolved() ? speciesCode.getSimpleName() : "UNRESOLVED") + "]"; |
|
238 } |
|
239 |
|
240 @Override |
|
241 public int hashCode() { |
|
242 return key.hashCode(); |
|
243 } |
|
244 |
|
245 @Override |
|
246 public boolean equals(Object obj) { |
|
247 if (!(obj instanceof ClassSpecializer.SpeciesData)) { |
|
248 return false; |
|
249 } |
|
250 @SuppressWarnings("rawtypes") |
|
251 ClassSpecializer.SpeciesData that = (ClassSpecializer.SpeciesData) obj; |
|
252 return this.outer() == that.outer() && this.key.equals(that.key); |
|
253 } |
|
254 |
|
255 /** Throws NPE if this species is not yet resolved. */ |
|
256 protected final Class<? extends T> speciesCode() { |
|
257 return Objects.requireNonNull(speciesCode); |
|
258 } |
|
259 |
|
260 /** |
|
261 * Return a {@link MethodHandle} which can get the indexed field of this species. |
|
262 * The return type is the type of the species field it accesses. |
|
263 * The argument type is the {@code fieldHolder} class of this species. |
|
264 */ |
|
265 protected MethodHandle getter(int i) { |
|
266 return getters.get(i); |
|
267 } |
|
268 |
|
269 /** |
|
270 * Return a {@link LambdaForm.Name} containing a {@link LambdaForm.NamedFunction} that |
|
271 * represents a MH bound to a generic invoker, which in turn forwards to the corresponding |
|
272 * getter. |
|
273 */ |
|
274 protected LambdaForm.NamedFunction getterFunction(int i) { |
|
275 LambdaForm.NamedFunction nf = nominalGetters.get(i); |
|
276 assert(nf.memberDeclaringClassOrNull() == speciesCode()); |
|
277 assert(nf.returnType() == BasicType.basicType(fieldTypes.get(i))); |
|
278 return nf; |
|
279 } |
|
280 |
|
281 protected List<LambdaForm.NamedFunction> getterFunctions() { |
|
282 return nominalGetters; |
|
283 } |
|
284 |
|
285 protected List<MethodHandle> getters() { |
|
286 return getters; |
|
287 } |
|
288 |
|
289 protected MethodHandle factory() { |
|
290 return factories.get(0); |
|
291 } |
|
292 |
|
293 protected MethodHandle transformHelper(int whichtm) { |
|
294 MethodHandle mh = transformHelpers[whichtm]; |
|
295 if (mh != null) return mh; |
|
296 mh = deriveTransformHelper(transformMethods().get(whichtm), whichtm); |
|
297 // Do a little type checking before we start using the MH. |
|
298 // (It will be called with invokeBasic, so this is our only chance.) |
|
299 final MethodType mt = transformHelperType(whichtm); |
|
300 mh = mh.asType(mt); |
|
301 return transformHelpers[whichtm] = mh; |
|
302 } |
|
303 |
|
304 private final MethodType transformHelperType(int whichtm) { |
|
305 MemberName tm = transformMethods().get(whichtm); |
|
306 ArrayList<Class<?>> args = new ArrayList<>(); |
|
307 ArrayList<Class<?>> fields = new ArrayList<>(); |
|
308 Collections.addAll(args, tm.getParameterTypes()); |
|
309 fields.addAll(fieldTypes()); |
|
310 List<Class<?>> helperArgs = deriveTransformHelperArguments(tm, whichtm, args, fields); |
|
311 return MethodType.methodType(tm.getReturnType(), helperArgs); |
|
312 } |
|
313 |
|
314 // Hooks for subclasses: |
|
315 |
|
316 /** |
|
317 * Given a key, derive the list of field types, which all instances of this |
|
318 * species must store. |
|
319 */ |
|
320 protected abstract List<Class<?>> deriveFieldTypes(K key); |
|
321 |
|
322 /** |
|
323 * Given the index of a method in the transforms list, supply a factory |
|
324 * method that takes the arguments of the transform, plus the local fields, |
|
325 * and produce a value of the required type. |
|
326 * You can override this to return null or throw if there are no transforms. |
|
327 * This method exists so that the transforms can be "grown" lazily. |
|
328 * This is necessary if the transform *adds* a field to an instance, |
|
329 * which sometimtes requires the creation, on the fly, of an extended species. |
|
330 * This method is only called once for any particular parameter. |
|
331 * The species caches the result in a private array. |
|
332 * |
|
333 * @param transform the transform being implemented |
|
334 * @param whichtm the index of that transform in the original list of transforms |
|
335 * @return the method handle which creates a new result from a mix of transform |
|
336 * arguments and field values |
|
337 */ |
|
338 protected abstract MethodHandle deriveTransformHelper(MemberName transform, int whichtm); |
|
339 |
|
340 /** |
|
341 * During code generation, this method is called once per transform to determine |
|
342 * what is the mix of arguments to hand to the transform-helper. The bytecode |
|
343 * which marshals these arguments is open-coded in the species-specific transform. |
|
344 * The two lists are of opaque objects, which you shouldn't do anything with besides |
|
345 * reordering them into the output list. (They are both mutable, to make editing |
|
346 * easier.) The imputed types of the args correspond to the transform's parameter |
|
347 * list, while the imputed types of the fields correspond to the species field types. |
|
348 * After code generation, this method may be called occasionally by error-checking code. |
|
349 * |
|
350 * @param transform the transform being implemented |
|
351 * @param whichtm the index of that transform in the original list of transforms |
|
352 * @param args a list of opaque objects representing the incoming transform arguments |
|
353 * @param fields a list of opaque objects representing the field values of the receiver |
|
354 * @param <X> the common element type of the various lists |
|
355 * @return a new list |
|
356 */ |
|
357 protected abstract <X> List<X> deriveTransformHelperArguments(MemberName transform, int whichtm, |
|
358 List<X> args, List<X> fields); |
|
359 |
|
360 /** Given a key, generate the name of the class which implements the species for that key. |
|
361 * This algorithm must be stable. |
|
362 * |
|
363 * @return class name, which by default is {@code outer().topClass().getName() + "$Species_" + deriveTypeString(key)} |
|
364 */ |
|
365 protected String deriveClassName() { |
|
366 return outer().topClass().getName() + "$Species_" + deriveTypeString(); |
|
367 } |
|
368 |
|
369 /** |
|
370 * Default implementation collects basic type characters, |
|
371 * plus possibly type names, if some types don't correspond |
|
372 * to basic types. |
|
373 * |
|
374 * @return a string suitable for use in a class name |
|
375 */ |
|
376 protected String deriveTypeString() { |
|
377 List<Class<?>> types = fieldTypes(); |
|
378 StringBuilder buf = new StringBuilder(); |
|
379 StringBuilder end = new StringBuilder(); |
|
380 for (Class<?> type : types) { |
|
381 BasicType basicType = BasicType.basicType(type); |
|
382 if (basicType.basicTypeClass() == type) { |
|
383 buf.append(basicType.basicTypeChar()); |
|
384 } else { |
|
385 buf.append('V'); |
|
386 end.append(classSig(type)); |
|
387 } |
|
388 } |
|
389 String typeString; |
|
390 if (end.length() > 0) { |
|
391 typeString = BytecodeName.toBytecodeName(buf.append("_").append(end).toString()); |
|
392 } else { |
|
393 typeString = buf.toString(); |
|
394 } |
|
395 return LambdaForm.shortenSignature(typeString); |
|
396 } |
|
397 |
|
398 /** |
|
399 * Report what immediate super-class to use for the concrete class of this species. |
|
400 * Normally this is {@code topClass}, but if that is an interface, the factory must override. |
|
401 * The super-class must provide a constructor which takes the {@code baseConstructorType} arguments, if any. |
|
402 * This hook also allows the code generator to use more than one canned supertype for species. |
|
403 * |
|
404 * @return the super-class of the class to be generated |
|
405 */ |
|
406 protected Class<? extends T> deriveSuperClass() { |
|
407 final Class<T> topc = topClass(); |
|
408 if (!topClassIsSuper) { |
|
409 try { |
|
410 final Constructor<T> con = reflectConstructor(topc, baseConstructorType().parameterArray()); |
|
411 if (!topc.isInterface() && !Modifier.isPrivate(con.getModifiers())) { |
|
412 topClassIsSuper = true; |
|
413 } |
|
414 } catch (Exception|InternalError ex) { |
|
415 // fall through... |
|
416 } |
|
417 if (!topClassIsSuper) { |
|
418 throw newInternalError("must override if the top class cannot serve as a super class"); |
|
419 } |
|
420 } |
|
421 return topc; |
|
422 } |
|
423 } |
|
424 |
|
425 protected abstract S newSpeciesData(K key); |
|
426 |
|
427 protected K topSpeciesKey() { |
|
428 return null; // null means don't report a top species |
|
429 } |
|
430 |
|
431 /** |
|
432 * Code generation support for instances. |
|
433 * Subclasses can modify the behavior. |
|
434 */ |
|
435 public class Factory { |
|
436 /** |
|
437 * Get a concrete subclass of the top class for a given combination of bound types. |
|
438 * |
|
439 * @param speciesData the species requiring the class, not yet linked |
|
440 * @return a linked version of the same species |
|
441 */ |
|
442 S loadSpecies(S speciesData) { |
|
443 String className = speciesData.deriveClassName(); |
|
444 assert(className.indexOf('/') < 0) : className; |
|
445 Class<?> salvage = null; |
|
446 try { |
|
447 salvage = BootLoader.loadClassOrNull(className); |
|
448 if (TRACE_RESOLVE && salvage != null) { |
|
449 // Used by jlink species pregeneration plugin, see |
|
450 // jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin |
|
451 System.out.println("[SPECIES_RESOLVE] " + className + " (salvaged)"); |
|
452 } |
|
453 } catch (Error ex) { |
|
454 if (TRACE_RESOLVE) { |
|
455 System.out.println("[SPECIES_FRESOLVE] " + className + " (Error) " + ex.getMessage()); |
|
456 } |
|
457 } |
|
458 final Class<? extends T> speciesCode; |
|
459 if (salvage != null) { |
|
460 speciesCode = salvage.asSubclass(topClass()); |
|
461 factory.linkSpeciesDataToCode(speciesData, speciesCode); |
|
462 factory.linkCodeToSpeciesData(speciesCode, speciesData, true); |
|
463 } else { |
|
464 // Not pregenerated, generate the class |
|
465 try { |
|
466 speciesCode = generateConcreteSpeciesCode(className, speciesData); |
|
467 if (TRACE_RESOLVE) { |
|
468 // Used by jlink species pregeneration plugin, see |
|
469 // jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin |
|
470 System.out.println("[SPECIES_RESOLVE] " + className + " (generated)"); |
|
471 } |
|
472 // This operation causes a lot of churn: |
|
473 linkSpeciesDataToCode(speciesData, speciesCode); |
|
474 // This operation commits the relation, but causes little churn: |
|
475 linkCodeToSpeciesData(speciesCode, speciesData, false); |
|
476 } catch (Error ex) { |
|
477 if (TRACE_RESOLVE) { |
|
478 System.out.println("[SPECIES_RESOLVE] " + className + " (Error #2)" ); |
|
479 } |
|
480 // We can get here if there is a race condition loading a class. |
|
481 // Or maybe we are out of resources. Back out of the CHM.get and retry. |
|
482 throw ex; |
|
483 } |
|
484 } |
|
485 |
|
486 if (!speciesData.isResolved()) { |
|
487 throw newInternalError("bad species class linkage for " + className + ": " + speciesData); |
|
488 } |
|
489 assert(speciesData == factory.loadSpeciesDataFromCode(speciesCode)); |
|
490 return speciesData; |
|
491 } |
|
492 |
|
493 /** |
|
494 * Generate a concrete subclass of the top class for a given combination of bound types. |
|
495 * |
|
496 * A concrete species subclass roughly matches the following schema: |
|
497 * |
|
498 * <pre> |
|
499 * class Species_[[types]] extends [[T]] { |
|
500 * final [[S]] speciesData() { return ... } |
|
501 * static [[T]] make([[fields]]) { return ... } |
|
502 * [[fields]] |
|
503 * final [[T]] transform([[args]]) { return ... } |
|
504 * } |
|
505 * </pre> |
|
506 * |
|
507 * The {@code [[types]]} signature is precisely the key for the species. |
|
508 * |
|
509 * The {@code [[fields]]} section consists of one field definition per character in |
|
510 * the type signature, adhering to the naming schema described in the definition of |
|
511 * {@link #chooseFieldName}. |
|
512 * |
|
513 * For example, a concrete species for two references and one integral bound value |
|
514 * has a shape like the following: |
|
515 * |
|
516 * <pre> |
|
517 * class TopClass { ... private static |
|
518 * final class Species_LLI extends TopClass { |
|
519 * final Object argL0; |
|
520 * final Object argL1; |
|
521 * final int argI2; |
|
522 * private Species_LLI(CT ctarg, ..., Object argL0, Object argL1, int argI2) { |
|
523 * super(ctarg, ...); |
|
524 * this.argL0 = argL0; |
|
525 * this.argL1 = argL1; |
|
526 * this.argI2 = argI2; |
|
527 * } |
|
528 * final SpeciesData speciesData() { return BMH_SPECIES; } |
|
529 * @Stable static SpeciesData BMH_SPECIES; // injected afterwards |
|
530 * static TopClass make(CT ctarg, ..., Object argL0, Object argL1, int argI2) { |
|
531 * return new Species_LLI(ctarg, ..., argL0, argL1, argI2); |
|
532 * } |
|
533 * final TopClass copyWith(CT ctarg, ...) { |
|
534 * return new Species_LLI(ctarg, ..., argL0, argL1, argI2); |
|
535 * } |
|
536 * // two transforms, for the sake of illustration: |
|
537 * final TopClass copyWithExtendL(CT ctarg, ..., Object narg) { |
|
538 * return BMH_SPECIES.transform(L_TYPE).invokeBasic(ctarg, ..., argL0, argL1, argI2, narg); |
|
539 * } |
|
540 * final TopClass copyWithExtendI(CT ctarg, ..., int narg) { |
|
541 * return BMH_SPECIES.transform(I_TYPE).invokeBasic(ctarg, ..., argL0, argL1, argI2, narg); |
|
542 * } |
|
543 * } |
|
544 * </pre> |
|
545 * |
|
546 * @param className of the species |
|
547 * @param speciesData what species we are generating |
|
548 * @return the generated concrete TopClass class |
|
549 */ |
|
550 Class<? extends T> generateConcreteSpeciesCode(String className, ClassSpecializer<T,K,S>.SpeciesData speciesData) { |
|
551 byte[] classFile = generateConcreteSpeciesCodeFile(className, speciesData); |
|
552 |
|
553 // load class |
|
554 InvokerBytecodeGenerator.maybeDump(classBCName(className), classFile); |
|
555 Class<?> speciesCode; |
|
556 |
|
557 ClassLoader cl = topClass().getClassLoader(); |
|
558 ProtectionDomain pd = null; |
|
559 if (cl != null) { |
|
560 pd = AccessController.doPrivileged( |
|
561 new PrivilegedAction<>() { |
|
562 @Override |
|
563 public ProtectionDomain run() { |
|
564 return topClass().getProtectionDomain(); |
|
565 } |
|
566 }); |
|
567 } |
|
568 try { |
|
569 speciesCode = UNSAFE.defineClass(className, classFile, 0, classFile.length, cl, pd); |
|
570 } catch (Exception ex) { |
|
571 throw newInternalError(ex); |
|
572 } |
|
573 |
|
574 return speciesCode.asSubclass(topClass()); |
|
575 } |
|
576 |
|
577 // These are named like constants because there is only one per specialization scheme: |
|
578 private final String SPECIES_DATA = classBCName(metaType); |
|
579 private final String SPECIES_DATA_SIG = classSig(SPECIES_DATA); |
|
580 private final String SPECIES_DATA_NAME = sdAccessor.getName(); |
|
581 private final int SPECIES_DATA_MODS = sdAccessor.getModifiers(); |
|
582 private final List<String> TRANSFORM_NAMES; // derived from transformMethods |
|
583 private final List<MethodType> TRANSFORM_TYPES; |
|
584 private final List<Integer> TRANSFORM_MODS; |
|
585 { |
|
586 // Tear apart transformMethods to get the names, types, and modifiers. |
|
587 List<String> tns = new ArrayList<>(); |
|
588 List<MethodType> tts = new ArrayList<>(); |
|
589 List<Integer> tms = new ArrayList<>(); |
|
590 for (int i = 0; i < transformMethods.size(); i++) { |
|
591 MemberName tm = transformMethods.get(i); |
|
592 tns.add(tm.getName()); |
|
593 final MethodType tt = tm.getMethodType(); |
|
594 tts.add(tt); |
|
595 tms.add(tm.getModifiers()); |
|
596 } |
|
597 TRANSFORM_NAMES = List.of(tns.toArray(new String[0])); |
|
598 TRANSFORM_TYPES = List.of(tts.toArray(new MethodType[0])); |
|
599 TRANSFORM_MODS = List.of(tms.toArray(new Integer[0])); |
|
600 } |
|
601 private static final int ACC_PPP = ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED; |
|
602 |
|
603 /*non-public*/ byte[] generateConcreteSpeciesCodeFile(String className0, ClassSpecializer<T,K,S>.SpeciesData speciesData) { |
|
604 final String className = classBCName(className0); |
|
605 final String superClassName = classBCName(speciesData.deriveSuperClass()); |
|
606 |
|
607 final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); |
|
608 final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC |
|
609 cw.visit(V1_6, NOT_ACC_PUBLIC + ACC_FINAL + ACC_SUPER, className, null, superClassName, null); |
|
610 |
|
611 final String sourceFile = className.substring(className.lastIndexOf('.')+1); |
|
612 cw.visitSource(sourceFile, null); |
|
613 |
|
614 // emit static types and BMH_SPECIES fields |
|
615 FieldVisitor fw = cw.visitField(NOT_ACC_PUBLIC + ACC_STATIC, sdFieldName, SPECIES_DATA_SIG, null, null); |
|
616 fw.visitAnnotation(STABLE_SIG, true); |
|
617 fw.visitEnd(); |
|
618 |
|
619 // handy holder for dealing with groups of typed values (ctor arguments and fields) |
|
620 class Var { |
|
621 final int index; |
|
622 final String name; |
|
623 final Class<?> type; |
|
624 final String desc; |
|
625 final BasicType basicType; |
|
626 final int slotIndex; |
|
627 Var(int index, int slotIndex) { |
|
628 this.index = index; |
|
629 this.slotIndex = slotIndex; |
|
630 name = null; type = null; desc = null; |
|
631 basicType = BasicType.V_TYPE; |
|
632 } |
|
633 Var(String name, Class<?> type, Var prev) { |
|
634 int slotIndex = prev.nextSlotIndex(); |
|
635 int index = prev.nextIndex(); |
|
636 if (name == null) name = "x"; |
|
637 if (name.endsWith("#")) |
|
638 name = name.substring(0, name.length()-1) + index; |
|
639 assert(!type.equals(void.class)); |
|
640 String desc = classSig(type); |
|
641 BasicType basicType = BasicType.basicType(type); |
|
642 this.index = index; |
|
643 this.name = name; |
|
644 this.type = type; |
|
645 this.desc = desc; |
|
646 this.basicType = basicType; |
|
647 this.slotIndex = slotIndex; |
|
648 } |
|
649 Var lastOf(List<Var> vars) { |
|
650 int n = vars.size(); |
|
651 return (n == 0 ? this : vars.get(n-1)); |
|
652 } |
|
653 <X> List<Var> fromTypes(List<X> types) { |
|
654 Var prev = this; |
|
655 ArrayList<Var> result = new ArrayList<>(types.size()); |
|
656 int i = 0; |
|
657 for (X x : types) { |
|
658 String vn = name; |
|
659 Class<?> vt; |
|
660 if (x instanceof Class) { |
|
661 vt = (Class<?>) x; |
|
662 // make the names friendlier if debugging |
|
663 assert((vn = vn + "_" + (i++)) != null); |
|
664 } else { |
|
665 @SuppressWarnings("unchecked") |
|
666 Var v = (Var) x; |
|
667 vn = v.name; |
|
668 vt = v.type; |
|
669 } |
|
670 prev = new Var(vn, vt, prev); |
|
671 result.add(prev); |
|
672 } |
|
673 return result; |
|
674 } |
|
675 |
|
676 int slotSize() { return basicType.basicTypeSlots(); } |
|
677 int nextIndex() { return index + (slotSize() == 0 ? 0 : 1); } |
|
678 int nextSlotIndex() { return slotIndex >= 0 ? slotIndex + slotSize() : slotIndex; } |
|
679 boolean isInHeap() { return slotIndex < 0; } |
|
680 void emitVarInstruction(int asmop, MethodVisitor mv) { |
|
681 if (asmop == ALOAD) |
|
682 asmop = typeLoadOp(basicType.basicTypeChar()); |
|
683 else |
|
684 throw new AssertionError("bad op="+asmop+" for desc="+desc); |
|
685 mv.visitVarInsn(asmop, slotIndex); |
|
686 } |
|
687 public void emitFieldInsn(int asmop, MethodVisitor mv) { |
|
688 mv.visitFieldInsn(asmop, className, name, desc); |
|
689 } |
|
690 } |
|
691 |
|
692 final Var NO_THIS = new Var(0, 0), |
|
693 AFTER_THIS = new Var(0, 1), |
|
694 IN_HEAP = new Var(0, -1); |
|
695 |
|
696 // figure out the field types |
|
697 final List<Class<?>> fieldTypes = speciesData.fieldTypes(); |
|
698 final List<Var> fields = new ArrayList<>(fieldTypes.size()); |
|
699 { |
|
700 Var nextF = IN_HEAP; |
|
701 for (Class<?> ft : fieldTypes) { |
|
702 String fn = chooseFieldName(ft, nextF.nextIndex()); |
|
703 nextF = new Var(fn, ft, nextF); |
|
704 fields.add(nextF); |
|
705 } |
|
706 } |
|
707 |
|
708 // emit bound argument fields |
|
709 for (Var field : fields) { |
|
710 cw.visitField(ACC_FINAL, field.name, field.desc, null, null).visitEnd(); |
|
711 } |
|
712 |
|
713 MethodVisitor mv; |
|
714 |
|
715 // emit implementation of speciesData() |
|
716 mv = cw.visitMethod((SPECIES_DATA_MODS & ACC_PPP) + ACC_FINAL, |
|
717 SPECIES_DATA_NAME, "()" + SPECIES_DATA_SIG, null, null); |
|
718 mv.visitCode(); |
|
719 mv.visitFieldInsn(GETSTATIC, className, sdFieldName, SPECIES_DATA_SIG); |
|
720 mv.visitInsn(ARETURN); |
|
721 mv.visitMaxs(0, 0); |
|
722 mv.visitEnd(); |
|
723 |
|
724 // figure out the constructor arguments |
|
725 MethodType superCtorType = ClassSpecializer.this.baseConstructorType(); |
|
726 MethodType thisCtorType = superCtorType.appendParameterTypes(fieldTypes); |
|
727 |
|
728 // emit constructor |
|
729 { |
|
730 mv = cw.visitMethod(ACC_PRIVATE, |
|
731 "<init>", methodSig(thisCtorType), null, null); |
|
732 mv.visitCode(); |
|
733 mv.visitVarInsn(ALOAD, 0); // this |
|
734 |
|
735 final List<Var> ctorArgs = AFTER_THIS.fromTypes(superCtorType.parameterList()); |
|
736 for (Var ca : ctorArgs) { |
|
737 ca.emitVarInstruction(ALOAD, mv); |
|
738 } |
|
739 |
|
740 // super(ca...) |
|
741 mv.visitMethodInsn(INVOKESPECIAL, superClassName, |
|
742 "<init>", methodSig(superCtorType), false); |
|
743 |
|
744 // store down fields |
|
745 Var lastFV = AFTER_THIS.lastOf(ctorArgs); |
|
746 for (Var f : fields) { |
|
747 // this.argL1 = argL1 |
|
748 mv.visitVarInsn(ALOAD, 0); // this |
|
749 lastFV = new Var(f.name, f.type, lastFV); |
|
750 lastFV.emitVarInstruction(ALOAD, mv); |
|
751 f.emitFieldInsn(PUTFIELD, mv); |
|
752 } |
|
753 |
|
754 mv.visitInsn(RETURN); |
|
755 mv.visitMaxs(0, 0); |
|
756 mv.visitEnd(); |
|
757 } |
|
758 |
|
759 // emit make() ...factory method wrapping constructor |
|
760 { |
|
761 MethodType ftryType = thisCtorType.changeReturnType(topClass()); |
|
762 mv = cw.visitMethod(NOT_ACC_PUBLIC + ACC_STATIC, |
|
763 "make", methodSig(ftryType), null, null); |
|
764 mv.visitCode(); |
|
765 // make instance |
|
766 mv.visitTypeInsn(NEW, className); |
|
767 mv.visitInsn(DUP); |
|
768 // load factory method arguments: ctarg... and arg... |
|
769 for (Var v : NO_THIS.fromTypes(ftryType.parameterList())) { |
|
770 v.emitVarInstruction(ALOAD, mv); |
|
771 } |
|
772 |
|
773 // finally, invoke the constructor and return |
|
774 mv.visitMethodInsn(INVOKESPECIAL, className, |
|
775 "<init>", methodSig(thisCtorType), false); |
|
776 mv.visitInsn(ARETURN); |
|
777 mv.visitMaxs(0, 0); |
|
778 mv.visitEnd(); |
|
779 } |
|
780 |
|
781 // For each transform, emit the customized override of the transform method. |
|
782 // This method mixes together some incoming arguments (from the transform's |
|
783 // static type signature) with the field types themselves, and passes |
|
784 // the resulting mish-mosh of values to a method handle produced by |
|
785 // the species itself. (Typically this method handle is the factory |
|
786 // method of this species or a related one.) |
|
787 for (int whichtm = 0; whichtm < TRANSFORM_NAMES.size(); whichtm++) { |
|
788 final String TNAME = TRANSFORM_NAMES.get(whichtm); |
|
789 final MethodType TTYPE = TRANSFORM_TYPES.get(whichtm); |
|
790 final int TMODS = TRANSFORM_MODS.get(whichtm); |
|
791 mv = cw.visitMethod((TMODS & ACC_PPP) | ACC_FINAL, |
|
792 TNAME, TTYPE.toMethodDescriptorString(), null, E_THROWABLE); |
|
793 mv.visitCode(); |
|
794 // return a call to the corresponding "transform helper", something like this: |
|
795 // MY_SPECIES.transformHelper(whichtm).invokeBasic(ctarg, ..., argL0, ..., xarg) |
|
796 mv.visitFieldInsn(GETSTATIC, className, |
|
797 sdFieldName, SPECIES_DATA_SIG); |
|
798 emitIntConstant(whichtm, mv); |
|
799 mv.visitMethodInsn(INVOKEVIRTUAL, SPECIES_DATA, |
|
800 "transformHelper", "(I)" + MH_SIG, false); |
|
801 |
|
802 List<Var> targs = AFTER_THIS.fromTypes(TTYPE.parameterList()); |
|
803 List<Var> tfields = new ArrayList<>(fields); |
|
804 // mix them up and load them for the transform helper: |
|
805 List<Var> helperArgs = speciesData.deriveTransformHelperArguments(transformMethods.get(whichtm), whichtm, targs, tfields); |
|
806 List<Class<?>> helperTypes = new ArrayList<>(helperArgs.size()); |
|
807 for (Var ha : helperArgs) { |
|
808 helperTypes.add(ha.basicType.basicTypeClass()); |
|
809 if (ha.isInHeap()) { |
|
810 assert(tfields.contains(ha)); |
|
811 mv.visitVarInsn(ALOAD, 0); |
|
812 ha.emitFieldInsn(GETFIELD, mv); |
|
813 } else { |
|
814 assert(targs.contains(ha)); |
|
815 ha.emitVarInstruction(ALOAD, mv); |
|
816 } |
|
817 } |
|
818 |
|
819 // jump into the helper (which is probably a factory method) |
|
820 final Class<?> rtype = TTYPE.returnType(); |
|
821 final BasicType rbt = BasicType.basicType(rtype); |
|
822 MethodType invokeBasicType = MethodType.methodType(rbt.basicTypeClass(), helperTypes); |
|
823 mv.visitMethodInsn(INVOKEVIRTUAL, MH, |
|
824 "invokeBasic", methodSig(invokeBasicType), false); |
|
825 if (rbt == BasicType.L_TYPE) { |
|
826 mv.visitTypeInsn(CHECKCAST, classBCName(rtype)); |
|
827 mv.visitInsn(ARETURN); |
|
828 } else { |
|
829 throw newInternalError("NYI: transform of type "+rtype); |
|
830 } |
|
831 mv.visitMaxs(0, 0); |
|
832 mv.visitEnd(); |
|
833 } |
|
834 |
|
835 cw.visitEnd(); |
|
836 |
|
837 return cw.toByteArray(); |
|
838 } |
|
839 |
|
840 private int typeLoadOp(char t) { |
|
841 switch (t) { |
|
842 case 'L': return ALOAD; |
|
843 case 'I': return ILOAD; |
|
844 case 'J': return LLOAD; |
|
845 case 'F': return FLOAD; |
|
846 case 'D': return DLOAD; |
|
847 default : throw newInternalError("unrecognized type " + t); |
|
848 } |
|
849 } |
|
850 |
|
851 private void emitIntConstant(int con, MethodVisitor mv) { |
|
852 if (ICONST_M1 - ICONST_0 <= con && con <= ICONST_5 - ICONST_0) |
|
853 mv.visitInsn(ICONST_0 + con); |
|
854 else if (con == (byte) con) |
|
855 mv.visitIntInsn(BIPUSH, con); |
|
856 else if (con == (short) con) |
|
857 mv.visitIntInsn(SIPUSH, con); |
|
858 else { |
|
859 mv.visitLdcInsn(con); |
|
860 } |
|
861 |
|
862 } |
|
863 |
|
864 // |
|
865 // Getter MH generation. |
|
866 // |
|
867 |
|
868 private MethodHandle findGetter(Class<?> speciesCode, List<Class<?>> types, int index) { |
|
869 Class<?> fieldType = types.get(index); |
|
870 String fieldName = chooseFieldName(fieldType, index); |
|
871 try { |
|
872 return IMPL_LOOKUP.findGetter(speciesCode, fieldName, fieldType); |
|
873 } catch (NoSuchFieldException | IllegalAccessException e) { |
|
874 throw newInternalError(e); |
|
875 } |
|
876 } |
|
877 |
|
878 private List<MethodHandle> findGetters(Class<?> speciesCode, List<Class<?>> types) { |
|
879 MethodHandle[] mhs = new MethodHandle[types.size()]; |
|
880 for (int i = 0; i < mhs.length; ++i) { |
|
881 mhs[i] = findGetter(speciesCode, types, i); |
|
882 assert(mhs[i].internalMemberName().getDeclaringClass() == speciesCode); |
|
883 } |
|
884 return List.of(mhs); |
|
885 } |
|
886 |
|
887 private List<MethodHandle> findFactories(Class<? extends T> speciesCode, List<Class<?>> types) { |
|
888 MethodHandle[] mhs = new MethodHandle[1]; |
|
889 mhs[0] = findFactory(speciesCode, types); |
|
890 return List.of(mhs); |
|
891 } |
|
892 |
|
893 List<LambdaForm.NamedFunction> makeNominalGetters(List<Class<?>> types, List<MethodHandle> getters) { |
|
894 LambdaForm.NamedFunction[] nfs = new LambdaForm.NamedFunction[types.size()]; |
|
895 for (int i = 0; i < nfs.length; ++i) { |
|
896 nfs[i] = new LambdaForm.NamedFunction(getters.get(i)); |
|
897 } |
|
898 return List.of(nfs); |
|
899 } |
|
900 |
|
901 // |
|
902 // Auxiliary methods. |
|
903 // |
|
904 |
|
905 protected void linkSpeciesDataToCode(ClassSpecializer<T,K,S>.SpeciesData speciesData, Class<? extends T> speciesCode) { |
|
906 speciesData.speciesCode = speciesCode.asSubclass(topClass); |
|
907 final List<Class<?>> types = speciesData.fieldTypes; |
|
908 speciesData.factories = this.findFactories(speciesCode, types); |
|
909 speciesData.getters = this.findGetters(speciesCode, types); |
|
910 speciesData.nominalGetters = this.makeNominalGetters(types, speciesData.getters); |
|
911 } |
|
912 |
|
913 private Field reflectSDField(Class<? extends T> speciesCode) { |
|
914 final Field field = reflectField(speciesCode, sdFieldName); |
|
915 assert(field.getType() == metaType); |
|
916 assert(Modifier.isStatic(field.getModifiers())); |
|
917 return field; |
|
918 } |
|
919 |
|
920 private S readSpeciesDataFromCode(Class<? extends T> speciesCode) { |
|
921 try { |
|
922 MemberName sdField = IMPL_LOOKUP.resolveOrFail(REF_getStatic, speciesCode, sdFieldName, metaType); |
|
923 Object base = MethodHandleNatives.staticFieldBase(sdField); |
|
924 long offset = MethodHandleNatives.staticFieldOffset(sdField); |
|
925 UNSAFE.loadFence(); |
|
926 return metaType.cast(UNSAFE.getObject(base, offset)); |
|
927 } catch (Error err) { |
|
928 throw err; |
|
929 } catch (Exception ex) { |
|
930 throw newInternalError("Failed to load speciesData from speciesCode: " + speciesCode.getName(), ex); |
|
931 } catch (Throwable t) { |
|
932 throw uncaughtException(t); |
|
933 } |
|
934 } |
|
935 |
|
936 protected S loadSpeciesDataFromCode(Class<? extends T> speciesCode) { |
|
937 if (speciesCode == topClass()) { |
|
938 return topSpecies; |
|
939 } |
|
940 S result = readSpeciesDataFromCode(speciesCode); |
|
941 if (result.outer() != ClassSpecializer.this) { |
|
942 throw newInternalError("wrong class"); |
|
943 } |
|
944 return result; |
|
945 } |
|
946 |
|
947 protected void linkCodeToSpeciesData(Class<? extends T> speciesCode, ClassSpecializer<T,K,S>.SpeciesData speciesData, boolean salvage) { |
|
948 try { |
|
949 assert(readSpeciesDataFromCode(speciesCode) == null || |
|
950 (salvage && readSpeciesDataFromCode(speciesCode).equals(speciesData))); |
|
951 |
|
952 MemberName sdField = IMPL_LOOKUP.resolveOrFail(REF_putStatic, speciesCode, sdFieldName, metaType); |
|
953 Object base = MethodHandleNatives.staticFieldBase(sdField); |
|
954 long offset = MethodHandleNatives.staticFieldOffset(sdField); |
|
955 UNSAFE.storeFence(); |
|
956 UNSAFE.putObject(base, offset, speciesData); |
|
957 UNSAFE.storeFence(); |
|
958 } catch (Error err) { |
|
959 throw err; |
|
960 } catch (Exception ex) { |
|
961 throw newInternalError("Failed to link speciesData to speciesCode: " + speciesCode.getName(), ex); |
|
962 } catch (Throwable t) { |
|
963 throw uncaughtException(t); |
|
964 } |
|
965 } |
|
966 |
|
967 /** |
|
968 * Field names in concrete species classes adhere to this pattern: |
|
969 * type + index, where type is a single character (L, I, J, F, D). |
|
970 * The factory subclass can customize this. |
|
971 * The name is purely cosmetic, since it applies to a private field. |
|
972 */ |
|
973 protected String chooseFieldName(Class<?> type, int index) { |
|
974 BasicType bt = BasicType.basicType(type); |
|
975 return "" + bt.basicTypeChar() + index; |
|
976 } |
|
977 |
|
978 MethodHandle findFactory(Class<? extends T> speciesCode, List<Class<?>> types) { |
|
979 final MethodType type = baseConstructorType().changeReturnType(topClass()).appendParameterTypes(types); |
|
980 try { |
|
981 return IMPL_LOOKUP.findStatic(speciesCode, "make", type); |
|
982 } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | TypeNotPresentException e) { |
|
983 throw newInternalError(e); |
|
984 } |
|
985 } |
|
986 } |
|
987 |
|
988 /** Hook that virtualizes the Factory class, allowing subclasses to extend it. */ |
|
989 protected Factory makeFactory() { |
|
990 return new Factory(); |
|
991 } |
|
992 |
|
993 |
|
994 // Other misc helpers: |
|
995 private static final String MH = "java/lang/invoke/MethodHandle"; |
|
996 private static final String MH_SIG = "L" + MH + ";"; |
|
997 private static final String STABLE = "jdk/internal/vm/annotation/Stable"; |
|
998 private static final String STABLE_SIG = "L" + STABLE + ";"; |
|
999 private static final String[] E_THROWABLE = new String[] { "java/lang/Throwable" }; |
|
1000 static { |
|
1001 assert(MH_SIG.equals(classSig(MethodHandle.class))); |
|
1002 assert(MH.equals(classBCName(MethodHandle.class))); |
|
1003 } |
|
1004 |
|
1005 static String methodSig(MethodType mt) { |
|
1006 return mt.toMethodDescriptorString(); |
|
1007 } |
|
1008 static String classSig(Class<?> cls) { |
|
1009 if (cls.isPrimitive() || cls.isArray()) |
|
1010 return MethodType.methodType(cls).toMethodDescriptorString().substring(2); |
|
1011 return classSig(classBCName(cls)); |
|
1012 } |
|
1013 static String classSig(String bcName) { |
|
1014 assert(bcName.indexOf('.') < 0); |
|
1015 assert(!bcName.endsWith(";")); |
|
1016 assert(!bcName.startsWith("[")); |
|
1017 return "L" + bcName + ";"; |
|
1018 } |
|
1019 static String classBCName(Class<?> cls) { |
|
1020 return classBCName(className(cls)); |
|
1021 } |
|
1022 static String classBCName(String str) { |
|
1023 assert(str.indexOf('/') < 0) : str; |
|
1024 return str.replace('.', '/'); |
|
1025 } |
|
1026 static String className(Class<?> cls) { |
|
1027 assert(!cls.isArray() && !cls.isPrimitive()); |
|
1028 return cls.getName(); |
|
1029 } |
|
1030 } |