ClassParser.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.bcel.classfile;
- import java.io.BufferedInputStream;
- import java.io.DataInputStream;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.zip.ZipEntry;
- import java.util.zip.ZipFile;
- import org.apache.bcel.Const;
- /**
- * Wrapper class that parses a given Java .class file. The method <a href ="#parse">parse</a> returns a
- * <a href ="JavaClass.html"> JavaClass</a> object on success. When an I/O error or an inconsistency occurs an
- * appropriate exception is propagated back to the caller.
- *
- * The structure and the names comply, except for a few conveniences, exactly with the
- * <a href="https://docs.oracle.com/javase/specs/"> JVM specification 1.0</a>. See this paper for further details about
- * the structure of a bytecode file.
- */
- public final class ClassParser {
- private static final int BUFSIZE = 8192;
- private DataInputStream dataInputStream;
- private final boolean fileOwned;
- private final String fileName;
- private String zipFile;
- private int classNameIndex;
- private int superclassNameIndex;
- private int major; // Compiler version
- private int minor; // Compiler version
- private int accessFlags; // Access rights of parsed class
- private int[] interfaces; // Names of implemented interfaces
- private ConstantPool constantPool; // collection of constants
- private Field[] fields; // class fields, i.e., its variables
- private Method[] methods; // methods defined in the class
- private Attribute[] attributes; // attributes defined in the class
- private final boolean isZip; // Loaded from ZIP file
- /**
- * Parses class from the given stream.
- *
- * @param inputStream Input stream
- * @param fileName File name
- */
- public ClassParser(final InputStream inputStream, final String fileName) {
- this.fileName = fileName;
- this.fileOwned = false;
- final String clazz = inputStream.getClass().getName(); // Not a very clean solution ...
- this.isZip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
- if (inputStream instanceof DataInputStream) {
- this.dataInputStream = (DataInputStream) inputStream;
- } else {
- this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
- }
- }
- /**
- * Parses class from given .class file.
- *
- * @param fileName file name
- */
- public ClassParser(final String fileName) {
- this.isZip = false;
- this.fileName = fileName;
- this.fileOwned = true;
- }
- /**
- * Parses class from given .class file in a ZIP-archive
- *
- * @param zipFile ZIP file name
- * @param fileName file name
- */
- public ClassParser(final String zipFile, final String fileName) {
- this.isZip = true;
- this.fileOwned = true;
- this.zipFile = zipFile;
- this.fileName = fileName;
- }
- /**
- * Parses the given Java class file and return an object that represents the contained data, i.e., constants, methods,
- * fields and commands. A <em>ClassFormatException</em> is raised, if the file is not a valid .class file. (This does
- * not include verification of the byte code as it is performed by the Java interpreter).
- *
- * @return Class object representing the parsed class file
- * @throws IOException if an I/O error occurs.
- * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
- */
- public JavaClass parse() throws IOException, ClassFormatException {
- ZipFile zip = null;
- try {
- if (fileOwned) {
- if (isZip) {
- zip = new ZipFile(zipFile);
- final ZipEntry entry = zip.getEntry(fileName);
- if (entry == null) {
- throw new IOException("File " + fileName + " not found");
- }
- dataInputStream = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry), BUFSIZE));
- } else {
- dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName), BUFSIZE));
- }
- }
- /****************** Read headers ********************************/
- // Check magic tag of class file
- readID();
- // Get compiler version
- readVersion();
- /****************** Read constant pool and related **************/
- // Read constant pool entries
- readConstantPool();
- // Get class information
- readClassInfo();
- // Get interface information, i.e., implemented interfaces
- readInterfaces();
- /****************** Read class fields and methods ***************/
- // Read class fields, i.e., the variables of the class
- readFields();
- // Read class methods, i.e., the functions in the class
- readMethods();
- // Read class attributes
- readAttributes();
- // Check for unknown variables
- // Unknown[] u = Unknown.getUnknownAttributes();
- // for (int i=0; i < u.length; i++)
- // System.err.println("WARNING: " + u[i]);
- // Everything should have been read now
- // if (file.available() > 0) {
- // int bytes = file.available();
- // byte[] buf = new byte[bytes];
- // file.read(buf);
- // if (!(isZip && (buf.length == 1))) {
- // System.err.println("WARNING: Trailing garbage at end of " + fileName);
- // System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf));
- // }
- // }
- } finally {
- // Read everything of interest, so close the file
- if (fileOwned) {
- try {
- if (dataInputStream != null) {
- dataInputStream.close();
- }
- } catch (final IOException ignored) {
- // ignore close exceptions
- }
- }
- try {
- if (zip != null) {
- zip.close();
- }
- } catch (final IOException ignored) {
- // ignore close exceptions
- }
- }
- // Return the information we have gathered in a new object
- return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, accessFlags, constantPool, interfaces, fields, methods, attributes,
- isZip ? JavaClass.ZIP : JavaClass.FILE);
- }
- /**
- * Reads information about the attributes of the class.
- *
- * @throws IOException if an I/O error occurs.
- * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
- */
- private void readAttributes() throws IOException, ClassFormatException {
- final int attributesCount = dataInputStream.readUnsignedShort();
- attributes = new Attribute[attributesCount];
- for (int i = 0; i < attributesCount; i++) {
- attributes[i] = Attribute.readAttribute(dataInputStream, constantPool);
- }
- }
- /**
- * Reads information about the class and its super class.
- *
- * @throws IOException if an I/O error occurs.
- * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
- */
- private void readClassInfo() throws IOException, ClassFormatException {
- accessFlags = dataInputStream.readUnsignedShort();
- /*
- * Interfaces are implicitly abstract, the flag should be set according to the JVM specification.
- */
- if ((accessFlags & Const.ACC_INTERFACE) != 0) {
- accessFlags |= Const.ACC_ABSTRACT;
- }
- if ((accessFlags & Const.ACC_ABSTRACT) != 0 && (accessFlags & Const.ACC_FINAL) != 0) {
- throw new ClassFormatException("Class " + fileName + " can't be both final and abstract");
- }
- classNameIndex = dataInputStream.readUnsignedShort();
- superclassNameIndex = dataInputStream.readUnsignedShort();
- }
- /**
- * Reads constant pool entries.
- *
- * @throws IOException if an I/O error occurs.
- * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
- */
- private void readConstantPool() throws IOException, ClassFormatException {
- constantPool = new ConstantPool(dataInputStream);
- }
- /**
- * Reads information about the fields of the class, i.e., its variables.
- *
- * @throws IOException if an I/O error occurs.
- * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
- */
- private void readFields() throws IOException, ClassFormatException {
- final int fieldsCount = dataInputStream.readUnsignedShort();
- fields = new Field[fieldsCount];
- for (int i = 0; i < fieldsCount; i++) {
- fields[i] = new Field(dataInputStream, constantPool);
- }
- }
- /******************** Private utility methods **********************/
- /**
- * Checks whether the header of the file is ok. Of course, this has to be the first action on successive file reads.
- *
- * @throws IOException if an I/O error occurs.
- * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
- */
- private void readID() throws IOException, ClassFormatException {
- if (dataInputStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
- throw new ClassFormatException(fileName + " is not a Java .class file");
- }
- }
- /**
- * Reads information about the interfaces implemented by this class.
- *
- * @throws IOException if an I/O error occurs.
- * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
- */
- private void readInterfaces() throws IOException, ClassFormatException {
- final int interfacesCount = dataInputStream.readUnsignedShort();
- interfaces = new int[interfacesCount];
- for (int i = 0; i < interfacesCount; i++) {
- interfaces[i] = dataInputStream.readUnsignedShort();
- }
- }
- /**
- * Reads information about the methods of the class.
- *
- * @throws IOException if an I/O error occurs.
- * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
- */
- private void readMethods() throws IOException {
- final int methodsCount = dataInputStream.readUnsignedShort();
- methods = new Method[methodsCount];
- for (int i = 0; i < methodsCount; i++) {
- methods[i] = new Method(dataInputStream, constantPool);
- }
- }
- /**
- * Reads major and minor version of compiler which created the file.
- *
- * @throws IOException if an I/O error occurs.
- * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
- */
- private void readVersion() throws IOException, ClassFormatException {
- minor = dataInputStream.readUnsignedShort();
- major = dataInputStream.readUnsignedShort();
- }
- }