001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.bcel.classfile;
020
021import java.io.BufferedInputStream;
022import java.io.DataInputStream;
023import java.io.FileInputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.util.zip.ZipEntry;
027import java.util.zip.ZipFile;
028
029import org.apache.bcel.Const;
030import org.apache.commons.io.IOUtils;
031
032/**
033 * Wrapper class that parses a given Java .class file. The method <a href ="#parse">parse</a> returns a
034 * <a href ="JavaClass.html"> JavaClass</a> object on success. When an I/O error or an inconsistency occurs an
035 * appropriate exception is propagated back to the caller.
036 *
037 * The structure and the names comply, except for a few conveniences, exactly with the
038 * <a href="https://docs.oracle.com/javase/specs/"> JVM specification 1.0</a>. See this paper for further details about
039 * the structure of a bytecode file.
040 */
041public final class ClassParser {
042
043    private static final int BUFSIZE = 8192;
044    private DataInputStream dataInputStream;
045    private final boolean fileOwned;
046    private final String fileName;
047    private String zipFile;
048    private int classNameIndex;
049    private int superclassNameIndex;
050    private int major; // Compiler version
051    private int minor; // Compiler version
052    private int accessFlags; // Access rights of parsed class
053    private int[] interfaces; // Names of implemented interfaces
054    private ConstantPool constantPool; // collection of constants
055    private Field[] fields; // class fields, i.e., its variables
056    private Method[] methods; // methods defined in the class
057    private Attribute[] attributes; // attributes defined in the class
058    private final boolean isZip; // Loaded from ZIP file
059
060    /**
061     * Parses class from the given stream.
062     *
063     * @param inputStream Input stream
064     * @param fileName File name
065     */
066    public ClassParser(final InputStream inputStream, final String fileName) {
067        this.fileName = fileName;
068        this.fileOwned = false;
069        final String clazz = inputStream.getClass().getName(); // Not a very clean solution ...
070        this.isZip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
071        if (inputStream instanceof DataInputStream) {
072            this.dataInputStream = (DataInputStream) inputStream;
073        } else {
074            this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
075        }
076    }
077
078    /**
079     * Parses class from given .class file.
080     *
081     * @param fileName file name
082     */
083    public ClassParser(final String fileName) {
084        this.isZip = false;
085        this.fileName = fileName;
086        this.fileOwned = true;
087    }
088
089    /**
090     * Parses class from given .class file in a ZIP-archive
091     *
092     * @param zipFile ZIP file name
093     * @param fileName file name
094     */
095    public ClassParser(final String zipFile, final String fileName) {
096        this.isZip = true;
097        this.fileOwned = true;
098        this.zipFile = zipFile;
099        this.fileName = fileName;
100    }
101
102    /**
103     * Parses the given Java class file and return an object that represents the contained data, i.e., constants, methods,
104     * fields and commands. A <em>ClassFormatException</em> is raised, if the file is not a valid .class file. (This does
105     * not include verification of the byte code as it is performed by the Java interpreter).
106     *
107     * @return Class object representing the parsed class file
108     * @throws IOException if an I/O error occurs.
109     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
110     */
111    public JavaClass parse() throws IOException, ClassFormatException {
112        ZipFile zip = null;
113        try {
114            if (fileOwned) {
115                if (isZip) {
116                    zip = new ZipFile(zipFile);
117                    final ZipEntry entry = zip.getEntry(fileName);
118
119                    if (entry == null) {
120                        throw new IOException("File " + fileName + " not found");
121                    }
122
123                    dataInputStream = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry), BUFSIZE));
124                } else {
125                    dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName), BUFSIZE));
126                }
127            }
128            /****************** Read headers ********************************/
129            // Check magic tag of class file
130            readID();
131            // Get compiler version
132            readVersion();
133            /****************** Read constant pool and related **************/
134            // Read constant pool entries
135            readConstantPool();
136            // Get class information
137            readClassInfo();
138            // Get interface information, i.e., implemented interfaces
139            readInterfaces();
140            /****************** Read class fields and methods ***************/
141            // Read class fields, i.e., the variables of the class
142            readFields();
143            // Read class methods, i.e., the functions in the class
144            readMethods();
145            // Read class attributes
146            readAttributes();
147            // Check for unknown variables
148            // Unknown[] u = Unknown.getUnknownAttributes();
149            // for (int i=0; i < u.length; i++)
150            // System.err.println("WARNING: " + u[i]);
151            // Everything should have been read now
152            // if (file.available() > 0) {
153            // int bytes = file.available();
154            // byte[] buf = new byte[bytes];
155            // file.read(buf);
156            // if (!(isZip && (buf.length == 1))) {
157            // System.err.println("WARNING: Trailing garbage at end of " + fileName);
158            // System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf));
159            // }
160            // }
161        } finally {
162            // Read everything of interest, so close the file
163            if (fileOwned) {
164                IOUtils.closeQuietly(dataInputStream);
165            }
166            IOUtils.closeQuietly(zip);
167        }
168        // Return the information we have gathered in a new object
169        return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, accessFlags, constantPool, interfaces, fields, methods, attributes,
170            isZip ? JavaClass.ZIP : JavaClass.FILE);
171    }
172
173    /**
174     * Reads information about the attributes of the class.
175     *
176     * @throws IOException if an I/O error occurs.
177     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
178     */
179    private void readAttributes() throws IOException, ClassFormatException {
180        final int attributesCount = dataInputStream.readUnsignedShort();
181        attributes = new Attribute[attributesCount];
182        for (int i = 0; i < attributesCount; i++) {
183            attributes[i] = Attribute.readAttribute(dataInputStream, constantPool);
184        }
185    }
186
187    /**
188     * Reads information about the class and its super class.
189     *
190     * @throws IOException if an I/O error occurs.
191     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
192     */
193    private void readClassInfo() throws IOException, ClassFormatException {
194        accessFlags = dataInputStream.readUnsignedShort();
195        /*
196         * Interfaces are implicitly abstract, the flag should be set according to the JVM specification.
197         */
198        if ((accessFlags & Const.ACC_INTERFACE) != 0) {
199            accessFlags |= Const.ACC_ABSTRACT;
200        }
201        if ((accessFlags & Const.ACC_ABSTRACT) != 0 && (accessFlags & Const.ACC_FINAL) != 0) {
202            throw new ClassFormatException("Class " + fileName + " can't be both final and abstract");
203        }
204        classNameIndex = dataInputStream.readUnsignedShort();
205        superclassNameIndex = dataInputStream.readUnsignedShort();
206    }
207
208    /**
209     * Reads constant pool entries.
210     *
211     * @throws IOException if an I/O error occurs.
212     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
213     */
214    private void readConstantPool() throws IOException, ClassFormatException {
215        constantPool = new ConstantPool(dataInputStream);
216    }
217
218    /**
219     * Reads information about the fields of the class, i.e., its variables.
220     *
221     * @throws IOException if an I/O error occurs.
222     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
223     */
224    private void readFields() throws IOException, ClassFormatException {
225        final int fieldsCount = dataInputStream.readUnsignedShort();
226        fields = new Field[fieldsCount];
227        for (int i = 0; i < fieldsCount; i++) {
228            fields[i] = new Field(dataInputStream, constantPool);
229        }
230    }
231
232    /******************** Private utility methods **********************/
233    /**
234     * Checks whether the header of the file is ok. Of course, this has to be the first action on successive file reads.
235     *
236     * @throws IOException if an I/O error occurs.
237     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
238     */
239    private void readID() throws IOException, ClassFormatException {
240        if (dataInputStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
241            throw new ClassFormatException(fileName + " is not a Java .class file");
242        }
243    }
244
245    /**
246     * Reads information about the interfaces implemented by this class.
247     *
248     * @throws IOException if an I/O error occurs.
249     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
250     */
251    private void readInterfaces() throws IOException, ClassFormatException {
252        final int interfacesCount = dataInputStream.readUnsignedShort();
253        interfaces = new int[interfacesCount];
254        for (int i = 0; i < interfacesCount; i++) {
255            interfaces[i] = dataInputStream.readUnsignedShort();
256        }
257    }
258
259    /**
260     * Reads information about the methods of the class.
261     *
262     * @throws IOException if an I/O error occurs.
263     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
264     */
265    private void readMethods() throws IOException {
266        final int methodsCount = dataInputStream.readUnsignedShort();
267        methods = new Method[methodsCount];
268        for (int i = 0; i < methodsCount; i++) {
269            methods[i] = new Method(dataInputStream, constantPool);
270        }
271    }
272
273    /**
274     * Reads major and minor version of compiler which created the file.
275     *
276     * @throws IOException if an I/O error occurs.
277     * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
278     */
279    private void readVersion() throws IOException, ClassFormatException {
280        minor = dataInputStream.readUnsignedShort();
281        major = dataInputStream.readUnsignedShort();
282    }
283}