summaryrefslogtreecommitdiffstats
path: root/docs/developer/compiler-weaver
diff options
context:
space:
mode:
Diffstat (limited to 'docs/developer/compiler-weaver')
-rw-r--r--docs/developer/compiler-weaver/advice-dec.gifbin0 -> 45985 bytes
-rw-r--r--docs/developer/compiler-weaver/ajdt-uml.gifbin0 -> 82233 bytes
-rw-r--r--docs/developer/compiler-weaver/dev-guide-diagrams.vsdbin0 -> 208896 bytes
-rw-r--r--docs/developer/compiler-weaver/dev-guide-uml.vsdbin0 -> 264192 bytes
-rw-r--r--docs/developer/compiler-weaver/index.html917
-rw-r--r--docs/developer/compiler-weaver/overview.gifbin0 -> 18250 bytes
-rw-r--r--docs/developer/compiler-weaver/pointcut-dec.gifbin0 -> 38274 bytes
-rw-r--r--docs/developer/compiler-weaver/top-tree.gifbin0 -> 40199 bytes
8 files changed, 917 insertions, 0 deletions
diff --git a/docs/developer/compiler-weaver/advice-dec.gif b/docs/developer/compiler-weaver/advice-dec.gif
new file mode 100644
index 000000000..43f540a4f
--- /dev/null
+++ b/docs/developer/compiler-weaver/advice-dec.gif
Binary files differ
diff --git a/docs/developer/compiler-weaver/ajdt-uml.gif b/docs/developer/compiler-weaver/ajdt-uml.gif
new file mode 100644
index 000000000..3a6ff797d
--- /dev/null
+++ b/docs/developer/compiler-weaver/ajdt-uml.gif
Binary files differ
diff --git a/docs/developer/compiler-weaver/dev-guide-diagrams.vsd b/docs/developer/compiler-weaver/dev-guide-diagrams.vsd
new file mode 100644
index 000000000..5636872df
--- /dev/null
+++ b/docs/developer/compiler-weaver/dev-guide-diagrams.vsd
Binary files differ
diff --git a/docs/developer/compiler-weaver/dev-guide-uml.vsd b/docs/developer/compiler-weaver/dev-guide-uml.vsd
new file mode 100644
index 000000000..d3fa03dea
--- /dev/null
+++ b/docs/developer/compiler-weaver/dev-guide-uml.vsd
Binary files differ
diff --git a/docs/developer/compiler-weaver/index.html b/docs/developer/compiler-weaver/index.html
new file mode 100644
index 000000000..7265bed47
--- /dev/null
+++ b/docs/developer/compiler-weaver/index.html
@@ -0,0 +1,917 @@
+<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.&nbsp;
+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()) &amp;&amp; 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()) &amp;&amp; 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.&nbsp; It does
+this by considering that certain principled places in bytecode represent
+possible join points; these are the “static shadow” of those join points.&nbsp; 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.&nbsp; 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.&nbsp;
+These are the only classes that must be redistributed with a system built using
+AspectJ.&nbsp; Because these classes are redistributed this library must always
+be kept as small as possible.&nbsp; It is also important to worry about binary
+compatibility when making changes to this library.&nbsp; 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.&nbsp; 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.&nbsp; 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.&nbsp; 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-&gt;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>&lt;!DOCTYPE suite SYSTEM "../tests/ajcTestSuite.dtd"&gt;
+&lt;suite&gt;
+ &lt;ajc-test dir="design/pcds"
+ title="simple throw join point"&gt;
+ &lt;compile files="Throws.java" /&gt;
+ &lt;run class="Throws"/&gt;
+ &lt;/ajc-test&gt;
+&lt;/suite&gt;
+</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 &quot;before throw&quot; 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 &quot;fail&quot; in them. The last line tells you
+what happened. There was an unexpected event, &quot;before AllTargetJoinPoints throw(catch(Throwable))&quot;.
+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>&lt;ajc-test dir=&quot;design/pcds&quot;
+ title=&quot;simple throw join point with around&quot;&gt;
+ &lt;compile files=&quot;AroundThrows.java&quot;&gt;
+ &lt;message kind=&quot;error&quot; line=&quot;16&quot;/&gt;
+ &lt;message kind=&quot;error&quot; line=&quot;22&quot;/&gt;
+ &lt;/compile&gt;
+&lt;/ajc-test&gt;</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> \ No newline at end of file
diff --git a/docs/developer/compiler-weaver/overview.gif b/docs/developer/compiler-weaver/overview.gif
new file mode 100644
index 000000000..b32f11b15
--- /dev/null
+++ b/docs/developer/compiler-weaver/overview.gif
Binary files differ
diff --git a/docs/developer/compiler-weaver/pointcut-dec.gif b/docs/developer/compiler-weaver/pointcut-dec.gif
new file mode 100644
index 000000000..fed640d36
--- /dev/null
+++ b/docs/developer/compiler-weaver/pointcut-dec.gif
Binary files differ
diff --git a/docs/developer/compiler-weaver/top-tree.gif b/docs/developer/compiler-weaver/top-tree.gif
new file mode 100644
index 000000000..d8e2b26ff
--- /dev/null
+++ b/docs/developer/compiler-weaver/top-tree.gif
Binary files differ