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