Bytecode Notes

The .class expression and String +

The java language form Foo.class is implemented in bytecode with a call to Class.forName guarded by an exception handler catching a ClassNotFoundException.

The java language + operator, when applied to String arguments, is implemented in bytecode by calls to StringBuffer.append.

In both of these cases, the current AspectJ compiler operates on the bytecode implementation of these language features; in short, it operates on what is really happening rather than what was written in source code. This means that there may be call join points to Class.forName or StringBuffer.append from programs that do not, at first glance, appear to contain such calls:

  class Test {
      void main(String[] args) {
          System.out.println(Test.class);        // calls Class.forName
          System.out.println(args[0] + args[1]); // calls StringBuffer.append
      }
  }

In short, the join point model of the current AspectJ compiler considers these as valid join points.

The Handler join point

The end of exception handlers cannot reliably be found in Java bytecode. Instead of removing the handler join point entirely, the current AspectJ compiler restricts what can be done with the handler join point:

  • After and around advice cannot apply to handler join points.
  • The control flow of a handler join point cannot be detected.

The first of these is relatively straightforward. If any piece of after advice (returning, throwing, or "finally") would normally apply to a handler join point, it will not in code output by the current AspectJ compiler. A compiler warning is generated whenever this is detected to be the case. Before advice is allowed.

The second is that the control flow of a handler join point is not picked out. For example, the following pointcut

  cflow(call(void foo()) || handler(java.io.IOException))

will capture all join points in the control flow of a call to void foo(), but it will not capture those in the control flow of an IOException handler. It is equivalent to cflow(call(void foo())). In general, cflow(handler(Type)) will not pick out any join points, the one exception to this is join points that occur during the execution of any before advice on the handler.

This does not restrict programs from placing before advice on handlers inside other control flows. This advice, for example, is perfectly fine:

  before(): handler(java.io.IOException) && cflow(void parse()) {
      System.out.println("about to handle an exception while parsing");
  }

A source-code implementation of AspectJ (such as AspectJ 1.0.6) is able to detect the endpoint of a handler join point, and as such will likely have fewer such restrictions.

Initializers and Inter-type Constructors

The code for Java initializers, such as the assignment to the field d in

  class C {
      double d = Math.sqrt(2);
  }

are considered part of constructors by the time AspectJ gets ahold of bytecode. That is, the assignment of d to the square root of two happens inside the default constructor of C.

Thus inter-type constructors will not necessarily run a target type's initialization code. In particular, if the inter-type constructor calls a super-constructor (as opposed to a this constructor), the target type's initialization code will not be run when that inter-type constructor is called.

  aspect A {
      C.new(Object o) {} // implicitly calls super()

      public static void main(String[] args) {
         System.out.println((new C()    ).d);    // prints 1.414...
         System.out.println((new C(null)).d);    // prints 0.0
  }

It is the job of an inter-type constructor to do all the required initialization, or to delegate to a this constructor if necessary.