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}