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