Apache Commons logo Commons BCEL

Appendix

HelloWorldBuilder

The following program reads a name from the standard input and prints a friendly "Hello". Since the readLine() method may throw an IOException it is enclosed by a try-catch clause.

import java.io.*;

public class HelloWorld {
    public static void main(String[] argv) {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String name = null;

        try {
            System.out.print("Please enter your name> ");
            name = in.readLine();
        } catch (IOException e) {
            return;
        }

        System.out.println("Hello, " + name);
    }
}
      

We will sketch here how the above Java class can be created from the scratch using the BCEL API. For ease of reading we will use textual signatures and not create them dynamically. For example, the signature

"(Ljava/lang/String;)Ljava/lang/StringBuffer;"

actually be created with

Type.getMethodSignature(Type.STRINGBUFFER, new Type[] { Type.STRING });

Initialization: First we create an empty class and an instruction list:

ClassGen cg = new ClassGen("HelloWorld", "java.lang.Object",
                            "<generated>", ACC_PUBLIC | ACC_SUPER, null);
ConstantPoolGen cp = cg.getConstantPool(); // cg creates constant pool
InstructionList il = new InstructionList();
      

We then create the main method, supplying the method's name and the symbolic type signature encoded with Type objects.

MethodGen  mg = new MethodGen(ACC_STATIC | ACC_PUBLIC, // access flags
                              Type.VOID,               // return type
                              new Type[] {             // argument types
                              new ArrayType(Type.STRING, 1) },
                              new String[] { "argv" }, // arg names
                              "main", "HelloWorld",    // method, class
                              il, cp);
InstructionFactory factory = new InstructionFactory(cg);
      

We now define some often used types:

ObjectType i_stream = new ObjectType("java.io.InputStream");
ObjectType p_stream = new ObjectType("java.io.PrintStream");
      

Create variables in and name: We call the constructors, i.e., execute BufferedReader(InputStreamReader(System.in)). The reference to the BufferedReader object stays on top of the stack and is stored in the newly allocated in variable.

il.append(factory.createNew("java.io.BufferedReader"));
il.append(InstructionConstants.DUP); // Use predefined constant
il.append(factory.createNew("java.io.InputStreamReader"));
il.append(InstructionConstants.DUP);
il.append(factory.createFieldAccess("java.lang.System", "in", i_stream, Constants.GETSTATIC));
il.append(factory.createInvoke("java.io.InputStreamReader", "<init>",
                                Type.VOID, new Type[] { i_stream },
                                Constants.INVOKESPECIAL));
il.append(factory.createInvoke("java.io.BufferedReader", "<init>", Type.VOID,
                                new Type[] {new ObjectType("java.io.Reader")},
                                Constants.INVOKESPECIAL));

LocalVariableGen lg = mg.addLocalVariable("in",
                        new ObjectType("java.io.BufferedReader"), null, null);
int in = lg.getIndex();
lg.setStart(il.append(new ASTORE(in))); // "i" valid from here
      

Create local variable name and initialize it to null.

lg = mg.addLocalVariable("name", Type.STRING, null, null);
int name = lg.getIndex();
il.append(InstructionConstants.ACONST_NULL);
lg.setStart(il.append(new ASTORE(name))); // "name" valid from here
      

Create try-catch block: We remember the start of the block, read a line from the standard input and store it into the variable name.

InstructionHandle try_start =
  il.append(factory.createFieldAccess("java.lang.System", "out", p_stream, Constants.GETSTATIC));

il.append(new PUSH(cp, "Please enter your name> "));
il.append(factory.createInvoke("java.io.PrintStream", "print", Type.VOID,
                                new Type[] { Type.STRING },
                                Constants.INVOKEVIRTUAL));
il.append(new ALOAD(in));
il.append(factory.createInvoke("java.io.BufferedReader", "readLine",
                                Type.STRING, Type.NO_ARGS,
                                Constants.INVOKEVIRTUAL));
il.append(new ASTORE(name));
      

Upon normal execution we jump behind exception handler, the target address is not known yet.

GOTO g = new GOTO(null);
InstructionHandle try_end = il.append(g);
      

We add the exception handler which simply returns from the method.

InstructionHandle handler = il.append(InstructionConstants.RETURN);
mg.addExceptionHandler(try_start, try_end, handler, "java.io.IOException");
      

"Normal" code continues, now we can set the branch target of the GOTO.

InstructionHandle ih =
  il.append(factory.createFieldAccess("java.lang.System", "out", p_stream, Constants.GETSTATIC));
g.setTarget(ih);
      

Printing "Hello": String concatenation compiles to StringBuffer operations.

il.append(factory.createNew(Type.STRINGBUFFER));
il.append(InstructionConstants.DUP);
il.append(new PUSH(cp, "Hello, "));
il.append(factory.createInvoke("java.lang.StringBuffer", "<init>",
                                Type.VOID, new Type[] { Type.STRING },
                                Constants.INVOKESPECIAL));
il.append(new ALOAD(name));
il.append(factory.createInvoke("java.lang.StringBuffer", "append",
                                Type.STRINGBUFFER, new Type[] { Type.STRING },
                                Constants.INVOKEVIRTUAL));
il.append(factory.createInvoke("java.lang.StringBuffer", "toString",
                                Type.STRING, Type.NO_ARGS,
                                Constants.INVOKEVIRTUAL));

il.append(factory.createInvoke("java.io.PrintStream", "println",
                                Type.VOID, new Type[] { Type.STRING },
                                Constants.INVOKEVIRTUAL));
il.append(InstructionConstants.RETURN);
      

Finalization: Finally, we have to set the stack size, which normally would have to be computed on the fly and add a default constructor method to the class, which is empty in this case.

mg.setMaxStack();
cg.addMethod(mg.getMethod());
il.dispose(); // Allow instruction handles to be reused
cg.addEmptyConstructor(ACC_PUBLIC);
      

Last but not least we dump the JavaClass object to a file.

try {
    cg.getJavaClass().dump("HelloWorld.class");
} catch (IOException e) {
    System.err.println(e);
}
      

Peephole optimizer

This class implements a simple peephole optimizer that removes any NOP instructions from the given class.

import java.io.*;

import java.util.Iterator;
import org.apache.bcel.classfile.*;
import org.apache.bcel.generic.*;
import org.apache.bcel.Repository;
import org.apache.bcel.util.InstructionFinder;

public class Peephole {

    public static void main(String[] argv) {
        try {
            // Load the class from CLASSPATH.
            JavaClass clazz  = Repository.lookupClass(argv[0]);
            Method[] methods = clazz.getMethods();
            ConstantPoolGen cp = new ConstantPoolGen(clazz.getConstantPool());

            for (int i = 0; i < methods.length; i++) {
                if (!(methods[i].isAbstract() || methods[i].isNative())) {
                    MethodGen mg = new MethodGen(methods[i], clazz.getClassName(), cp);
                    Method stripped = removeNOPs(mg);

                    if (stripped != null)      // Any NOPs stripped?
                        methods[i] = stripped; // Overwrite with stripped method
                  }
            }

            // Dump the class to "class name"_.class
            clazz.setConstantPool(cp.getFinalConstantPool());
            clazz.dump(clazz.getClassName() + "_.class");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static Method removeNOPs(MethodGen mg) {
        InstructionList il = mg.getInstructionList();
        InstructionFinder f = new InstructionFinder(il);
        String pat = "NOP+"; // Find at least one NOP
        InstructionHandle next = null;
        int count = 0;

        for (Iterator iter = f.search(pat); iter.hasNext();) {
            InstructionHandle[] match = (InstructionHandle[]) iter.next();
            InstructionHandle first = match[0];
            InstructionHandle last  = match[match.length - 1];

            // Some nasty Java compilers may add NOP at end of method.
            if ((next = last.getNext()) == null) {
                break;
            }

            count += match.length;

            /**
             * Delete NOPs and redirect any references to them to the following (non-nop) instruction.
             */
            try {
                il.delete(first, last);
            } catch (TargetLostException e) {
                for (InstructionHandle target : e.getTargets()) {
                    for (InstructionTargeter targeter = target.getTargeters()) {
                        targeter.updateTarget(target, next);
                    }
                }
            }
        }

        Method m = null;

        if (count > 0) {
            System.out.println("Removed " + count + " NOP instructions from method " + mg.getName());
            m = mg.getMethod();
        }

        il.dispose(); // Reuse instruction handles
        return m;
    }
}
      

BCELifier

If you want to learn how certain things are generated using BCEL you can do the following: Write your program with the needed features in Java and compile it as usual. Then use BCELifier to create a class that creates that very input class using BCEL.
(Think about this sentence for a while, or just try it ...)

Constant pool UML diagram


Figure 8: UML diagram for constant pool classes

Verifier

Running a console based verifier

java org.apache.bcel.verifier.Verifier fully.qualified.class.Name
      
lets JustIce work standalone. If you get a "java.lang.OutOfMemoryError", you should increase the maximum Java heap space. A command like
java -Xmx1887436800 org.apache.bcel.verifier.Verifier f.q.c.Name
      
will usually resolve the problem. The value above is suitable for big server machines; if your machine starts swapping to disk, try to lower the value.

Running a graphics based verifier

If you prefer a graphical application, you should use a command like
java org.apache.bcel.verifier.GraphicalVerifier
      
to launch one. Again, you may have to resolve a memory issue depending on the classes to verify.