1 package mockit.asm.types;
2
3 import edu.umd.cs.findbugs.annotations.NonNull;
4
5 import java.lang.reflect.Constructor;
6 import java.lang.reflect.Method;
7
8 import org.checkerframework.checker.index.qual.NonNegative;
9
10 /**
11 * A Java field or method type. This class can be used to make it easier to manipulate type and method descriptors.
12 */
13 @SuppressWarnings("ClassReferencesSubclass")
14 public abstract class JavaType {
15 private static final JavaType[] NO_ARGS = {};
16
17 /**
18 * The length of the internal name of this Java type.
19 */
20 @NonNegative
21 final int len;
22
23 /**
24 * Constructs a Java type.
25 *
26 * @param len
27 * the length of this descriptor.
28 */
29 JavaType(@NonNegative int len) {
30 this.len = len;
31 }
32
33 /**
34 * Returns the Java type corresponding to the given type descriptor.
35 *
36 * @param typeDescriptor
37 * a field or method type descriptor.
38 */
39 @NonNull
40 public static JavaType getType(@NonNull String typeDescriptor) {
41 return getType(typeDescriptor.toCharArray(), 0);
42 }
43
44 /**
45 * Returns the Java types corresponding to the argument types of the given method descriptor.
46 */
47 @NonNull
48 public static JavaType[] getArgumentTypes(@NonNull String methodDescriptor) {
49 char[] buf = methodDescriptor.toCharArray();
50 int off = 1;
51 int size = 0;
52
53 while (true) {
54 char c = buf[off];
55 off++;
56
57 if (c == ')') {
58 break;
59 }
60 if (c == 'L') {
61 off = findNextTypeTerminatorCharacter(buf, off);
62 size++;
63 } else if (c != '[') {
64 size++;
65 }
66 }
67
68 return getArgumentTypes(buf, size);
69 }
70
71 @NonNegative
72 private static int findNextTypeTerminatorCharacter(@NonNull char[] desc, @NonNegative int i) {
73 while (desc[i++] != ';') {
74 }
75 return i;
76 }
77
78 @NonNull
79 private static JavaType[] getArgumentTypes(@NonNull char[] buf, @NonNegative int argCount) {
80 if (argCount == 0) {
81 return NO_ARGS;
82 }
83
84 JavaType[] argTypes = new JavaType[argCount];
85 int off = 1;
86
87 for (int i = 0; buf[off] != ')'; i++) {
88 JavaType argType = getType(buf, off);
89 argTypes[i] = argType;
90 off += argType.len + (argType instanceof ObjectType ? 2 : 0);
91 }
92
93 return argTypes;
94 }
95
96 /**
97 * Returns the Java type corresponding to the return type of the given method descriptor.
98 */
99 @NonNull
100 public static JavaType getReturnType(@NonNull String methodDescriptor) {
101 char[] buf = methodDescriptor.toCharArray();
102 return getType(buf, methodDescriptor.indexOf(')') + 1);
103 }
104
105 /**
106 * Computes the size of the arguments and of the return value of a method.
107 *
108 * @param desc
109 * the descriptor of a method.
110 *
111 * @return the size of the arguments of the method (plus one for the implicit <code>this</code> argument),
112 * <code>argSize</code>, and the size of its return value, <code>retSize</code>, packed into a single
113 *
114 * <pre>{@code int i = (argSize << 2) | retSize }</pre>
115 *
116 * (<code>argSize</code> is therefore equal to
117 *
118 * <pre>{@code i >> 2 }</pre>
119 *
120 * , and
121 *
122 * <pre>{@code retSize }</pre>
123 *
124 * to
125 *
126 * <pre>
127 * @{code i & 0x03 }
128 * </pre>
129 *
130 * ).
131 */
132 public static int getArgumentsAndReturnSizes(@NonNull String desc) {
133 int argSize = 1;
134 int i = 1;
135
136 while (true) {
137 char currentChar = desc.charAt(i);
138 i++;
139
140 switch (currentChar) {
141 case ')': {
142 char nextChar = desc.charAt(i);
143 return argSize << 2 | (nextChar == 'V' ? 0 : isDoubleSizePrimitiveType(nextChar) ? 2 : 1);
144 }
145 case 'L':
146 i = findNextTypeTerminatorCharacter(desc, i);
147 argSize++;
148 break;
149 case '[': {
150 i = findStartOfArrayElementType(desc, i);
151 char arrayElementType = desc.charAt(i);
152 if (isDoubleSizePrimitiveType(arrayElementType)) {
153 argSize--;
154 }
155 break;
156 }
157 default:
158 if (isDoubleSizePrimitiveType(currentChar)) {
159 argSize += 2;
160 } else {
161 argSize++;
162 }
163 break;
164 }
165 }
166 }
167
168 private static boolean isDoubleSizePrimitiveType(char typeCode) {
169 return typeCode == 'D' || typeCode == 'J';
170 }
171
172 @NonNegative
173 private static int findNextTypeTerminatorCharacter(@NonNull String desc, @NonNegative int i) {
174 while (desc.charAt(i++) != ';') {
175 }
176 return i;
177 }
178
179 @NonNegative
180 private static int findStartOfArrayElementType(@NonNull String desc, @NonNegative int i) {
181 while (desc.charAt(i) == '[') {
182 i++;
183 }
184 return i;
185 }
186
187 /**
188 * Returns the Java type corresponding to the given type descriptor. For method descriptors, <code>buf</code> is
189 * supposed to contain nothing more than the descriptor itself.
190 *
191 * @param buf
192 * a buffer containing a type descriptor.
193 * @param off
194 * the offset of this descriptor in the previous buffer.
195 */
196 @NonNull
197 static JavaType getType(@NonNull char[] buf, @NonNegative int off) {
198 PrimitiveType primitiveType = PrimitiveType.getPrimitiveType(buf[off]);
199
200 if (primitiveType != null) {
201 return primitiveType;
202 }
203
204 return ReferenceType.getReferenceType(buf, off);
205 }
206
207 /**
208 * Returns the binary name of the class corresponding to this type. This method must not be used on method types.
209 */
210 @NonNull
211 public abstract String getClassName();
212
213 // ------------------------------------------------------------------------
214 // Conversion to type descriptors
215 // ------------------------------------------------------------------------
216
217 /**
218 * Returns the descriptor corresponding to this Java type.
219 */
220 @NonNull
221 public final String getDescriptor() {
222 StringBuilder buf = new StringBuilder();
223 getDescriptor(buf);
224 return buf.toString();
225 }
226
227 /**
228 * Appends the descriptor corresponding to this Java type to the given string buffer.
229 *
230 * @param typeDesc
231 * the string builder to which the descriptor must be appended
232 */
233 abstract void getDescriptor(@NonNull StringBuilder typeDesc);
234
235 // -------------------------------------------------------------------------------------------------------
236 // Direct conversion from classes to type descriptors, and vice-versa, without intermediate JavaType objects
237 // -------------------------------------------------------------------------------------------------------
238
239 /**
240 * Returns the internal name of the given class. The internal name of a class is its fully qualified name, as
241 * returned by Class.getName(), where '.' are replaced by '/'.
242 *
243 * @param aClass
244 * an object or array class
245 */
246 @NonNull
247 public static String getInternalName(@NonNull Class<?> aClass) {
248 return aClass.getName().replace('.', '/');
249 }
250
251 /**
252 * Returns the descriptor corresponding to the given constructor.
253 */
254 @NonNull
255 public static String getConstructorDescriptor(@NonNull Constructor<?> constructor) {
256 StringBuilder buf = getMemberDescriptor(constructor.getParameterTypes());
257 buf.append('V');
258 return buf.toString();
259 }
260
261 @NonNull
262 private static StringBuilder getMemberDescriptor(@NonNull Class<?>[] parameterTypes) {
263 StringBuilder buf = new StringBuilder();
264 buf.append('(');
265
266 for (Class<?> parameterType : parameterTypes) {
267 getDescriptor(buf, parameterType);
268 }
269
270 buf.append(')');
271 return buf;
272 }
273
274 /**
275 * Returns the descriptor corresponding to the given method.
276 */
277 @NonNull
278 public static String getMethodDescriptor(@NonNull Method method) {
279 StringBuilder buf = getMemberDescriptor(method.getParameterTypes());
280 getDescriptor(buf, method.getReturnType());
281 return buf.toString();
282 }
283
284 /**
285 * Appends the descriptor of the given class to the given string builder.
286 */
287 private static void getDescriptor(@NonNull StringBuilder buf, @NonNull Class<?> aClass) {
288 Class<?> d = aClass;
289
290 while (true) {
291 if (d.isPrimitive()) {
292 char typeCode = PrimitiveType.getPrimitiveType(d).getTypeCode();
293 buf.append(typeCode);
294 return;
295 }
296 if (!d.isArray()) {
297 ReferenceType.getDescriptor(buf, d);
298 return;
299 }
300 buf.append('[');
301 d = d.getComponentType();
302 }
303 }
304
305 // ------------------------------------------------------------------------
306 // Corresponding size and opcodes
307 // ------------------------------------------------------------------------
308
309 /**
310 * Returns the size of values of this type. This method must not be used for method types.
311 *
312 * @return the size of values of this type, i.e., 2 for <code>long</code> and <code>double</code>, 0 for
313 * <code>void</code> and 1 otherwise.
314 */
315 @NonNegative
316 public abstract int getSize();
317
318 /**
319 * Returns a JVM instruction opcode adapted to this Java type. This method must not be used for method types.
320 *
321 * @param opcode
322 * a JVM instruction opcode. This opcode must be one of ILOAD, ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL,
323 * IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN.
324 *
325 * @return an opcode that is similar to the given opcode, but adapted to this Java type. For example, if this type
326 * is <code>float</code> and <code>opcode</code> is IRETURN, this method returns FRETURN.
327 */
328 public abstract int getOpcode(int opcode);
329
330 public abstract int getLoadOpcode();
331
332 public abstract int getConstOpcode();
333
334 /**
335 * Returns a string representation of this type.
336 *
337 * @return the descriptor of this type.
338 */
339 @Override
340 public final String toString() {
341 return getDescriptor();
342 }
343 }