Class dec.trek.Call

The Call class is used to insert instrumentation calls into a classfile.

Method Summary

  • addAfter – setup to insert a call after the specified code object
  • addAt – setup to insert a call at the specified instruction
  • addBefore – setup to insert a call before the specified code object
  • discard – aborts the call’s insertion
  • doIf – inserts test prior to the inserted call.
  • doIfBoolean – inserts test of boolean variable prior to the inserted call.
  • doIfInt – inserts test of integer variable prior to the inserted call.
  • doIfString – inserts test of String variable prior to the inserted call.
  • done – completes the insertion.
  • pass – makes a variable an argument of the method being called
  • passAs – passes an object reference to the method being called and explicitly sets the argument data type to use in the method's definition
  • passBoolean – makes the specified value a boolean argument of the method being called
  • passDouble – makes the specified value a double argument of the method being called
  • passFloat – makes the specified value a float argument of the method being called
  • passIndex – makes the index of the insert point’s array element an argument of the method being called
  • passInt – makes the specified value an int argument of the method being called
  • passLong – makes the specified value a long argument of the method being called
  • passStack – passes the stack arguments of an addAt’s instruction to the method being called
  • passString – makes the specified value a String argument of the method being called

Overview

How to insert a call

The steps to insert a call into a classfile are:

  • To position the call and identify the method to call, invoke Call.add*(). This returns the Call object you will use in the next three bullets. The position where a call is being inserted is called the insert point.
  • To preface the call with if-tests, call doIf*() for each desired if-test. If you insert multiple if-tests, the effect is:
    if (test1 && test2 && …) theCall(…);
  • To pass arguments to the method, call pass*() for each of its arguments – leftmost to rightmost.
  • To complete the insertion, call done(). However the actual classfile is not modified on disk until saveTrek or endTrek is called.

Other considerations:

  • 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.
  • To allow this insertion to be manipulated as described in the previous bullet, assign it an appropriate change ID. The mname argument of add* is used to set the change ID of an insertion (as well as the name of the method to be called). If it contains name, the change ID and the method's name are both set to name. If it contains id:name, the change ID is set to id, and the method's name is set to name.

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 a call is inserted using addAfter(stmt), a jump to the statement following stmt will not execute the inserted code.

If multiple calls have the same insert point, the inserted code will be ordered from lowest to highest AddXxx priority – and within a priority in the order the AddXxx’s were done. The AddXxx priorities, in increasing order, are addBefore(Trek), addBefore(ClassFile), addBefore(Method), all other Add’s, addAfter(return/throw statement), addAfter(Method), addAfter(ClassFile), addAfter(Trek). Thus code inserted by addBefore(Trek) is guaranteed to precede all other code inserted at the same point, and code inserted by addAfter(Trek) is guaranteed to follow all other code inserted at the same point.

Specifying values

When doing an if-test or passing an argument, you can specify:

  • A run-time constant. At the time of the trek, this may be the value of some variable. The point is that a specific value is inserted into the code being instrumented.
  • A run-time variable. This is specified by entering a variable reference whose leftmost name may be one of:
    • A local variable that is active at the insert point.
    • A static member of the insert point’s class.
    • A non-static member of the insert point’s class if the insert point’s method is not static.
    • The insert point’s value at run-time -- via the special name @ (or pass()).
    • The insert point’s object at run-time -- via the special name @@.
    • /each/package/piece/class/name, which means a static member of the specified class.

The cases where an insert point has a value are:

  • If addAt was done at a Store-type instruction or an iinc instruction, the insert point’s value is the value that is about to be stored.
  • If addAt was done at a Load-type instruction, the insert point’s value is the value that is about to be read.
  • If addAfter(name, return/throw statement) was done, it is the statement’s return value.
  • If addAfter(name, switch statement) was done, it is the switch statement’s key value.

An insert point has an object if it is at a getfield, putfield, invokevirtual, invokespecial, or invokevirtual instruction.

Defining methods in your instrumentation class

Each such method must conform to the following.

The data type of each of its arguments must be defined according to the following rules.

  • If the type of the values being passed is byte, char, or short, the argument must be declared as int.
  • For the other primitive data types, the argument's type must match the value's type.
  • If an object reference is passed using pass, the argument's type must be String for a String value and Object otherwise. (This is so instrumentation methods can be used more broadly).
  • If an object reference is passed using passAs, the argument's type must match the class argument used on the call to passAs. ( The advantage of using passAs("Xxx", value) for an instrumentation method that specifically works on Xxx objects is that you no longer need a statement like: Xxx xxx = (Xxx)arg; at the start of the method).

The method being called can contain any code you wish to write. However if you are instrumenting a package that this method itself calls, you must guard against infinite recursion. For example, if you instrumented com.me.PrintUtil to call MyLogger, then MyLogger cannot blithely call PrintUtil.println. One way to deal with this is to define a flag, say active, that MyLogger would check and set on entry, and clear on exit. So if MyLogger was called and active was already set, MyLogger would return immediately. If you subclass RunLog, you can use the active field inherited from RunLog.

Inserting calls at the start and end of an program

A "simple" program has a well-defined start and end point -- the start and end of its main method. If you need to insert code at these points, you can use addBefore(Trek) and AddAfter(Trek). To get approximately the same effect with applets, you would need to insert code at the start of the applet's start method and at the end of the applet's stop method. To get approximately the same effect with servlets, you would need to insert code at the start and end of the servlet's DoPost and DoGet methods.

In a GUI program, the main thread often terminates before the overall program is done. So for the code you inserted at the end of main to have the desired effect, you would need to cause the main thread to not terminate until your program has finished its work. For example, you could call wait() (in a try block with an empty catch) at the end of your main method, and you could call mainthread.interrupt() when the user closes your program.

Real programs often have multiple end points because they make calls to System.exit (or runtime.exit). Thus to instrument the termination of such programs, you must also insert your desired end point code at each such call.

Fields

public static boolean systemMethodsSafe

JTrek defines a system class to be any whose package starts with java. or sun.. There are some additional code insertion rules for such classes:

  • If an addXxx method is called with an insert point that is within a system class's constructor, null is returned. This is because some JVMs will crash if code is inserted in some such contructors.
  • If an addXxx method is called with an insert point anywhere else within a system class, if (Runlog.sysInited!=0) is inserted before the call. To activate each such call, set RunLog.sysInited to 1 after you have initialized your instrumentation class. This protocol exists because some system methods are called before any user code executes -- meaning before you have initialized your instrumentation class.

If you believe you are only instrumenting system classes to which these issues do not apply, you can disable these rules by setting Call.systemMethodsSafe to true.

Methods

public static Call addAfter(String mname, Trek trek)

Inserts a call at the end of the main class’s main method. The main class is the first class on the application’s command line or passed to the SetClassList method.

Returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call. However if the main class has no main method, then null is returned and the call is not inserted.

If main contains multiple return/throw statements, the call is inserted after all of them.

Parameters:

mname – the qualified-class-name.method-name of the method to call (eg. mypkg.Logger.printValue). The identified method must be a static method.

trek – the call is placed at the end of the main method of the trek identified by trek

public static Call addAfter(String mname, ClassFile cf)

Inserts a call at the end of the specified class’s finalize() method. If the class has no such method, null is returned instead.

Returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call. However if cf identifies a class that has no constructor code – like an abstract class, then null is returned and the call is not inserted.

Parameters:

mname – the qualified-class-name.method-name of the method to call. The identified method must be a static method.

cf – the call is placed at the end of the classfile identified by cf.

public static Call addAfter(String mname, Method meth)

Inserts a call at the end of meth. Also returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call. However if meth identifies a method that contains no code – like an abstract method, then null is returned and the call is not inserted.

If meth contains multiple return/throw statements, the call is inserted at all of them.

Parameters:

mname – the qualified-class-name.method-name of the method to call. The identified method must be a static method.

meth – the call is placed at the end of the method identified by meth.

public static Call addAfter(String mname, Statement stmt)

Inserts a call after stmt. However if the specified statement is a block-starting statement (eg. for, else, try), the call is actually inserted 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 call is inserted after the key value has been loaded on the stack. If it identifies a throw statement, the call is inserted after the throwable has been loaded on the stack. If it identifies a return statement that has a return value, the call is inserted after the return value has been loaded on the stack. If it identifies a return statement that does not have a return value, the call is inserted before the return statement.

Returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call.  However if the call cannot be inserted, null is returned instead.

If you do call.pass() and the statement is a switch statement, throw statement, or a return statement with a return value, the switch key, throwable, or return value is what will be passed to mname.

Parameters:

mname – the qualified-class-name.method-name of the method to call. The identified method must be a static method.

stmt – the call is placed after the statement identified by stmt.

public static Call addAfter(String mname, Statement stmt, boolean truepath)

Inserts a call after the specified if statement. Also returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call. However if the requested path does not exist, null is returned instead. A Then path will exist unless the if statement's if instruction itself does a break or continue. An Else path will exist if the source code contained an else and the compiler did not optimize it away.

Parameters:

mname – the qualified-class-name.method-name of the method to call. The identified method must be a static method.

stmt – the call is placed after the statement identified by stmt, which must be an if statement.

truepath – if true is passed, the call is inserted such that it will be executed only when the if statement evaluates to true. If false is passed, the call is inserted such that it will be executed only when the if statement evaluates to false.

Example:

When the following statements are scanned:

if (aa > 0)
. aa++;
else aa--;

The following atStartOf(Statement stmt) method would insert a call to watch.ifTrue before the aa++ and a call to watch.ifFalse before the aa--.

if (!stmt.getType() != ST_IF) return;
Call call = Call.addAfter("watch.ifTrue", stmt, true);
call.done();
call = Call.addAfter("watch.ifFalse", stmt, false);
call.done();

public static Call addAt(String mname, Instruction inst)

Inserts a call immediately before the specified instruction. Returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call. (In particular, if you do call.passStack(), the instruction’s stack arguments will be passed to mname). However if the call cannot be inserted, null is returned instead.

Parameters:

mname – the qualified-class-name.method-name of the method to call. The identified method must be a static method.

inst – the instruction at which to insert code

Example:

When the following statement is scanned:

obj.end = temp + obj.size;

and inst is the Store instruction for setting the new value of obj.end, the following at(Instruction inst) method would insert a call to watch.printValue, passing it this new value.

Call call = Call.addAt("watch.printValue", inst);
call.pass();
call.done();

public static Call addBefore(String mname, Trek trek)

Inserts a call at the start of the main class’s main method. The main class is the first class on the application’s command line or passed to the SetClassList method.

Returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call. However if the main class has no main method, then null is returned and the call is not inserted.

Parameters:

mname – the qualified-class-name.method-name of the method to call. The identified method must be a static method.

trek – the call is placed at the start of the main method of the trek identified by trek

public static Call addBefore(String mname, ClassFile cf)

Inserts a call at the start of the specified class’s constructors.

Returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call. However if cf identifies a class that has no constructor code – like an abstract class, then null is returned and the call is not inserted.

Parameters:

mname – the qualified-class-name.method-name of the method to call. The identified method must be a static method.

cf – the call is placed at the start of the constructors identified by cf.

public static Call addBefore(String mname, Method meth)

Inserts a call at the start of meth. Also returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call. However if meth identifies a method that contains no code – like an abstract method, then null is returned and the call is not inserted.

Parameters:

mname – the qualified-class-name.method-name of the method to call. The identified method must be a static method.

stmt – the call is placed at the start of the method identified by meth.

public static Call addBefore(String mname, Statement stmt)

Inserts a call before stmt. Also returns a Call object so you can invoke doIfXxx, passXxx, and done to define the tests and arguments that you want for this call. However if the call cannot be inserted, null is returned instead.

Parameters:

mname – the qualified-class-name.method-name of the method to call. The identified method must be a static method.

stmt – the call is placed before the statement identified by stmt.

public void discard()

Aborts building of the call. You can now discard the Call object. This might be used if pass(variable) threw an exception, for example.

public void doIf(String var1, int opr, String val2)

Inserts an if-test prior to the call. The operands derived from var1 and val2 must have the same data type. If either is invalid (say from incorrect user input), an exception is thrown.

    • If they are numeric or boolean, if (opd1 opr opd2) is inserted.
    • If they are objects, if (opd1.equals (opd2)) or if (!opd1.equals (opd2)) is inserted. Note that if the operands are strings, a String compare will be done because the String class overrides Object.equals.

Parameters:

var1 – a variable reference that is valid at the current insert point.

opr – one of the DOIF constants defined in the Trek class. But if the variables are objects or boolean, only DOIF_EQ and DOIF_NE are allowed.

val2 –  "xxx", 'xxx', true, false, a numeric constant (eg. -31.4), or a variable reference that is valid at the current insert point. Note that var1 is compared to the string xxx if val2 has either of the first two formats. That is, the quotes are not part of the value.

public void doIfBoolean(String var, boolean val)

If val is true, inserts if (var) prior to the call. If val is false, inserts if (!var) prior to the call.

Parameters:

var – a variable reference whose data type is boolean.

val – a boolean value

public void doIfInt(String var, int opr, int val)

Inserts if (var opr val) prior to the call.

Parameters:

var – a variable reference whose data type is int, short, char, or byte.

opr – one of the DOIF constants defined in the Trek class.

val – an int value

public void doIfString(String var, int opr, String val)

Inserts if (var.equals(val)) or if (!var.equals(val)) prior to the call.

Parameters:

var – a variable reference whose data type is String.

opr – either DOIF_EQ or DOIF_NE.

val – a String value

public void done()

Inserts the instructions to do the call into the in-memory representation of the program. If there is already inserted code there, the new code is positioned according to the priority rules described in Position of the inserted code.

public void pass()

The call’s insert point must have a value, and that value is appended to the argument list of the call being built.

The data type of this argument is set to the value's data type unless the value is a non-String object or a short integer (ie. a byte, a char, or a short). In these two cases, it is respectively set to Object and int.

public void pass(String variable)

Appends the specified variable reference to the argument list of the call being built.

The data type of this argument is set to the value's data type unless the value is a non-String object or a short integer (ie. a byte, a char, or a short). In these two cases, it is respectively set to Object and int. Also if this argument is a method reference whose return type is void, the method is called but the String "<Side Effects>" is what is actually added to the argument list.

Parameters:

variable – a variable reference that is valid at the current insert point. If it is invalid (say from incorrect user input), an exception is thrown.

public void passAs(String classname)

The call’s insert point must have a value that is an object reference, and that value is appended to the argument list of the call being built.

The data type of this argument in the method reference is set via classname. If classname is null, the argument's data type is set to the value's class. If it is non-null, it is set to the specified class and that class must be a superclass of the value's class.

Parameters:

classname – the fully qualified name of the class to pass the object reference as, or null

public void passAs(String classname, String variable)

Appends the specified object reference to the argument list of the call being built.

The data type of this argument in the method reference is set via classname. If classname is null, the argument's data type is set to the value's class. If it is non-null, it is set to the specified class and that class must be a superclass of the value's class. Thus except for String references, pass(object ref) is equivalent to passAs("java.lang.Object", object ref).

Parameters:

classname – the fully qualified name of the class to pass the object reference as, or null

variable – a variable reference that is valid at the current insert point

public void passBoolean(boolean ival)

Appends the specified boolean value to the argument list of the call being built

Parameters:

ival – the boolean value to use

public void passDouble(double dval)

Appends the specified double value to the argument list of the call being built

Parameters:

dval – the double value to use

public void passFloat(float fval)

Appends the specified float value to the argument list of the call being built

Parameters:

fval – the float value to use

public void passIndex()

Appends an array element’s index to the argument list of the call being built. The Call object must be from an addAt (method name, instruction that loads or stores an array element).

public void passInt(int ival)

Appends the specified integer value to the argument list of the call being built

Parameters:

ival – the integer value to use. May be an integer constant, character constant, or a trek-time variable whose data type is byte, char, short, or int.

public void passLong(long lval)

Appends the specified long value to the argument list of the call being built

Parameters:

lval – the long value to use

public void passStack()

If the Call object is from an addAt(), the instruction’s stack arguments if any are passed. However passStack(desc) must be used for instructions with untyped stack arguments.

passStack must be used with care. If the arguments generated by passStack do not match the argument data types expected for the addAt()'s mname, an error will occur at runtime.

public void passStack(String desc)

If the Call object is from an addAt(), the instruction’s stack arguments are passed. However passStack(desc) may only be used for instructions with untyped stack arguments: dup*, pop*, and swap.

passStack must be used with care. If the arguments generated by passStack do not match the argument data types expected for the addAt()'s mname, an error will occur at runtime.

Parameters:

desc – the one or two descriptors that identify the data type(s) of this instruction's stack arguments, as described in The Java Virtual Machine Specification. For example, if a dup2's stack arguments were two floats, desc should be FF.

public void passString(String str)

Appends the specified String value to the argument list of the call being built

Parameters:

str – the String value to use

Example:

Statement stmt = any Statement object;
Call call = Call.addAfter("class.gather-method", stmt);
call.passString(stmt.toString());
call.done();

would call the specified gather-method, passing one argument containing the source code for the statement after which the call is being inserted.