1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.tomcat.util.bcel.classfile;
18
19 import java.io.BufferedInputStream;
20 import java.io.DataInput;
21 import java.io.DataInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.ArrayList;
25 import java.util.List;
26
27 import org.apache.tomcat.util.bcel.Const;
28
29 /**
30 * Wrapper class that parses a given Java .class file. The method <a href ="#parse">parse</a> returns a
31 * <a href ="JavaClass.html"> JavaClass</a> object on success. When an I/O error or an inconsistency occurs an
32 * appropriate exception is propagated back to the caller.
33 *
34 * The structure and the names comply, except for a few conveniences, exactly with the
35 * <a href="https://docs.oracle.com/javase/specs/"> JVM specification 1.0</a>. See this paper for further details about
36 * the structure of a bytecode file.
37 */
38 public final class ClassParser {
39
40 private static final int BUFSIZE = 8192;
41 private final DataInput dataInputStream;
42 private String className;
43 private String superclassName;
44 private int accessFlags; // Access rights of parsed class
45 private String[] interfaceNames; // Names of implemented interfaces
46 private ConstantPool constantPool; // collection of constants
47 private Annotations runtimeVisibleAnnotations; // "RuntimeVisibleAnnotations" attribute defined in the class
48 private List<Annotations> runtimeVisibleFieldOrMethodAnnotations; // "RuntimeVisibleAnnotations" attribute defined elsewhere
49
50 private static final String[] INTERFACES_EMPTY_ARRAY = {};
51
52 /**
53 * Parses class from the given stream.
54 *
55 * @param inputStream Input stream
56 */
57 public ClassParser(final InputStream inputStream) {
58 this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
59 }
60
61
62 /**
63 * Parses the given Java class file and return an object that represents the contained data, i.e., constants, methods,
64 * fields and commands. A <em>ClassFormatException</em> is raised, if the file is not a valid .class file. (This does
65 * not include verification of the byte code as it is performed by the Java interpreter).
66 *
67 * @return Class object representing the parsed class file
68 * @throws IOException if an I/O error occurs.
69 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
70 */
71 public JavaClass parse() throws IOException, ClassFormatException {
72 //****************** Read headers ********************************
73 // Check magic tag of class file
74 readID();
75 // Get compiler version
76 readVersion();
77 //***************** Read constant pool and related **************
78 // Read constant pool entries
79 readConstantPool();
80 // Get class information
81 readClassInfo();
82 // Get interface information, i.e., implemented interfaces
83 readInterfaces();
84 //***************** Read class fields and methods ***************
85 // Read class fields, i.e., the variables of the class
86 readFields();
87 // Read class methods, i.e., the functions in the class
88 readMethods();
89 // Read class attributes
90 readAttributes(false);
91
92 // Return the information we have gathered in a new object
93 return new JavaClass(className, superclassName, accessFlags, constantPool, interfaceNames,
94 runtimeVisibleAnnotations, runtimeVisibleFieldOrMethodAnnotations);
95 }
96
97
98 /**
99 * Reads information about the attributes of the class.
100 * @param fieldOrMethod false if processing a class
101 * @throws IOException if an I/O error occurs.
102 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
103 */
104 private void readAttributes(boolean fieldOrMethod) throws IOException, ClassFormatException {
105 final int attributesCount = dataInputStream.readUnsignedShort();
106 for (int i = 0; i < attributesCount; i++) {
107 ConstantUtf8 c;
108 String name;
109 int name_index;
110 int length;
111 // Get class name from constant pool via 'name_index' indirection
112 name_index = dataInputStream.readUnsignedShort();
113 c = (ConstantUtf8) constantPool.getConstant(name_index,
114 Const.CONSTANT_Utf8);
115 name = c.getBytes();
116 // Length of data in bytes
117 length = dataInputStream.readInt();
118 if (name.equals("RuntimeVisibleAnnotations")) {
119 if (fieldOrMethod) {
120 Annotations fieldOrMethodAnnotations = new Annotations(dataInputStream, constantPool);
121 if (runtimeVisibleFieldOrMethodAnnotations == null) {
122 runtimeVisibleFieldOrMethodAnnotations = new ArrayList<>();
123 }
124 runtimeVisibleFieldOrMethodAnnotations.add(fieldOrMethodAnnotations);
125 } else {
126 if (runtimeVisibleAnnotations != null) {
127 throw new ClassFormatException(
128 "RuntimeVisibleAnnotations attribute is not allowed more than once in a class file");
129 }
130 runtimeVisibleAnnotations = new Annotations(dataInputStream, constantPool);
131 }
132 } else {
133 // All other attributes are skipped
134 Utility.skipFully(dataInputStream, length);
135 }
136 }
137 }
138
139
140 /**
141 * Reads information about the class and its super class.
142 *
143 * @throws IOException if an I/O error occurs.
144 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
145 */
146 private void readClassInfo() throws IOException, ClassFormatException {
147 accessFlags = dataInputStream.readUnsignedShort();
148 /*
149 * Interfaces are implicitly abstract, the flag should be set according to the JVM specification.
150 */
151 if ((accessFlags & Const.ACC_INTERFACE) != 0) {
152 accessFlags |= Const.ACC_ABSTRACT;
153 }
154 if ((accessFlags & Const.ACC_ABSTRACT) != 0 && (accessFlags & Const.ACC_FINAL) != 0) {
155 throw new ClassFormatException("Class can't be both final and abstract");
156 }
157
158 int classNameIndex = dataInputStream.readUnsignedShort();
159 className = Utility.getClassName(constantPool, classNameIndex);
160
161 int superclass_name_index = dataInputStream.readUnsignedShort();
162 if (superclass_name_index > 0) {
163 // May be zero -> class is java.lang.Object
164 superclassName = Utility.getClassName(constantPool, superclass_name_index);
165 } else {
166 superclassName = "java.lang.Object";
167 }
168 }
169
170
171 /**
172 * Reads constant pool entries.
173 *
174 * @throws IOException if an I/O error occurs.
175 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
176 */
177 private void readConstantPool() throws IOException, ClassFormatException {
178 constantPool = new ConstantPool(dataInputStream);
179 }
180
181
182 /**
183 * Reads information about the fields of the class, i.e., its variables.
184 *
185 * @throws IOException if an I/O error occurs.
186 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
187 */
188 private void readFields() throws IOException, ClassFormatException {
189 final int fieldsCount = dataInputStream.readUnsignedShort();
190 for (int i = 0; i < fieldsCount; i++) {
191 // file.readUnsignedShort(); // Unused access flags
192 // file.readUnsignedShort(); // name index
193 // file.readUnsignedShort(); // signature index
194 Utility.skipFully(dataInputStream, 6);
195
196 readAttributes(true);
197 }
198 }
199
200
201 /**
202 * Checks whether the header of the file is ok. Of course, this has to be the first action on successive file reads.
203 *
204 * @throws IOException if an I/O error occurs.
205 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
206 */
207 private void readID() throws IOException, ClassFormatException {
208 if (dataInputStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
209 throw new ClassFormatException("It is not a Java .class file");
210 }
211 }
212
213
214 /**
215 * Reads information about the interfaces implemented by this class.
216 *
217 * @throws IOException if an I/O error occurs.
218 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
219 */
220 private void readInterfaces() throws IOException, ClassFormatException {
221 final int interfacesCount = dataInputStream.readUnsignedShort();
222 if (interfacesCount > 0) {
223 interfaceNames = new String[interfacesCount];
224 for (int i = 0; i < interfacesCount; i++) {
225 int index = dataInputStream.readUnsignedShort();
226 interfaceNames[i] = Utility.getClassName(constantPool, index);
227 }
228 } else {
229 interfaceNames = INTERFACES_EMPTY_ARRAY;
230 }
231 }
232
233
234 /**
235 * Reads information about the methods of the class.
236 *
237 * @throws IOException if an I/O error occurs.
238 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
239 */
240 private void readMethods() throws IOException, ClassFormatException {
241 final int methodsCount = dataInputStream.readUnsignedShort();
242 for (int i = 0; i < methodsCount; i++) {
243 // file.readUnsignedShort(); // Unused access flags
244 // file.readUnsignedShort(); // name index
245 // file.readUnsignedShort(); // signature index
246 Utility.skipFully(dataInputStream, 6);
247
248 readAttributes(true);
249 }
250 }
251
252
253 /**
254 * Reads major and minor version of compiler which created the file.
255 *
256 * @throws IOException if an I/O error occurs.
257 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
258 */
259 private void readVersion() throws IOException, ClassFormatException {
260 // file.readUnsignedShort(); // Unused minor
261 // file.readUnsignedShort(); // Unused major
262 Utility.skipFully(dataInputStream, 4);
263 }
264 }
265