Introduction to Trek Class Library

The Trek class library models Java code as a hierarchy of objects. A trek is a journey thru a list of classfiles and the objects in them. Each classfile contains fields and methods. A method in turn contains local variables and statements, and finally a statement contans instructions. The list of classfiles is established by initTrek.

There are three types of applications you can write:

  1. A coding-time application scans a program’s classfiles and generates a report about some aspect of the program.
  2. A run-time application modifies the code of a program. Thus there are two phases to using a run-time application. First you run your Trek-based application to instrument your program. Then you run your program to allow the instrumented code to generate the desired log of run-time activity.
  3. A tuning application also modifies the code of a program, such as to remove debugging assertions in production code.

The source code for the JTrek applications included with JTrek are in the src directory of the JTrek kit.

Summary of Trek API

  • The xxxTrek methods are for managing a trek thru a program hierarchy. You can do an automatic scan, a manual scan, or a combination of both. In an automatic scan, you do not write code to navigate the hierarchy, you simply supply atXxx methods for the places in the scan where you want to perform an action.
  • The atStartOf, atEndOf, atReference methods in the Trek class are the methods that you can selectively override to control what actions are performed during an automatic scan of a program hierarchy.
  • The firstXxx, lastXxx, next, and prev methods are for manually scanning thru the hierarchy.
  • The addXxx, append, delete, undoChange, passXxx, doIfXxx, and done methods are for modifying classfiles.
  • The toXxx methods are for returning presentation strings for the various code objects.
  • The getXxx and has/isXxx methods are respectively for returning value properties and boolean properties of the various code objects.
  • There are getCmdXxx convenience methods for setting the properties of a trek from an application command line, and there are setXxx methods for setting the properties of a trek when these convenience methods are not used.

Coding a simple Trek application

You would usually:

  • Import the Trek package's symbols – import dec.trek.*;
  • Have a class that subclasses the Trek class – such as class mytrek extends Trek {…}. This class must at least contain any Trek atXxx methods you need, but it is usually convenient to place all your top-level code in this class.
  • Provide an atXxx method for each point in an automatic scan where you wish to perform an action. For example, if you wanted to output the number of statements in each method of a program, you would provide atStartOf(Statement stmt) and atEndOf(Method method). These would respectively count each statement scanned, and output and clear the count when the end of a method was scanned.
  • Have a method that actually does the trek.

The method that does the trek should be coded as follows.

  • The code managing the trek should be enclosed in a try block, and its catch block should call toErrorMessage. If you consider the caught error a program failure, you would normally then call System.exit(1).
  • You should create an object of your subclass – such as mytrek trek = mytrek();
  • If the trek is being done in a console application, you can use getCmdLine to gather the input arguments of the trek. Otherwise you will need to call setClassList et al yourself.
  • If the trek generates a report file, you should open it now.
  • You should init the trek, scan the program hierarchy, and end the trek. If you do an automatic scan using doTrek, it will call initTrek and endTrek automatically if initTrek has not been called before doTrek is called.
  • If the trek generates a report file, you should close it now.

Example application that does an automatic scan

The following application would display the declaration of each class in the program identified via argv.

import dec.trek.*;
class mytrek extends Trek {

  public static void main(argv[]) {
    try {
      mytrek trek = new mytrek();
      trek.getCmdLine(argv);
      trek.doTrek();
    }
    catch (Exception e) {
      System.out.println(trek.toErrorMessage(e));
      System.exit(1);
    }
  }

  // Called by doTrek as its scan reaches each classfile.
  public atStartOf(ClassFile cf) {
    System.out.println(cf.toString());
  }
}

Example application that does a manual scan

This is the same application – except that it does a manual scan.

import dec.trek.*;
class mytrek extends Trek {

  public static void main(argv[]) {
    try {
      mytrek trek = new mytrek();
      trek.getCmdLine(argv);
      initTrek();

      for (ClassFile cf=firstClassFile(); cf!=null; cf=cf.next()) {
        System.out.println(cf.toString());
      }
      endTrek();
    }

    catch (Exception e) {
      System.out.println(trek.toErrorMessage(e));
    }
  }
}

Running a Trek application

The most simple case requires a command of the form: java mytrek class, where class is the fully qualified name of a class you want to scan. However for this command to actually work, your environment needs to be setup correctly:

  • The directory of java.exe must be in your path.
  • The directory of the Trek class library must be in your Java classpath.
  • The location of class's package, and the other packages within the scope of the scan, must be in your Java classpath.

If these things had not been setup, you would have to supply additional information on the command line to run this program:

  • The full path of java.exe.
  • A -classpath switch before mytrek that included the directory of the Trek class library.
  • An -ip switch after main-class that specified the locations of the packages being scanned (eg. "." for the current directory).

If a program and its class libraries are large enough, it is possible for Java to run out of memory when it is executing a Trek application. If this occurs, an OutOfMemoryException will occur. To fix this, you need to increase the amount of heap memory available to the Java run-time. With java.exe for example, you do this by specifying the –mxvalue switch.

Creating and running an instrumented program

This section is an introduction to the Call class. Use of the Call class does not require any knowledge of the Java Virtual Machine or its classfile format. The Trek class library also provides the Code class, which can be used to insert and delete specific instructions in a program.

Example application that instruments a program

The following application would instrument classes to display the name of each method that is called during a run of the instrumented program.

import dec.trek.*;
class mytrek extends Trek {

  public static void main(argv[]) {
    try {
      mytrek trek = new mytrek();
      trek.getCmdLine(argv);
      trek.doTrek();
    }
    catch (Exception e) {
      System.out.println(trek.toErrorMessage(e));
    }
  }

  // Called by doTrek as its scan reaches each method.
  public atStartOf(Method meth) {
    Call call = Call.addBefore("Example.display", meth);
    call.passString(meth.toName());
    call.done();
  }
}

You would also need to create the class that contains then method(s) called by the inserted code:

class Example{
  public static void display(String name) {
    System.out.println(name);
  }
}

Using this JTrek application

The following commands would instrument MyProg.class and run the instrumented version -- assuming the classes of  mytrek, Example, MyProg, and the Trek library were in the default classpath.

java mytrek MyProg
java MyProg

Managing instrumented classfiles

java mytrek MyProg will overwrite the MyProg.class it scans. Thus you will need to rectify this before you can do your next un-instrumented run. Similarly if you instrument class libraries used by MyProg, by specifying  java mytrek MyProg -sc user or a broader scope, JTrek will create instrumented copies of them under your current directory. Thus if your normal classpath has "." near its beginning, you will need to delete these files before you can do your next un-instrumented run.

One way to avoid these issues is to create your instrumented classfiles in a separate directory tree. To do this:

  • Specify -op directory when you run mytrek (to create directory\MyProg.class   and leave the original MyProg.class unchanged, and so on).
  • Include the instrumented classfiles in your classpath only when you want to do an instrumented run:
    java -classpath directory;normal-classpath MyProg

If you were to do java mytrek MyProg twice, the instrumented MyProg.class would be the input file the second time. Thus you would end up with a MyProg.class that contained 2 calls to Example.display in each of its methods. To help you avoid doing this by accident, JTrek:

  • Inserts a com.compaq.JTrekCreated attribute when it saves an instrumented classfile.
  • Shows this attribute when you dump a classfile and specify -inc a.
  • Outputs a an "already instrumented" message to trek.log when it scans a JTrek-created class.

Error handling and debugging

If you call a method in the Trek class library and it throws an error because it directly failed, the Trek class library will not catch the error. You of course can catch the error in your application, as described above. Most methods can throw errors as a result of programmer error, such as passing a null for a required argument. In general, method descriptions only document those errors that the program might want to "handle" at runtime.

If a method in the Trek class library throws an error about a single object while processing a region of the program hierarchy, the Trek class library will catch the error, log it, and continue processing. The Trek object’s log stream property controls where errors are logged to. Initially it is set to current-directory/trek.log. To set it to a different value, call setLogStream. After endTrek() completes processing of a trek, it checks if any errors have been logged. If so, it throws an "Errors logged" message that your application can catch, as described above.

If you need to know what code threw an error or you wish to report a bug in the Trek class library, rerun the application with the –debug switch set. This will cause a stack trace to be included in the output describing the thrown error.

An instrumented classfile retains whatever debugger hooks the compiler put into it. In other words, you can run a debugger on an instrumented program. Thus you can set a breakpoint in a method called by code that you inserted. For example, you could use the watch application to insert a number of conditional watchpoints in your program, and then set a breakpoint at the method (ie. WatchLog.printLine) that is accessed when a watchpoint is actually hit.

Advanced topics

Coding a GUI Trek application

A GUI Trek application is typically organized as follows. You:

  1. Construct a Trek object
  2. Give the user a way to choose the program to process
  3. Call setClassList, and possibly other setXxx methods.
  4. Call initTrek
  5. Do one or more treks thru the program
  6. Call endTrek
  7. Go back to step 2, or exit

If you want to layer an application on simple Trek applications, you can set this up by expanding step 1 to be:

SimpleApp1 trek = new SimpleApp1();
SimpleApp2 subtrek = new SimpleApp2();
subtrek.joinTrek(trek);

If your application instruments programs and your user can insert and remove changes, you can code this by calling saveTrek and then cleanTrek each time you need to write the changes to disk.

If you want to maintain a status line during a trek, you should:

  • Code a class that implements the VerboseSink interface.
  • Add trek.setVerboseSink(new your-class()) to step 1.
  • Bracket step 5 with trek.setVerboseMode(true) and trek.setVerboseMode(false).

If you want to save information about program objects between runs of your application, you can use toObjectId and getObject for this.

Instrumentation via redirection

Sometimes you want to understand how a program uses a class rather than insert code in the program itself. Whether you instrument the used class via JTrek or manually modify it, you can make your program use the instrumented class simply by placing its location earlier in the classpath than the real class. Similarly you can make a class in your program inherit different methods by placing an instrumented version of its superclass earlier in the classpath than the real superclass.

To facilitate doing the same kind of thing on a per-method basis, the Trek class library provides methodref.redirect. By scanning thru the constant pools of your program and using redirect, you can make each call to a method use your copy of the method instead of the real method.

Using the Trek class library from multiple threads

Any method in the Trek class library can be simultaneously called from another thread if the calls are applied to objects from different treks.

After an initTrek has completed, multiple threads can freely access objects in its trek. But if a thread wants to modify objects in this trek, it must synchronize these operations with any other threads that access or modify objects in this trek. The methods that can modify objects in a trek are Trek.cleanTrek, Trek.endTrek, and Call.done, Code.done, and Instruction.delete, Statement.delete, and Statement.undoChange.