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}