View Javadoc
1   package mockit.asm.classes;
2   
3   import edu.umd.cs.findbugs.annotations.NonNull;
4   import edu.umd.cs.findbugs.annotations.Nullable;
5   
6   import mockit.asm.AnnotatedReader;
7   import mockit.asm.fields.FieldReader;
8   import mockit.asm.jvmConstants.Access;
9   import mockit.asm.jvmConstants.ClassVersion;
10  import mockit.asm.methods.MethodReader;
11  
12  import org.checkerframework.checker.index.qual.NonNegative;
13  
14  /**
15   * A Java class parser to make a {@link ClassVisitor} visit an existing class.
16   * <p>
17   * The Java type to be parsed is given in the form of a byte array conforming to the
18   * <a href="https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html">Java class file format</a>. For each field
19   * and method encountered, the appropriate visit method of a given class visitor is called.
20   */
21  public final class ClassReader extends AnnotatedReader {
22      /**
23       * Start index of the class header information (access, name...) in {@link #code}.
24       */
25      @NonNegative
26      final int header;
27  
28      @NonNegative
29      private final int version;
30      @NonNull
31      private final ClassInfo classInfo;
32  
33      private ClassVisitor cv;
34      @NonNegative
35      private int innerClassesCodeIndex;
36      @NonNegative
37      private int attributesCodeIndex;
38  
39      /**
40       * The start index of each bootstrap method.
41       */
42      @Nullable
43      private int[] bootstrapMethods;
44  
45      /**
46       * Initializes a new class reader with the given bytecode array for a classfile.
47       */
48      public ClassReader(@NonNull byte[] code) {
49          super(code);
50          header = codeIndex; // the class header information starts just after the constant pool
51          version = readShort(6);
52          access = readUnsignedShort();
53          classInfo = new ClassInfo();
54          codeIndex += 2;
55          classInfo.superName = readClass();
56      }
57  
58      /**
59       * Returns the classfile {@linkplain ClassVersion version} of the class being read.
60       */
61      public int getVersion() {
62          return version;
63      }
64  
65      /**
66       * Returns the class's {@linkplain Access access} flags.
67       */
68      public int getAccess() {
69          return access;
70      }
71  
72      /**
73       * Returns the internal of name of the super class. For interfaces, the super class is {@link Object}.
74       */
75      @NonNull
76      public String getSuperName() {
77          assert classInfo.superName != null;
78          return classInfo.superName;
79      }
80  
81      /**
82       * Returns the bytecode array of the Java classfile that was read.
83       */
84      @NonNull
85      public byte[] getBytecode() {
86          return code;
87      }
88  
89      /**
90       * Makes the given visitor visit the Java class of this Class Reader.
91       */
92      public void accept(ClassVisitor visitor) {
93          cv = visitor;
94  
95          codeIndex = header + 2;
96          String classDesc = readNonnullClass();
97          codeIndex += 2;
98  
99          readInterfaces();
100         readClassAttributes();
101         visitor.visit(version, access, classDesc, classInfo);
102         readAnnotations(visitor);
103         readInnerClasses();
104         readFieldsAndMethods();
105 
106         visitor.visitEnd();
107     }
108 
109     private void readInterfaces() {
110         int interfaceCount = readUnsignedShort();
111 
112         if (interfaceCount > 0) {
113             String[] interfaces = new String[interfaceCount];
114 
115             for (int i = 0; i < interfaceCount; i++) {
116                 interfaces[i] = readNonnullClass();
117             }
118 
119             classInfo.interfaces = interfaces;
120         }
121     }
122 
123     private void readClassAttributes() {
124         innerClassesCodeIndex = 0;
125         codeIndex = getAttributesStartIndex();
126         readAttributes();
127         classInfo.signature = signature;
128     }
129 
130     @Nullable
131     @Override
132     protected Boolean readAttribute(@NonNull String attributeName) {
133         if ("SourceFile".equals(attributeName)) {
134             classInfo.sourceFileName = readNonnullUTF8();
135             return true;
136         }
137 
138         if ("EnclosingMethod".equals(attributeName)) {
139             return false;
140         }
141 
142         if ("NestHost".equals(attributeName)) {
143             classInfo.hostClassName = readNonnullClass();
144             return true;
145         }
146 
147         if ("NestMembers".equals(attributeName)) {
148             readNestMembers();
149             return true;
150         }
151 
152         if ("BootstrapMethods".equals(attributeName)) {
153             readBootstrapMethods();
154             return true;
155         }
156 
157         if ("InnerClasses".equals(attributeName)) {
158             innerClassesCodeIndex = codeIndex;
159             return false;
160         }
161 
162         return null;
163     }
164 
165     private void readNestMembers() {
166         int numberOfClasses = readUnsignedShort();
167         String[] nestMembers = new String[numberOfClasses];
168 
169         for (int i = 0; i < numberOfClasses; i++) {
170             nestMembers[i] = readNonnullClass();
171         }
172 
173         classInfo.nestMembers = nestMembers;
174     }
175 
176     private void readBootstrapMethods() {
177         int bsmCount = readUnsignedShort();
178         bootstrapMethods = new int[bsmCount];
179 
180         for (int i = 0; i < bsmCount; i++) {
181             bootstrapMethods[i] = codeIndex;
182             codeIndex += 2;
183             int codeOffset = readUnsignedShort();
184             codeIndex += codeOffset << 1;
185         }
186     }
187 
188     private void readInnerClasses() {
189         int startIndex = innerClassesCodeIndex;
190 
191         if (startIndex != 0) {
192             codeIndex = startIndex;
193 
194             for (int innerClassCount = readUnsignedShort(); innerClassCount > 0; innerClassCount--) {
195                 String innerName = readNonnullClass();
196                 String outerName = readClass();
197                 String simpleInnerName = readUTF8();
198                 int innerAccess = readUnsignedShort();
199 
200                 cv.visitInnerClass(innerName, outerName, simpleInnerName, innerAccess);
201             }
202         }
203     }
204 
205     private void readFieldsAndMethods() {
206         codeIndex = getCodeIndexAfterInterfaces(classInfo.interfaces.length);
207 
208         FieldReader fieldReader = new FieldReader(this, cv);
209         codeIndex = fieldReader.readFields();
210 
211         MethodReader methodReader = new MethodReader(this, cv);
212         codeIndex = methodReader.readMethods();
213     }
214 
215     @NonNegative
216     private int getCodeIndexAfterInterfaces(@NonNegative int interfaceCount) {
217         return header + 8 + 2 * interfaceCount;
218     }
219 
220     /**
221      * Returns the start index of the attribute_info structure of this class.
222      */
223     @NonNegative
224     private int getAttributesStartIndex() {
225         if (attributesCodeIndex == 0) {
226             skipHeader();
227             skipClassMembers(); // fields
228             skipClassMembers(); // methods
229             attributesCodeIndex = codeIndex;
230         }
231 
232         return attributesCodeIndex;
233     }
234 
235     private void skipHeader() {
236         int interfaceCount = readUnsignedShort(header + 6);
237         codeIndex = getCodeIndexAfterInterfaces(interfaceCount);
238     }
239 
240     private void skipClassMembers() {
241         for (int memberCount = readUnsignedShort(); memberCount > 0; memberCount--) {
242             codeIndex += 6; // skips access, name and desc
243             skipMemberAttributes();
244         }
245     }
246 
247     private void skipMemberAttributes() {
248         for (int attributeCount = readUnsignedShort(); attributeCount > 0; attributeCount--) {
249             codeIndex += 2; // skips attribute name
250             int codeOffsetToNextAttribute = readInt();
251             codeIndex += codeOffsetToNextAttribute;
252         }
253     }
254 
255     boolean positionAtBootstrapMethodsAttribute() {
256         codeIndex = getAttributesStartIndex();
257 
258         for (int attributeCount = readUnsignedShort(); attributeCount > 0; attributeCount--) {
259             String attrName = readNonnullUTF8();
260 
261             if ("BootstrapMethods".equals(attrName)) {
262                 return true;
263             }
264 
265             int codeOffsetToNextAttribute = readInt();
266             codeIndex += codeOffsetToNextAttribute;
267         }
268 
269         return false;
270     }
271 
272     @NonNegative
273     public int getBSMCodeIndex(@NonNegative int bsmStartIndex) {
274         assert bootstrapMethods != null;
275         return bootstrapMethods[bsmStartIndex];
276     }
277 }