|
TechTrader Bytecode Toolkit | ||||||||
PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES |
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. |
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
BCClass
Represents a Class at the bytecode level. A BCClass can be
created from scratch or from an existing class, file, or
stream, and it can then be written to a .class file, a stream,
or loaded into a class loader via its getBytes() method.
The BCClass acts as
a factory and manager of its fields and methods, represented
by BCField
and
BCMethod
objects,
respectively.
Code
A Code object can be obtained from a
BCMethod
,
and allows for
the manipulation of bytecode instructions. The Code instance
acts like a ListIterator
over the instructions
contained in the method it was obtained from. Instead of an
add() method, however, it contains methods representing the
instruction set of the JVM, where each method adds its
respective byte representation at the iterator's current
position. Additionally, there are convenience methods
provided that do not map exactly to any JVM opcode.
Module Dependencies
|
TechTrader Bytecode Toolkit | ||||||||
PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES |