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