org.plumelib.bcelutil.BcelUtil Maven / Gradle / Ivy
Show all versions of bcel-util Show documentation
package org.plumelib.bcelutil;
import java.io.File;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Iterator;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
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.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ArrayType;
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.RETURN;
import org.apache.bcel.generic.Type;
import org.checkerframework.checker.index.qual.SameLen;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.ClassGetName;
import org.checkerframework.checker.signature.qual.FqBinaryName;
import org.checkerframework.checker.signature.qual.InternalForm;
import org.plumelib.reflection.ReflectionPlume;
import org.plumelib.reflection.Signatures;
/** Static utility methods for working with BCEL. */
public final class BcelUtil {
/** This class is a collection of methods; it does not represent anything. */
private BcelUtil() {
throw new Error("do not instantiate");
}
/** Controls whether the checks in {@link #checkMgen} are performed. */
public static boolean skipChecks = false;
/** The type that represents String[]. */
private static final Type stringArray = Type.getType("[Ljava.lang.String;");
// 'ToString' methods
/**
* 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
* parameters.
*
* For example, if the orignal Java source declarationwas: private final String
* constantToString (int index) Then the output of methodDeclarationToString would be: private
* final java.lang.String constantToString (int)
*
* @param m the method
* @return a string describing the method declaration
*/
public static String methodDeclarationToString(Method m) {
StringBuilder sb = new StringBuilder();
String flags = accessFlagsToString(m);
boolean argsExist = false;
if (flags != null && !flags.isEmpty()) {
sb.append(String.format("%s ", flags));
}
sb.append(String.format("%s %s(", m.getReturnType(), m.getName()));
for (Type at : m.getArgumentTypes()) {
sb.append(String.format("%s, ", at));
argsExist = true;
}
if (argsExist) {
sb.setLength(sb.length() - 2); // remove trailing ", "
}
sb.append(")");
return sb.toString();
}
/**
* Return a string representation of the access flags of method m. In the string, the flags are
* space-separated and in a canonical order.
*
* @param m the method whose access flags to retrieve
* @return a string representation of the access flags of method m
*/
static String accessFlagsToString(Method m) {
int flags = m.getAccessFlags();
StringBuilder buf = new StringBuilder();
// Note that pow is a binary mask for the flag (= 2^i).
for (int i = 0, pow = 1; i <= Const.MAX_ACC_FLAG_I; i++) {
if ((flags & pow) != 0) {
if (buf.length() > 0) {
buf.append(" ");
}
if (i < Const.ACCESS_NAMES_LENGTH) {
buf.append(Const.getAccessName(i));
} else {
buf.append(String.format("ACC_BIT(%x)", pow));
}
}
pow <<= 1;
}
return buf.toString();
}
/**
* Return a printed description of the given instructions.
*
* @param il the instructions to describe
* @param pool the constant pool the instructions refer to
* @return a printed representation of the instructions in {@code il}
*/
public static String instructionListToString(InstructionList il, ConstantPoolGen pool) {
StringBuilder out = new StringBuilder();
for (Iterator i = il.iterator(); i.hasNext(); ) {
InstructionHandle handle = i.next();
out.append(handle.getInstruction().toString(pool.getConstantPool()) + "\n");
}
return out.toString();
}
/**
* Return a description of the local variables (one per line).
*
* @param mg the method whose local variables to describe
* @return a description of the local variables (one per line)
*/
public static String localVariablesToString(MethodGen mg) {
StringBuilder out = new StringBuilder();
out.append(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.append(String.format(" %s [index %d]%n", lvg, lvg.getIndex()));
}
}
return out.toString();
}
/**
* Return the attribute name for the specified attribute, looked up in the original class file
* ConstantPool.
*
* @param a the attribute
* @return the attribute name for the specified attribute
*/
public static String attributeNameToString(Attribute a) {
ConstantPool pool = a.getConstantPool();
int conIndex = a.getNameIndex();
Constant c = pool.getConstant(conIndex);
String attName = ((ConstantUtf8) c).getBytes();
return attName;
}
/**
* Return the attribute name for the specified attribute, looked up in the given ConstantPoolGen.
*
* @param a the attribute
* @param pool the constant pool
* @return the attribute name for the specified attribute
*/
public static String attributeNameToString(Attribute a, ConstantPoolGen pool) {
int conIndex = a.getNameIndex();
Constant c = pool.getConstant(conIndex);
String attName = ((ConstantUtf8) c).getBytes();
return attName;
}
// 'is' (boolean test) methods
/**
* Returns whether or not the method is a constructor.
*
* @param mg the MethodGen to test
* @return true iff the method is a constructor
*/
public static boolean isConstructor(MethodGen mg) {
if (mg.getName().equals("")) {
throw new Error("method name cannot be empty");
}
return mg.getName().equals("");
}
/**
* Returns whether or not the method is a constructor.
*
* @param m the Method to test
* @return true iff the method is a constructor
*/
public static boolean isConstructor(Method m) {
if (m.getName().equals("")) {
throw new Error("method name cannot be empty");
}
return m.getName().equals("");
}
/**
* Returns whether or not the method is a class initializer.
*
* @param mg the method to test
* @return true iff the method is a class initializer
*/
public static boolean isClinit(MethodGen mg) {
return mg.getName().equals("");
}
/**
* Returns whether or not the method is a class initializer.
*
* @param m the method to test
* @return true iff the method is a class initializer
*/
public static boolean isClinit(Method m) {
return m.getName().equals("");
}
/**
* Returns whether or not the class is part of the JDK (rt.jar).
*
* @param gen the class to test
* @return true iff the class is in a package that is in the JDK (rt.jar)
*/
public static boolean inJdk(ClassGen gen) {
return inJdk(gen.getClassName());
}
/**
* Returns whether or not the class is part of the JDK (rt.jar).
*
* @param classname the class to test, in the format of Class.getName(); the class should not be
* an array
* @return true iff the class is in a package that is in the JDK (rt.jar)
*/
public static boolean inJdk(@ClassGetName String classname) {
return classname.startsWith("java.")
|| classname.startsWith("com.oracle.")
|| classname.startsWith("com.sun.")
|| classname.startsWith("javax.")
|| classname.startsWith("jdk.")
|| classname.startsWith("org.ietf.")
|| classname.startsWith("org.jcp.")
|| classname.startsWith("org.omg.")
|| classname.startsWith("org.w3c.")
|| classname.startsWith("org.xml.")
|| classname.startsWith("sun.")
|| classname.startsWith("sunw.");
}
/**
* Returns whether or not the class is part of the JDK (rt.jar).
*
* @param classname the class to test, in internal form
* @return true iff the class is part of the JDK (rt.jar)
*/
public static boolean inJdkInternalform(@InternalForm String classname) {
return classname.startsWith("java/")
|| classname.startsWith("com/oracle/")
|| classname.startsWith("com/sun/")
|| classname.startsWith("javax/")
|| classname.startsWith("jdk/")
|| classname.startsWith("org/ietj/")
|| classname.startsWith("org/jcp/")
|| classname.startsWith("org/omg/")
|| classname.startsWith("org/w3c/")
|| classname.startsWith("org/xml/")
|| classname.startsWith("sun/")
|| classname.startsWith("sunw/");
}
/**
* Returns whether or not the specified attribute is a local variable type table.
*
* @param a the attribute
* @param pool the constant pool
* @return true iff the attribute is a local variable type table
*/
public static boolean isLocalVariableTypeTable(Attribute a, ConstantPoolGen pool) {
return attributeNameToString(a, pool).equals("LocalVariableTypeTable");
}
/**
* Returns whether or not this is a standard main method (static, void, name is 'main', and one
* formal parameter: a string array).
*
* @param mg the method to check
* @return true iff the method is a main method
*/
public static boolean isMain(MethodGen mg) {
Type[] argTypes = mg.getArgumentTypes();
return mg.isStatic()
&& (mg.getReturnType() == Type.VOID)
&& mg.getName().equals("main")
&& (argTypes.length == 1)
&& argTypes[0].equals(stringArray);
}
// consistency check methods
/**
* Checks the specified method for consistency.
*
* Does nothing if {@link #skipChecks} is false.
*
* @param mgen the class to check
*/
public static void checkMgen(MethodGen mgen) {
if (skipChecks) {
return;
}
try {
mgen.toString(); // ensure it can be formatted without exceptions
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) {
Error e =
new Error(
String.format(
"failure while checking method %s.%s%n", mgen.getClassName(), mgen.getName()),
t);
e.printStackTrace();
throw e;
}
}
/**
* Checks all of the methods in gen for consistency.
*
* @param gen the class to check
*/
public static void checkMgens(final ClassGen gen) {
if (skipChecks) {
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()));
}
// Diagnostic output
if (false) {
dumpStackTrace();
dumpMethods(gen);
}
}
// 'dump' methods
/** Print the current java call stack. */
public static void dumpStackTrace() {
StackTraceElement[] ste = Thread.currentThread().getStackTrace();
// [0] is getStackTrace
// [1] is dumpStackTrace
if (ste.length < 3) {
System.out.println("No stack trace information available");
} else {
StackTraceElement caller = ste[2];
System.out.printf(
"%s.%s (%s line %d)",
caller.getClassName(),
caller.getMethodName(),
caller.getFileName(),
caller.getLineNumber());
for (int ii = 3; ii < ste.length; ii++) {
System.out.printf(" [%s line %d]", ste[ii].getFileName(), ste[ii].getLineNumber());
}
System.out.printf("%n");
}
}
/**
* Print the methods in the class, to standard output.
*
* @param gen the class whose methods to print
*/
static void dumpMethods(ClassGen gen) {
System.out.printf("Class %s methods:%n", gen.getClassName());
for (Method m : gen.getMethods()) {
System.out.printf(" %s%n", m);
}
}
/**
* Dumps the contents of the specified class to the specified directory. The file is named
* dumpDir/[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 dumpDir directory in which to write the file
* @see #dump(JavaClass, File)
*/
public static void dump(JavaClass jc, String dumpDir) {
dump(jc, new File(dumpDir));
}
/**
* Dumps the contents of the specified class to the specified directory. The file is named
* dumpDir/[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 dumpDir directory in which to write the file
*/
public static void dump(JavaClass jc, File dumpDir) {
try {
dumpDir.mkdir();
File path = new File(dumpDir, jc.getClassName() + ".bcel");
PrintStream p = new PrintStream(path);
// Print the class, superclass, and interfaces
p.printf("class %s extends %s%n", jc.getClassName(), jc.getSuperclassName());
String[] inames = jc.getInterfaceNames();
boolean first = true;
if ((inames != null) && (inames.length > 0)) {
p.printf(" implements ");
for (String iname : inames) {
if (!first) p.printf(", ");
p.printf("%s", iname);
first = false;
}
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);
}
for (Method m : jc.getMethods()) {
Code code = m.getCode();
if (code != null) {
p.printf("%nMethod %s%n", m);
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: " + jc.getClassName() + " to " + dumpDir.getName(),
e);
}
}
// miscellaneous methods
/**
* Adds instructions to the start of a method.
*
* @param mg method to be augmented
* @param newList instructions to prepend to the method
*/
public static void addToStart(MethodGen mg, InstructionList newList) {
// Add the code before the first instruction
InstructionList il = mg.getInstructionList();
InstructionHandle oldStart = il.getStart();
InstructionHandle newStart = il.insert(newList);
// Move any LineNumbers and local variables that currently point to
// the first instruction to include the new instructions. Other
// targeters (branches, exceptions) should not include the new code.
if (oldStart.hasTargeters()) {
// getTargeters() returns non-null because hasTargeters => true
for (InstructionTargeter it : oldStart.getTargeters()) {
if ((it instanceof LineNumberGen) || (it instanceof LocalVariableGen)) {
it.updateTarget(oldStart, newStart);
}
}
}
mg.setMaxStack();
mg.setMaxLocals();
}
/**
* Returns the constant string at the specified offset.
*
* @param pool the constant pool
* @param index the index in the constant pool
* @return the constant string at the specified offset in the constant pool
*/
public static String getConstantString(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 + " of class " + c.getClass());
}
}
/**
* Sets the locals to be the formal parameters. Any other locals are removed. An instruction list
* with at least one instruction must exist.
*
* @param mg the method whose locals to set
*/
public static void resetLocalsToFormals(MethodGen mg) {
// Get the parameter types and names.
Type @SameLen({"argTypes", "mg.getArgumentTypes()"}) [] argTypes = mg.getArgumentTypes();
String @SameLen({"argTypes", "argNames", "mg.getArgumentTypes()", "mg.getArgumentNames()"}) []
argNames = 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 < argNames.length; ii++) {
mg.addLocalVariable(argNames[ii], argTypes[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.
*
* @param mg the method to clear out
*/
public static void makeMethodBodyEmpty(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.
*
* @param mg the method to clear out
*/
public static void removeLocalVariableTypeTables(MethodGen mg) {
for (Attribute a : mg.getCodeAttributes()) {
if (isLocalVariableTypeTable(a, mg.getConstantPool())) {
mg.removeCodeAttribute(a);
}
}
}
/**
* Returns the Java class name, in the format of {@link Class#getName()}, that corresponds to
* type.
*
* @param type the type
* @return the Java classname that corresponds to type
*/
public static @ClassGetName String typeToClassgetname(Type type) {
String signature = type.getSignature();
return Signatures.fieldDescriptorToClassGetName(signature);
}
/**
* Returns the class that corresponds to type.
*
* @param type the type
* @return the Java class that corresponds to type
*/
public static Class> typeToClass(Type type) {
String classname = typeToClassgetname(type);
try {
return ReflectionPlume.classForName(classname);
} catch (Exception e) {
throw new RuntimeException("can't find class for " + classname, e);
}
}
/**
* Returns a copy of the given type array, with newType added to the end.
*
* @param types the array to extend
* @param newType the element to add to the end of the array
* @return a new array, with newType at the end
*/
public static Type[] postpendToArray(Type[] types, Type newType) {
if (types.length == Integer.MAX_VALUE) {
throw new Error("array " + Arrays.toString(types) + " is too large to extend");
}
Type[] newTypes = new Type[types.length + 1];
System.arraycopy(types, 0, newTypes, 0, types.length);
newTypes[types.length] = newType;
return newTypes;
}
/**
* Returns a copy of the given type array, with newType inserted at the beginning.
*
* @param types the array to extend
* @param newType the element to add to the beginning of the array
* @return a new array, with newType at the beginning
*/
public static Type[] prependToArray(Type newType, Type[] types) {
if (types.length == Integer.MAX_VALUE) {
throw new Error("array " + Arrays.toString(types) + " is too large to extend");
}
Type[] newTypes = new Type[types.length + 1];
System.arraycopy(types, 0, newTypes, 1, types.length);
newTypes[0] = newType;
return newTypes;
}
/**
* Return the type corresponding to a given class or primitive name.
*
* @param classname the binary name of a class (= fully-qualified name, except for inner classes),
* or a primitive, but not an array
* @return the type corresponding to the given class name
* @see #fqBinaryNameToType
*/
// TODO: Poor name because this handles any non-array, not just classes.
// TODO: Parameter type should be @BinaryNameOrPrimitive
public static Type classnameToType(@BinaryName String classname) {
classname = classname.intern();
if (classname == "int") { // interned
return Type.INT;
} else if (classname == "boolean") { // interned
return Type.BOOLEAN;
} else if (classname == "byte") { // interned
return Type.BYTE;
} else if (classname == "char") { // interned
return Type.CHAR;
} else if (classname == "double") { // interned
return Type.DOUBLE;
} else if (classname == "float") { // interned
return Type.FLOAT;
} else if (classname == "long") { // interned
return Type.LONG;
} else if (classname == "short") { // interned
return Type.SHORT;
} else { // must be a non-primitive
return new ObjectType(classname);
}
}
/**
* Return the type corresponding to a given fully-qualified binary name.
*
* @param classname the fully-qualified binary name of a type, which uses "$" rather than "." for
* nested classes
* @return the type corresponding to the given name
*/
public static Type fqBinaryNameToType(@FqBinaryName String classname) {
Signatures.ClassnameAndDimensions cad =
Signatures.ClassnameAndDimensions.parseFqBinaryName(classname);
Type eltType = classnameToType(cad.classname);
if (cad.dimensions == 0) {
return eltType;
} else {
return new ArrayType(eltType, cad.dimensions);
}
}
}