Class dec.trek.Code

The Code class is used to insert arbitrary code into a classfile.

Method Summary

  • addAfter – sets up to insert code after the specified statement
  • addAt – sets up to insert code before the specified instruction
  • addBefore – sets up to insert code before the specified statement
  • append – appends more to what is being inserted
  • discard – aborts the code insertion
  • done – completes the code insertion.

Overview

How to insert code

The steps to insert code into a classfile are:

  • To position the code, invoke Code.add*(). This returns the Code object you will use in the next two bullets. The position where code will be inserted is called the insert point.
  • To insert code, do one or more append calls.
  • To complete the insertion, call done(). However the actual classfile is not modified on disk until saveTrek or endTrek is called.

If this insertion might be redundant or if it might need to replace an earlier insertion, use hasChange and undoChange to recognize and deal with these situations.

Position of the inserted code

The inserted code becomes part of the statement it is associated with. This point becomes significant when break, continue, end-loop, and other jumping statements are considered. That is, a jump to a statement with inserted code will execute the inserted code. Conversely when code is inserted using addAfter(stmt), a jump to the statement following stmt will not execute the inserted code.

Validity of the inserted code

If you append a bytecode array, it is entirely up to you to do so correctly. append does not verify the bytecodes in any way. If you append an instruction via its opcode, append will insert a syntactically valid instruction or throw an exception.

Either way, it is entirely up to you to insert a consistent sequence of instructions. In particular, an inserted instruction will fail at runtime or its classfile will not load if you do any of the following:

  • Incorrectly setup the stack for the instruction being inserted. For example:
    • The data types of the items on the stack must match the data types expected by the instruction being inserted.
    • There must be an object reference on the stack when doing a getfield or putfield. (Note that you can load this by doing an append(Code.aload_0)).
  • Insert a branching instruction whose jump offset does not identify the start of an instruction.
  • Insert a get/putstatic with an instance member or vice versa (but if the member's classfile is accessible at trek-time, the mistake will be reported immediately by append throwing an exception).
  • Insert an instruction with a class or member argument, and forget to have its classfile in your classpath at runtime. (Thus a bad class name will also show up this way).
  • Insert a Tload* of a created local variable such that it is executed before its first Tstore* is executed.

The arguments of an instruction

If an append is for loading or storing a local variable, the argument that identifies the local variable must be a Local. If an append is for loading or storing a field, the argument that identifies the field must be a Field or a FieldRef. If an append is for a call, the argument that identifies the method must be a Method or a MethodRef.

If an append is for loading a constant, the argument that identifies the constant value must be a ValueRef, an Integer, a Long, a Float, a Double, or a String. If the instruction implies particular data types or values, the argument must be consistent with this. For example, ldc2_w's argument must be a Long, a long ValueRef, a Double, or a double ValueRef. Similarly bipush's argument must be an int or Integer whose value is from -127 to +127. (Syntactically it could be an int ValueRef, but compilers do not generate ValueRefs for small integers).

If an append is for a branching instruction, the instruction's argument is a relative branch from the start of the instruction. It may be passed as an Integer or as an int. Be forewarned though; it is quite tricky to correctly set a branch offset if the target instruction is not part of the current code insertion. Even in the simple case, you must know the size in bytes of each instruction you plan to append.

If an append is for a type-manipulating instruction like instanceof, the instruction's argument must identify an array type or a class. An array type may be identified by an array object, an array TypeRef, or an array descriptor. An array object is a Local, Field, FieldRef, Method, or MethodRef that is an array; specifying such an object sets the array type to the data type of the specified object. An array descriptor is a String that encodes the data type of an array as described in The Java Virtual Machine Specification. A class may be identified by a ClassFile, a class TypeRef, or a String. In the last case, you must specify the fully qualified name of the class.

Automatic creation of the wide instruction

If the instruction being appended uses a local variable with an LVT index > 255 (or is an iinc whose increment is outside the range -127 to 127), the wide form of the instruction is appended. Conversely if Code.wide is passed to append, an exception is thrown.

Methods

public static Code addAfter(String changid, Statement stmt)

Returns a Code object or throws an exception, and sets the insert point after stmt. However if the specified statement is a block-starting statement (eg. for, else, try), the insert point is actually set before the first statement within the block.

The specified statement may not be a "jumping" statement, with these exceptions. If it identifies a switch statement, the insert point is set after where the key value has been loaded on the stack. If it identifies a throw statement, the insert point is set after where the throwable has been loaded on the stack. If it identifies a return statement that has a return value, the insert point is set after where the return value has been loaded on the stack. If it identifies a return statement that does not have a return value, the insert point is set before the return statement.

Parameters:

changid – the change ID to assign to this code change, or null to assign "".

stmt – the statement after which code will be inserted.

public static Code addAt(String changid, Instruction inst)

Returns a Code object or throws an exception, and sets the insert point to right before the instruction.

Parameters:

changid – the change ID to assign to this code change, or null to assign "".

inst – the instruction at which to insert code

public static Code addBefore(String changid, Statement stmt)

Returns a Code object or throws an exception, and sets the insert point before stmt.

Parameters:

changid – the change ID to assign to this code change, or null to assign "".

stmt – the statement before which code will be inserted.

public void append(int opcode)

Appends the specified instruction to what is being inserted. If the opcode is not valid or requires an argument, an exception is thrown.

Parameters:

opcode – one of the opcode constants defined in the Code class.

public void append(int opcode, Object arg)

Appends the specified instruction to what is being inserted. If the opcode is not valid or does not have one argument, an exception is thrown.

If the properties of arg do not match what is expected for this instruction, an exception is thrown. For example, if opcode is Code.fload and arg is other than a Local whose type is float, an exception is thrown.

Parameters:

opcode – one of the opcode constants defined in the Code class.

arg – a ClassFile, TypeRef, Local, Field, FieldRef, Method, MethodRef, Integer, Long, Float, Double, or String object

public void append(int opcode, int arg)

Appends the specified instruction to what is being inserted. This is a convenience method that is equivalent to doing append(opcode, new Integer(arg)).

Parameters:

opcode – one of the opcode constants defined in the Code class.

arg – an integer value

public void append(int opcode, Object arg1, int arg2)

Appends the specified instruction to what is being inserted. If the opcode is not Code.iinc, Code.multianewarray, or Code.invokeinterface, an exception is thrown.

For Code.iinc, arg1 must be a Local of type int and arg2 must be -127 to 127 and non-0. For Code.multianewarray, arg1 must be an array type and arg2 must be 1 to 255. For Code.invokeinterface, arg1 must be a Method or MethodRef and arg2 must be 1 to 255.

Parameters:

opcode – one of the opcode constants defined in the Code class.

arg1 – the object you want this instruction to reference

arg2 – an integer value

public void append(byte code[])

Appends the specified bytecodes to what is being inserted

Parameters:

code – one or more instructions represented in the format described in The Java Virtual Machine Specification.

public void discard()

Aborts building of the code. You can now discard the Code object. This might be used if append threw an exception.

public void done()

Inserts the code into the in-memory representation of the program. The priority of a Code insert is the same as for a Call insert whose insert point is set from a statement or instruction. See the Call class's info on insert priority for details.