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
032 * href ="#parse">parse</A> returns a <A href ="JavaClass.html">
033 * JavaClass</A> object on success. When an I/O error or an
034 * inconsistency occurs an appropiate exception is propagated back to
035 * the caller.
036 *
037 * The structure and the names comply, except for a few conveniences,
038 * exactly with the <A href="http://docs.oracle.com/javase/specs/">
039 * JVM specification 1.0</a>. See this paper for
040 * further details about the structure of a bytecode file.
041 *
042 * @version $Id: ClassParser.java 1749603 2016-06-21 20:50:19Z ggregory $
043 */
044public final class ClassParser {
045
046    private DataInputStream dataInputStream;
047    private final boolean fileOwned;
048    private final String file_name;
049    private String zip_file;
050    private int class_name_index;
051    private int superclass_name_index;
052    private int major; // Compiler version
053    private int minor; // Compiler version
054    private int access_flags; // Access rights of parsed class
055    private int[] interfaces; // Names of implemented interfaces
056    private ConstantPool constant_pool; // collection of constants
057    private Field[] fields; // class fields, i.e., its variables
058    private Method[] methods; // methods defined in the class
059    private Attribute[] attributes; // attributes defined in the class
060    private final boolean is_zip; // Loaded from zip file
061    private static final int BUFSIZE = 8192;
062
063
064    /**
065     * Parse class from the given stream.
066     *
067     * @param inputStream Input stream
068     * @param file_name File name
069     */
070    public ClassParser(final InputStream inputStream, final String file_name) {
071        this.file_name = file_name;
072        fileOwned = false;
073        final String clazz = inputStream.getClass().getName(); // Not a very clean solution ...
074        is_zip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
075        if (inputStream instanceof DataInputStream) {
076            this.dataInputStream = (DataInputStream) inputStream;
077        } else {
078            this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
079        }
080    }
081
082
083    /** Parse class from given .class file.
084     *
085     * @param file_name file name
086     */
087    public ClassParser(final String file_name) {
088        is_zip = false;
089        this.file_name = file_name;
090        fileOwned = true;
091    }
092
093
094    /** Parse class from given .class file in a ZIP-archive
095     *
096     * @param zip_file zip file name
097     * @param file_name file name
098     */
099    public ClassParser(final String zip_file, final String file_name) {
100        is_zip = true;
101        fileOwned = true;
102        this.zip_file = zip_file;
103        this.file_name = file_name;
104    }
105
106
107    /**
108     * Parse the given Java class file and return an object that represents
109     * the contained data, i.e., constants, methods, fields and commands.
110     * A <em>ClassFormatException</em> is raised, if the file is not a valid
111     * .class file. (This does not include verification of the byte code as it
112     * is performed by the java interpreter).
113     *
114     * @return Class object representing the parsed class file
115     * @throws  IOException
116     * @throws  ClassFormatException
117     */
118    public JavaClass parse() throws IOException, ClassFormatException {
119        ZipFile zip = null;
120        try {
121            if (fileOwned) {
122                if (is_zip) {
123                    zip = new ZipFile(zip_file);
124                    final ZipEntry entry = zip.getEntry(file_name);
125
126                    if (entry == null) {
127                        throw new IOException("File " + file_name + " not found");
128                    }
129
130                    dataInputStream = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry),
131                            BUFSIZE));
132                } else {
133                    dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(
134                            file_name), BUFSIZE));
135                }
136            }
137            /****************** Read headers ********************************/
138            // Check magic tag of class file
139            readID();
140            // Get compiler version
141            readVersion();
142            /****************** Read constant pool and related **************/
143            // Read constant pool entries
144            readConstantPool();
145            // Get class information
146            readClassInfo();
147            // Get interface information, i.e., implemented interfaces
148            readInterfaces();
149            /****************** Read class fields and methods ***************/
150            // Read class fields, i.e., the variables of the class
151            readFields();
152            // Read class methods, i.e., the functions in the class
153            readMethods();
154            // Read class attributes
155            readAttributes();
156            // Check for unknown variables
157            //Unknown[] u = Unknown.getUnknownAttributes();
158            //for (int i=0; i < u.length; i++)
159            //  System.err.println("WARNING: " + u[i]);
160            // Everything should have been read now
161            //      if(file.available() > 0) {
162            //        int bytes = file.available();
163            //        byte[] buf = new byte[bytes];
164            //        file.read(buf);
165            //        if(!(is_zip && (buf.length == 1))) {
166            //      System.err.println("WARNING: Trailing garbage at end of " + file_name);
167            //      System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf));
168            //        }
169            //      }
170        } finally {
171            // Read everything of interest, so close the file
172            if (fileOwned) {
173                try {
174                    if (dataInputStream != null) {
175                        dataInputStream.close();
176                    }
177                } catch (final IOException ioe) {
178                    //ignore close exceptions
179                }
180            }
181            try {
182                if (zip != null) {
183                    zip.close();
184                }
185            } catch (final IOException ioe) {
186                //ignore close exceptions
187            }
188        }
189        // Return the information we have gathered in a new object
190        return new JavaClass(class_name_index, superclass_name_index, file_name, major, minor,
191                access_flags, constant_pool, interfaces, fields, methods, attributes, is_zip
192                        ? JavaClass.ZIP
193                        : JavaClass.FILE);
194    }
195
196
197    /**
198     * Read information about the attributes of the class.
199     * @throws  IOException
200     * @throws  ClassFormatException
201     */
202    private void readAttributes() throws IOException, ClassFormatException {
203        final int attributes_count = dataInputStream.readUnsignedShort();
204        attributes = new Attribute[attributes_count];
205        for (int i = 0; i < attributes_count; i++) {
206            attributes[i] = Attribute.readAttribute(dataInputStream, constant_pool);
207        }
208    }
209
210
211    /**
212     * Read information about the class and its super class.
213     * @throws  IOException
214     * @throws  ClassFormatException
215     */
216    private void readClassInfo() throws IOException, ClassFormatException {
217        access_flags = dataInputStream.readUnsignedShort();
218        /* Interfaces are implicitely abstract, the flag should be set
219         * according to the JVM specification.
220         */
221        if ((access_flags & Const.ACC_INTERFACE) != 0) {
222            access_flags |= Const.ACC_ABSTRACT;
223        }
224        if (((access_flags & Const.ACC_ABSTRACT) != 0)
225                && ((access_flags & Const.ACC_FINAL) != 0)) {
226            throw new ClassFormatException("Class " + file_name + " can't be both final and abstract");
227        }
228        class_name_index = dataInputStream.readUnsignedShort();
229        superclass_name_index = dataInputStream.readUnsignedShort();
230    }
231
232
233    /**
234     * Read constant pool entries.
235     * @throws  IOException
236     * @throws  ClassFormatException
237     */
238    private void readConstantPool() throws IOException, ClassFormatException {
239        constant_pool = new ConstantPool(dataInputStream);
240    }
241
242
243    /**
244     * Read information about the fields of the class, i.e., its variables.
245     * @throws  IOException
246     * @throws  ClassFormatException
247     */
248    private void readFields() throws IOException, ClassFormatException {
249        final int fields_count = dataInputStream.readUnsignedShort();
250        fields = new Field[fields_count];
251        for (int i = 0; i < fields_count; i++) {
252            fields[i] = new Field(dataInputStream, constant_pool);
253        }
254    }
255
256
257    /******************** Private utility methods **********************/
258    /**
259     * Check whether the header of the file is ok.
260     * Of course, this has to be the first action on successive file reads.
261     * @throws  IOException
262     * @throws  ClassFormatException
263     */
264    private void readID() throws IOException, ClassFormatException {
265        if (dataInputStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
266            throw new ClassFormatException(file_name + " is not a Java .class file");
267        }
268    }
269
270
271    /**
272     * Read information about the interfaces implemented by this class.
273     * @throws  IOException
274     * @throws  ClassFormatException
275     */
276    private void readInterfaces() throws IOException, ClassFormatException {
277        final int interfaces_count = dataInputStream.readUnsignedShort();
278        interfaces = new int[interfaces_count];
279        for (int i = 0; i < interfaces_count; i++) {
280            interfaces[i] = dataInputStream.readUnsignedShort();
281        }
282    }
283
284
285    /**
286     * Read information about the methods of the class.
287     * @throws  IOException
288     * @throws  ClassFormatException
289     */
290    private void readMethods() throws IOException, ClassFormatException {
291        final int methods_count = dataInputStream.readUnsignedShort();
292        methods = new Method[methods_count];
293        for (int i = 0; i < methods_count; i++) {
294            methods[i] = new Method(dataInputStream, constant_pool);
295        }
296    }
297
298
299    /**
300     * Read major and minor version of compiler which created the file.
301     * @throws  IOException
302     * @throws  ClassFormatException
303     */
304    private void readVersion() throws IOException, ClassFormatException {
305        minor = dataInputStream.readUnsignedShort();
306        major = dataInputStream.readUnsignedShort();
307    }
308}