View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
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 { // values from JVM spec Table 4.4.A
26          No0, // 0
27          Utf8(2), // 1 (has variable size)
28          No2, // 2
29          Integer(4), // 3
30          Float(4), // 4
31          Long(8), // 5
32          Double(8), // 6
33          Class(2), // 7
34          String(2), // 8
35          FieldRef(4), // 9
36          MethodRef(4), // 10
37          InterfaceMethodRef(4), // 11
38          NameAndType(4), // 12
39          No13, No14, MethodHandle(3), // 15, added in Java 7
40          MethodType(2), // 16, added in Java 7
41          ConstantDynamic(4), // 17, added in Java 11
42          InvokeDynamic(4), // 18, added in Java 7
43          Module(2), // 19, added in Java 9
44          Package(2); // 20, added in Java 9
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       * The constant pool starts at index 10 in the code array; this is the end index, which must be computed as it's not
71       * stored anywhere.
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             // noinspection AssignmentToMethodParameter
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         // for (int i = 0; i < numElementValuePairs; i++) {
366         // int cpElementNameIndex = readUnsignedShort(codeIndex);
367         // codeIndex += 2;
368         //
369         // int tag = code[codeIndex++];
370         // // TODO: continue implementing
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)) { // if the array element type is double size...
533                             i++;
534                             sum++; // ...then count it here, otherwise let the outer loop count it
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 }