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