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