APPENDIX 15.A SIMPLE CODE COVERAGE


In Section 15.6.3.2, we presented code coverage as an example of an application that requires "interception" at the line or statement level. Here, we show how a tool that collects line coverage information can be implemented easily as a code transformer.

In the following text, line numbers in parentheses refer to the source code shown in Listing 15-2. The example is fully functional. It can be downloaded from the JMangler web site along with other sample transformers (see http://javalab.cs.unibonn.de/research/jmangler/).

Listing 15-2. Source code for the code coverage tool
  (1)  import de.fub.bytecode.*;  (2)  import de.fub.bytecode.generic.*;  (3)  import de.fub.bytecode.classfile.*;  (4)  import org.cs3.jmangler.*;  (5)  import org.cs3.jmangler.tau.*;  (6)  import org.cs3.jmangler.supplier.*;  (7)  import java.util.*;  (8)  public class CodeCoverTransformer  (9)    implements CodeTransformerComponent { (10)    public static void recordCoveredLine (11)                            (String className, int line) (12)      {System.out.println (13)        ("covered line "+line+" in class "+className);} (14)    private static String recorderMethName = (15)                              "recordCoveredLine"; (16)    private static Type recorderReturnType = Type.VOID; (17)    private static Type[] recorderArgTypes = new Type[2]; (18)     {recorderArgTypes[0] = Type.STRING; (19)      recorderArgTypes[1] = Type.INT;} (20)    public void addRecordCaller (21)      (ClassGen classGen, ConstantPoolGen poolGen, (22)       InstructionFactory factory, int lineNo, (23)       InstructionList intoList, (24)       InstructionHandle beforeHandle) (25)    { (26)      InstructionList recordCaller = (27)                          new InstructionList(); (28)      InstructionHandle callHandle = recordCaller.append( (29)        new LDC_W(poolGen.addString (30)          (classGen.getClassName()))); (31)      recordCaller.append (32)        (new LDC_W(poolGen.addInteger(lineNo))); (33)      recordCaller.append( (34)        factory.createInvoke("CodeCoverTransformer", (35)                             recorderMethName, (36)                             recorderReturnType, (37)                             recorderArgTypes, (38)                             Constants.INVOKESTATIC)); (39)      intoList.insert(beforeHandle, recordCaller); (40)      intoList.redirectBranches(beforeHandle, (41)                                callHandle); (42)    } // addRecordCaller (43)    public void transformCode (44)                      (UnextendableClassSet classSet) (45)    { (46)      Iterator classIterator = (47)        classSet.getIteratorForTransformableClasses(); (48)      while (classIterator.hasNext()) { (49)        ClassGen classGen = (50)          (ClassGen)classIterator.next(); (51)        String className = classGen.getClassName(); (52)        if (className.equals(this.getClass().getName())) (53)          continue; (54)        ConstantPoolGen poolGen = (55)          classGen.getConstantPool(); (56)        InstructionFactory factory = (57)          new InstructionFactory(classGen); (58)        Method[] methods = classGen.getMethods(); (59)        for (int methInd = 0; (60)             methInd < methods.length; (61)             methInd++) { (62)          Method meth = methods[methInd]; (63)          if (meth.isNative() || meth.isAbstract()) (64)            continue; (65)          MethodGen methGen = (66)            new MethodGen(meth, className, poolGen); (67)          InstructionList instList = (68)            methGen.getInstructionList(); (69)          LineNumberGen[] lines = (70)            methGen.getLineNumbers(); (71)          for (int lineInd = 0; (72)               lineInd < lines.length; (73)               lineInd++) { (74)            LineNumberGen line = lines[lineInd]; (75)            addRecordCaller(classGen, poolGen, factory, (76)                           line.getSourceLine(),instList, (77)                           line.getInstruction()); (78)          } (79)          methGen.setMaxStack(); (80)          methods[methInd] = methGen.getMethod();} (81)        classGen.setMethods(methods); (82)      } // while (83)    } // transformCode (84)    public void sessionStart() {} (85)    public void sessionEnd() {} (86)    public String verboseMessage() {return toString();} (87)  } // CodeCoverTransformer 

15.A.1. Implementation of the CodeCoverTransformer

First of all, we need to implement a class CodeCoverTransformer that implements the interface CodeTransformerComponent (8-9). Inside the CodeCoverTransformer class, we provide all the necessary ingredients for our tool, including the actual recordCoveredLine method (10-13). It is called at runtime, while all the other elements of CodeCoverTransformer are executed at load time. In our simple code coverage tool, recordCoveredLine just prints a message to the standard output stream.

The most interesting method required by the CodeTransformerComponent interface is transformCode (4383) that is called by the JMangler framework at load time (according to the Hollywood principle: "Don't call us, we'll call you!"). JMangler passes an UnextendableClassSet to transformCode that contains all the classes loaded by a classloader within a session. The getIteratorForTransformableClasses method allows us to iterate over all classes in that set that still need to be transformed, which is what we want to do (46-48). At each step, the iterator returns an instance of ClassGen[7] (49).

[7] This class is part of the BCEL library and represents a Java class as a modifiable data structure. BCEL distinguishes between modifiable entities that end in ...Gen (like ClassGen, MethodGen, FieldGen, and so on) and their non-modifiable counterparts.

From each ClassGen instance, we request the following information that we need later on:

  • Its name (51)

  • Its constant pool (54), a standard ingredient of java class files that contains all the constant data used within a class[8]

    [8] This includes constant numbers and string literals, but also class, method, and field names, for example.

  • An instruction factory (56), a BCEL utility class that allows the creation of complex bytecode instructions in a convenient way

  • Its methods as declared in the corresponding Java source file (58)

Before we proceed, we perform a sanity check in order to prevent the CodeCoverTransformer from transforming itself (5253). We then iterate over all non-native and non-abstract methods of the current transformable class (5964)that is, all the methods that are actually implemented in Java bytecode. We create an instance of MethodGen for each of those methods in order to be able to modify it (6566). Next, we request an InstructionList instance (67-68) and the line number table (6970) from the current method. The instruction list is a modifiable data structure that represents the actual method code.

The line number table [36] allows us to determine the positions in the instruction list for each line of the original Java source code. For instance, in Table 15-2, the statements implementing line 3 start at program counter value 0.

Table 15-2. A Sample Line Number Table

Source File

Byte Code

 1 public class Sample { 2   void sayHello() { 3     System.out.println               ("Hello"); 4     return; 5   } 6 } 

 Method void sayHello()    0 getstatic #8    3 ldc #1    5 invokevirtual #9    8 return 

 

Line Number

 

Program Counter

 

3

 

0

 

4

 

8


So we iterate over the line number table (71-78) and call the auxiliary method addRecordCaller (75-77) to perform the modification at each step (see the following).

Since calls to recordCoveredLine make use of the Java operand stack, we need to call setMaxStack for each MethodGen instance afterwards (79) in order to let BCEL determine its correct (static) size. Then we convert the MethodGen instance back to its non-modifiable form (as required by BCEL) (80) and finally write all the modified methods back to the ClassGen instance (81).

The remaining actions required by BCEL and the Java classloader framework are all performed by JMangler, so we have completed all the necessary steps by now.

The addRecordCaller method (20-42) works as follows. First, it creates the call sequence by generating a new instruction list (26-38). This instruction list consists of first pushing the name of the current class onto the operand stack (LDC_W[9]) (2830), then pushing the line number onto the operand stack (also LDC_W) (3132), and finally calling recordCoveredLine (3338), which pops the two arguments from the operand stack. The latter call is generated by createInvoke in InstructionFactory that takes some meta information about recordCoveredLine (the name of its defining class, its own name, and its return and parameter types) and the INVOKESTATIC instruction for class method invocations; createInvoke also takes care of the correct handling of the constant pool.

[9] Load Constant with wide indexwide index is an index into the constant pool possibly greater than 255.

This new call sequence is inserted into the instruction list (39). We insert the new call sequence before the given position so that a source line is recorded even when it throws an exception (which may be the correct specified behavior of your program at that line). Because the current line possibly is the branch target of other instructions (for example, of an if/then/else statement), we finally need to redirect all the branches from the old position to the start of the new call sequence in order to have the line number recorded in all cases (4041).

15.A.2. Activation

In order to activate the CodeCoverTransformer described in the previous section, we need to define a configuration file for JMangler as partly explained in Section 15.5.3. For our purposes, the following three lines are sufficient.

 transformer CodeCoverTransformer {   name =   org.cs3.jmangler.samples.codecoverage.CodeCoverTransformer; } 

The first line of the configuration file assigns a short name to the fully qualified class name given in the third line.[10] This declaration also activates the named transformer.

[10] Such short names are convenient in more sophisticated configuration files that, for example, express relationships between different transformers.

The scheme of using configuration files for transformer activation eases deployment in two ways: there is no need to write any code in order to apply a certain transformer, and the application of a transformer is independent of the base program to which it should be applied. The configuration file containing these lines, say codecover.config, can be used on arbitrary Java programs.

 jmangler --cf codecover.config AnArbitraryJavaProgram 

This invocation starts the JVM, loads JMangler and the transformers specified in the configuration file, and then initiates execution of the program passed as a command line parameter.

Now suppose we are given the slightly more interesting variant of the standard "Hello, World" application:

 (1)  public class HelloWorld { (2)    public static void main(String[] args) { (3)      if (args.length > 0) (4)        {System.out.println(args[0]);} (5)      else (6)        {System.out.println("Hello, World!");} (7)    } (8)  } 

We start the CodeCoverTransformer on an invocation of "HelloWorld" that has no arguments and observe the output.

[View full width]

>> jmangler --cf codecover.config HelloWorld covered line 3 in class HelloWorld covered line 6 in class HelloWorld Hello, World! covered line 7 in class HelloWorld[11]

[11] Line 7 is covered because the Java compiler places the implicit return statement for method main at that line.

Obviously, line 4 has not been covered. So we conclude that in order to guarantee complete coverage of all possible branches, we need to extend our test suite by a call of the "Hello, World" application that has a non-empty argument.

We hope that this example conveys the following facts. On the one hand, a JMangler user needs an above-average understanding of the internal workings of the Java Virtual Machine, its instruction set, its class file format, and a class file transformation framework like the BCEL library. So JMangler is clearly a tool for experts. On the other hand, JMangler allows one to implement powerful transformers in a relatively convenient way, as illustrated by the compact and straightforward code given in Listing 15-2.



Aspect-Oriented Software Development
Aspect-Oriented Software Development with Use Cases
ISBN: 0321268881
EAN: 2147483647
Year: 2003
Pages: 307

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net