TechTrader Bytecode Toolkit

Package com.techtrader.modules.tools.bytecode

Bytecode Manipuation

See:
          Description

Interface Summary
Constants Interface to track constants used in bytecode.
InstructionPtr An InstructionPtr represents an entity that maintains ptrs to instructions in a code block.
 

Class Summary
ArrayInstruction Represents any array load or store instruction.
ArrayLoadInstruction Represents any array load instruction.
ArrayStoreInstruction Represents any array store instruction.
Attribute Representation of an attribute in a .class file.
BCClass Start here to understand this package.
BCEntity Abstract superclass for all complex bytecode entities.
BCField Representation of a bytecode field of a class; a BCField can only be obtained from a BCClass.
BCMethod Representation of a bytecode method of a class; a BCMethod can only be obtained from a BCClass.
ClassConstantInstruction Pseudo-instruction used to place Class types onto the stack.
ClassInstruction Represents an instruction that takes as an argument a Class to operate on.
CmpInstruction An instruction comparing two stack values that varies depending on the value type.
Code Representation of a code block of a class; a Code instance can only be obtained from a BCMethod.
ConstantInstruction Represents an instruction that that loads a constant onto the stack.
ConstantValueAttribute Representation of a constant value in a .class file.
ConvertInstruction Represents one of the conversion opcodes defined in the Constants interface for converting between primitive types.
DeprecatedAttribute Attribute signifying that a method or class is deprecated.
ExceptionHandler Represents a try {} catch() {} statement in bytecode.
ExceptionsAttribute Attribute indicating what checked exceptions a method can throw; referenced from a BCMethod.
FieldInstruction Represents an instruction that takes as an argument a field to operate on.
GetFieldInstruction Represents the GETFIELD or GETSTATIC instruction.
IIncInstruction Represents the IINC instruction.
InnerClass Represents an inner class.
InnerClassesAttribute Attribute describing the inner classes of a .class file.
Instruction An Instruction represents an opcode in a method of a Class.
JumpInstruction Represents an IF, GOTO, JSR, or similar instruction that specifies as its argument a position in the code block to jump to.
LineNumber Represents a linenumber; used for error reporting.
LineNumberTableAttribute A LineNumberTableAttributs holds a table of line number to program counter mappings, so that errors can be reported with the correct line number.
LoadInstruction Represents an instruction to load a value from a local variable onto the stack; can be any of aload_*, iload_*, etc.
LocalVariable Represents a local variable in a method.
LocalVariableInstruction Represents an instruction that has an argument of an index into the local variable array of the current frame.
LocalVariableTableAttribute Represents a local variable table for a method.
LookupSwitchInstruction The LOOKUPSWITCH instruction.
MathInstruction Represents one of the math operations defined in the Constants interface.
MethodInstruction Represents an instruction that takes as an argument a method to operate on.
MonitorEnterInstruction MONITORENTER
MonitorExitInstruction MONITOREXIT
MonitorInstruction A MONITORENTER or MONITOREXIT instruction.
MultiANewArrayInstruction Represents the MULTIANEWARRAY opcode instruction, which creates a new multi-dimensional array.
NewArrayInstruction Represents a NEWARRAY instruction, which is used to create new arrays of primitive types.
PutFieldInstruction Represents the PUTFIELD or PUTSTATIC instruction.
RetInstruction Represents the RET instruction used in the implementation of finally.
ReturnInstruction Represents a return instruction.
SourceFileAttribute Attribute naming the source file for this .class file.
StackInstruction Represents an instruction that manipulates the stack of the current frame.
StoreInstruction Represents an instruction to store a value in a local variable from the stack; can be any of astore*, istore*, etc.
SyntheticAttribute Attribute that should be present for .class files with generated source code.
TableSwitchInstruction A TABLESWITCH instruction.
TypedLocalVariableInstruction An instruction manipulating a local variable that varies with the variable type.
UnknownAttribute An unrecognized attribute; .class files are allowed to contain attributes that are not recognized, and the JVM must ignore them.
WideInstruction Representation of the WIDE instruction, which is used to allow other instructions to index values beyond what they can normally index baed on the length of their arguments.
 

Package com.techtrader.modules.tools.bytecode Description

Bytecode Manipuation

This package exports facilities for bytecode manipulation. In order to perform any advanced manipulation, some understanding of the .class file format and of the jvm instruction set is necessary.

It is recommended that new users start with the BCClass class documentation to get a feel for the utilities that this package provides.

The Code class is used to manipulate actual instructions (as opposed to larger structures of the class like methods, fields, superclass, etc). It contains methods corresponding the the instruction set of the JVM; see its javadoc documentation for details.

Implementation Note: Bytecode files contain a constant pool, which contains entries for all the constants, the class, field, and method references, etc used in the class. Often, these constants are pointed to by multiple other constants or instructions in the code, and so changing a constant in one place can affect the behavior of other code, or even render the class invalid. At the same time, adding duplicate constant pool entries for repeated constants can lead to a much larger .class file than necessary, which drains resources. This framework guarantees that no constant will appear more than once in the constant pool, but at the same time it tries to minimize the chances of a bytecode change having effects beyond its exepected scope. In this regard, the framework has the following behavior with respect to the constant pool:

Sample Code

Let's start with the classic Hello World example. The following code creates a 'Hello' class with a single main() method that prints the line 'Hello World!':


package com.techtrader.modules.tools.bytecode.test;


import java.io.File;
import java.io.PrintStream;

import com.techtrader.modules.tools.bytecode.BCClass;
import com.techtrader.modules.tools.bytecode.BCMethod;
import com.techtrader.modules.tools.bytecode.Code;


public class CreateHello
{
	public static void main (String[] args)
		throws Exception
	{
		// create the class; set name and superclass
		BCClass bcClass = new BCClass ();
		bcClass.setName ("com.techtrader.modules.tools.bytecode.test.Hello");
		bcClass.setSuperclassType (Object.class);

		// add a default constructor
		bcClass.addDefaultConstructor ();

		// add a static main method that takes a String[] arg
		BCMethod main = bcClass.addMethod ("main", void.class, 
			new Class[] { String[].class });
		main.setStatic (true);

		// add code to the method
		Code code = main.addCode ();

		// get the 'out' field of the 'System' class and put it on the stack
		code.getstatic ().setField (System.class.getField ("out"));

		// load the 'Hello World!' constant onto the stack
		code.ldc ().setStringConstant ("Hello World!");

		// invoke the 'println(String)' method on the 'out' field of 'System'
		// with the given 'Hello World!' argument
		code.invokevirtual ().setMethod (PrintStream.class.getMethod 
			("println", new Class[] { String.class }));

		// return
		code.vreturn ();

		code.calculateMaxLocals ();
		code.calculateMaxStack ();

		// write the class to a file
		bcClass.write (new File ("Hello.class"));
	}
}

		

Now lets do something a little more useful, like dynamically generate a java bean.
Hypothetical Bean Source

	
package com.techtrader.modules.tools.bytecode.test;


public class Person
	implements Cloneable
{
	private int _age;
	private String _name;
	private Person _parent;

	
	public int getAge ()
	{
		return _age;
	}


	public void setAge (int age)
	{
		_age = age;
	}


	public String getName ()
	{
		return _name;
	}

		
	public void setName (String name)
	{
		_name = name;
	}


	public Person getParent ()
	{
		return _parent;
	}


	public void setParent (Person parent)
	{
		_parent = parent;
	}
}

		
Producer Code

package com.techtrader.modules.tools.bytecode.test;


import java.io.File;

import com.techtrader.modules.tools.bytecode.BCClass;
import com.techtrader.modules.tools.bytecode.BCField;
import com.techtrader.modules.tools.bytecode.BCMethod;
import com.techtrader.modules.tools.bytecode.Code;


public class CreatePerson
{
	private static final String PERSON = 
		"com.techtrader.modules.tools.bytecode.test.Person";


	public static void main (String[] args)
		throws Exception
	{
		// create the class
		BCClass bcClass = new BCClass (PERSON); 
		bcClass.setSuperclassType (Object.class);
		bcClass.addInterfaceType (Cloneable.class);

		// add the fields
		bcClass.addField ("_name", String.class);
		bcClass.addField ("_age", int.class);
		bcClass.addField ("_parent", PERSON);

		// create the default constructor
		bcClass.addDefaultConstructor ();

		// create the accessor/mutator methods
		addGetter (bcClass, "getName", name);
		addSetter (bcClass, "setName", name);

		addGetter (bcClass, "getAge", age);
		addSetter (bcClass, "setAge", age);
	
		addGetter (bcClass, "getParent", parent);
		addSetter (bcClass, "setParent", parent);

		// write the class to a file
		bcClass.write (new File ("Person.class"));
	}


	private static void addGetter (BCClass bcClass, String name, BCField field)
	{
		String fieldType = field.getTypeName ();

		Code code = bcClass.addMethod (name, fieldType, null).addCode ();

		code.aload_0 ();								// load the 'this' ptr
		code.getfield ().setField (field);				// get the field value
		code.returnins ().setTypeName (fieldType);		// return the value

		code.calculateMaxLocals ();
		code.calculateMaxStack ();
	}


	private static void addSetter (BCClass bcClass, String name, BCField field)
	{
		String fieldType = field.getTypeName ();

		Code code = bcClass.addMethod (name, "void", new String[] 
			{ fieldType }).addCode ();
	
		code.aload_0 ();						// load the 'this' ptr
		code.load ().setTypeName (fieldType).setIndex (1);	// load the  arg
		code.putfield ().setField (field);		// set the field with the arg
		code.vreturn ();						// return void

		code.calculateMaxLocals ();
		code.calculateMaxStack ();
	}
}

		

Finally, let's do something really interesting. The following code reads .class files and modifies their bytecode to print a line to System.out just before the class being read sets any of its own non-static fields. The line will report the current method, the name of the field being set, and the value it is about to be set to.


package com.techtrader.modules.tools.bytecode.test;


import java.io.File;
import java.io.PrintStream;
import java.io.IOException;

import com.techtrader.modules.tools.bytecode.BCClass;
import com.techtrader.modules.tools.bytecode.BCMethod;
import com.techtrader.modules.tools.bytecode.Code;
import com.techtrader.modules.tools.bytecode.Instruction;
import com.techtrader.modules.tools.bytecode.PutFieldInstruction;


public class PrintSetFields
{
	/**
	 *	Usage: PrintSetFields <classfile>+
	 */
	public static void main (String[] args)
		throws Exception
	{
		for (int i = 0; i < args.length; i++)
			printSetFields (new File (args[i]));	
	}


	/**
	 *	Reads a single .class file and instruments the bytecode in it to
	 *	print a line to System.out before the class sets any of its own 
	 *	non-static fields.
	 */
	public static void printSetFields (File file)
		throws Exception
	{
		// read the class file
		BCClass type = new BCClass (file);

		// create a template putfield instruction to match against, where
		// the name and type of the field can be anything, but it must belong
		// to the class being read
		Instruction template = new Code ().putfield ().
			setField (null, (String) null, type.getName ());

		// instrument each method...
		BCMethod[] methods = type.getMethods ();
		Code code;
		int extraLocal;
		for (int i = 0; i < methods.length; i++)
		{
			// skip interface/abstract methods without code
			if (methods[i].isAbstract ())
				continue;

			// allocate an extra local variable for the value of any matches
			code = methods[i].getCode ();
			extraLocal = code.getNextLocalsIndex ();

			// search for matching instructions
			while (code.searchForward (template))
				printSetField (code, extraLocal);

			// if the extra local was used, update the max locals
			code.calculateMaxLocals ();
		}

		// write the enhanced bytecode back to the class file
		type.write (file);
	}


	/**
	 *	Method to insert the proper print statements before the putfield()	
  	 *	instruction that was just matched.  The code iterator should be just
	 *	after the matching putfield() instruction when this method is called.
 	 */
	private static void printSetField (Code code, int extraLocal)
		throws Exception
	{
		// iterator will be placed after match
		PutFieldInstruction ins = (PutFieldInstruction) code.previous ();
		Class fieldType = ins.getFieldType ();

		// store the value being set in the added local variable
		code.store ().setType (fieldType).setIndex (extraLocal);	
	
		// print out the method and name of the field being set
		code.getstatic ().setField (System.class.getDeclaredField ("out"));
		code.ldc ().setStringConstant ("in method " 
			+ ((BCMethod) code.getOwner ()).getName () + ", setting field: " 
			+ ins.getFieldName () + " to: ");
		code.invokevirtual ().setMethod (PrintStream.class.getMethod 
			("print", new Class[] { String.class }));

		// demote all Object types so we can use reflection to find the
		// correct form of println() to invoke with the field value
		if (Object.class.isAssignableFrom (fieldType))
			fieldType = Object.class;

		// print the value and end the printed line
		code.getstatic ().setField (System.class.getDeclaredField ("out"));
		code.load ().setType (fieldType).setIndex (extraLocal);
		code.invokevirtual ().setMethod (PrintStream.class.getMethod 
			("println", new Class[] { fieldType }));

		// load the value back onto the stack so the putfield() works
		code.load ().setType (fieldType).setIndex (extraLocal);

		// move back to the original code position
		code.next ();
	}
}

		

Notable Classes

Module Dependencies


TechTrader Bytecode Toolkit