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