package plume; import org.apache.bcel.Constants; import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.Constant; import org.apache.bcel.classfile.ConstantClass; import org.apache.bcel.classfile.ConstantPool; import org.apache.bcel.classfile.ConstantUtf8; import org.apache.bcel.classfile.Method; import org.apache.bcel.classfile.*; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.CodeExceptionGen; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.InstructionTargeter; import org.apache.bcel.generic.LineNumberGen; import org.apache.bcel.generic.LocalVariableGen; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.ArrayType; import org.apache.bcel.generic.Type; import org.apache.bcel.generic.RETURN; import java.util.*; import java.io.*; import static java.lang.System.out; /** * Static utility methods for working with BCEL. */ public class BCELUtil { /** Controls whether the checks in checkMgen are actually performed * */ public static boolean skip_checks = false; private static final Type string_array = Type.getType("[Ljava.lang.String;"); static void dump_method_declarations(ClassGen gen) { out.printf("method signatures for class %s\n", gen.getClassName()); for (Method m : gen.getMethods()) { out.printf(" %s\n", get_method_declaration(m)); } } /** * Returns a string describing a method declaration. It contains the access * flags (public, private, static, etc), the return type, the method name, and * the types of each of its arguments. */ public static String get_method_declaration(Method m) { StringBuilder sb = new StringBuilder(); Formatter f = new Formatter(sb); f.format("%s %s %s (", get_access_flags(m), m.getReturnType(), m.getName()); for (Type at : m.getArgumentTypes()) { f.format("%s, ", at); } f.format(")"); return (sb.toString().replace(", )", ")")); } static String get_access_flags(Method m) { int flags = m.getAccessFlags(); StringBuffer buf = new StringBuffer(); for (int i = 0, pow = 1; i <= Constants.MAX_ACC_FLAG; i++) { if ((flags & pow) != 0) { if (buf.length() > 0) buf.append(" "); if (i < Constants.ACCESS_NAMES.length) buf.append(Constants.ACCESS_NAMES[i]); else buf.append(String.format("ACC_BIT %x", pow)); } pow <<= 1; } return (buf.toString()); } /** * Returns the attribute name for the specified attribute. */ public static String get_attribute_name(Attribute a) { ConstantPool pool = a.getConstantPool(); int con_index = a.getNameIndex(); Constant c = pool.getConstant(con_index); String att_name = ((ConstantUtf8) c).getBytes(); return (att_name); } /** Returns the constant string at the specified offset */ public static String get_constant_str(ConstantPool pool, int index) { Constant c = pool.getConstant(index); assert c != null : "Bad index " + index + " into pool"; if (c instanceof ConstantUtf8) return ((ConstantUtf8) c).getBytes(); else if (c instanceof ConstantClass) { ConstantClass cc = (ConstantClass) c; return cc.getBytes(pool) + " [" + cc.getNameIndex() + "]"; } else { throw new Error("unexpected constant " + c + " class " + c.getClass()); } } /** returns whether or not the specified method is a constructor * */ public static boolean is_constructor(MethodGen mg) { return (mg.getName().equals("<init>") || mg.getName().equals("")); } /** returns whether or not the specified method is a constructor * */ public static boolean is_constructor(Method m) { return (m.getName().equals("<init>") || m.getName().equals("")); } /** returns whether or not the specified method is a class initializer */ public static boolean is_clinit (MethodGen mg) { return (mg.getName().equals("<clinit>")); } /** returns whether or not the specified method is a class initializer */ public static boolean is_clinit (Method m) { return (m.getName().equals("<clinit>")); } /** returns whether or not the class is part of the JDK (rt.jar) * */ public static boolean in_jdk(ClassGen gen) { return (in_jdk(gen.getClassName())); } /** returns whether or not the classname is part of the JDK (rt.jar) * */ public static boolean in_jdk(String classname) { return classname.startsWith("java.") || classname.startsWith("com.sun.") || classname.startsWith("javax.") || classname.startsWith("org.ietf.") || classname.startsWith("org.omg.") || classname.startsWith("org.w3c.") || classname.startsWith("org.xml.") || classname.startsWith("sun.") || classname.startsWith("[") || classname.startsWith("sunw."); } static void dump_methods(ClassGen gen) { System.out.printf("Class %s methods:\n", gen.getClassName()); for (Method m : gen.getMethods()) System.out.printf(" %s\n", m); } /** * Checks the specific method for consistency. */ public static void checkMgen(MethodGen mgen) { if (skip_checks) return; try { mgen.toString(); mgen.getLineNumberTable(mgen.getConstantPool()); InstructionList ilist = mgen.getInstructionList(); if (ilist == null || ilist.getStart() == null) return; CodeExceptionGen[] exceptionHandlers = mgen.getExceptionHandlers(); for (CodeExceptionGen gen : exceptionHandlers) { assert ilist.contains(gen.getStartPC()) : "exception handler " + gen + " has been forgotten in " + mgen.getClassName() + "." + mgen.getName(); } MethodGen nmg = new MethodGen(mgen.getMethod(), mgen.getClassName(), mgen .getConstantPool()); nmg.getLineNumberTable(mgen.getConstantPool()); } catch (Throwable t) { System.out.printf("failure in method %s.%s\n", mgen.getClassName(), mgen .getName()); t.printStackTrace(); throw new Error(t); } } /** * Checks all of the methods in gen for consistency. */ public static void checkMgens(final ClassGen gen) { if (skip_checks) return; Method[] methods = gen.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; // System.out.println ("Checking method " + method + " in class " // + gen.getClassName()); checkMgen(new MethodGen(method, gen.getClassName(), gen.getConstantPool())); } if (false) { Throwable t = new Throwable(); t.fillInStackTrace(); StackTraceElement[] ste = t.getStackTrace(); StackTraceElement caller = ste[1]; System.out.printf("%s.%s (%s line %d)", caller.getClassName(), caller.getMethodName(), caller.getFileName(), caller.getLineNumber()); for (int ii = 2; ii < ste.length; ii++) System.out.printf(" [%s line %d]", ste[ii].getFileName(), ste[ii] .getLineNumber()); System.out.printf("\n"); dump_methods(gen); } } /** Adds code in nl to start of method mg * */ public static void add_to_start(MethodGen mg, InstructionList nl) { // Add the code before the first instruction InstructionList il = mg.getInstructionList(); InstructionHandle old_start = il.getStart(); InstructionHandle new_start = il.insert(nl); // Move any LineNumbers and local variable that currently point to // the first instruction to include the new instructions. Other // targeters (branches, exceptions) should not include the new // code if (old_start.hasTargeters()) { // getTargeters() returns non-null because hasTargeters => true for (InstructionTargeter it : old_start.getTargeters()) { if ((it instanceof LineNumberGen) || (it instanceof LocalVariableGen)) it.updateTarget(old_start, new_start); } } mg.setMaxStack(); mg.setMaxLocals(); } /** @see #dump(JavaClass, File) **/ public static void dump (JavaClass jc, String dump_dir) { dump (jc, new File (dump_dir)); } /** * Dumps the contents of the specified class to the specified directory. * The file is named dump_dir/[class].bcel. It contains a synopsis * of the fields and methods followed by the jvm code for each method. * * @param jc javaclass to dump * @param dump_dir directory in which to write the file */ public static void dump(JavaClass jc, File dump_dir) { try { dump_dir.mkdir(); File path = new File(dump_dir, jc.getClassName() + ".bcel"); PrintStream p = new PrintStream(path); // Print the class, super class and interfaces p.printf("class %s extends %s\n", jc.getClassName(), jc .getSuperclassName()); String[] inames = jc.getInterfaceNames(); if ((inames != null) && (inames.length > 0)) { p.printf(" "); for (String iname : inames) p.printf("implements %s ", iname); p.printf("\n"); } // Print each field p.printf("\nFields\n"); for (Field f : jc.getFields()) p.printf(" %s\n", f); // Print the signature of each method p.printf("\nMethods\n"); for (Method m : jc.getMethods()) p.printf(" %s\n", m); // If this is not an interface, print the code for each method if (!jc.isInterface()) { for (Method m : jc.getMethods()) { p.printf("\nMethod %s\n", m); Code code = m.getCode(); if (code != null) p.printf(" %s\n", code.toString().replace("\n", "\n ")); } } // Print the details of the constant pool. p.printf("Constant Pool:\n"); ConstantPool cp = jc.getConstantPool(); Constant[] constants = cp.getConstantPool(); for (int ii = 0; ii < constants.length; ii++) { p.printf(" %d %s\n", ii, constants[ii]); } p.close(); } catch (Exception e) { throw new Error("Unexpected error dumping javaclass", e); } } // TODO: write Javadoc @SuppressWarnings("rawtypes") public static String instruction_descr(InstructionList il, ConstantPoolGen pool) { String out = ""; // not generic because BCEL is not generic for (Iterator i = il.iterator(); i.hasNext();) { InstructionHandle handle = (InstructionHandle) i.next(); out += handle.getInstruction().toString(pool.getConstantPool()) + "\n"; } return (out); } /** * Return a description of the local variables (one per line). */ public static String local_var_descr(MethodGen mg) { String out = String.format("Locals for %s [cnt %d]\n", mg, mg .getMaxLocals()); LocalVariableGen[] lvgs = mg.getLocalVariables(); if ((lvgs != null) && (lvgs.length > 0)) { for (LocalVariableGen lvg : lvgs) out += String.format(" %s [index %d]\n", lvg, lvg.getIndex()); } return (out); } /** * Builds an array of line numbers for the specified instruction list. Each * opcode is assigned the next source line number starting at 1000. */ public static void add_line_numbers(MethodGen mg, InstructionList il) { il.setPositions(true); for (InstructionHandle ih : il.getInstructionHandles()) { mg.addLineNumber(ih, 1000 + ih.getPosition()); } } /** * Sets the locals to 'this' and each of the arguments. Any other locals are * removed. An instruction list with at least one instruction must exist. */ @SuppressWarnings("nullness") public static void setup_init_locals(MethodGen mg) { // Get the parameter types and names. Type[] arg_types = mg.getArgumentTypes(); String[] arg_names = mg.getArgumentNames(); // Remove any existing locals mg.setMaxLocals(0); mg.removeLocalVariables(); // Add a local for the instance variable (this) if (!mg.isStatic()) mg.addLocalVariable("this", new ObjectType(mg.getClassName()), null, null); // Add a local for each parameter for (int ii = 0; ii < arg_names.length; ii++) { mg.addLocalVariable(arg_names[ii], arg_types[ii], null, null); } // Reset the current number of locals so that when other locals // are added they get added at the correct offset mg.setMaxLocals(); return; } /** * Empties the method of all code (except for a return). This * includes line numbers, exceptions, local variables, etc. */ public static void empty_method (MethodGen mg) { mg.setInstructionList(new InstructionList(new RETURN())); mg.removeExceptionHandlers(); mg.removeLineNumbers(); mg.removeLocalVariables(); mg.setMaxLocals(); } /** * Remove the local variable type table attribute (LVTT) from mg. * Evidently some changes require this to be updated, but without * BCEL support that would be hard to do. It should be safe to just delete * it since it is optional and really only of use to a debugger. */ public static void remove_local_variable_type_tables (MethodGen mg) { for (Attribute a : mg.getCodeAttributes()) { if (is_local_variable_type_table (a, mg.getConstantPool())) { mg.removeCodeAttribute (a); } } } /** * Returns whether or not the specified attribute is a local variable type * table. */ public static boolean is_local_variable_type_table (Attribute a, ConstantPoolGen pool) { return (get_attribute_name (a, pool).equals ("LocalVariableTypeTable")); } /** * Returns the attribute name for the specified attribute. */ public static String get_attribute_name (Attribute a, ConstantPoolGen pool) { int con_index = a.getNameIndex(); Constant c = pool.getConstant (con_index); String att_name = ((ConstantUtf8) c).getBytes(); return (att_name); } /** * Returns whether or not this is a standard main method (static, * name is 'main', and one argument of string array. */ public static boolean is_main (MethodGen mg) { Type[] arg_types = mg.getArgumentTypes(); return (mg.isStatic() && mg.getName().equals("main") && (arg_types.length == 1) && arg_types[0].equals(string_array)); } /** Returns the java classname that corresponds to type **/ public static String type_to_classname (Type type) { String signature = type.getSignature(); return UtilMDE.classnameFromJvm (signature); } /** Returns the class that corresponds to type **/ @SuppressWarnings("rawtypes") public static Class type_to_class (Type type) { String classname = type_to_classname (type); try { Class<?> c = UtilMDE.classForName (classname); return c; } catch (Exception e) { throw new RuntimeException ("can't find class for " + classname, e); } } /** * Returns a type array with new_type added to the end of types */ public static Type[] add_type (Type[] types, Type new_type) { Type[] new_types = new Type[types.length + 1]; for (int ii = 0; ii < types.length; ii++) { new_types[ii] = types[ii]; } new_types[types.length] = new_type; return (new_types); } /** * Returns a type array with new_type inserted at the beginning */ public static Type[] insert_type (Type new_type, Type[] types) { Type[] new_types = new Type[types.length + 1]; for (int ii = 0; ii < types.length; ii++) { new_types[ii+1] = types[ii]; } new_types[0] = new_type; return (new_types); } public static Type classname_to_type (String classname) { // Get the array depth (if any) int array_depth = 0; while (classname.endsWith ("[]")) { classname = classname.substring (0, classname.length()-2); array_depth++; } classname = classname.intern(); // Get the base type Type t = null; if (classname == "int") // interned t = Type.INT; else if (classname == "boolean") // interned t = Type.BOOLEAN; else if (classname == "byte") // interned t = Type.BYTE; else if (classname == "char") // interned t = Type.CHAR; else if (classname == "double") // interned t = Type.DOUBLE; else if (classname == "float") // interned t = Type.FLOAT; else if (classname == "long") // interned t = Type.LONG; else if (classname == "short") // interned t = Type.SHORT; else // must be a non-primitive t = new ObjectType (classname); // If there was an array, build the array type if (array_depth > 0) { t = new ArrayType (t, array_depth); } return t; } }