Pencarian

Rss Posts

 

 

 

JSR 292 Goodness: Fast code coverage tool in less than 10k

Feb 12, 2011

JSR 292 introduces a new bytecode instruction invokedynamic but also several new kind of constant pool constants. Which means that most of the tools that parse bytecodes like ASM, BCEL, findbugs or EMMA will need to be updated to be java 7 compatible.
EMMA is a code coverage tool, a tool that helps developers to know if their tests cover all the code of the application. While it’s not the only code coverage tool available in Java, it’s the most popular from my personal experience.
In this blog entry, I would like to show how to write a simple code coverage tool indycov that use JSR 292 API to have a runtime overhead close to zero.

How a code coverage tool works ?

A code coverage tool records all paths taken when running the application and checks at the end if all lines of codes was recorded.
By example, if I run the code below with no argument, it will print "foo" and "bar" and the code coverage tool will say that the else branch that prints "baz" will be not covered.

public static void main(String[] args) {
    System.out.println("foo");
    if (args.length == 0) {
      System.out.println("bar");
    } else {
      System.out.println("baz");
    }
  }

To record if an instruction was executed or not, code coverage tools add a probe which is a small amount of bytecodes that will call the runtime of the tool to say: "I have visited this instruction".
In fact, tools, only add probes where necessary, at the begining of each basic block of the control flow. A basic block is a collection of instructions without any jump (return, thow, if, break etc).
By example, the code above has 4 basic blocks: the once printing "foo", the one printing "bar", the one printing "baz" and the one containing the return at the end of the method.

So a code coverge tool is a tool that find basic blocks and add probes at the begining of each one.

Using JSR 292 API to implement a code coverage tool.

Finding basic block is easy with bytecode that come from 1.6 or 1.7 compiler because the compiler is required to add stack maps
in the bytecode flow. Stack maps are used to verify the bytecode in linear time and are inserted at the join points of the control flow.
So finding basic block in a 1.7 compatible bytecode can be done in one pass thanks to the stack maps info inserted by the compiler.

All existing code coverage tools have an impact on the performance of the application because the code of the probe is executed each time you call a basic block even if it should be executed once.
If you are a regular reader of this blog, you already know how to create a probe that will be executed once. The trick is use use invokedynamic, to record the visit in the bootstrap method and
to use a target method handle that is equivalent to no-op. So subsequent call will not execute any code.

main([Ljava/lang/String;)V
    INVOKEDYNAMIC probe ()V [fr/umlv/indycov/RT#bsm, 1]
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "foo"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    ALOAD 0
    ARRAYLENGTH
    IFNE L0
    INVOKEDYNAMIC probe ()V [fr/umlv/indycov/RT#bsm, 2]
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "bar"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GOTO L1
    L0
    FRAME SAME
    INVOKEDYNAMIC probe ()V [fr/umlv/indycov/RT#bsm, 3]
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "baz"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    L1
    FRAME SAME
    INVOKEDYNAMIC probe ()V [fr/umlv/indycov/RT#bsm, 4]
    RETURN

A no-op, is a method handle that takes nothing and return void. This method handle can be retrieved with Methodhandles.identity(void.class).
So the bootstrap method is the following. The first line records that the basic block with number ‘index’ is visited.

  public static CallSite bsm(Lookup lookup, String name, MethodType type, Object index) {
    classValue.get(lookup.lookupClass()).cover((Integer)index);
    return new ConstantCallSite(MethodHandles.identity(void.class));
  }

The code of the prototype is freely available (as attachment of this blog)  and works like an agent.
It relies on ASM 4 (still in beta) to do the bytecode transformation.

Side note: This prototype doesn’t handle exception correctly. If an exception is thrown, it will escape from the basic block without ending it.
How to modify the prototype to take care of exception is let to interrested readers.

Running the code with one argument "foo"

  java -XX:+UnlockExperimentalVMOptions -XX:+EnableInvokeDynamic -javaagent:lib/indycov.jar -cp test-classes/ TestCoverage foo

will print

  foo
  baz
  TestCoverage: no coverage for line(s) 2 to 2
  TestCoverage: no coverage for line(s) 5 to 6

line 2 is the declaration of the class, it’s because javac adds a default constructor which is not used.
lines 5 to 6 are the ones that print "bar".

If you want to play with it don’t forget to compile your sources with the debug flag on. Otherwise, the generated bytecodes will not contain mapping information between opcodes and line numbers.

Cheers,
Rémi

Attachment Size
indycov.zip 681.84 KB

Bob Balfe: Google contributes GUI designer tool to Eclipse!

Dec 15, 2010

Wow, perfect timing for our Lotusphere presentation. You can download the tool right from Google or you can read about the functionality it brings.

Tools being donated include the WindowBuilder Java UI design tool as well as CodePro Profiler, a runtime Java analysis gauging factors like memory leaks. Both tools became Google property when the company bought Instantiations in August; they will now become open source projects at Eclipse. WindowBuilder has been used for development related to Standard Widget Toolkit, GWT (Google Web Toolkit), and Swing.

Check out the full article on InfoWorld.