123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917 |
- <html>
-
- <head>
- <meta http-equiv="Content-Language" content="en-us">
- <meta name="GENERATOR" content="Microsoft FrontPage 6.0">
- <meta name="ProgId" content="FrontPage.Editor.Document">
- <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
- <title>AspectJ Developer's Guide</title>
- <style>
- <!--
- pre { border-style: solid; border-width: 1px; margin-left: 24; padding-left: 4px;
- padding-right: 4px; padding-top: 1px; padding-bottom: 1px;
- background-color: #EAF3FF; margin-right:24 }
- h3 { background-color: #99CCFF }
- h2 { background-color: #CCCCFF }
- h1 { background-color: #99CCFF }
- -->
- </style>
- </head>
-
- <body>
-
- <h1 align="center">Guide for Developers of the AspectJ Compiler and Weaver</h1>
- <p>This document is written for developers who want to understand the
- implementation of AspectJ. It provides a top-down picture of the compiler
- and weaver implementations. This high-level picture should make it easier
- to read and understand the source code for AspectJ.</p>
- <p>The AspectJ compiler/weaver (ajc) is composed of three primary modules.</p>
- <ul>
- <li><b>org.aspectj.ajdt.core</b> - this is the compiler front-end and
- extends the eclipse Java compiler from <b>org.eclipse.jdt.core</b>.
- Because of the dependencies on parts of eclipse this generates a large ~6MB jar.</li>
- <li><b>weaver</b> - this provides the bytecode weaving functionality.
- It has very few external dependencies to minimize the size required for
- deployment of load-time weavers. Currently the build process doesn't
- produce a separate jar for just the weaver, but that will have to change for
- AspectJ-1.2.</li>
- <li><b>runtime</b> - these are the classes that are used by generated code
- at runtime and must be redistributed with any system built using AspectJ.
- This module has no external dependencies and produces a tiny ~30KB jar.</li>
- </ul>
- <p>
- <img border="0" src="overview.gif"></p>
- <p>The AspectJ compiler accepts both AspectJ bytecode and source code and
- produces pure Java bytecode as a result. Internally it has two stages. The
- front-end (org.aspectj.ajdt.core) compiles both AspectJ and pure Java source
- code into pure Java bytecode annotated with additional attributes representing
- any non-java forms such as advice and pointcut declarations. The back-end of the
- AspectJ compiler (weaver) implements the transformations encoded in these
- attributes to produce woven class files. The back-end can be run stand-alone to
- weave pre-compiled aspects into pre-compiled .jar files. In addition, the
- back-end exposes a weaving API which can be used to implement ClassLoaders that
- will weave advice into classes dynamically as they are loaded by the virtual
- machine.</p>
- <h2>Compiler front-end (org.aspectj.ajdt.core)</h2>
- <p>The front-end of the AspectJ compiler is implemented as an extension of the
- Java compiler from eclipse.org. The source-file portion of the AspectJ compiler
- is made complicated by inter-type declarations, declare parents, declare soft,
- and privileged aspects. All of these constructs require changes to the
- underlying compiler to modify Java’s name-binding and static checking behavior.</p>
- <p>As the compiler extends the jdt.core compiler, the package structure of this
- module mimics that of the jdt.core module. The design works hard to minimize the
- set of changes required to org.eclipse.jdt.core because a fun 3-way merge is
- required each time we want to move to a new underlying version of this code.
- The ultimate goal is to contribute all of our changes to jdt.core back into the
- main development branch some day.</p>
- <p>The basic structure of a compile is very simple:</p>
- <ol>
- <li>Perform a shallow parse on all source files</li>
- <li>Pass these compilation units through AjLookupManager to do type binding
- and some AspectJ augmentation</li><li>For each source file do a deep parse,
- annotation/analysis, and then code generation</ol>
- <h3>Top-level parse tree</h3>
- <p>Let's trace the following example program through the compiler.</p>
- <pre>package example.parse.tree;
-
- import org.aspectj.lang.*;
-
- public class Main {
- public static void main(String[] args) {
- new Main().doit();
- }
-
- private void doit() {
- System.out.println("hello");
- }
- }
-
- aspect A {
- pointcut entries(Main o): execution(void doit()) && this(o);
- before(Main o): entries(o) {
- o.counter++;
- System.out.println("entering: " + thisJoinPoint);
- }
-
- private int Main.counter = 0;
- }</pre>
- <p>When parsed, this program will produce the following tree.</p>
- <p><img border="0" src="top-tree.gif"></p>
- <h3>PointcutDeclaration processing</h3>
- <p>Let's look more closely at the pointcut
- declaration:</p>
- <pre>pointcut entries(Main o): execution(void doit()) && this(o);</pre>
- <p><img border="0" src="pointcut-dec.gif"></p>
- <p>The pointcut declaration is implemented as a subtype of a method declaration.
- The actual pointcut is parsed by the weaver module. This parsing happens
- as part of the shallow parse phase. This is because this information might
- be needed to implement a declare soft.</p>
- <h3>AdviceDeclaration processing</h3>
- <p>Next we look at the processing for an advice declaration:</p>
- <pre>before(Main o): entries(o) {
- o.counter++;
- System.out.println("entering: " + thisJoinPoint);
- }</pre>
- <p>
- After parsing, the AdviceDeclaration.postParse method will be called to make this
- a valid MethodDeclaration so that the standard eclipse code for analyzing a
- method body can be applied to the advice. After postParse, the selector is
- filled in and several additional arguments are added for the special
- thisJoinPoint forms that could be used in the body.</p>
- <p>
- <img border="0" src="advice-dec.gif"></p>
- <p>
- At this point the statements field which will hold the body of the advice is
- still null. This field is not filled in until the second stage of the
- compiler when full parsing is done on each source file as a prelude to
- generating the classfile.</p>
- <h3>
- Overview of the main classes in org.aspectj.ajdt.core</h3>
- <p>
- The main classes in this module are shown in the following diagram:</p>
- <p>
- <img border="0" src="ajdt-uml.gif"></p>
- <h2>Weaving back-end (weaver)</h2>
- <p>This provides all of the weaving functionality. It has very few
- dependencies to keep the code as small as possible for deployment in load-time
- weavers - only asm, bridge and util which are each very small modules with no
- further dependencies. This also depends on a patched version of the bcel library from apache.org.
- The patches are only to fix bcel bugs that can't be
- worked around in any other way.</p>
- <p>There are only four packages in this system.</p>
- <ul>
- <li>org.aspectj.weaver - general classes that can be used by any weaver
- implementation</li>
- <li>org.aspectj.weaver.patterns - patterns to represent pointcut designators
- and related matching constructs</li>
- <li>org.aspectj.weaver.ast - a very small library to represent simple
- expressions without any bcel dependencies</li>
- <li>org.aspectj.weaver.bcel - the concrete implementation of shadows and the
- weaver using the bcel library from apache.org</li>
- </ul>
- <p class="MsoNormal">The back-end of the AspectJ compiler instruments the code
- of the system by inserting calls to the precompiled advice methods. It does
- this by considering that certain principled places in bytecode represent
- possible join points; these are the “static shadow” of those join points. For
- each such static shadow, it checks each piece of advice in the system and
- determines if the advice's pointcut could match that static shadow. If it could
- match, it inserts a call to the advice’s implementation method guarded by any
- dynamic testing needed to ensure the match.</p>
- <h2>Runtime support library (runtime)</h2>
- <p>This library provides classes that are used by the generated code at runtime.
- These are the only classes that must be redistributed with a system built using
- AspectJ. Because these classes are redistributed this library must always
- be kept as small as possible. It is also important to worry about binary
- compatibility when making changes to this library. There are two packages
- that are considered public and may be used by AspectJ programs.</p>
- <ul>
- <li>org.aspectj.lang</li>
- <li>org.apectj.lang.reflect</li>
- </ul>
- <p>There are also several packages all under the header org.aspectj.runtime that
- are considered private to the implementation and may only be used by code
- generated by the AspectJ compiler.</p>
- <p></p>
- <h2>Mappings from AspectJ language to implementation</h2>
- <table border="1" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="100%" height="234">
- <tr>
- <td width="12%" height="19"></td>
- <td width="23%" height="19">org.aspectj.ajdt.internal.compiler</td>
- <td width="40%" height="19">weaver - org.aspectj.weaver.</td>
- </tr>
- <tr>
- <td width="12%" height="19">aspect</td>
- <td width="23%" height="19">ast.AspectDeclaration</td>
- <td width="40%" height="19">CrosscuttingMembers</td>
- </tr>
- <tr>
- <td width="12%" height="19">advice</td>
- <td width="23%" height="19">ast.AdviceDeclaration</td>
- <td width="40%" height="19">Advice + bcel.BcelShadowMunger</td>
- </tr>
- <tr>
- <td width="12%" height="19">pointcut declaration</td>
- <td width="23%" height="19">ast.PointcutDeclaration</td>
- <td width="40%" height="19">ResolvedPointcutDefinition</td>
- </tr>
- <tr>
- <td width="12%" height="19">declare error/warning</td>
- <td width="23%" height="19">ast.DeclareDeclaration</td>
- <td width="40%" height="19">Checker + patterns.DeclareErrorOrWarning</td>
- </tr>
- <tr>
- <td width="12%" height="38">declare soft</td>
- <td width="23%" height="38">ast.DeclareDeclaration +
- problem.AjProblemReporter</td>
- <td width="40%" height="38">Advice (w/ kind = Softener) +
- patterns.DeclareSoft</td>
- </tr>
- <tr>
- <td width="12%" height="38">declare parents</td>
- <td width="23%" height="38">ast.DeclareDeclaration +
- lookup.AjLookupEnvironment</td>
- <td width="40%" height="38">patterns.DeclareParents + NewParentTypeMunger</td>
- </tr>
- <tr>
- <td width="12%" height="18">inter-type decls</td>
- <td width="23%" height="18">ast.InterType*Declaration + lookup.InterType*Binding
- + lookup.AjLookupEnvironment</td>
- <td width="40%" height="18">New*TypeMunger + bcel.BcelTypeMunger</td>
- </tr>
- <tr>
- <td width="12%" height="19">if pcd</td>
- <td width="23%" height="19">ast.IfPseudoToken + ast.IfMethodDeclaration</td>
- <td width="40%" height="19">patterns.IfPointcut</td>
- </tr>
- <tr>
- <td width="12%" height="17">pcd</td>
- <td width="23%" height="17">ast.PointcutDesignator</td>
- <td width="40%" height="17">patterns.Pointcut hierarchy</td>
- </tr>
- </table>
- <p></p>
- <h1>Tutorial: implementing a throw join point</h1>
- <p>This tutorial will walk step-by-step through the process of adding a new join
- point to AspectJ for the moment when an exception is thrown. In Java
- source code, the shadow of this point is a throw statement. In Java bytecode,
- the shadow is the athrow instruction.</p>
- <p>This tutorial is recommended to anyone who wants to get a better feel for how
- the implementation of AspectJ really works. Even if you're just working on
- a bug fix or minor enhancement, the process of working with the AspectJ
- implementation will be similar to that described below. The size of your
- actual code changes will likely be smaller, but you are likely to need to be
- familiar with all of the pieces of the implementation described below.</p>
- <h2>Part 1: Adding the join point and corresponding pcd</h2>
- <p>The first part of this tutorial will implement the main features of the throw
- join point. We will create a new join point shadow corresponding to the athrow
- instruction and also create a new pointcut designator (pcd) for matching it.</p>
- <h3>Step 1. Synchronize with repository and run the existing test suite</h3>
- <p>Do a Team->Synchronize With Repository and make sure that your tree is
- completely in sync with the existing repository. Make sure to address any
- differences before moving on.</p>
- <p>Run the existing test suite. I currently do this in four steps:</p>
- <ul>
- <li>weaver/testsrc/BcWeaverModuleTests.java</li>
- <li>org.aspectj.ajdt.core/testsrc/EajcModuleTests.java</li>
- <li>ajde/testsrc/AjdeModuleTests.java</li>
- <li>Harness on ajctests.xml -- at least under 1.4, preferably under both 1.3 and
- 1.4.</li>
- </ul>
- <p>There should be no failures when you run these tests. If there are
- failures, resolve them with the AspectJ developers before moving on.</p>
- <h3>Step 2. Write a proto test case</h3>
- <p>a. Create a new file in tests/design/pcds/Throw.java</p>
- <pre>import org.aspectj.testing.Tester;
-
- public class Throws {
- public static void main(String[] args) {
- try {
- willThrow();
- Tester.checkFailed("should have thrown exception");
- } catch (RuntimeException re) {
- Tester.checkEqual("expected exception", re.getMessage());
- }
- }
-
- static void willThrow() {
- throw new RuntimeException("expected exception");
- }
- }
-
- aspect A {
- before(): withincode(void willThrow()) {
- System.out.println("about to execute: " + thisJoinPoint);
- }
- }</pre>
- <p>b. Create a temporary test harness file to run just this test in myTests.xml</p>
- <pre><!DOCTYPE suite SYSTEM "../tests/ajcTestSuite.dtd">
- <suite>
- <ajc-test dir="design/pcds"
- title="simple throw join point">
- <compile files="Throws.java" />
- <run class="Throws"/>
- </ajc-test>
- </suite>
- </pre>
- <p>c. Run this test using the harness. You should see:</p>
- <pre>about to execute: execution(void Throws.willThrow())
- about to execute: call(java.lang.RuntimeException(String))
- PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 2 seconds</pre>
- <h3>Step 3. Implement the new join point shadow kind</h3>
- <p>Modify runtime/org.aspectj.lang/JoinPoint.java to add a name for the Throw
- shadow kind.</p>
- <pre>static String THROW = "throw";</pre>
- <p>Modify weaver/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.
- </p>
- <pre>public static final Kind Throw = new Kind(JoinPoint.THROW, 12, true);
- </pre>
- <p>We also modify the neverHasTarget method to include the Throw kind because in
- Java there is no target for the throwing of an exception.</p>
- <pre>public boolean neverHasTarget() {
- return this == ConstructorCall
- || this == ExceptionHandler
- || this == PreInitialization
- || this == StaticInitialization
- || this == Throw;
- }
- </pre>
- <p>In the read method on Shadow.Kind, add another case to read in our new
- Shadow.Kind.</p>
- <pre>case 12: return Throw;
- </pre>
- <h3>Step 4. Create this new kind of joinpoint for the throw bytecode</h3>
- <p>Modify weaver/org.aspectj.weaver.bcel/BcelClassWeaver.java to recognize this
- new joinpoint kind. In the method
- <pre>private void match(
- LazyMethodGen mg,
- InstructionHandle ih,
- BcelShadow enclosingShadow,
- List shadowAccumulator)
- {
- </pre>
- <p>Add a test for this instruction, i.e.</p>
- <pre>} else if (i == InstructionConstants.ATHROW) {
- match(BcelShadow.makeThrow(world, mg, ih, enclosingShadow),
- shadowAccumulator);
- }
- </pre>
- <p>Then, modify BcelShadow.java to create this new kind of join point shadow:</p>
- <pre>public static BcelShadow makeThrow(
- BcelWorld world,
- LazyMethodGen enclosingMethod,
- InstructionHandle throwHandle,
- BcelShadow enclosingShadow)
- {
- final InstructionList body = enclosingMethod.getBody();
- TypeX throwType = TypeX.THROWABLE; //!!! not as precise as we'd like
- TypeX inType = enclosingMethod.getEnclosingClass().getType();
- BcelShadow s =
- new BcelShadow(
- world,
- Throw,
- Member.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;
- } </pre>
- <p>Finally modify weaver/org.aspectj.weaver/Member.java to generate the needed
- signature</p>
- <pre>public static Member makeThrowSignature(TypeX inType, TypeX throwType) {
- return new Member(
- HANDLER,
- inType,
- Modifier.STATIC,
- "throw",
- "(" + throwType.getSignature() + ")V");
- }</pre>
- <p>Run the proto test again and you should see:</p>
- <pre>about to execute: execution(void Throws.willThrow())
- about to execute: call(java.lang.RuntimeException(String))
- about to execute: throw(catch(Throwable))
- PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 3 seconds
- </pre>
- <p>That last line shows the 'throw(catch(Throwable))'
- join point. This is a slightly confusing string form, but it is the first sign
- of our brand new join point. The reason for the weird 'catch(Throwable)' part is
- that we used Member.HANDLER for the kind of the signature of this join point.
- That's clearly not correct. We'll fix that at the end of the lesson as
- part of the clean-up. For now, let's go on with the interesting parts.</p>
- <h3>Step 5. Extend our proto-test to use a pointcut designator for matching</h3>
- <p>Add a second piece of before advice to the test aspect A:</p>
- <pre>before(): throw(Throwable) {
- System.out.println("about to throw: " + thisJoinPoint);
- }</pre>
-
- <p>When we run the test again we'll get a long error message from the harness.
- The interesting part of the message is the following:</p>
- <pre>[ 0] [error 0]: error can't find referenced pointcut at C:\aspectj\eclipse\tests\design\pcds\Throws.java:23:0
- </pre>
- <p>This error is not quite what you might have expected. You might have
- hoped for a syntax error saying that there is not 'throw' pointcut designator
- defined. Unfortunately, this is a weakness in the syntax of AspectJ where
- primitive PCDs and named PCDs have the same syntax, so the compiler can't tell
- the difference between a misspelled or non-existent primitive PCD and a named
- PCD reference that is missing. This also has some impact on extending the
- primitive PCDs because it will break existing programs. In this case, when
- we add the throw PCD we will break any existing programs that use throw as the
- name for a user-defined PCD. Fortunately because throw is a Java keyword
- this particular change is very safe.</p>
- <h3>Step 6. Extend the PCD parser to handle this new primitive PCD</h3>
- <p>Modify the
- parseSinglePointcut method in weaver/org.aspectj.weaver.patterns/PatternParser.java to add one more else if clause for the throw pcd:</p>
- <pre>} else if (kind.equals("throw")) {
- parseIdentifier(); eat("(");
- TypePattern typePat = parseTypePattern();
- eat(")");
- return new KindedPointcut(Shadow.Throw,
- new SignaturePattern(Member.HANDLER, ModifiersPattern.ANY,
- TypePattern.ANY, TypePattern.ANY, NamePattern.ANY,
- new TypePatternList(new TypePattern[] {typePat}),
- ThrowsPattern.ANY));</pre>
- <p>Modify the matches method in weaver/org.aspectj.weaver.patterns/SignaturePattern.java
- to add:</p>
- <pre>if (kind == Member.HANDLER) {
- return parameterTypes.matches(world.resolve(sig.getParameterTypes()),
- TypePattern.STATIC).alwaysTrue();
- } </pre>
- <p>Run the proto test again and you should see:</p>
-
- <pre>about to execute: execution(void Throws.willThrow())
- about to execute: call(java.lang.RuntimeException(String))
- about to execute: throw(catch(Throwable))
- about to throw: throw(catch(Throwable))
- PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 1 seconds
- </pre>
-
- Make sure that you see the 'about to throw' printed before moving on.
- This shows that the throw PCD is now successfully matching the throw join point
- shadow we added earlier.<h3>Step 7. Check that we're properly providing the
- single thrown argument (and clean-up the test)</h3>
- <p>Now that we have a valid pcd for this advice, we can simplify our test case.
- Modify our test aspect A to be the following. In addition to removing the
- overly generic withincode pcd, this change also prints the actual
- object that is about to be thrown:</p>
- <pre>aspect A {
- before(Throwable t): throw(*) && args(t) {
- System.out.println("about to throw: '" + t+ "' at " + thisJoinPoint);
- }
- }</pre>
-
- <p>When we run the test again we should see the output below:</p>
- <pre>about to throw: 'java.lang.RuntimeException: expected exception' at throw(catch(Throwable))
- PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 1 seconds
- </pre>
- <p>Congratulations! You've just
- implemented the throw join point and PCD. This code isn't yet ready to be checked into any repository. It still
- has some rough edges that need to be smoothed. However, you've now
- added a new join point to the AspectJ language and a corresponding PCD to match
- it. This is a good time to take a break before moving on to part two.</p>
- <h2>Part 2: Getting the signature of this new join point right</h2>
- <p>We know that throw(catch(Throwable)) is not the right thing to be printing
- for the signature at this join point. What is the correct signature?
- At the beginning of the tutorial, we explained that the preferred design for the
- pcd was to have throw(StaticTypeOfExceptionThrown). In step 4, we set the
- type of the exception thrown to be 'Throwable'. Can we set this to be more
- accurate? Looking at the source code, it seems easy to identify the static
- type of the exception that is thrown:</p>
- <pre>throw new RuntimeException("expected exception");</pre>
- <p>In the source code to a Java program there is a well-defined static type for
- the exception that is thrown. This static type is used for various stages
- of flow analysis to make sure that checked exceptions are always correctly
- handled or declared. The ThrowStatement class in our own compiler has a
- special field for exceptionType that stores the static type of the exception
- thrown. Unfortunately, this static type is much harder to recover from the
- corresponding bytecode. In this case we would need to do flow analysis to
- figure out what the static type is for the object on the top of the stack
- when the athrow instruction executes. This analysis can certainly be done.
- In fact this analysis is a small part of what every JVM must do to verify the
- type safety of a loaded classfile.</p>
- <p>However, the current AspectJ weaver doesn't do any of this analysis.
- There are many good reasons to extend it in this direction in order to optimize
- the code produced by the weaver. If we were really implementing this
- feature, this would be the time for a long discussion on the aspectj-dev list to
- decide if this was the right time to extend the weaver with the code flow
- analysis needed to support a static type for the throw join point. For the
- purposes of this tutorial, we're going to assume that it isn't the right time to
- do this (implementing flow analysis for bytecodes would add another 50 pages to
- this tutorial). Instead we're going to change the definition of the throw
- join point to state that its argument always has a static type of Throwable.
- We still allow dynamic matching in args to select more specific types. In
- general, good AspectJ code should use this dynamic matching anyway to correspond
- to good OO designs.</p>
- <h3>Step 1. Change the signature of the throw pcd</h3>
- <p>Since we aren't going to recover the static type of the exception thrown, we
- need to fix the parser for the throw pcd to remove this information. We'll
- fix the PatternParser code that we added in step 1.6 to read as follows:</p>
- <pre>} else if (kind.equals("throw")) {
- parseIdentifier(); eat("(");
- eat(")");
- return new KindedPointcut(Shadow.Throw,
- new SignaturePattern(Member.THROW, ModifiersPattern.ANY,
- TypePattern.ANY, TypePattern.ANY, NamePattern.ANY,
- TypePatternList.ANY,
- ThrowsPattern.ANY));</pre>
- <p>Notice that this code also starts to fix the member kind to be Member.THROW
- instead of the bogus Member.HANDLER that we were using before. To make
- this work we have a set of things to do. First, let's create this new kind
- in org.aspectj.weaver.Member. Find where the HANDLER kind is defined
- there, and add a corresponding throw kind:</p>
- <pre>public static final Kind THROW = new Kind("THROW", 8);
- </pre>
- <p>We also need to fix the serialization kind in
- Member.Kind.read(DataInputStream) just above this constant list to add a case
- for this new kind:</p>
- <pre>case 8: return THROW;
- </pre>
- <p>Still in this file, we also need to fix Member.makeThrowSignature to use this
- new kind:</p>
- <pre>public static Member makeThrowSignature(TypeX inType, TypeX throwType) {
- return new ResolvedMember(
- THROW,
- inType,
- Modifier.STATIC,
- "throw",
- "(" + throwType.getSignature() + ")V");
- }
- </pre>
- <p>If you run the test now you'll get an error from the parser reminding us that
- the throw pcd now doesn't accept a type pattern:</p>
- <pre>------------ FAIL: simple throw join point()
- ...
- C:\aspectj\eclipse\tests\design\pcds\Throws.java:19:0 Syntax error on token "*", ")" expected
-
- FAIL Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 failed) 1 seconds</pre>
- <p>This is an easy fix to the test case as we modify our pcd for the new syntax
- in the aspect A in our Throws.java test code:</p>
- <pre>before(Throwable t): throw() && args(t) {</pre>
- <p> Now when we run the test case it looks like everything's fixed and we're
- passing:</p>
- <pre>PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 2 seconds</pre>
- <h3>Part 2. Make a real test case</h3>
- <p>The pass result from running our test should worry you. Unlike previous
- runs, this test run doesn't show the output from our System.out.println in the
- before advice. So, it's clear this advice is not running. The
- problem is that even though the advice is not running, the test case is passing.
- We need to make this a real test case to fix this. We'll do that by adding
- code that notes when the advice runs and then checks for this event. This
- code uses the Tester.event and Tester.checkEvent methods:</p>
- <pre>import org.aspectj.testing.Tester;
-
- public class Throws {
- public static void main(String[] args) {
- try {
- willThrow();
- Tester.checkFailed("should have thrown exception");
- } catch (RuntimeException re) {
- Tester.checkEqual("expected exception", re.getMessage());
- }
- Tester.checkEvents(new String[] { "before throw" });
- }
-
- static void willThrow() {
- throw new RuntimeException("expected exception");
- }
- }
-
- aspect A {
- before(Throwable t): throw() && args(t) {
- Tester.event("before throw");
- //System.out.println("about to throw: '" + t+ "' at " + thisJoinPoint);
- }
- }</pre>
- <p>Now when we run our test case it will fail. This failure is good
- because we're not matching the throw join point anymore.</p>
- <pre>------------ FAIL: simple throw join point()
- ...
- [ 1] [fail 0]: fail [ expected event "before throw" not found]
-
- FAIL Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 failed) 1 seconds</pre>
- <h3>Step 3. Fix signature matching again</h3>
- <p>In org.aspectj.weaver.patterns.SignaturePattern.matches, we need to handle
- throw signature matching the same way we handle advice signature matching.
- Both of these pcds match solely on the kind of join point and use combinations
- with other pcds to narrow their matches. So, find the line for kind ==
- Member.ADVICE and add the same line below it for Member.THROW.</p>
- <pre>if (kind == Member.ADVICE) return true;
- if (kind == Member.THROW) return true;</pre>
- <p>This change will make our test case pass again. Run it to be sure.</p>
- <p>There's an interesting tension between a good automated test and a good test
- for development. Our new test case now correctly includes an automated
- test to let us know when we are and are not matching the new throw join point.
- However, without the println the test doesn't feel as satisfactory to me to run
- during development. I often like to turn this kind of printing back on the
- see what's happening. If you uncomment to System.out.println in the test
- aspect A and rerun the test, you won't be very happy with the results:</p>
- <pre>------------ FAIL: simple throw join point()
- ...
- unimplemented
- java.lang.RuntimeException: unimplemented
- at org.aspectj.weaver.Member.getSignatureString(Member.java:596)
- ...
-
- FAIL Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 failed) 1 seconds</pre>
- <p>It looks like there's more work to do to add the new member kind for
- Member.THROW. This problem only shows up when we try to print
- thisJoinPoint. It's showing that we haven't updated the reflection API to
- understand this new signature kind.</p>
- <h3>Step 4. Extend org.aspectj.lang.reflect to understand throw signatures</h3>
- <p>We need to add a couple of classes to the reflection API to implement the
- throw signature. Because we decided at the beginning of this section to
- not include the static type of the exception thrown in the throw signature,
- these classes are extremely simple. Nevertheless, we have to build them.
- Notice that when we add new source files to the system we need to include the
- standard eclipse CPL license header.</p>
- <pre>/* *******************************************************************
- * 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 { }</pre>
- <pre>/* *******************************************************************
- * 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);
- }
-
- String toString(StringMaker sm) {
- return "throw";
- }
- }</pre>
- <p>To finish up our work in the runtime module, we need to extend
- org.aspectj.runtime.reflect.Factory to add a factory method for this new
- signature kind:</p>
- <pre>public ThrowSignature makeThrowSig(String stringRep) {
- ThrowSignatureImpl ret = new ThrowSignatureImpl(stringRep);
- ret.setLookupClassLoader(lookupClassLoader);
- return ret;
- }</pre>
- <p>We're not done yet. We still need to fix up the
- org.aspectj.weaver.Member class to use these new methods and types and fix the
- unimplemented exception that started us down this road in the first place.
- First let's add a method to create a string for the throw signature. This
- is a very simple method copied from the other create*SignatureString methods.</p>
- <pre>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();
- }</pre>
- <p>Now we need to modify three methods to add cases for the new Member.THROW
- kind. First, Member.getSignatureMakerName add:</p>
- <pre>} else if (kind == THROW) {
- return "makeThrowSig";
- </pre>
- <p>Next, to Member.getSignatureType add:</p>
- <pre>} else if (kind == THROW) {
- return "org.aspectj.lang.reflect.ThrowSignature";
- </pre>
- <p>Finally, to Member.getSignatureString add:</p>
- <pre>} else if (kind == THROW) {
- return getThrowSignatureString(world);
- </pre>
- <p>With all of these changes in place we should have working code for
- thisJoinPoint reflection using our new join point and signature kinds.
- Rerun the test to confirm:</p>
- <pre>about to throw: 'java.lang.RuntimeException: expected exception' at throw(throw)
- PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 1 seconds</pre>
- <h3>Step 5. Extend the test for automated coverage of reflection</h3>
- <p>Modify the before advice to include at least minimal checks of the new
- reflective information:</p>
- <pre>before(Throwable t): throw() && args(t) {
- Tester.event("before throw");
- Tester.checkEqual(thisJoinPoint.getSignature().toShortString(), "throw");
- Tester.checkEqual(t.getMessage(), "expected exception");
- }</pre>
- <p> As usual, you should rerun the tests and make sure they pass.</p>
- <p> With these changes to the reflection code, it looks like we have a working
- version of the throw join point and there are no obvious pieces that we've
- skipped. Take a break before proceeding to the final phase of tests.</p>
- <h2>Part 3: More serious testing</h2>
- <p>Now it's time to get a decent testing story. The test work that we will
- do here is probably too little for adding a new join point to the aspectj
- language; however, it should at least give you a sense of what's involved.</p>
- <h3>Step 1. Run the test suite again</h3>
- <p>Rerun the tests you ran at the beginning of part 1. Any failures that
- occur should be resolved at this point. At the time of writing this
- tutorial, I found 31 failures in the BcWeaverModuleTests. These failures
- are for all of the test cases that check the exact set of shadows produces by a
- given program. These test cases need to be updated based on the new join
- point we're adding. These particular test cases will probably be removed
- from the AspectJ test suite very soon because they've shown themselves to be
- very fragile over time and they often break for changes that are not introducing
- new bugs. However, you should be aware of this kind of failure because you
- may find it in other unit tests.</p>
- <p>You should expect to see at least one other test case fail when you run
- ajcTests.xml. Here's the failure message:</p>
- <pre>------------ FAIL: validate (enclosing) join point and source locations()
- ...
- [ 1] [fail 0]: fail [ unexpected event "before AllTargetJoinPoints throw(throw)" found]</pre>
- <p>Most of this message can be ignored. To find out what went wrong you
- should look for messages that have "fail" in them. The last line tells you
- what happened. There was an unexpected event, "before AllTargetJoinPoints throw(catch(Throwable))".
- This is the signature for one of the new throw join points that we added in part
- 1. How could an existing test case match this new join point? The
- failing test case uses 'within(TargetClass)' to collect information about ALL
- join points that are lexically within a given class. Whenever we add a new
- kind of join point to the language we will extend the set of points matched by
- pcds like within. This means that these changes need to be very
- prominently noted in the release notes for any AspectJ release. Since
- we're not writing documentation in this tutorial, we will move on an fix the
- test case.</p>
- <h3>Step 2. Fix the failing test case</h3>
- <p>Now we need to fix this failing test case. The first step is to copy
- the test specification into our local myTests.xml file. The easiest way to
- do this is to copy the title of the failing test from the output buffer, then
- open ajcTests.xml and use find to search for this title. Then copy the xml
- spec for this one test into myTests.xml. Finally, run myTests.xml to make
- sure you got the failing test. You should see the same failure as before
- in step 1, but you should see it a lot faster because we're only running 2
- tests.</p>
- <p>To fix the test we need to find the source code. If you look at the
- test specification, you can see that the source file is the new directory with
- the name NegativeSourceLocation.java. Looking at the bottom of this file,
- we see a large list of expected events. These are the join points that we
- expect to see. If we look back up in TargetClass, we can see that the only
- occurence of throw is just before the handler for catch(Error) and right after
- the call to new Error. We should add our new expected event between these
- two:</p>
- <pre>, "before AllTargetJoinPoints call(java.lang.Error(String))"
- , "before AllTargetJoinPoints throw(throw)" // added for new throw join point
- , "before AllTargetJoinPoints handler(catch(Error))"</pre>
- <p>Run the test suite again to see that this test now passes.</p>
- <h3>Step 3. Extend test coverage to after advice</h3>
- <p>There is a lot we should do now to extend test coverage for this new kind of
- join point. For the purpose of this tutorial, we're just going to make
- sure that the new join point kind is compatible with all 5 kinds of advice.
- Let's extend our current simple Throws test to check for before and the three
- kinds of after advice:</p>
- <pre>import org.aspectj.testing.Tester;
-
- public class Throws {
- public static void main(String[] args) {
- try {
- willThrow(true);
- Tester.checkFailed("should have thrown exception");
- } catch (RuntimeException re) {
- Tester.checkEqual("expected exception", re.getMessage());
- }
- Tester.checkEvents(new String[]
- { "before throw", "after throwing throw", "after throw" });
- }
-
- static void willThrow(boolean shouldThrow) {
- int x;
- if (shouldThrow) throw new RuntimeException("expected exception");
- else x = 42;
- System.out.println("x = " + x);
- }
- }
-
- aspect A {
- before(Throwable t): throw() && args(t) {
- Tester.event("before throw");
- Tester.checkEqual(thisJoinPoint.getSignature().toShortString(), "throw");
- Tester.checkEqual(t.getMessage(), "expected exception");
- }
-
- after() returning: throw() {
- Tester.checkFailed("shouldn't ever return normally from a throw");
- }
-
- after() throwing(RuntimeException re): throw() {
- Tester.event("after throwing throw");
- Tester.checkEqual(re.getMessage(), "expected exception");
- }
-
- after(): throw() {
- Tester.event("after throw");
- }
- }</pre>
- <p>Run this test to confirm that it still passes. This is a very nice
- property of the orthogonality of the implementation of join points and advice.
- We never had to do any implementation work to make our new join point kind work
- for before and all three kinds of after advice.</p>
- <h3>Step 4. Look at around advice on throw join points</h3>
- <p>Let's create a new test case to see how this new join point interacts with
- around advice.</p>
- <pre>import org.aspectj.testing.Tester;
-
- public class AroundThrows {
- public static void main(String[] args) {
- try {
- willThrow(true);
- Tester.checkFailed("should have thrown exception");
- } catch (RuntimeException re) {
- Tester.checkEqual("expected exception", re.getMessage());
- }
- }
-
- static void willThrow(boolean shouldThrow) {
- int x;
- if (!shouldThrow) x = 42;
- else throw new RuntimeException("expected exception");
- System.out.println("x = " + x);
- }
- }
-
- aspect A {
- void around(): throw() {
- System.out.println("about to throw something");
- proceed();
- }
- }</pre>
- <p>When we run this test case we get a very unpleasant result:</p>
- <pre>------------ FAIL: simple throw join point with around()
- ...
- [ 1] --- thrown
- java.lang.VerifyError: (class: AroundThrows, method: willThrow signature: (Z)V) Accessing value from uninitialized register 1
- ...
- FAIL Suite.Spec(c:\aspectj\eclipse\tests) 3 tests (1 failed, 2 passed) 3 seconds
- </pre>
- <p>A VerifyError at runtime is the second worst kind of bug the AspectJ compiler
- can produce. The worst is silently behaving incorrectly.</p>
- <p>Unfortunately, this VerifyError is either impossible or very hard to fix.
- Think about what would happen if the around advice body didn't call proceed.
- In this case the local variable x would in fact be uninitialized. There is
- another serious language design question here, and for a real implementation
- this would once again be the time to start a discussion on the aspectj-dev
- mailing list to reach consensus on the best design. For the purpose of
- this tutorial we're once again going to make the language design choice that is
- easiest to implement and press on.</p>
- <h3>Step 5. Prohibit around advice on this new join point kind</h3>
- <p>The easiest solution to implement is to prohibit around advice on throw join
- points. There are already a number of these kinds of rules implemented in
- the org.aspectj.weaver.Shadow.match(Shadow, World) method. We can add our
- new rule at the beginning of the if(kind == AdviceKind.Around) block:</p>
- <pre>} else if (kind == AdviceKind.Around) {
- if (shadow.getKind() == Shadow.Throw) {
- world.showMessage(IMessage.ERROR,
- "around on throw not supported (possibly compiler limitation)",
- getSourceLocation(), shadow.getSourceLocation());
- return false;
- }</pre>
- <p> Now if we rerun our test we'll see errors telling us that around
- is prohibited on throw join points:</p>
- <pre>------------ FAIL: simple throw join point with around()
- ...
- [ 0] [error 0]: error at C:\aspectj\eclipse\tests\design\pcds\AroundThrows.java:22 around on throw not supported (possibly compiler limitation)
- [ 0] [error 1]: error at C:\aspectj\eclipse\tests\design\pcds\AroundThrows.java:16 around on throw not supported (possibly compiler limitation)
- ...
- FAIL Suite.Spec(c:\aspectj\eclipse\tests) 3 tests (1 failed, 2 passed) 3 seconds</pre>
- <p>To finish this test case up we need to modify the specification to be looking
- for these errors as the correct behavior. This will produce the following
- specification:</p>
- <pre><ajc-test dir="design/pcds"
- title="simple throw join point with around">
- <compile files="AroundThrows.java">
- <message kind="error" line="16"/>
- <message kind="error" line="22"/>
- </compile>
- </ajc-test></pre>
- <p>Run myTests.xml one last time to see both tests passing.</p>
- <h3>Step 6. Final preparations for a commit or patch</h3>
- <p>You probably want to stop here for the purposes of this tutorial. We've
- pointed out several language design decisions that would need to be resolved
- before actually adding a throw join point to AspectJ. Some of those might
- involve a large amount of additional implementation work. If this was
- actually going into the tree, it would also be important to add several more
- test cases exploring the space of what can be done with throw.</p>
- <p>Assuming those issues were resolved and you are ready to commit this new
- feature to the tree there are three steps left to follow:</p>
- <ol>
- <li>Move our new test specifications from myTests.xml to the end of
- ajcTests.xml</li>
- <li>Rerun ajcTests.xml and the unit tests to ensure everything's okay.</li>
- <li>Update from the repository to get any changes from other committers
- since you started work on this new feature.</li>
- <li>Rerun ajcTests.xml and the unit tests to make sure nothing broke as a
- result of the update.</li>
- <li>Finally you can commit these changes to the AspectJ tree.</li>
- </ol>
- </body>
-
- </html>
|