All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.plumelib.bcelutil.BcelUtil Maven / Gradle / Ivy

There is a newer version: 1.2.2
Show newest version
package org.plumelib.bcelutil;

import java.io.File;
import java.io.PrintStream;
import java.util.Formatter;
import java.util.HashMap;
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.InternalForm;
import org.checkerframework.common.value.qual.MinLen;
import org.plumelib.signature.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 actually performed. */
  public static boolean skipChecks = false;

  /** The type that represents String[]. */
  private static final Type stringArray = Type.getType("[Ljava.lang.String;");

  /**
   * Prints method declarations to System.out.
   *
   * @param gen class whose methods to print
   */
  static void dumpMethodDeclarations(ClassGen gen) {
    System.out.printf("method signatures for class %s%n", gen.getClassName());
    for (Method m : gen.getMethods()) {
      System.out.printf("  %s%n", getMethodDeclaration(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.
   *
   * @param m the method
   * @return a string describing the method declaration
   */
  public static String getMethodDeclaration(Method m) {

    StringBuilder sb = new StringBuilder();
    Formatter f = new Formatter(sb);

    f.format("%s %s %s (", getAccessFlags(m), m.getReturnType(), m.getName());
    for (Type at : m.getArgumentTypes()) {
      f.format("%s, ", at);
    }
    f.format(")");
    return (sb.toString().replace(", )", ")"));
  }

  /**
   * Return a string representation of the access flags of method m.
   *
   * @param m the method whose access flags to retrieve
   * @return a string representation of the access flags of method m
   */
  static String getAccessFlags(Method m) {

    int flags = m.getAccessFlags();

    StringBuilder buf = new StringBuilder();
    for (int i = 0, pow = 1; i <= Const.MAX_ACC_FLAG; 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 the attribute name for the specified attribute.
   *
   * @param a the attribute
   * @return the attribute name for the specified attribute
   */
  public static String getAttributeName(Attribute a) {

    ConstantPool pool = a.getConstantPool();
    int conIndex = a.getNameIndex();
    Constant c = pool.getConstant(conIndex);
    String attName = ((ConstantUtf8) c).getBytes();
    return (attName);
  }

  /**
   * 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 getConstantStr(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());
    }
  }

  /**
   * Returns whether or not the method is a constructor.
   *
   * @param mg the method to test
   * @return true iff the method is a constructor
   */
  public static boolean isConstructor(MethodGen mg) {
    return (mg.getName().equals("") || 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) {
    return (m.getName().equals("") || 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/");
  }

  /**
   * 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);
    }
  }

  /**
   * 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 in 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) { Throwable t = new Throwable(); t.fillInStackTrace(); StackTraceElement[] ste = t.getStackTrace(); if (ste.length < 2) { System.out.println("No stack trace information available"); } else { 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"); } dumpMethods(gen); } } /** * Adds instructions to the start of a method. * * @param mg method to be augmented * @param nl instructions to prepend to the method */ public static void addToStart(MethodGen mg, InstructionList nl) { // Add the code before the first instruction InstructionList il = mg.getInstructionList(); InstructionHandle oldStart = il.getStart(); InstructionHandle newStart = il.insert(nl); // 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(); } /** * 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(); 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); } } /** * 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} */ @SuppressWarnings("rawtypes") public static String instructionDescr(InstructionList il, ConstantPoolGen pool) { StringBuilder out = new StringBuilder(); // not generic because BCEL is not generic for (Iterator i = il.iterator(); i.hasNext(); ) { InstructionHandle handle = (InstructionHandle) 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 localVarDescr(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()); } /** * Builds an array of line numbers for the specified instruction list. Each opcode is assigned the * next source line number starting at 1000. * * @param mg the method whose line numbers to extract * @param il the instruction list to augment with line numbers */ public static void addLineNumbers(MethodGen mg, InstructionList il) { il.setPositions(true); for (InstructionHandle ih : il.getInstructionHandles()) { mg.addLineNumber(ih, 1000 + ih.getPosition()); } } /** * Sets the locals to 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 setupInitLocals(MethodGen mg) { // Get the parameter types and names. @SuppressWarnings( "nullness" // The arguments to the annotation aren't necessarily initialized before they // are written here. Since annotations are erased at runtime, this is safe. ) Type @SameLen({"argTypes", "mg.getArgumentTypes()"}) [] argTypes = mg.getArgumentTypes(); @SuppressWarnings( "nullness" // The arguments to the annotation aren't necessarily initialized before they // are written here. Since annotations are erased at runtime, this is safe. ) 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 emptyMethod(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 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 (getAttributeName(a, pool).equals("LocalVariableTypeTable")); } /** * Return the attribute name for the specified attribute. * * @param a the attribute * @param pool the constant pool * @return the attribute name for the specified attribute */ public static String getAttributeName(Attribute a, ConstantPoolGen pool) { int conIndex = a.getNameIndex(); Constant c = pool.getConstant(conIndex); String attName = ((ConstantUtf8) c).getBytes(); return (attName); } /** * Returns whether or not this is a standard main method (static, 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.getName().equals("main") && (argTypes.length == 1) && argTypes[0].equals(stringArray)); } /** * 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 { Class c = classForName(classname); return c; } 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) { Type[] newTypes = new Type[types.length + 1]; System.arraycopy(types, 0, newTypes, 0, types.length); newTypes[types.length] = newType; Type[] newTypesCast = newTypes; return (newTypesCast); } /** * 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) { @SuppressWarnings({ "index", // newTypes is @MinLen(1) except in the presence of overflow, // which the Value Checker accounts for, but the Index Checker does not. "value" // newTypes is @MinLen(1) except in the presence of overflow, // which the Value Checker accounts for, but the Index Checker does not. }) Type @MinLen(1) [] newTypes = new Type[types.length + 1]; System.arraycopy(types, 0, newTypes, 1, types.length); newTypes[0] = newType; Type[] newTypesCast = newTypes; return (newTypesCast); } /** * Return the type corresponding to a given class name. * * @param classname the binary name of a class (= fully-qualified name, except for inner classes) * @return the type corresponding to the given class name */ public static Type classnameToType(@BinaryName String classname) { @BinaryName String tmp = classname; classname = tmp.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); } return t; } /** Used by {@link #classForName}. */ private static HashMap> primitiveClasses = new HashMap>(8); static { primitiveClasses.put("boolean", Boolean.TYPE); primitiveClasses.put("byte", Byte.TYPE); primitiveClasses.put("char", Character.TYPE); primitiveClasses.put("double", Double.TYPE); primitiveClasses.put("float", Float.TYPE); primitiveClasses.put("int", Integer.TYPE); primitiveClasses.put("long", Long.TYPE); primitiveClasses.put("short", Short.TYPE); } // TODO: This method is a private copy (but protected to permit testing). We made a copy because // the method is in plume-util and because plume-util depends on bcel-util and; therefore, // bcel-util cannot depend on plume-util. In the future, this should probably be moved into a // common dependency, such as the checker-framework's Signatures class. /** * Like {@link Class#forName(String)}, but also works when the string represents a primitive type * or a fully-qualified name (as opposed to a binary name). * *

If the given name can't be found, this method changes the last '.' to a dollar sign ($) and * tries again. This accounts for inner classes that are incorrectly passed in in fully-qualified * format instead of binary format. (It should try multiple dollar signs, not just at the last * position.) * *

Recall the rather odd specification for {@link Class#forName(String)}: the argument is a * binary name for non-arrays, but a field descriptor for arrays. This method uses the same rules, * but additionally handles primitive types and, for non-arrays, fully-qualified names. * * @param className name of the class * @return the Class corresponding to className * @throws ClassNotFoundException if the class is not found */ // The annotation encourages proper use, even though this can take a // fully-qualified name (only for a non-array). // TODO: protected public static Class classForName(@ClassGetName String className) throws ClassNotFoundException { Class result = primitiveClasses.get(className); if (result != null) { return result; } else { try { return Class.forName(className); } catch (ClassNotFoundException e) { int pos = className.lastIndexOf('.'); if (pos < 0) { throw e; } @SuppressWarnings("signature") // checked below & exception is handled @ClassGetName String innerName = className.substring(0, pos) + "$" + className.substring(pos + 1); try { return Class.forName(innerName); } catch (ClassNotFoundException ee) { throw e; } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy