Following
is a walkthrough of adding a new simple pointcut pattern to aspectj.
It's based on the walkthrough provided in
"docs/developer/compiler-weaver/index.html" under the docs module of
the org.aspectj project checked out from dev.org, but with many extra
steps to make it work with the current version of aspectj.
Target syntax with an example
What
we are about to add is a simple pointcut pattern, called "throw()",
which matches every point in the bytecode that throws an exception.
Following is the test case we want to pass:
// Throws.aj
public class Throws {
static void willThrow() {
throw new RuntimeException("expected exception ");
}
public static void main(String[] args) {
try {
willThrow();
} catch (RuntimeException re) {
}
}
}
aspect A {
before(): withincode(void willThrow()){
System.out.println("about to execute: " + thisJoinPoint);
}
before(Throwable t): throw() && args(t){
System.out.println("about to throw: " + t + " at " + thisJoinPoint);
}
}
The expected output from this example is:
about to execute: call(java.lang.RuntimeException(String))
about to execute: throw(throw)
about to throw: java.lang.RuntimeException: expected exception at throw(throw)
The first two lines are produced by the first before advice: the
first
line is before the execution of constructor of RuntimeException and the
second line is before the execution of the second before advice. The
third line is produced by the second before advice, which is right
before the ATHROW bytecode in the willThrow() method.
Part1: make the parser recognize "throw()".
The
pointcut patterns are parsed by
weaver/src/org/aspectj/weaver/patterns/PatternParser.java. Let's add
the following code into
org.aspectj.weaver.patterns.PatternParser.parseSinglePointcut() before
the last else statement:
else if (kind.equals("throw")) {
eat("(");
eat(")");
return new KindedPointcut(Shadow.Throw,
new SignaturePattern(Member.THROW, ModifiersPattern.ANY,
TypePattern.ANY, TypePattern.ANY, NamePattern.ANY,
TypePatternList.ANY, ThrowsPattern.ANY,
AnnotationTypePattern.ANY));
}
We need to add couple things more to make the added code compilable:
- Modify runtime/src/org/aspectj/lang/JoinPoint.java to add a name
for the Throw shadow kind.
static String THROW = "throw";
- Modify weaver/src/org/aspectj/weaver/Shadow.java to
add the Throw shadow kind. This adds a static typesafe enum for the
Throw Kind. The constructor uses the name from the runtime API to
ensure that these names will always match. The '12' is used for
serialization of this kind to classfiles and is part of the binary API
for aspectj. The final 'true' indicates that this joinpoint has its
arguments on the stack. This is because the throw bytecode in Java
operates on a single argument that is a Throwable which must be the top
element on the stack. This argument is removed from the stack by the
bytecode.
public static final Kind Throw = new Kind(JoinPoint.THROW, 12, true);
Also, modify next couple lines to
public static final int MAX_SHADOW_KIND = 12;
public static final Kind[] SHADOW_KINDS = new Kind[] {
MethodCall, ConstructorCall, MethodExecution, ConstructorExecution,
FieldGet, FieldSet, StaticInitialization, PreInitialization,
AdviceExecution, Initialization, ExceptionHandler, Throw,
};
- Also modify the neverHasTarget method of Shadow.Kind
in the same file to include the Throw kind because in Java there is no
target for the throwing of an exception.
public boolean neverHasTarget() {
return this == ConstructorCall
|| this == ExceptionHandler
|| this == PreInitialization
// here
|| this == Throw
//
|| this == StaticInitialization;
}
- In the read method on Shadow.Kind, add another case to read in
our new Shadow.Kind.
case 12: return Throw;
- Modify weaver/src/org/aspectj/weaver/Member.java as follow:
In the read method on Member.Kind, add another case
case 8: return THROW;
After line
public static final Kind HANDLER = new Kind("HANDLER", 7);
add line
public static final Kind THROW = new Kind("THROW", 8);
OK, the new pointcut pattern "throw()" is now
accepted by the parser now. The result of the parsing process is an
instance of KindedPointCut which will be processed later.
Part2: matching the shadows.
After
a class is compiled, org.aspectj.weaver.bcel.BcelClassWeaver.weave()
will be called to perform the weaving process. The entry point for our
new throw pointcut is in the method
private void match(
LazyMethodGen mg,
InstructionHandle ih,
BcelShadow enclosingShadow,
List shadowAccumulator)
Add following code after the test of "i instanceof InvokeInstruction"
else if (i instanceof ATHROW) {
match(BcelShadow.makeThrow(world, mg, ih, enclosingShadow),
shadowAccumulator);
}
This statement creates a new BcelShadow for the current ATHROW
instruction, checkes whether it matches any shadowMungers, and if so,
adds it to the shadowAccumulator.
Obviously,
we need to add the makeThrow method to BcelShadow
(weaver/src/org/aspectj/weaver/bcel/BcelShadow.java) to make this work:
public static BcelShadow makeThrow(
BcelWorld world,
LazyMethodGen enclosingMethod,
InstructionHandle throwHandle,
BcelShadow enclosingShadow)
{
final InstructionList body = enclosingMethod.getBody();
UnresolvedType throwType = UnresolvedType.THROWABLE; //!!! not as precise as we'd like
UnresolvedType inType = enclosingMethod.getEnclosingClass().getType();
BcelShadow s =
new BcelShadow(
world,
Throw,
MemberImpl.makeThrowSignature(inType, throwType),
enclosingMethod,
enclosingShadow);
ShadowRange r = new ShadowRange(body);
r.associateWithShadow(s);
r.associateWithTargets(
Range.genStart(body, throwHandle),
Range.genEnd(body, throwHandle));
retargetAllBranches(throwHandle, r.getStart());
return s;
}
The MemberImpl.makeThrowSignature() method is implemented as
following (in file weaver/src/org/aspectj/weaver/MemberImpl.java):
public static Member makeThrowSignature(UnresolvedType inType, UnresolvedType throwType) {
return new MemberImpl(
THROW,
inType,
Modifier.STATIC,
"throw",
throwType.getSignature());
}
Finally, to make any ATHROW instruction always a matching shadow for
our new pointcut pattern throw(), we need to add code to the
matches(...) method of org.aspectj.weaver.patterns.SignaturePattern
class (weaver/src/org/aspectj/weaver/patterns/SignaturePattern.java)
after line
if (kind == Member.ADVICE) return true;
Add
if (kind == Member.THROW) return true;
Part3: weaving and code generation.
Part2 enables
shadow matching of our new pointcut. The actual weaving of the advice
body does not need to be changed. However, we will get null point
exceptions if we try to compile our test case using current
implementation, starting at line 1039, within method
org.aspectj.weaver.bcel.LazyClassGen.initializeTjp(..) (in file
weaver/src/org/aspectj/weaver/bcel/LazyClassGen.java), which is the
initialization code for ThisJoinPoint. In particular, this method
assume the signature of the BcelShadow we just created has more
information, such as SignatureString, SignatureMakerName,
SignatureType, etc. We need to fix this using following steps:
- Modify weaver/src/org/aspectj/weaver/MemberImpl.java as
following:
- Add
} else if (kind == THROW){
this.returnType = UnresolvedType.forSignature(signature);
this.parameterTypes = UnresolvedType.NONE;
to the constructor
public MemberImpl(
Kind kind,
UnresolvedType declaringType,
int modifiers,
String name,
String signature)
- Add
} else if (kind == THROW){
this.signature = returnType.getErasureSignature();
this.declaredSignature = returnType.getSignature();
to constructor
public MemberImpl(
Kind kind,
UnresolvedType declaringType,
int modifiers,
UnresolvedType returnType,
String name,
UnresolvedType[] parameterTypes)
- Add to method getSignatureMakerName()
}else if (kind == THROW){
return "makeThrowSig";
- Add to getSignatureType()
} else if (kind == THROW) {
return "org.aspectj.lang.reflect.ThrowSignature";
- Add to getSignatureString()
} else if (kind == THROW) {
return getThrowSignatureString(world);
- Add
private String getThrowSignatureString(World world) {
StringBuffer buf = new StringBuffer();
buf.append('-'); // no modifiers
buf.append('-'); // no name
buf.append(makeString(getDeclaringType()));
buf.append('-');
return buf.toString();
}
- Create a new file ThrowSignature.java under
runtime/src/org/aspectj/lang/reflect.
/* ***************
* Copyright (c) 2004 Contributors.
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Common Public License v1.0
* which accompanies this distribution and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* Jim Hugunin initial implementation
* **************/
package org.aspectj.lang.reflect;
import org.aspectj.lang.Signature;
public interface ThrowSignature extends Signature { }
- Create a new file ThrowSignatureImpl.java under
runtime/src/org/aspectj/runtime/reflect
/* ***************
* Copyright (c) 2004 Contributors.
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Common Public License v1.0
* which accompanies this distribution and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* Jim Hugunin initial implementation
* **************/
package org.aspectj.runtime.reflect;
import org.aspectj.lang.reflect.ThrowSignature;
class ThrowSignatureImpl extends SignatureImpl implements ThrowSignature {
ThrowSignatureImpl(Class declaringType) {
super(0, "throw", declaringType);
}
ThrowSignatureImpl(String stringRep) {
super(stringRep);
}
protected String createToString(StringMaker sm) {
return "throw";
}
}
- Add makeThrowSig() method to org.aspectj.runtime.reflect.Factory
(runtime/src/org/aspectj/runtime/reflect/Factory.java).
public ThrowSignature makeThrowSig(String stringRep) {
ThrowSignatureImpl ret = new ThrowSignatureImpl(stringRep);
ret.setLookupClassLoader(lookupClassLoader);
return ret;
}
- Modify weaver/src/org/aspectj/weaver/Shadow.java as following:
- Add to getArgTypes()
if (getKind() == Throw) return new UnresolvedType[] { getSignature().getReturnType() };
- Add to getGenericArgTypes()
if (getKind() == Throw) return new UnresolvedType[] { getSignature().getReturnType() };
- Add to getArgType(..)
if (getKind() == Throw) return getSignature().getReturnType();
- Add to getArgCount()
if (getKind() == Throw) return 1;
- Add to getReturnType()
else if (kind == Throw) return ResolvedType.VOID;
These modifications basically try to fix the
wrong assumption that any pointcut, except for the FieldGetSet ones,
has method like signature. We mimic what has been done for FieldGetSet
pointcuts to workaround the problems.
Done!
Congratulations! We've added a
very simple pointcut pattern to aspectj. Build the aspectj using the
build.xml file under the build module, and link ajdt with the new jars
as described in the previous article, and then start a new Eclipse
instance, copy our test case there and run it. You should be able to
get the expected outputs.