1
2
3
4
5 package mockit.asm.metadata;
6
7 import edu.umd.cs.findbugs.annotations.NonNull;
8 import edu.umd.cs.findbugs.annotations.Nullable;
9
10 import java.nio.charset.Charset;
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.EnumSet;
14 import java.util.List;
15
16 import mockit.asm.jvmConstants.Access;
17
18 import org.checkerframework.checker.index.qual.NonNegative;
19
20 public final class ClassMetadataReader extends ObjectWithAttributes {
21 private static final Charset UTF8 = Charset.forName("UTF-8");
22 private static final ConstantPoolTag[] CONSTANT_POOL_TAGS = ConstantPoolTag.values();
23
24 enum ConstantPoolTag {
25 No0,
26 Utf8(2),
27 No2,
28 Integer(4),
29 Float(4),
30 Long(8),
31 Double(8),
32 Class(2),
33 String(2),
34 FieldRef(4),
35 MethodRef(4),
36 InterfaceMethodRef(4),
37 NameAndType(4),
38 No13, No14, MethodHandle(3),
39 MethodType(2),
40 ConstantDynamic(4),
41 InvokeDynamic(4),
42 Module(2),
43 Package(2);
44
45 @NonNegative
46 final int itemSize;
47
48 ConstantPoolTag() {
49 itemSize = 0;
50 }
51
52 ConstantPoolTag(@NonNegative int itemSize) {
53 this.itemSize = itemSize;
54 }
55 }
56
57 public enum Attribute {
58 Annotations, Parameters, Signature
59 }
60
61 @NonNull
62 private final byte[] code;
63 @NonNull
64 private final int[] cpItemCodeIndexes;
65 @Nullable
66 private final EnumSet<Attribute> attributesToRead;
67
68
69
70
71
72 @NonNegative
73 private final int cpEndIndex;
74
75 @NonNegative
76 private int fieldsEndIndex;
77 @NonNegative
78 private int methodsEndIndex;
79
80 @NonNegative
81 public static int readVersion(@NonNull byte[] code) {
82 int byte0 = (code[4] & 0xFF) << 24;
83 int byte1 = (code[5] & 0xFF) << 16;
84 int byte2 = (code[6] & 0xFF) << 8;
85 int byte3 = code[7] & 0xFF;
86 return byte0 | byte1 | byte2 | byte3;
87 }
88
89 public ClassMetadataReader(@NonNull byte[] code) {
90 this(code, null);
91 }
92
93 public ClassMetadataReader(@NonNull byte[] code, @Nullable EnumSet<Attribute> attributesToRead) {
94 this.code = code;
95 int cpItemCount = readUnsignedShort(8);
96 int[] cpTable = new int[cpItemCount];
97 cpItemCodeIndexes = cpTable;
98 this.attributesToRead = attributesToRead;
99 cpEndIndex = findEndIndexOfConstantPoolTable(cpTable);
100 }
101
102 @NonNegative
103 private int readUnsignedShort(@NonNegative int codeIndex) {
104 byte[] b = code;
105 int i = codeIndex;
106 int byte0 = (b[i] & 0xFF) << 8;
107 i++;
108 int byte1 = b[i] & 0xFF;
109 return byte0 | byte1;
110 }
111
112 private int readInt(@NonNegative int codeIndex) {
113 byte[] b = code;
114 int i = codeIndex;
115 int byte0 = (b[i] & 0xFF) << 24;
116 i++;
117 int byte1 = (b[i] & 0xFF) << 16;
118 i++;
119 int byte2 = (b[i] & 0xFF) << 8;
120 i++;
121 int byte3 = b[i] & 0xFF;
122 return byte0 | byte1 | byte2 | byte3;
123 }
124
125 @NonNegative
126 private int findEndIndexOfConstantPoolTable(@NonNull int[] cpTable) {
127 byte[] b = code;
128 int codeIndex = 10;
129
130 for (int cpItemIndex = 1, n = cpTable.length; cpItemIndex < n; cpItemIndex++) {
131 int tagValue = b[codeIndex];
132 codeIndex++;
133 ConstantPoolTag tag = CONSTANT_POOL_TAGS[tagValue];
134
135 cpTable[cpItemIndex] = codeIndex;
136
137 int cpItemSize = tag.itemSize;
138
139 if (tag == ConstantPoolTag.Long || tag == ConstantPoolTag.Double) {
140 cpItemIndex++;
141 } else if (tag == ConstantPoolTag.Utf8) {
142 int stringLength = readUnsignedShort(codeIndex);
143 cpItemSize += stringLength;
144 }
145
146 codeIndex += cpItemSize;
147 }
148
149 return codeIndex;
150 }
151
152 @NonNegative
153 public int getVersion() {
154 return readVersion(code);
155 }
156
157 @NonNegative
158 public int getAccessFlags() {
159 return readUnsignedShort(cpEndIndex);
160 }
161
162 @NonNull
163 public String getThisClass() {
164 int cpClassIndex = readUnsignedShort(cpEndIndex + 2);
165 return getTypeDescription(cpClassIndex);
166 }
167
168 @NonNull
169 private String getTypeDescription(@NonNegative int cpClassIndex) {
170 int cpClassCodeIndex = cpItemCodeIndexes[cpClassIndex];
171 int cpDescriptionIndex = readUnsignedShort(cpClassCodeIndex);
172 return getString(cpDescriptionIndex);
173 }
174
175 @NonNull
176 private String getString(@NonNegative int cpStringIndex) {
177 int codeIndex = cpItemCodeIndexes[cpStringIndex];
178 int stringLength = readUnsignedShort(codeIndex);
179 return new String(code, codeIndex + 2, stringLength, UTF8);
180 }
181
182 @Nullable
183 public String getSuperClass() {
184 int cpClassIndex = readUnsignedShort(cpEndIndex + 4);
185
186 if (cpClassIndex == 0) {
187 return null;
188 }
189
190 return getTypeDescription(cpClassIndex);
191 }
192
193 @Nullable
194 public String[] getInterfaces() {
195 int codeIndex = cpEndIndex + 6;
196 int interfaceCount = readUnsignedShort(codeIndex);
197
198 if (interfaceCount == 0) {
199 return null;
200 }
201
202 codeIndex += 2;
203
204 String[] interfaces = new String[interfaceCount];
205
206 for (int i = 0; i < interfaceCount; i++) {
207 int cpInterfaceIndex = readUnsignedShort(codeIndex);
208 codeIndex += 2;
209 interfaces[i] = getTypeDescription(cpInterfaceIndex);
210 }
211
212 return interfaces;
213 }
214
215 private static class MemberInfo extends ObjectWithAttributes {
216 @NonNegative
217 public final int accessFlags;
218 @NonNull
219 public final String name;
220 @NonNull
221 public final String desc;
222 @Nullable
223 public String signature;
224
225 MemberInfo(@NonNegative int accessFlags, @NonNull String name, @NonNull String desc,
226 @NonNegative int attributeCount) {
227 this.accessFlags = accessFlags;
228 this.name = name;
229 this.desc = desc;
230 }
231
232 public final boolean isStatic() {
233 return (accessFlags & Access.STATIC) != 0;
234 }
235
236 public final boolean isAbstract() {
237 return (accessFlags & Access.ABSTRACT) != 0;
238 }
239
240 public final boolean isSynthetic() {
241 return (accessFlags & Access.SYNTHETIC) != 0;
242 }
243 }
244
245 public static final class FieldInfo extends MemberInfo {
246 FieldInfo(int accessFlags, @NonNull String name, @NonNull String desc, @NonNegative int attributeCount) {
247 super(accessFlags, name, desc, attributeCount);
248 }
249 }
250
251 @NonNull
252 public List<FieldInfo> getFields() {
253 int codeIndex = cpEndIndex + 6;
254 int interfaceCount = readUnsignedShort(codeIndex);
255 codeIndex += 2 + 2 * interfaceCount;
256
257 int fieldCount = readUnsignedShort(codeIndex);
258 codeIndex += 2;
259
260 List<FieldInfo> fields;
261
262 if (fieldCount == 0) {
263 fields = Collections.emptyList();
264 } else {
265 fields = new ArrayList<>(fieldCount);
266
267 for (int i = 0; i < fieldCount; i++) {
268 int accessFlags = readUnsignedShort(codeIndex);
269 codeIndex += 2;
270
271 int cpNameIndex = readUnsignedShort(codeIndex);
272 codeIndex += 2;
273 String fieldName = getString(cpNameIndex);
274
275 int cpDescIndex = readUnsignedShort(codeIndex);
276 codeIndex += 2;
277 String fieldDesc = getString(cpDescIndex);
278
279 int attributeCount = readUnsignedShort(codeIndex);
280 codeIndex += 2;
281
282 FieldInfo fieldInfo = new FieldInfo(accessFlags, fieldName, fieldDesc, attributeCount);
283 codeIndex = readAttributes(attributeCount, fieldInfo, codeIndex);
284 fields.add(fieldInfo);
285 }
286 }
287
288 fieldsEndIndex = codeIndex;
289 return fields;
290 }
291
292 @NonNegative
293 private int readAttributes(@NonNegative int attributeCount, @Nullable ObjectWithAttributes attributeOwner,
294 @NonNegative int codeIndex) {
295 EnumSet<Attribute> attributes = attributesToRead;
296 boolean readAnnotations = false;
297
298 if (attributes == null) {
299
300 attributeOwner = null;
301 } else {
302 readAnnotations = attributes.contains(Attribute.Annotations);
303 }
304
305 MethodInfo method = attributeOwner instanceof MethodInfo ? (MethodInfo) attributeOwner : null;
306
307 for (int i = 0; i < attributeCount; i++) {
308 int cpNameIndex = readUnsignedShort(codeIndex);
309 codeIndex += 2;
310 String attributeName = getString(cpNameIndex);
311
312 int attributeLength = readInt(codeIndex);
313 codeIndex += 4;
314
315 if (attributeOwner != null) {
316 if (method != null) {
317 method.readAttributes(attributeName, codeIndex);
318 }
319
320 if (readAnnotations && "RuntimeVisibleAnnotations".equals(attributeName)) {
321 attributeOwner.annotations = readAnnotations(codeIndex);
322 }
323 }
324
325 codeIndex += attributeLength;
326 }
327
328 return codeIndex;
329 }
330
331 public static final class AnnotationInfo {
332 @NonNull
333 public final String name;
334
335 AnnotationInfo(@NonNull String name) {
336 this.name = name;
337 }
338 }
339
340 @NonNull
341 private List<AnnotationInfo> readAnnotations(@NonNegative int codeIndex) {
342 int numAnnotations = readUnsignedShort(codeIndex);
343 codeIndex += 2;
344
345 List<AnnotationInfo> annotationInfos = new ArrayList<>(numAnnotations);
346
347 for (int i = 0; i < numAnnotations; i++) {
348 codeIndex = readAnnotation(annotationInfos, codeIndex);
349 }
350
351 return annotationInfos;
352 }
353
354 @NonNegative
355 private int readAnnotation(@NonNull List<AnnotationInfo> currentAnnotations, @NonNegative int codeIndex) {
356 int cpTypeIndex = readUnsignedShort(codeIndex);
357 codeIndex += 2;
358
359 String annotationTypeDesc = getString(cpTypeIndex);
360
361 readUnsignedShort(codeIndex);
362 codeIndex += 2;
363
364
365
366
367
368
369
370
371
372 AnnotationInfo annotation = new AnnotationInfo(annotationTypeDesc);
373 currentAnnotations.add(annotation);
374
375 return codeIndex;
376 }
377
378 @NonNegative
379 private int getFieldsEndIndex() {
380 int codeIndex = fieldsEndIndex;
381
382 if (codeIndex == 0) {
383 codeIndex = cpEndIndex + 6;
384 int interfaceCount = readUnsignedShort(codeIndex);
385 codeIndex += 2 + 2 * interfaceCount;
386
387 int fieldCount = readUnsignedShort(codeIndex);
388 codeIndex += 2;
389
390 for (int i = 0; i < fieldCount; i++) {
391 codeIndex += 6;
392
393 int attributeCount = readUnsignedShort(codeIndex);
394 codeIndex += 2;
395
396 codeIndex = readAttributes(attributeCount, null, codeIndex);
397 }
398
399 fieldsEndIndex = codeIndex;
400 }
401
402 return codeIndex;
403 }
404
405 public final class MethodInfo extends MemberInfo {
406 @Nullable
407 public String[] parameters;
408
409 MethodInfo(int accessFlags, @NonNull String name, @NonNull String desc, @NonNegative int attributeCount) {
410 super(accessFlags, name, desc, attributeCount);
411 }
412
413 public boolean isMethod() {
414 return name.charAt(0) != '<';
415 }
416
417 public boolean isConstructor() {
418 return "<init>".equals(name);
419 }
420
421 void readAttributes(@NonNull String attributeName, @NonNegative int codeIndex) {
422 assert attributesToRead != null;
423
424 if ("Code".equals(attributeName)) {
425 if (attributesToRead.contains(Attribute.Parameters)) {
426 readParameters(codeIndex);
427 }
428 } else if ("Signature".equals(attributeName) && attributesToRead.contains(Attribute.Signature)) {
429 readSignature(codeIndex);
430 }
431 }
432
433 private void readParameters(@NonNegative int codeIndex) {
434 codeIndex += 4;
435
436 int codeLength = readInt(codeIndex);
437 codeIndex += 4 + codeLength;
438
439 int exceptionTableLength = readUnsignedShort(codeIndex);
440 codeIndex += 2 + 8 * exceptionTableLength;
441
442 int attributeCount = readUnsignedShort(codeIndex);
443 codeIndex += 2;
444
445 readParameters(attributeCount, codeIndex);
446 }
447
448 private void readParameters(@NonNegative int attributeCount, @NonNegative int codeIndex) {
449 for (int i = 0; i < attributeCount; i++) {
450 int cpNameIndex = readUnsignedShort(codeIndex);
451 codeIndex += 2;
452 String attributeName = getString(cpNameIndex);
453
454 int attributeLength = readInt(codeIndex);
455 codeIndex += 4;
456
457 if ("LocalVariableTable".equals(attributeName)) {
458 parameters = readParametersFromLocalVariableTable(codeIndex);
459 break;
460 }
461
462 codeIndex += attributeLength;
463 }
464 }
465
466 @Nullable
467 private String[] readParametersFromLocalVariableTable(@NonNegative int codeIndex) {
468 int localVariableTableLength = readUnsignedShort(codeIndex);
469 codeIndex += 2;
470
471 int arraySize = getSumOfArgumentSizes(desc);
472
473 if (arraySize == 0) {
474 return null;
475 }
476
477 if (!isStatic()) {
478 arraySize++;
479 }
480
481 String[] parameterNames = new String[arraySize];
482
483 for (int i = 0; i < localVariableTableLength; i++) {
484 codeIndex += 4;
485
486 int cpLocalVarNameIndex = readUnsignedShort(codeIndex);
487 codeIndex += 2;
488 String localVarName = getString(cpLocalVarNameIndex);
489
490 if ("this".equals(localVarName)) {
491 codeIndex += 4;
492 continue;
493 }
494
495 codeIndex += 2;
496
497 int localVarIndex = readUnsignedShort(codeIndex);
498 codeIndex += 2;
499
500 if (localVarIndex < arraySize) {
501 parameterNames[localVarIndex] = localVarName;
502 }
503 }
504
505 return compactArray(parameterNames);
506 }
507
508 @NonNegative
509 private int getSumOfArgumentSizes(@NonNull String memberDesc) {
510 int sum = 0;
511 int i = 1;
512
513 while (true) {
514 char c = memberDesc.charAt(i);
515 i++;
516
517 switch (c) {
518 case ')':
519 return sum;
520 case 'L':
521 while (memberDesc.charAt(i) != ';') {
522 i++;
523 }
524 i++;
525 sum++;
526 break;
527 case '[':
528 while ((c = memberDesc.charAt(i)) == '[') {
529 i++;
530 }
531 if (isDoubleSizeType(c)) {
532 i++;
533 sum++;
534 }
535 break;
536 default:
537 if (isDoubleSizeType(c)) {
538 sum += 2;
539 } else {
540 sum++;
541 }
542 break;
543 }
544 }
545 }
546
547 private boolean isDoubleSizeType(char typeCode) {
548 return typeCode == 'D' || typeCode == 'J';
549 }
550
551 @Nullable
552 private String[] compactArray(@NonNull String[] arrayPossiblyWithNulls) {
553 int n = arrayPossiblyWithNulls.length;
554 int j = n - 1;
555 int i = 0;
556
557 while (i < j) {
558 if (arrayPossiblyWithNulls[i] == null) {
559 System.arraycopy(arrayPossiblyWithNulls, i + 1, arrayPossiblyWithNulls, i, j - i);
560 arrayPossiblyWithNulls[j] = null;
561 j--;
562 } else {
563 i++;
564 }
565 }
566
567 return n == 1 && arrayPossiblyWithNulls[0] == null ? null : arrayPossiblyWithNulls;
568 }
569
570 private void readSignature(@NonNegative int codeIndex) {
571 int cpSignatureIndex = readUnsignedShort(codeIndex);
572 signature = getString(cpSignatureIndex);
573 }
574 }
575
576 @NonNull
577 public List<MethodInfo> getMethods() {
578 int codeIndex = getFieldsEndIndex();
579 int methodCount = readUnsignedShort(codeIndex);
580 codeIndex += 2;
581
582 List<MethodInfo> methods = new ArrayList<>(methodCount);
583
584 for (int i = 0; i < methodCount; i++) {
585 int accessFlags = readUnsignedShort(codeIndex);
586 codeIndex += 2;
587
588 int cpNameIndex = readUnsignedShort(codeIndex);
589 codeIndex += 2;
590 String methodName = getString(cpNameIndex);
591
592 int cpDescIndex = readUnsignedShort(codeIndex);
593 codeIndex += 2;
594 String methodDesc = getString(cpDescIndex);
595
596 int attributeCount = readUnsignedShort(codeIndex);
597 codeIndex += 2;
598
599 MethodInfo methodInfo = new MethodInfo(accessFlags, methodName, methodDesc, attributeCount);
600 codeIndex = readAttributes(attributeCount, methodInfo, codeIndex);
601 methods.add(methodInfo);
602 }
603
604 methodsEndIndex = codeIndex;
605 return methods;
606 }
607
608 @NonNegative
609 private int getMethodsEndIndex() {
610 int codeIndex = methodsEndIndex;
611
612 if (codeIndex == 0) {
613 codeIndex = getFieldsEndIndex();
614
615 int methodCount = readUnsignedShort(codeIndex);
616 codeIndex += 2;
617
618 for (int i = 0; i < methodCount; i++) {
619 codeIndex += 6;
620
621 int attributeCount = readUnsignedShort(codeIndex);
622 codeIndex += 2;
623
624 codeIndex = readAttributes(attributeCount, null, codeIndex);
625 }
626
627 methodsEndIndex = codeIndex;
628 }
629
630 return codeIndex;
631 }
632
633 @NonNull
634 public List<AnnotationInfo> getAnnotations() {
635 if (annotations == null) {
636 int codeIndex = getMethodsEndIndex();
637 int attributeCount = readUnsignedShort(codeIndex);
638 codeIndex += 2;
639
640 readAttributes(attributeCount, this, codeIndex);
641
642 if (annotations == null) {
643 annotations = Collections.emptyList();
644 }
645 }
646
647 return annotations;
648 }
649 }