ClassParser.java

  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.bcel.classfile;

  18. import java.io.BufferedInputStream;
  19. import java.io.DataInputStream;
  20. import java.io.FileInputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.util.zip.ZipEntry;
  24. import java.util.zip.ZipFile;

  25. import org.apache.bcel.Const;

  26. /**
  27.  * Wrapper class that parses a given Java .class file. The method <a href ="#parse">parse</a> returns a
  28.  * <a href ="JavaClass.html"> JavaClass</a> object on success. When an I/O error or an inconsistency occurs an
  29.  * appropriate exception is propagated back to the caller.
  30.  *
  31.  * The structure and the names comply, except for a few conveniences, exactly with the
  32.  * <a href="https://docs.oracle.com/javase/specs/"> JVM specification 1.0</a>. See this paper for further details about
  33.  * the structure of a bytecode file.
  34.  */
  35. public final class ClassParser {

  36.     private static final int BUFSIZE = 8192;
  37.     private DataInputStream dataInputStream;
  38.     private final boolean fileOwned;
  39.     private final String fileName;
  40.     private String zipFile;
  41.     private int classNameIndex;
  42.     private int superclassNameIndex;
  43.     private int major; // Compiler version
  44.     private int minor; // Compiler version
  45.     private int accessFlags; // Access rights of parsed class
  46.     private int[] interfaces; // Names of implemented interfaces
  47.     private ConstantPool constantPool; // collection of constants
  48.     private Field[] fields; // class fields, i.e., its variables
  49.     private Method[] methods; // methods defined in the class
  50.     private Attribute[] attributes; // attributes defined in the class
  51.     private final boolean isZip; // Loaded from ZIP file

  52.     /**
  53.      * Parses class from the given stream.
  54.      *
  55.      * @param inputStream Input stream
  56.      * @param fileName File name
  57.      */
  58.     public ClassParser(final InputStream inputStream, final String fileName) {
  59.         this.fileName = fileName;
  60.         this.fileOwned = false;
  61.         final String clazz = inputStream.getClass().getName(); // Not a very clean solution ...
  62.         this.isZip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
  63.         if (inputStream instanceof DataInputStream) {
  64.             this.dataInputStream = (DataInputStream) inputStream;
  65.         } else {
  66.             this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
  67.         }
  68.     }

  69.     /**
  70.      * Parses class from given .class file.
  71.      *
  72.      * @param fileName file name
  73.      */
  74.     public ClassParser(final String fileName) {
  75.         this.isZip = false;
  76.         this.fileName = fileName;
  77.         this.fileOwned = true;
  78.     }

  79.     /**
  80.      * Parses class from given .class file in a ZIP-archive
  81.      *
  82.      * @param zipFile ZIP file name
  83.      * @param fileName file name
  84.      */
  85.     public ClassParser(final String zipFile, final String fileName) {
  86.         this.isZip = true;
  87.         this.fileOwned = true;
  88.         this.zipFile = zipFile;
  89.         this.fileName = fileName;
  90.     }

  91.     /**
  92.      * Parses the given Java class file and return an object that represents the contained data, i.e., constants, methods,
  93.      * fields and commands. A <em>ClassFormatException</em> is raised, if the file is not a valid .class file. (This does
  94.      * not include verification of the byte code as it is performed by the Java interpreter).
  95.      *
  96.      * @return Class object representing the parsed class file
  97.      * @throws IOException if an I/O error occurs.
  98.      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
  99.      */
  100.     public JavaClass parse() throws IOException, ClassFormatException {
  101.         ZipFile zip = null;
  102.         try {
  103.             if (fileOwned) {
  104.                 if (isZip) {
  105.                     zip = new ZipFile(zipFile);
  106.                     final ZipEntry entry = zip.getEntry(fileName);

  107.                     if (entry == null) {
  108.                         throw new IOException("File " + fileName + " not found");
  109.                     }

  110.                     dataInputStream = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry), BUFSIZE));
  111.                 } else {
  112.                     dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName), BUFSIZE));
  113.                 }
  114.             }
  115.             /****************** Read headers ********************************/
  116.             // Check magic tag of class file
  117.             readID();
  118.             // Get compiler version
  119.             readVersion();
  120.             /****************** Read constant pool and related **************/
  121.             // Read constant pool entries
  122.             readConstantPool();
  123.             // Get class information
  124.             readClassInfo();
  125.             // Get interface information, i.e., implemented interfaces
  126.             readInterfaces();
  127.             /****************** Read class fields and methods ***************/
  128.             // Read class fields, i.e., the variables of the class
  129.             readFields();
  130.             // Read class methods, i.e., the functions in the class
  131.             readMethods();
  132.             // Read class attributes
  133.             readAttributes();
  134.             // Check for unknown variables
  135.             // Unknown[] u = Unknown.getUnknownAttributes();
  136.             // for (int i=0; i < u.length; i++)
  137.             // System.err.println("WARNING: " + u[i]);
  138.             // Everything should have been read now
  139.             // if (file.available() > 0) {
  140.             // int bytes = file.available();
  141.             // byte[] buf = new byte[bytes];
  142.             // file.read(buf);
  143.             // if (!(isZip && (buf.length == 1))) {
  144.             // System.err.println("WARNING: Trailing garbage at end of " + fileName);
  145.             // System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf));
  146.             // }
  147.             // }
  148.         } finally {
  149.             // Read everything of interest, so close the file
  150.             if (fileOwned) {
  151.                 try {
  152.                     if (dataInputStream != null) {
  153.                         dataInputStream.close();
  154.                     }
  155.                 } catch (final IOException ignored) {
  156.                     // ignore close exceptions
  157.                 }
  158.             }
  159.             try {
  160.                 if (zip != null) {
  161.                     zip.close();
  162.                 }
  163.             } catch (final IOException ignored) {
  164.                 // ignore close exceptions
  165.             }
  166.         }
  167.         // Return the information we have gathered in a new object
  168.         return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, accessFlags, constantPool, interfaces, fields, methods, attributes,
  169.             isZip ? JavaClass.ZIP : JavaClass.FILE);
  170.     }

  171.     /**
  172.      * Reads information about the attributes of the class.
  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 readAttributes() throws IOException, ClassFormatException {
  178.         final int attributesCount = dataInputStream.readUnsignedShort();
  179.         attributes = new Attribute[attributesCount];
  180.         for (int i = 0; i < attributesCount; i++) {
  181.             attributes[i] = Attribute.readAttribute(dataInputStream, constantPool);
  182.         }
  183.     }

  184.     /**
  185.      * Reads information about the class and its super 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 readClassInfo() throws IOException, ClassFormatException {
  191.         accessFlags = dataInputStream.readUnsignedShort();
  192.         /*
  193.          * Interfaces are implicitly abstract, the flag should be set according to the JVM specification.
  194.          */
  195.         if ((accessFlags & Const.ACC_INTERFACE) != 0) {
  196.             accessFlags |= Const.ACC_ABSTRACT;
  197.         }
  198.         if ((accessFlags & Const.ACC_ABSTRACT) != 0 && (accessFlags & Const.ACC_FINAL) != 0) {
  199.             throw new ClassFormatException("Class " + fileName + " can't be both final and abstract");
  200.         }
  201.         classNameIndex = dataInputStream.readUnsignedShort();
  202.         superclassNameIndex = dataInputStream.readUnsignedShort();
  203.     }

  204.     /**
  205.      * Reads constant pool entries.
  206.      *
  207.      * @throws IOException if an I/O error occurs.
  208.      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
  209.      */
  210.     private void readConstantPool() throws IOException, ClassFormatException {
  211.         constantPool = new ConstantPool(dataInputStream);
  212.     }

  213.     /**
  214.      * Reads information about the fields of the class, i.e., its variables.
  215.      *
  216.      * @throws IOException if an I/O error occurs.
  217.      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
  218.      */
  219.     private void readFields() throws IOException, ClassFormatException {
  220.         final int fieldsCount = dataInputStream.readUnsignedShort();
  221.         fields = new Field[fieldsCount];
  222.         for (int i = 0; i < fieldsCount; i++) {
  223.             fields[i] = new Field(dataInputStream, constantPool);
  224.         }
  225.     }

  226.     /******************** Private utility methods **********************/
  227.     /**
  228.      * Checks whether the header of the file is ok. Of course, this has to be the first action on successive file reads.
  229.      *
  230.      * @throws IOException if an I/O error occurs.
  231.      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
  232.      */
  233.     private void readID() throws IOException, ClassFormatException {
  234.         if (dataInputStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
  235.             throw new ClassFormatException(fileName + " is not a Java .class file");
  236.         }
  237.     }

  238.     /**
  239.      * Reads information about the interfaces implemented by this class.
  240.      *
  241.      * @throws IOException if an I/O error occurs.
  242.      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
  243.      */
  244.     private void readInterfaces() throws IOException, ClassFormatException {
  245.         final int interfacesCount = dataInputStream.readUnsignedShort();
  246.         interfaces = new int[interfacesCount];
  247.         for (int i = 0; i < interfacesCount; i++) {
  248.             interfaces[i] = dataInputStream.readUnsignedShort();
  249.         }
  250.     }

  251.     /**
  252.      * Reads information about the methods of the class.
  253.      *
  254.      * @throws IOException if an I/O error occurs.
  255.      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
  256.      */
  257.     private void readMethods() throws IOException {
  258.         final int methodsCount = dataInputStream.readUnsignedShort();
  259.         methods = new Method[methodsCount];
  260.         for (int i = 0; i < methodsCount; i++) {
  261.             methods[i] = new Method(dataInputStream, constantPool);
  262.         }
  263.     }

  264.     /**
  265.      * Reads major and minor version of compiler which created the file.
  266.      *
  267.      * @throws IOException if an I/O error occurs.
  268.      * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file
  269.      */
  270.     private void readVersion() throws IOException, ClassFormatException {
  271.         minor = dataInputStream.readUnsignedShort();
  272.         major = dataInputStream.readUnsignedShort();
  273.     }
  274. }