| package org.apache.bcel.classfile; |
| |
| /* ==================================================================== |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2001 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Apache" and "Apache Software Foundation" and |
| * "Apache BCEL" must not be used to endorse or promote products |
| * derived from this software without prior written permission. For |
| * written permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * "Apache BCEL", nor may "Apache" appear in their name, without |
| * prior written permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| import org.apache.bcel.Constants; |
| import org.apache.bcel.util.ByteSequence; |
| import java.io.*; |
| import java.util.ArrayList; |
| import java.util.zip.*; |
| |
| /** |
| * Utility functions that do not really belong to any class in particular. |
| * |
| * @version $Id$ |
| * @author <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A> |
| */ |
| public abstract class Utility { |
| private static int consumed_chars; /* How many chars have been consumed |
| * during parsing in signatureToString(). |
| * Read by methodSignatureToString(). |
| * Set by side effect,but only internally. |
| */ |
| private static boolean wide=false; /* The `WIDE' instruction is used in the |
| * byte code to allow 16-bit wide indices |
| * for local variables. This opcode |
| * precedes an `ILOAD', e.g.. The opcode |
| * immediately following takes an extra |
| * byte which is combined with the |
| * following byte to form a |
| * 16-bit value. |
| */ |
| /** |
| * Convert bit field of flags into string such as `static final'. |
| * |
| * @param access_flags Access flags |
| * @return String representation of flags |
| */ |
| public static final String accessToString(int access_flags) { |
| return accessToString(access_flags, false); |
| } |
| |
| /** |
| * Convert bit field of flags into string such as `static final'. |
| * |
| * Special case: Classes compiled with new compilers and with the |
| * `ACC_SUPER' flag would be said to be "synchronized". This is |
| * because SUN used the same value for the flags `ACC_SUPER' and |
| * `ACC_SYNCHRONIZED'. |
| * |
| * @param access_flags Access flags |
| * @param for_class access flags are for class qualifiers ? |
| * @return String representation of flags |
| */ |
| public static final String accessToString(int access_flags, |
| boolean for_class) |
| { |
| StringBuffer buf = new StringBuffer(); |
| |
| int p = 0; |
| for(int i=0; p < Constants.MAX_ACC_FLAG; i++) { // Loop through known flags |
| p = pow2(i); |
| |
| if((access_flags & p) != 0) { |
| /* Special case: Classes compiled with new compilers and with the |
| * `ACC_SUPER' flag would be said to be "synchronized". This is |
| * because SUN used the same value for the flags `ACC_SUPER' and |
| * `ACC_SYNCHRONIZED'. |
| */ |
| if(for_class && ((p == Constants.ACC_SUPER) || (p == Constants.ACC_INTERFACE))) |
| continue; |
| |
| buf.append(Constants.ACCESS_NAMES[i] + " "); |
| } |
| } |
| |
| return buf.toString().trim(); |
| } |
| |
| /** |
| * @return "class" or "interface", depending on the ACC_INTERFACE flag |
| */ |
| public static final String classOrInterface(int access_flags) { |
| return ((access_flags & Constants.ACC_INTERFACE) != 0)? "interface" : "class"; |
| } |
| |
| /** |
| * Disassemble a byte array of JVM byte codes starting from code line |
| * `index' and return the disassembled string representation. Decode only |
| * `num' opcodes (including their operands), use -1 if you want to |
| * decompile everything. |
| * |
| * @param code byte code array |
| * @param constant_pool Array of constants |
| * @param index offset in `code' array |
| * <EM>(number of opcodes, not bytes!)</EM> |
| * @param length number of opcodes to decompile, -1 for all |
| * @param verbose be verbose, e.g. print constant pool index |
| * @return String representation of byte codes |
| */ |
| public static final String codeToString(byte[] code, |
| ConstantPool constant_pool, |
| int index, int length, boolean verbose) |
| { |
| StringBuffer buf = new StringBuffer(code.length * 20); // Should be sufficient |
| ByteSequence stream = new ByteSequence(code); |
| |
| try { |
| for(int i=0; i < index; i++) // Skip `index' lines of code |
| codeToString(stream, constant_pool, verbose); |
| |
| for(int i=0; stream.available() > 0; i++) { |
| if((length < 0) || (i < length)) { |
| String indices = fillup(stream.getIndex() + ":", 6, true, ' '); |
| buf.append(indices + codeToString(stream, constant_pool, verbose) + '\n'); |
| } |
| } |
| } catch(IOException e) { |
| System.out.println(buf.toString()); |
| e.printStackTrace(); |
| throw new ClassFormatError("Byte code error: " + e); |
| } |
| |
| return buf.toString(); |
| } |
| |
| public static final String codeToString(byte[] code, |
| ConstantPool constant_pool, |
| int index, int length) { |
| return codeToString(code, constant_pool, index, length, true); |
| } |
| |
| /** |
| * Disassemble a stream of byte codes and return the |
| * string representation. |
| * |
| * @param bytes stream of bytes |
| * @param constant_pool Array of constants |
| * @param verbose be verbose, e.g. print constant pool index |
| * @return String representation of byte code |
| */ |
| public static final String codeToString(ByteSequence bytes, |
| ConstantPool constant_pool, boolean verbose) |
| throws IOException |
| { |
| short opcode = (short)bytes.readUnsignedByte(); |
| int default_offset=0, low, high, npairs; |
| int index, vindex, constant; |
| int[] match, jump_table; |
| int no_pad_bytes=0, offset; |
| StringBuffer buf = new StringBuffer(Constants.OPCODE_NAMES[opcode]); |
| |
| /* Special case: Skip (0-3) padding bytes, i.e., the |
| * following bytes are 4-byte-aligned |
| */ |
| if((opcode == Constants.TABLESWITCH) || (opcode == Constants.LOOKUPSWITCH)) { |
| int remainder = bytes.getIndex() % 4; |
| no_pad_bytes = (remainder == 0)? 0 : 4 - remainder; |
| |
| for(int i=0; i < no_pad_bytes; i++) { |
| byte b; |
| |
| if((b=bytes.readByte()) != 0) |
| System.err.println("Warning: Padding byte != 0 in " + |
| Constants.OPCODE_NAMES[opcode] + ":" + b); |
| } |
| |
| // Both cases have a field default_offset in common |
| default_offset = bytes.readInt(); |
| } |
| |
| switch(opcode) { |
| /* Table switch has variable length arguments. |
| */ |
| case Constants.TABLESWITCH: |
| low = bytes.readInt(); |
| high = bytes.readInt(); |
| |
| offset = bytes.getIndex() - 12 - no_pad_bytes - 1; |
| default_offset += offset; |
| |
| buf.append("\tdefault = " + default_offset + ", low = " + low + |
| ", high = " + high + "("); |
| |
| jump_table = new int[high - low + 1]; |
| for(int i=0; i < jump_table.length; i++) { |
| jump_table[i] = offset + bytes.readInt(); |
| buf.append(jump_table[i]); |
| |
| if(i < jump_table.length - 1) |
| buf.append(", "); |
| } |
| buf.append(")"); |
| |
| break; |
| |
| /* Lookup switch has variable length arguments. |
| */ |
| case Constants.LOOKUPSWITCH: { |
| |
| npairs = bytes.readInt(); |
| offset = bytes.getIndex() - 8 - no_pad_bytes - 1; |
| |
| match = new int[npairs]; |
| jump_table = new int[npairs]; |
| default_offset += offset; |
| |
| buf.append("\tdefault = " + default_offset + ", npairs = " + npairs + |
| " ("); |
| |
| for(int i=0; i < npairs; i++) { |
| match[i] = bytes.readInt(); |
| |
| jump_table[i] = offset + bytes.readInt(); |
| |
| buf.append("(" + match[i] + ", " + jump_table[i] + ")"); |
| |
| if(i < npairs - 1) |
| buf.append(", "); |
| } |
| buf.append(")"); |
| } |
| break; |
| |
| /* Two address bytes + offset from start of byte stream form the |
| * jump target |
| */ |
| case Constants.GOTO: case Constants.IFEQ: case Constants.IFGE: case Constants.IFGT: |
| case Constants.IFLE: case Constants.IFLT: case Constants.JSR: case Constants.IFNE: |
| case Constants.IFNONNULL: case Constants.IFNULL: case Constants.IF_ACMPEQ: |
| case Constants.IF_ACMPNE: case Constants.IF_ICMPEQ: case Constants.IF_ICMPGE: case Constants.IF_ICMPGT: |
| case Constants.IF_ICMPLE: case Constants.IF_ICMPLT: case Constants.IF_ICMPNE: |
| buf.append("\t\t#" + ((bytes.getIndex() - 1) + bytes.readShort())); |
| break; |
| |
| /* 32-bit wide jumps |
| */ |
| case Constants.GOTO_W: case Constants.JSR_W: |
| buf.append("\t\t#" + ((bytes.getIndex() - 1) + bytes.readInt())); |
| break; |
| |
| /* Index byte references local variable (register) |
| */ |
| case Constants.ALOAD: case Constants.ASTORE: case Constants.DLOAD: case Constants.DSTORE: case Constants.FLOAD: |
| case Constants.FSTORE: case Constants.ILOAD: case Constants.ISTORE: case Constants.LLOAD: case Constants.LSTORE: |
| case Constants.RET: |
| if(wide) { |
| vindex = bytes.readUnsignedShort(); |
| wide=false; // Clear flag |
| } |
| else |
| vindex = bytes.readUnsignedByte(); |
| |
| buf.append("\t\t%" + vindex); |
| break; |
| |
| /* |
| * Remember wide byte which is used to form a 16-bit address in the |
| * following instruction. Relies on that the method is called again with |
| * the following opcode. |
| */ |
| case Constants.WIDE: |
| wide = true; |
| buf.append("\t(wide)"); |
| break; |
| |
| /* Array of basic type. |
| */ |
| case Constants.NEWARRAY: |
| buf.append("\t\t<" + Constants.TYPE_NAMES[bytes.readByte()] + ">"); |
| break; |
| |
| /* Access object/class fields. |
| */ |
| case Constants.GETFIELD: case Constants.GETSTATIC: case Constants.PUTFIELD: case Constants.PUTSTATIC: |
| index = bytes.readUnsignedShort(); |
| buf.append("\t\t" + |
| constant_pool.constantToString(index, Constants.CONSTANT_Fieldref) + |
| (verbose? " (" + index + ")" : "")); |
| break; |
| |
| /* Operands are references to classes in constant pool |
| */ |
| case Constants.NEW: |
| case Constants.CHECKCAST: |
| buf.append("\t"); |
| case Constants.INSTANCEOF: |
| index = bytes.readUnsignedShort(); |
| buf.append("\t<" + constant_pool.constantToString(index, |
| Constants.CONSTANT_Class) + |
| ">" + (verbose? " (" + index + ")" : "")); |
| break; |
| |
| /* Operands are references to methods in constant pool |
| */ |
| case Constants.INVOKESPECIAL: case Constants.INVOKESTATIC: case Constants.INVOKEVIRTUAL: |
| index = bytes.readUnsignedShort(); |
| buf.append("\t" + constant_pool.constantToString(index, |
| Constants.CONSTANT_Methodref) + |
| (verbose? " (" + index + ")" : "")); |
| break; |
| |
| case Constants.INVOKEINTERFACE: |
| index = bytes.readUnsignedShort(); |
| int nargs = bytes.readUnsignedByte(); // historical, redundant |
| buf.append("\t" + |
| constant_pool.constantToString(index, |
| Constants.CONSTANT_InterfaceMethodref) + |
| (verbose? " (" + index + ")\t" : "") + nargs + "\t" + |
| bytes.readUnsignedByte()); // Last byte is a reserved space |
| break; |
| |
| /* Operands are references to items in constant pool |
| */ |
| case Constants.LDC_W: case Constants.LDC2_W: |
| index = bytes.readUnsignedShort(); |
| |
| buf.append("\t\t" + constant_pool.constantToString |
| (index, constant_pool.getConstant(index).getTag()) + |
| (verbose? " (" + index + ")" : "")); |
| break; |
| |
| case Constants.LDC: |
| index = bytes.readUnsignedByte(); |
| |
| buf.append("\t\t" + |
| constant_pool.constantToString |
| (index, constant_pool.getConstant(index).getTag()) + |
| (verbose? " (" + index + ")" : "")); |
| break; |
| |
| /* Array of references. |
| */ |
| case Constants.ANEWARRAY: |
| index = bytes.readUnsignedShort(); |
| |
| buf.append("\t\t<" + compactClassName(constant_pool.getConstantString |
| (index, Constants.CONSTANT_Class), false) + |
| ">" + (verbose? " (" + index + ")": "")); |
| break; |
| |
| /* Multidimensional array of references. |
| */ |
| case Constants.MULTIANEWARRAY: { |
| index = bytes.readUnsignedShort(); |
| int dimensions = bytes.readUnsignedByte(); |
| |
| buf.append("\t<" + compactClassName(constant_pool.getConstantString |
| (index, Constants.CONSTANT_Class), false) + |
| ">\t" + dimensions + (verbose? " (" + index + ")" : "")); |
| } |
| break; |
| |
| /* Increment local variable. |
| */ |
| case Constants.IINC: |
| if(wide) { |
| vindex = bytes.readUnsignedShort(); |
| constant = bytes.readShort(); |
| wide = false; |
| } |
| else { |
| vindex = bytes.readUnsignedByte(); |
| constant = bytes.readByte(); |
| } |
| buf.append("\t\t%" + vindex + "\t" + constant); |
| break; |
| |
| default: |
| if(Constants.NO_OF_OPERANDS[opcode] > 0) { |
| for(int i=0; i < Constants.TYPE_OF_OPERANDS[opcode].length; i++) { |
| buf.append("\t\t"); |
| switch(Constants.TYPE_OF_OPERANDS[opcode][i]) { |
| case Constants.T_BYTE: buf.append(bytes.readByte()); break; |
| case Constants.T_SHORT: buf.append(bytes.readShort()); break; |
| case Constants.T_INT: buf.append(bytes.readInt()); break; |
| |
| default: // Never reached |
| System.err.println("Unreachable default case reached!"); |
| System.exit(-1); |
| } |
| } |
| } |
| } |
| |
| return buf.toString(); |
| } |
| |
| public static final String codeToString(ByteSequence bytes, ConstantPool constant_pool) |
| throws IOException |
| { |
| return codeToString(bytes, constant_pool, true); |
| } |
| |
| /** |
| * Shorten long class names, <em>java/lang/String</em> becomes |
| * <em>String</em>. |
| * |
| * @param str The long class name |
| * @return Compacted class name |
| */ |
| public static final String compactClassName(String str) { |
| return compactClassName(str, true); |
| } |
| |
| /** |
| * Shorten long class name <em>str</em>, i.e., chop off the <em>prefix</em>, |
| * if the |
| * class name starts with this string and the flag <em>chopit</em> is true. |
| * Slashes <em>/</em> are converted to dots <em>.</em>. |
| * |
| * @param str The long class name |
| * @param prefix The prefix the get rid off |
| * @param chopit Flag that determines whether chopping is executed or not |
| * @return Compacted class name |
| */ |
| public static final String compactClassName(String str, |
| String prefix, |
| boolean chopit) |
| { |
| int len = prefix.length(); |
| |
| str = str.replace('/', '.'); // Is `/' on all systems, even DOS |
| |
| if(chopit) { |
| // If string starts with `prefix' and contains no further dots |
| if(str.startsWith(prefix) && |
| (str.substring(len).indexOf('.') == -1)) |
| str = str.substring(len); |
| } |
| |
| return str; |
| } |
| |
| /** |
| * Shorten long class names, <em>java/lang/String</em> becomes |
| * <em>java.lang.String</em>, |
| * e.g.. If <em>chopit</em> is <em>true</em> the prefix <em>java.lang</em> |
| * is also removed. |
| * |
| * @param str The long class name |
| * @param chopit Flag that determines whether chopping is executed or not |
| * @return Compacted class name |
| */ |
| public static final String compactClassName(String str, boolean chopit) { |
| return compactClassName(str, "java.lang.", chopit); |
| } |
| |
| private static final boolean is_digit(char ch) { |
| return (ch >= '0') && (ch <= '9'); |
| } |
| |
| private static final boolean is_space(char ch) { |
| return (ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'); |
| } |
| |
| /** |
| * @return `flag' with bit `i' set to 1 |
| */ |
| public static final int setBit(int flag, int i) { |
| return flag | pow2(i); |
| } |
| |
| /** |
| * @return `flag' with bit `i' set to 0 |
| */ |
| public static final int clearBit(int flag, int i) { |
| int bit = pow2(i); |
| return (flag & bit) == 0? flag : flag ^ bit; |
| } |
| |
| /** |
| * @return true, if bit `i' in `flag' is set |
| */ |
| public static final boolean isSet(int flag, int i) { |
| return (flag & pow2(i)) != 0; |
| } |
| |
| /** |
| * Converts string containing the method return and argument types |
| * to a byte code method signature. |
| * |
| * @param ret Return type of method |
| * @param argv Types of method arguments |
| * @return Byte code representation of method signature |
| */ |
| public final static String methodTypeToSignature(String ret, String[] argv) |
| throws ClassFormatError |
| { |
| StringBuffer buf = new StringBuffer("("); |
| String str; |
| |
| if(argv != null) |
| for(int i=0; i < argv.length; i++) { |
| str = getSignature(argv[i]); |
| |
| if(str.endsWith("V")) // void can't be a method argument |
| throw new ClassFormatError("Invalid type: " + argv[i]); |
| |
| buf.append(str); |
| } |
| |
| str = getSignature(ret); |
| |
| buf.append(")" + str); |
| |
| return buf.toString(); |
| } |
| |
| /** |
| * @param signature Method signature |
| * @return Array of argument types |
| * @throw ClassFormatError |
| */ |
| public static final String[] methodSignatureArgumentTypes(String signature) |
| throws ClassFormatError |
| { |
| return methodSignatureArgumentTypes(signature, true); |
| } |
| |
| /** |
| * @param signature Method signature |
| * @param chopit Shorten class names ? |
| * @return Array of argument types |
| * @throw ClassFormatError |
| */ |
| public static final String[] methodSignatureArgumentTypes(String signature, |
| boolean chopit) |
| throws ClassFormatError |
| { |
| ArrayList vec = new ArrayList(); |
| int index; |
| String[] types; |
| |
| try { // Read all declarations between for `(' and `)' |
| if(signature.charAt(0) != '(') |
| throw new ClassFormatError("Invalid method signature: " + signature); |
| |
| index = 1; // current string position |
| |
| while(signature.charAt(index) != ')') { |
| vec.add(signatureToString(signature.substring(index), chopit)); |
| index += consumed_chars; // update position |
| } |
| } catch(StringIndexOutOfBoundsException e) { // Should never occur |
| throw new ClassFormatError("Invalid method signature: " + signature); |
| } |
| |
| types = new String[vec.size()]; |
| vec.toArray(types); |
| return types; |
| } |
| /** |
| * @param signature Method signature |
| * @return return type of method |
| * @throw ClassFormatError |
| */ |
| public static final String methodSignatureReturnType(String signature) |
| throws ClassFormatError |
| { |
| return methodSignatureReturnType(signature, true); |
| } |
| /** |
| * @param signature Method signature |
| * @param chopit Shorten class names ? |
| * @return return type of method |
| * @throw ClassFormatError |
| */ |
| public static final String methodSignatureReturnType(String signature, |
| boolean chopit) |
| throws ClassFormatError |
| { |
| int index; |
| String type; |
| |
| try { |
| // Read return type after `)' |
| index = signature.lastIndexOf(')') + 1; |
| type = signatureToString(signature.substring(index), chopit); |
| } catch(StringIndexOutOfBoundsException e) { // Should never occur |
| throw new ClassFormatError("Invalid method signature: " + signature); |
| } |
| |
| return type; |
| } |
| |
| /** |
| * Converts method signature to string with all class names compacted. |
| * |
| * @param signature to convert |
| * @param name of method |
| * @param access flags of method |
| * @return Human readable signature |
| */ |
| public static final String methodSignatureToString(String signature, |
| String name, |
| String access) { |
| return methodSignatureToString(signature, name, access, true); |
| } |
| |
| public static final String methodSignatureToString(String signature, |
| String name, |
| String access, |
| boolean chopit) { |
| return methodSignatureToString(signature, name, access, chopit, null); |
| } |
| |
| /** |
| * A returnÂtype signature represents the return value from a method. |
| * It is a series of bytes in the following grammar: |
| * |
| * <return_signature> ::= <field_type> | V |
| * |
| * The character V indicates that the method returns no value. Otherwise, the |
| * signature indicates the type of the return value. |
| * An argument signature represents an argument passed to a method: |
| * |
| * <argument_signature> ::= <field_type> |
| * |
| * A method signature represents the arguments that the method expects, and |
| * the value that it returns. |
| * <method_signature> ::= (<arguments_signature>) <return_signature> |
| * <arguments_signature>::= <argument_signature>* |
| * |
| * This method converts such a string into a Java type declaration like |
| * `void main(String[])' and throws a `ClassFormatError' when the parsed |
| * type is invalid. |
| * |
| * @param signature Method signature |
| * @param name Method name |
| * @param access Method access rights |
| * @return Java type declaration |
| * @throw ClassFormatError |
| */ |
| public static final String methodSignatureToString(String signature, |
| String name, |
| String access, |
| boolean chopit, |
| LocalVariableTable vars) |
| throws ClassFormatError |
| { |
| StringBuffer buf = new StringBuffer("("); |
| String type; |
| int index; |
| int var_index = (access.indexOf("static") >= 0)? 0 : 1; |
| |
| try { // Read all declarations between for `(' and `)' |
| if(signature.charAt(0) != '(') |
| throw new ClassFormatError("Invalid method signature: " + signature); |
| |
| index = 1; // current string position |
| |
| while(signature.charAt(index) != ')') { |
| buf.append(signatureToString(signature.substring(index), chopit)); |
| |
| if(vars != null) { |
| LocalVariable l = vars.getLocalVariable(var_index); |
| |
| if(l != null) |
| buf.append(" " + l.getName()); |
| } else |
| buf.append(" arg" + var_index); |
| |
| var_index++; |
| buf.append(", "); |
| index += consumed_chars; // update position |
| } |
| |
| index++; // update position |
| |
| // Read return type after `)' |
| type = signatureToString(signature.substring(index), chopit); |
| |
| } catch(StringIndexOutOfBoundsException e) { // Should never occur |
| throw new ClassFormatError("Invalid method signature: " + signature); |
| } |
| |
| if(buf.length() > 1) // Tack off the extra ", " |
| buf.setLength(buf.length() - 2); |
| |
| buf.append(")"); |
| |
| return access + ((access.length() > 0)? " " : "") + // May be an empty string |
| type + " " + name + buf.toString(); |
| } |
| |
| // Guess what this does |
| private static final int pow2(int n) { |
| return 1 << n; |
| } |
| |
| /** |
| * Replace all occurences of <em>old</em> in <em>str</em> with <em>new</em>. |
| * |
| * @param str String to permute |
| * @param old String to be replaced |
| * @param new Replacement string |
| * @return new String object |
| */ |
| public static final String replace(String str, String old, String new_) { |
| int index, old_index; |
| StringBuffer buf = new StringBuffer(); |
| |
| try { |
| if((index = str.indexOf(old)) != -1) { // `old' found in str |
| old_index = 0; // String start offset |
| |
| // While we have something to replace |
| while((index = str.indexOf(old, old_index)) != -1) { |
| buf.append(str.substring(old_index, index)); // append prefix |
| buf.append(new_); // append replacement |
| |
| old_index = index + old.length(); // Skip `old'.length chars |
| } |
| |
| buf.append(str.substring(old_index)); // append rest of string |
| str = buf.toString(); |
| } |
| } catch(StringIndexOutOfBoundsException e) { // Should not occur |
| System.err.println(e); |
| } |
| |
| return str; |
| } |
| |
| /** |
| * Converts signature to string with all class names compacted. |
| * |
| * @param signature to convert |
| * @return Human readable signature |
| */ |
| public static final String signatureToString(String signature) { |
| return signatureToString(signature, true); |
| } |
| |
| /** |
| * The field signature represents the value of an argument to a function or |
| * the value of a variable. It is a series of bytes generated by the |
| * following grammar: |
| * |
| * <PRE> |
| * <field_signature> ::= <field_type> |
| * <field_type> ::= <base_type>|<object_type>|<array_type> |
| * <base_type> ::= B|C|D|F|I|J|S|Z |
| * <object_type> ::= L<fullclassname>; |
| * <array_type> ::= [<field_type> |
| * |
| * The meaning of the base types is as follows: |
| * B byte signed byte |
| * C char character |
| * D double double precision IEEE float |
| * F float single precision IEEE float |
| * I int integer |
| * J long long integer |
| * L<fullclassname>; ... an object of the given class |
| * S short signed short |
| * Z boolean true or false |
| * [<field sig> ... array |
| * </PRE> |
| * |
| * This method converts this string into a Java type declaration such as |
| * `String[]' and throws a `ClassFormatError' when the parsed type is |
| * invalid. |
| * |
| * @param signature Class signature |
| * @param chopit Flag that determines whether chopping is executed or not |
| * @return Java type declaration |
| * @throws ClassFormatError |
| */ |
| public static final String signatureToString(String signature, |
| boolean chopit) |
| { |
| consumed_chars = 1; // This is the default, read just one char like `B' |
| |
| try { |
| switch(signature.charAt(0)) { |
| case 'B' : return "byte"; |
| case 'C' : return "char"; |
| case 'D' : return "double"; |
| case 'F' : return "float"; |
| case 'I' : return "int"; |
| case 'J' : return "long"; |
| |
| case 'L' : { // Full class name |
| int index = signature.indexOf(';'); // Look for closing `;' |
| |
| if(index < 0) |
| throw new ClassFormatError("Invalid signature: " + signature); |
| |
| consumed_chars = index + 1; // "Lblabla;" `L' and `;' are removed |
| |
| return compactClassName(signature.substring(1, index), chopit); |
| } |
| |
| case 'S' : return "short"; |
| case 'Z' : return "boolean"; |
| |
| case '[' : { // Array declaration |
| int n; |
| StringBuffer buf, brackets; |
| String type; |
| char ch; |
| int consumed_chars; // Shadows global var |
| |
| brackets = new StringBuffer(); // Accumulate []'s |
| |
| // Count opening brackets and look for optional size argument |
| for(n=0; signature.charAt(n) == '['; n++) |
| brackets.append("[]"); |
| |
| consumed_chars = n; // Remember value |
| |
| // The rest of the string denotes a `<field_type>' |
| type = signatureToString(signature.substring(n), chopit); |
| |
| Utility.consumed_chars += consumed_chars; |
| return type + brackets.toString(); |
| } |
| |
| case 'V' : return "void"; |
| |
| default : throw new ClassFormatError("Invalid signature: `" + |
| signature + "'"); |
| } |
| } catch(StringIndexOutOfBoundsException e) { // Should never occur |
| throw new ClassFormatError("Invalid signature: " + e + ":" + signature); |
| } |
| } |
| |
| /** Parse Java type such as "char", or "java.lang.String[]" and return the |
| * signature in byte code format, e.g. "C" or "[Ljava/lang/String;" respectively. |
| * |
| * @param type Java type |
| * @return byte code signature |
| */ |
| public static String getSignature(String type) { |
| StringBuffer buf = new StringBuffer(); |
| char[] chars = type.toCharArray(); |
| boolean char_found = false, delim = false; |
| int index = -1; |
| |
| loop: |
| for(int i=0; i < chars.length; i++) { |
| switch(chars[i]) { |
| case ' ': case '\t': case '\n': case '\r': case '\f': |
| if(char_found) |
| delim = true; |
| break; |
| |
| case '[': |
| if(!char_found) |
| throw new RuntimeException("Illegal type: " + type); |
| |
| index = i; |
| break loop; |
| |
| default: |
| char_found = true; |
| if(!delim) |
| buf.append(chars[i]); |
| } |
| } |
| |
| int brackets = 0; |
| |
| if(index > 0) |
| brackets = countBrackets(type.substring(index)); |
| |
| type = buf.toString(); |
| buf.setLength(0); |
| |
| for(int i=0; i < brackets; i++) |
| buf.append('['); |
| |
| boolean found = false; |
| |
| for(int i=Constants.T_BOOLEAN; (i <= Constants.T_VOID) && !found; i++) { |
| if(Constants.TYPE_NAMES[i].equals(type)) { |
| found = true; |
| buf.append(Constants.SHORT_TYPE_NAMES[i]); |
| } |
| } |
| |
| if(!found) // Class name |
| buf.append('L' + type.replace('.', '/') + ';'); |
| |
| return buf.toString(); |
| } |
| |
| private static int countBrackets(String brackets) { |
| char[] chars = brackets.toCharArray(); |
| int count = 0; |
| boolean open = false; |
| |
| for(int i=0; i<chars.length; i++) { |
| switch(chars[i]) { |
| case '[': |
| if(open) |
| throw new RuntimeException("Illegally nested brackets:" + brackets); |
| open = true; |
| break; |
| |
| case ']': |
| if(!open) |
| throw new RuntimeException("Illegally nested brackets:" + brackets); |
| open = false; |
| count++; |
| break; |
| |
| default: |
| // Don't care |
| } |
| } |
| |
| if(open) |
| throw new RuntimeException("Illegally nested brackets:" + brackets); |
| |
| return count; |
| } |
| |
| /** |
| * Return type of method signature as a byte value as defined in <em>Constants</em> |
| * |
| * @param signature in format described above |
| * @return type of method signature |
| * @see Constants |
| */ |
| public static final byte typeOfMethodSignature(String signature) |
| throws ClassFormatError |
| { |
| int index; |
| |
| try { |
| if(signature.charAt(0) != '(') |
| throw new ClassFormatError("Invalid method signature: " + signature); |
| |
| index = signature.lastIndexOf(')') + 1; |
| return typeOfSignature(signature.substring(index)); |
| } catch(StringIndexOutOfBoundsException e) { |
| throw new ClassFormatError("Invalid method signature: " + signature); |
| } |
| } |
| |
| /** |
| * Return type of signature as a byte value as defined in <em>Constants</em> |
| * |
| * @param signature in format described above |
| * @return type of signature |
| * @see Constants |
| */ |
| public static final byte typeOfSignature(String signature) |
| throws ClassFormatError |
| { |
| try { |
| switch(signature.charAt(0)) { |
| case 'B' : return Constants.T_BYTE; |
| case 'C' : return Constants.T_CHAR; |
| case 'D' : return Constants.T_DOUBLE; |
| case 'F' : return Constants.T_FLOAT; |
| case 'I' : return Constants.T_INT; |
| case 'J' : return Constants.T_LONG; |
| case 'L' : return Constants.T_REFERENCE; |
| case '[' : return Constants.T_ARRAY; |
| case 'V' : return Constants.T_VOID; |
| case 'Z' : return Constants.T_BOOLEAN; |
| case 'S' : return Constants.T_SHORT; |
| default: |
| throw new ClassFormatError("Invalid method signature: " + signature); |
| } |
| } catch(StringIndexOutOfBoundsException e) { |
| throw new ClassFormatError("Invalid method signature: " + signature); |
| } |
| } |
| |
| /** Map opcode names to opcode numbers. E.g., return Constants.ALOAD for "aload" |
| */ |
| public static short searchOpcode(String name) { |
| name = name.toLowerCase(); |
| |
| for(short i=0; i < Constants.OPCODE_NAMES.length; i++) |
| if(Constants.OPCODE_NAMES[i].equals(name)) |
| return i; |
| |
| return -1; |
| } |
| |
| /** |
| * Convert (signed) byte to (unsigned) short value, i.e., all negative |
| * values become positive. |
| */ |
| private static final short byteToShort(byte b) { |
| return (b < 0)? (short)(256 + b) : (short)b; |
| } |
| |
| /** Convert bytes into hexidecimal string |
| * |
| * @return bytes as hexidecimal string, e.g. 00 FA 12 ... |
| */ |
| public static final String toHexString(byte[] bytes) { |
| StringBuffer buf = new StringBuffer(); |
| |
| for(int i=0; i < bytes.length; i++) { |
| short b = byteToShort(bytes[i]); |
| String hex = Integer.toString(b, 0x10); |
| |
| if(b < 0x10) // just one digit, prepend '0' |
| buf.append('0'); |
| |
| buf.append(hex); |
| |
| if(i < bytes.length - 1) |
| buf.append(' '); |
| } |
| |
| return buf.toString(); |
| } |
| |
| /** |
| * Return a string for an integer justified left or right and filled up with |
| * `fill' characters if necessary. |
| * |
| * @param i integer to format |
| * @param length length of desired string |
| * @param left_justify format left or right |
| * @param fill fill character |
| * @return formatted int |
| */ |
| public static final String format(int i, int length, boolean left_justify, char fill) { |
| return fillup(Integer.toString(i), length, left_justify, fill); |
| } |
| |
| /** |
| * Fillup char with up to length characters with char `fill' and justify it left or right. |
| * |
| * @param str string to format |
| * @param length length of desired string |
| * @param left_justify format left or right |
| * @param fill fill character |
| * @return formatted string |
| */ |
| public static final String fillup(String str, int length, boolean left_justify, char fill) { |
| int len = length - str.length(); |
| char[] buf = new char[(len < 0)? 0 : len]; |
| |
| for(int j=0; j < buf.length; j++) |
| buf[j] = fill; |
| |
| if(left_justify) |
| return str + new String(buf); |
| else |
| return new String(buf) + str; |
| } |
| |
| static final boolean equals(byte[] a, byte[] b) { |
| int size; |
| |
| if((size=a.length) != b.length) |
| return false; |
| |
| for(int i=0; i < size; i++) |
| if(a[i] != b[i]) |
| return false; |
| |
| return true; |
| } |
| |
| public static final void printArray(PrintStream out, Object[] obj) { |
| out.println(printArray(obj, true)); |
| } |
| |
| public static final void printArray(PrintWriter out, Object[] obj) { |
| out.println(printArray(obj, true)); |
| } |
| |
| public static final String printArray(Object[] obj) { |
| return printArray(obj, true); |
| } |
| |
| public static final String printArray(Object[] obj, boolean braces) { |
| if(obj == null) |
| return null; |
| |
| StringBuffer buf = new StringBuffer(); |
| if(braces) |
| buf.append('{'); |
| |
| for(int i=0; i < obj.length; i++) { |
| if(obj[i] != null) |
| buf.append(obj[i].toString()); |
| else |
| buf.append("null"); |
| |
| if(i < obj.length - 1) |
| buf.append(", "); |
| } |
| |
| if(braces) |
| buf.append('}'); |
| |
| return buf.toString(); |
| } |
| |
| /** @return true, if character is one of (a, ... z, A, ... Z, 0, ... 9, _) |
| */ |
| public static boolean isJavaIdentifierPart(char ch) { |
| return ((ch >= 'a') && (ch <= 'z')) || |
| ((ch >= 'A') && (ch <= 'Z')) || |
| ((ch >= '0') && (ch <= '9')) || |
| (ch == '_'); |
| } |
| |
| /** Encode byte array it into Java identifier string, i.e., a string |
| * that only contains the following characters: (a, ... z, A, ... Z, |
| * 0, ... 9, _, $). The encoding algorithm itself is not too |
| * clever: if the current byte's ASCII value already is a valid Java |
| * identifier part, leave it as it is. Otherwise it writes the |
| * escape character($) followed by <p><ul><li> the ASCII value as a |
| * hexadecimal string, if the value is not in the range |
| * 200..247</li> <li>a Java identifier char not used in a lowercase |
| * hexadecimal string, if the value is in the range |
| * 200..247</li><ul></p> |
| * |
| * <p>This operation inflates the original byte array by roughly 40-50%</p> |
| * |
| * @param bytes the byte array to convert |
| * @param compress use gzip to minimize string |
| */ |
| public static String encode(byte[] bytes, boolean compress) throws IOException { |
| if(compress) { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| GZIPOutputStream gos = new GZIPOutputStream(baos); |
| |
| gos.write(bytes, 0, bytes.length); |
| gos.close(); |
| baos.close(); |
| |
| bytes = baos.toByteArray(); |
| } |
| |
| CharArrayWriter caw = new CharArrayWriter(); |
| JavaWriter jw = new JavaWriter(caw); |
| |
| for(int i=0; i < bytes.length; i++) { |
| int in = bytes[i] & 0x000000ff; // Normalize to unsigned |
| jw.write(in); |
| } |
| |
| return caw.toString(); |
| } |
| |
| /** Decode a string back to a byte array. |
| * |
| * @param bytes the byte array to convert |
| * @param uncompress use gzip to uncompress the stream of bytes |
| */ |
| public static byte[] decode(String s, boolean uncompress) throws IOException { |
| char[] chars = s.toCharArray(); |
| |
| CharArrayReader car = new CharArrayReader(chars); |
| JavaReader jr = new JavaReader(car); |
| |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| |
| int ch; |
| |
| while((ch = jr.read()) >= 0) { |
| bos.write(ch); |
| } |
| |
| bos.close(); |
| car.close(); |
| jr.close(); |
| |
| byte[] bytes = bos.toByteArray(); |
| |
| if(uncompress) { |
| GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(bytes)); |
| |
| byte[] tmp = new byte[bytes.length * 3]; // Rough estimate |
| int count = 0; |
| int b; |
| |
| while((b = gis.read()) >= 0) |
| tmp[count++] = (byte)b; |
| |
| bytes = new byte[count]; |
| System.arraycopy(tmp, 0, bytes, 0, count); |
| } |
| |
| return bytes; |
| } |
| |
| // A-Z, g-z, _, $ |
| private static final int FREE_CHARS = 48; |
| private static int[] CHAR_MAP = new int[FREE_CHARS]; |
| private static int[] MAP_CHAR = new int[256]; // Reverse map |
| private static final char ESCAPE_CHAR = '$'; |
| |
| static { |
| int j = 0, k = 0; |
| for(int i='A'; i <= 'Z'; i++) { |
| CHAR_MAP[j] = i; |
| MAP_CHAR[i] = j; |
| j++; |
| } |
| |
| for(int i='g'; i <= 'z'; i++) { |
| CHAR_MAP[j] = i; |
| MAP_CHAR[i] = j; |
| j++; |
| } |
| |
| CHAR_MAP[j] = '$'; |
| MAP_CHAR['$'] = j; |
| j++; |
| |
| CHAR_MAP[j] = '_'; |
| MAP_CHAR['_'] = j; |
| } |
| |
| /** Decode characters into bytes. |
| * Used by <a href="Utility.html#decode(java.lang.String, boolean)">decode()</a> |
| */ |
| private static class JavaReader extends FilterReader { |
| public JavaReader(Reader in) { |
| super(in); |
| } |
| |
| public int read() throws IOException { |
| int b = in.read(); |
| |
| if(b != ESCAPE_CHAR) { |
| return b; |
| } else { |
| int i = in.read(); |
| |
| if(i < 0) |
| return -1; |
| |
| if(((i >= '0') && (i <= '9')) || ((i >= 'a') && (i <= 'f'))) { // Normal escape |
| int j = in.read(); |
| |
| if(j < 0) |
| return -1; |
| |
| char[] tmp = { (char)i, (char)j }; |
| int s = Integer.parseInt(new String(tmp), 16); |
| |
| return s; |
| } else { // Special escape |
| return MAP_CHAR[i]; |
| } |
| } |
| } |
| |
| public int read(char[] cbuf, int off, int len) throws IOException { |
| for(int i=0; i < len; i++) |
| cbuf[off + i] = (char)read(); |
| |
| return len; |
| } |
| } |
| |
| /** Encode bytes into valid java identifier characters. |
| * Used by <a href="Utility.html#encode(byte[], boolean)">encode()</a> |
| */ |
| private static class JavaWriter extends FilterWriter { |
| public JavaWriter(Writer out) { |
| super(out); |
| } |
| |
| public void write(int b) throws IOException { |
| if(isJavaIdentifierPart((char)b) && (b != ESCAPE_CHAR)) { |
| out.write(b); |
| } else { |
| out.write(ESCAPE_CHAR); // Escape character |
| |
| // Special escape |
| if(b >= 0 && b < FREE_CHARS) { |
| out.write(CHAR_MAP[b]); |
| } else { // Normal escape |
| char[] tmp = Integer.toHexString(b).toCharArray(); |
| |
| if(tmp.length == 1) { |
| out.write('0'); |
| out.write(tmp[0]); |
| } else { |
| out.write(tmp[0]); |
| out.write(tmp[1]); |
| } |
| } |
| } |
| } |
| |
| public void write(char[] cbuf, int off, int len) throws IOException { |
| for(int i=0; i < len; i++) |
| write(cbuf[off + i]); |
| } |
| |
| public void write(String str, int off, int len) throws IOException { |
| write(str.toCharArray(), off, len); |
| } |
| } |
| } |