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 * http://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 */ 019 020package org.apache.bytecode; 021 022 023import java.io.ByteArrayInputStream; 024import java.io.ByteArrayOutputStream; 025import java.io.EOFException; 026import java.io.IOException; 027import java.io.InputStream; 028import java.lang.reflect.Constructor; 029import java.lang.reflect.Field; 030import java.lang.reflect.InvocationTargetException; 031import java.lang.reflect.Member; 032import java.lang.reflect.Method; 033import java.util.HashMap; 034import java.util.Map; 035 036 037/** 038 * This is the class file reader for obtaining the parameter names 039 * for declared methods in a class. The class must have debugging 040 * attributes for us to obtain this information. <p> 041 * <p/> 042 * This does not work for inherited methods. To obtain parameter 043 * names for inherited methods, you must use a paramReader for the 044 * class that originally declared the method. <p> 045 * <p/> 046 * don't get tricky, it's the bare minimum. Instances of this class 047 * are not threadsafe -- don't share them. <p> 048 */ 049public class ClassReader extends ByteArrayInputStream { 050 // constants values that appear in java class files, 051 // from jvm spec 2nd ed, section 4.4, pp 103 052 private static final int CONSTANT_Class = 7; 053 private static final int CONSTANT_Fieldref = 9; 054 private static final int CONSTANT_Methodref = 10; 055 private static final int CONSTANT_InterfaceMethodref = 11; 056 private static final int CONSTANT_String = 8; 057 private static final int CONSTANT_Integer = 3; 058 private static final int CONSTANT_Float = 4; 059 private static final int CONSTANT_Long = 5; 060 private static final int CONSTANT_Double = 6; 061 private static final int CONSTANT_NameAndType = 12; 062 private static final int CONSTANT_Utf8 = 1; 063 /** 064 * the constant pool. constant pool indices in the class file 065 * directly index into this array. The value stored in this array 066 * is the position in the class file where that constant begins. 067 */ 068 private int[] cpoolIndex; 069 private Object[] cpool; 070 071 private Map attrMethods; 072 073 /** 074 * Loads the bytecode for a given class, by using the class's defining 075 * classloader and assuming that for a class named P.C, the bytecodes are 076 * in a resource named /P/C.class. 077 * 078 * @param c the class of interest 079 * @return Returns a byte array containing the bytecode 080 * @throws IOException 081 */ 082 protected static byte[] getBytes(Class c) throws IOException { 083 InputStream fin = c.getResourceAsStream('/' + c.getName().replace('.', '/') + ".class"); 084 if (fin == null) { 085 throw new IOException("Unable to load bytecode for class " + c.getName() 086 ); 087 } 088 try { 089 ByteArrayOutputStream out = new ByteArrayOutputStream(); 090 byte[] buf = new byte[1024]; 091 int actual; 092 do { 093 actual = fin.read(buf); 094 if (actual > 0) { 095 out.write(buf, 0, actual); 096 } 097 } while (actual > 0); 098 return out.toByteArray(); 099 } finally { 100 fin.close(); 101 } 102 } 103 104 static String classDescriptorToName(String desc) { 105 return desc.replace('/', '.'); 106 } 107 108 protected static Map findAttributeReaders(Class c) { 109 HashMap map = new HashMap(); 110 Method[] methods = c.getMethods(); 111 112 for (int i = 0; i < methods.length; i++) { 113 String name = methods[i].getName(); 114 if (name.startsWith("read") && methods[i].getReturnType() == void.class) { 115 map.put(name.substring(4), methods[i]); 116 } 117 } 118 119 return map; 120 } 121 122 123 protected static String getSignature(Member method, Class[] paramTypes) { 124 // compute the method descriptor 125 126 StringBuffer b = new StringBuffer((method instanceof Method) ? method.getName() : "<init>"); 127 b.append('('); 128 129 for (int i = 0; i < paramTypes.length; i++) { 130 addDescriptor(b, paramTypes[i]); 131 } 132 133 b.append(')'); 134 if (method instanceof Method) { 135 addDescriptor(b, ((Method) method).getReturnType()); 136 } else if (method instanceof Constructor) { 137 addDescriptor(b, void.class); 138 } 139 140 return b.toString(); 141 } 142 143 private static void addDescriptor(StringBuffer b, Class c) { 144 if (c.isPrimitive()) { 145 if (c == void.class) 146 b.append('V'); 147 else if (c == int.class) 148 b.append('I'); 149 else if (c == boolean.class) 150 b.append('Z'); 151 else if (c == byte.class) 152 b.append('B'); 153 else if (c == short.class) 154 b.append('S'); 155 else if (c == long.class) 156 b.append('J'); 157 else if (c == char.class) 158 b.append('C'); 159 else if (c == float.class) 160 b.append('F'); 161 else if (c == double.class) b.append('D'); 162 } else if (c.isArray()) { 163 b.append('['); 164 addDescriptor(b, c.getComponentType()); 165 } else { 166 b.append('L').append(c.getName().replace('.', '/')).append(';'); 167 } 168 } 169 170 171 /** 172 * @return Returns the next unsigned 16 bit value. 173 */ 174 protected final int readShort() { 175 return (read() << 8) | read(); 176 } 177 178 /** 179 * @return Returns the next signed 32 bit value. 180 */ 181 protected final int readInt() { 182 return (read() << 24) | (read() << 16) | (read() << 8) | read(); 183 } 184 185 /** 186 * Skips n bytes in the input stream. 187 */ 188 protected void skipFully(int n) throws IOException { 189 while (n > 0) { 190 int c = (int) skip(n); 191 if (c <= 0) 192 throw new EOFException("Error looking for paramter names in bytecode: unexpected end of file"); 193 n -= c; 194 } 195 } 196 197 protected final Member resolveMethod(int index) throws IOException, ClassNotFoundException, NoSuchMethodException { 198 int oldPos = pos; 199 try { 200 Member m = (Member) cpool[index]; 201 if (m == null) { 202 pos = cpoolIndex[index]; 203 Class owner = resolveClass(readShort()); 204 NameAndType nt = resolveNameAndType(readShort()); 205 String signature = nt.name + nt.type; 206 if (nt.name.equals("<init>")) { 207 Constructor[] ctors = owner.getConstructors(); 208 for (int i = 0; i < ctors.length; i++) { 209 String sig = getSignature(ctors[i], ctors[i].getParameterTypes()); 210 if (sig.equals(signature)) { 211 cpool[index] = m = ctors[i]; 212 return m; 213 } 214 } 215 } else { 216 Method[] methods = owner.getDeclaredMethods(); 217 for (int i = 0; i < methods.length; i++) { 218 String sig = getSignature(methods[i], methods[i].getParameterTypes()); 219 if (sig.equals(signature)) { 220 cpool[index] = m = methods[i]; 221 return m; 222 } 223 } 224 } 225 throw new NoSuchMethodException(signature); 226 } 227 return m; 228 } finally { 229 pos = oldPos; 230 } 231 232 } 233 234 protected final Field resolveField(int i) throws IOException, ClassNotFoundException, NoSuchFieldException { 235 int oldPos = pos; 236 try { 237 Field f = (Field) cpool[i]; 238 if (f == null) { 239 pos = cpoolIndex[i]; 240 Class owner = resolveClass(readShort()); 241 NameAndType nt = resolveNameAndType(readShort()); 242 cpool[i] = f = owner.getDeclaredField(nt.name); 243 } 244 return f; 245 } finally { 246 pos = oldPos; 247 } 248 } 249 250 private static class NameAndType { 251 String name; 252 String type; 253 254 public NameAndType(String name, String type) { 255 this.name = name; 256 this.type = type; 257 } 258 } 259 260 protected final NameAndType resolveNameAndType(int i) throws IOException { 261 int oldPos = pos; 262 try { 263 NameAndType nt = (NameAndType) cpool[i]; 264 if (nt == null) { 265 pos = cpoolIndex[i]; 266 String name = resolveUtf8(readShort()); 267 String type = resolveUtf8(readShort()); 268 cpool[i] = nt = new NameAndType(name, type); 269 } 270 return nt; 271 } finally { 272 pos = oldPos; 273 } 274 } 275 276 277 protected final Class resolveClass(int i) throws IOException, ClassNotFoundException { 278 int oldPos = pos; 279 try { 280 Class c = (Class) cpool[i]; 281 if (c == null) { 282 pos = cpoolIndex[i]; 283 String name = resolveUtf8(readShort()); 284 cpool[i] = c = Class.forName(classDescriptorToName(name)); 285 } 286 return c; 287 } finally { 288 pos = oldPos; 289 } 290 } 291 292 protected final String resolveUtf8(int i) throws IOException { 293 int oldPos = pos; 294 try { 295 String s = (String) cpool[i]; 296 if (s == null) { 297 pos = cpoolIndex[i]; 298 int len = readShort(); 299 skipFully(len); 300 cpool[i] = s = new String(buf, pos - len, len, "utf-8"); 301 } 302 return s; 303 } finally { 304 pos = oldPos; 305 } 306 } 307 308 protected final void readCpool() throws IOException { 309 int count = readShort(); // cpool count 310 cpoolIndex = new int[count]; 311 cpool = new Object[count]; 312 for (int i = 1; i < count; i++) { 313 int c = read(); 314 cpoolIndex[i] = super.pos; 315 switch (c) // constant pool tag 316 { 317 case CONSTANT_Fieldref: 318 case CONSTANT_Methodref: 319 case CONSTANT_InterfaceMethodref: 320 case CONSTANT_NameAndType: 321 322 readShort(); // class index or (12) name index 323 // fall through 324 325 case CONSTANT_Class: 326 case CONSTANT_String: 327 328 readShort(); // string index or class index 329 break; 330 331 case CONSTANT_Long: 332 case CONSTANT_Double: 333 334 readInt(); // hi-value 335 336 // see jvm spec section 4.4.5 - double and long cpool 337 // entries occupy two "slots" in the cpool table. 338 i++; 339 // fall through 340 341 case CONSTANT_Integer: 342 case CONSTANT_Float: 343 344 readInt(); // value 345 break; 346 347 case CONSTANT_Utf8: 348 349 int len = readShort(); 350 skipFully(len); 351 break; 352 353 default: 354 // corrupt class file 355 throw new IllegalStateException("Error looking for paramter names in bytecode: unexpected bytes in file"); 356 } 357 } 358 } 359 360 protected final void skipAttributes() throws IOException { 361 int count = readShort(); 362 for (int i = 0; i < count; i++) { 363 readShort(); // name index 364 skipFully(readInt()); 365 } 366 } 367 368 /** 369 * Reads an attributes array. The elements of a class file that 370 * can contain attributes are: fields, methods, the class itself, 371 * and some other types of attributes. 372 */ 373 protected final void readAttributes() throws IOException { 374 int count = readShort(); 375 for (int i = 0; i < count; i++) { 376 int nameIndex = readShort(); // name index 377 int attrLen = readInt(); 378 int curPos = pos; 379 380 String attrName = resolveUtf8(nameIndex); 381 382 Method m = (Method) attrMethods.get(attrName); 383 384 if (m != null) { 385 try { 386 m.invoke(this, new Object[]{}); 387 } catch (IllegalAccessException e) { 388 pos = curPos; 389 skipFully(attrLen); 390 } catch (InvocationTargetException e) { 391 try { 392 throw e.getTargetException(); 393 } catch (Error ex) { 394 throw ex; 395 } catch (RuntimeException ex) { 396 throw ex; 397 } catch (IOException ex) { 398 throw ex; 399 } catch (Throwable ex) { 400 pos = curPos; 401 skipFully(attrLen); 402 } 403 } 404 } else { 405 // don't care what attribute this is 406 skipFully(attrLen); 407 } 408 } 409 } 410 411 /** 412 * Reads a code attribute. 413 * 414 * @throws IOException 415 */ 416 public void readCode() throws IOException { 417 readShort(); // max stack 418 readShort(); // max locals 419 skipFully(readInt()); // code 420 skipFully(8 * readShort()); // exception table 421 422 // read the code attributes (recursive). This is where 423 // we will find the LocalVariableTable attribute. 424 readAttributes(); 425 } 426 427 protected ClassReader(byte buf[], Map attrMethods) { 428 super(buf); 429 430 this.attrMethods = attrMethods; 431 } 432} 433