You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 44KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. <html>
  2. <head>
  3. <meta http-equiv="Content-Language" content="en-us">
  4. <meta name="GENERATOR" content="Microsoft FrontPage 6.0">
  5. <meta name="ProgId" content="FrontPage.Editor.Document">
  6. <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
  7. <title>AspectJ Developer's Guide</title>
  8. <style>
  9. <!--
  10. pre { border-style: solid; border-width: 1px; margin-left: 24; padding-left: 4px;
  11. padding-right: 4px; padding-top: 1px; padding-bottom: 1px;
  12. background-color: #EAF3FF; margin-right:24 }
  13. h3 { background-color: #99CCFF }
  14. h2 { background-color: #CCCCFF }
  15. h1 { background-color: #99CCFF }
  16. -->
  17. </style>
  18. </head>
  19. <body>
  20. <h1 align="center">Guide for Developers of the AspectJ Compiler and Weaver</h1>
  21. <p>This document is written for developers who want to understand the
  22. implementation of AspectJ. It provides a top-down picture of the compiler
  23. and weaver implementations. This high-level picture should make it easier
  24. to read and understand the source code for AspectJ.</p>
  25. <p>The AspectJ compiler/weaver (ajc) is composed of three primary modules.</p>
  26. <ul>
  27. <li><b>org.aspectj.ajdt.core</b> - this is the compiler front-end and
  28. extends the eclipse Java compiler from <b>org.eclipse.jdt.core</b>.
  29. Because of the dependencies on parts of eclipse this generates a large ~6MB jar.</li>
  30. <li><b>weaver</b> - this provides the bytecode weaving functionality.
  31. It has very few external dependencies to minimize the size required for
  32. deployment of load-time weavers. Currently the build process doesn't
  33. produce a separate jar for just the weaver, but that will have to change for
  34. AspectJ-1.2.</li>
  35. <li><b>runtime</b> - these are the classes that are used by generated code
  36. at runtime and must be redistributed with any system built using AspectJ.
  37. This module has no external dependencies and produces a tiny ~30KB jar.</li>
  38. </ul>
  39. <p>
  40. <img border="0" src="overview.gif"></p>
  41. <p>The AspectJ compiler accepts both AspectJ bytecode and source code and
  42. produces pure Java bytecode as a result. Internally it has two stages. The
  43. front-end (org.aspectj.ajdt.core) compiles both AspectJ and pure Java source
  44. code into pure Java bytecode annotated with additional attributes representing
  45. any non-java forms such as advice and pointcut declarations. The back-end of the
  46. AspectJ compiler (weaver) implements the transformations encoded in these
  47. attributes to produce woven class files. The back-end can be run stand-alone to
  48. weave pre-compiled aspects into pre-compiled .jar files. In addition, the
  49. back-end exposes a weaving API which can be used to implement ClassLoaders that
  50. will weave advice into classes dynamically as they are loaded by the virtual
  51. machine.</p>
  52. <h2>Compiler front-end (org.aspectj.ajdt.core)</h2>
  53. <p>The front-end of the AspectJ compiler is implemented as an extension of the
  54. Java compiler from eclipse.org. The source-file portion of the AspectJ compiler
  55. is made complicated by inter-type declarations, declare parents, declare soft,
  56. and privileged aspects. All of these constructs require changes to the
  57. underlying compiler to modify Java’s name-binding and static checking behavior.</p>
  58. <p>As the compiler extends the jdt.core compiler, the package structure of this
  59. module mimics that of the jdt.core module. The design works hard to minimize the
  60. set of changes required to org.eclipse.jdt.core because a fun 3-way merge is
  61. required each time we want to move to a new underlying version of this code.&nbsp;
  62. The ultimate goal is to contribute all of our changes to jdt.core back into the
  63. main development branch some day.</p>
  64. <p>The basic structure of a compile is very simple:</p>
  65. <ol>
  66. <li>Perform a shallow parse on all source files</li>
  67. <li>Pass these compilation units through AjLookupManager to do type binding
  68. and some AspectJ augmentation</li><li>For each source file do a deep parse,
  69. annotation/analysis, and then code generation</ol>
  70. <h3>Top-level parse tree</h3>
  71. <p>Let's trace the following example program through the compiler.</p>
  72. <pre>package example.parse.tree;
  73. import org.aspectj.lang.*;
  74. public class Main {
  75. public static void main(String[] args) {
  76. new Main().doit();
  77. }
  78. private void doit() {
  79. System.out.println("hello");
  80. }
  81. }
  82. aspect A {
  83. pointcut entries(Main o): execution(void doit()) &amp;&amp; this(o);
  84. before(Main o): entries(o) {
  85. o.counter++;
  86. System.out.println("entering: " + thisJoinPoint);
  87. }
  88. private int Main.counter = 0;
  89. }</pre>
  90. <p>When parsed, this program will produce the following tree.</p>
  91. <p><img border="0" src="top-tree.gif"></p>
  92. <h3>PointcutDeclaration processing</h3>
  93. <p>Let's look more closely at the pointcut
  94. declaration:</p>
  95. <pre>pointcut entries(Main o): execution(void doit()) &amp;&amp; this(o);</pre>
  96. <p><img border="0" src="pointcut-dec.gif"></p>
  97. <p>The pointcut declaration is implemented as a subtype of a method declaration.
  98. The actual pointcut is parsed by the weaver module. This parsing happens
  99. as part of the shallow parse phase. This is because this information might
  100. be needed to implement a declare soft.</p>
  101. <h3>AdviceDeclaration processing</h3>
  102. <p>Next we look at the processing for an advice declaration:</p>
  103. <pre>before(Main o): entries(o) {
  104. o.counter++;
  105. System.out.println("entering: " + thisJoinPoint);
  106. }</pre>
  107. <p>
  108. After parsing, the AdviceDeclaration.postParse method will be called to make this
  109. a valid MethodDeclaration so that the standard eclipse code for analyzing a
  110. method body can be applied to the advice. After postParse, the selector is
  111. filled in and several additional arguments are added for the special
  112. thisJoinPoint forms that could be used in the body.</p>
  113. <p>
  114. <img border="0" src="advice-dec.gif"></p>
  115. <p>
  116. At this point the statements field which will hold the body of the advice is
  117. still null. This field is not filled in until the second stage of the
  118. compiler when full parsing is done on each source file as a prelude to
  119. generating the classfile.</p>
  120. <h3>
  121. Overview of the main classes in org.aspectj.ajdt.core</h3>
  122. <p>
  123. The main classes in this module are shown in the following diagram:</p>
  124. <p>
  125. <img border="0" src="ajdt-uml.gif"></p>
  126. <h2>Weaving back-end (weaver)</h2>
  127. <p>This provides all of the weaving functionality. It has very few
  128. dependencies to keep the code as small as possible for deployment in load-time
  129. weavers - only asm, bridge and util which are each very small modules with no
  130. further dependencies. This also depends on a patched version of the bcel library from apache.org.
  131. The patches are only to fix bcel bugs that can't be
  132. worked around in any other way.</p>
  133. <p>There are only four packages in this system.</p>
  134. <ul>
  135. <li>org.aspectj.weaver - general classes that can be used by any weaver
  136. implementation</li>
  137. <li>org.aspectj.weaver.patterns - patterns to represent pointcut designators
  138. and related matching constructs</li>
  139. <li>org.aspectj.weaver.ast - a very small library to represent simple
  140. expressions without any bcel dependencies</li>
  141. <li>org.aspectj.weaver.bcel - the concrete implementation of shadows and the
  142. weaver using the bcel library from apache.org</li>
  143. </ul>
  144. <p class="MsoNormal">The back-end of the AspectJ compiler instruments the code
  145. of the system by inserting calls to the precompiled advice methods.&nbsp; It does
  146. this by considering that certain principled places in bytecode represent
  147. possible join points; these are the “static shadow” of those join points.&nbsp; For
  148. each such static shadow, it checks each piece of advice in the system and
  149. determines if the advice's pointcut could match that static shadow.&nbsp; If it could
  150. match, it inserts a call to the advice’s implementation method guarded by any
  151. dynamic testing needed to ensure the match.</p>
  152. <h2>Runtime support library (runtime)</h2>
  153. <p>This library provides classes that are used by the generated code at runtime.&nbsp;
  154. These are the only classes that must be redistributed with a system built using
  155. AspectJ.&nbsp; Because these classes are redistributed this library must always
  156. be kept as small as possible.&nbsp; It is also important to worry about binary
  157. compatibility when making changes to this library.&nbsp; There are two packages
  158. that are considered public and may be used by AspectJ programs.</p>
  159. <ul>
  160. <li>org.aspectj.lang</li>
  161. <li>org.apectj.lang.reflect</li>
  162. </ul>
  163. <p>There are also several packages all under the header org.aspectj.runtime that
  164. are considered private to the implementation and may only be used by code
  165. generated by the AspectJ compiler.</p>
  166. <p></p>
  167. <h2>Mappings from AspectJ language to implementation</h2>
  168. <table border="1" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="100%" height="234">
  169. <tr>
  170. <td width="12%" height="19"></td>
  171. <td width="23%" height="19">org.aspectj.ajdt.internal.compiler</td>
  172. <td width="40%" height="19">weaver - org.aspectj.weaver.</td>
  173. </tr>
  174. <tr>
  175. <td width="12%" height="19">aspect</td>
  176. <td width="23%" height="19">ast.AspectDeclaration</td>
  177. <td width="40%" height="19">CrosscuttingMembers</td>
  178. </tr>
  179. <tr>
  180. <td width="12%" height="19">advice</td>
  181. <td width="23%" height="19">ast.AdviceDeclaration</td>
  182. <td width="40%" height="19">Advice + bcel.BcelShadowMunger</td>
  183. </tr>
  184. <tr>
  185. <td width="12%" height="19">pointcut declaration</td>
  186. <td width="23%" height="19">ast.PointcutDeclaration</td>
  187. <td width="40%" height="19">ResolvedPointcutDefinition</td>
  188. </tr>
  189. <tr>
  190. <td width="12%" height="19">declare error/warning</td>
  191. <td width="23%" height="19">ast.DeclareDeclaration</td>
  192. <td width="40%" height="19">Checker + patterns.DeclareErrorOrWarning</td>
  193. </tr>
  194. <tr>
  195. <td width="12%" height="38">declare soft</td>
  196. <td width="23%" height="38">ast.DeclareDeclaration +
  197. problem.AjProblemReporter</td>
  198. <td width="40%" height="38">Advice (w/ kind = Softener) +
  199. patterns.DeclareSoft</td>
  200. </tr>
  201. <tr>
  202. <td width="12%" height="38">declare parents</td>
  203. <td width="23%" height="38">ast.DeclareDeclaration +
  204. lookup.AjLookupEnvironment</td>
  205. <td width="40%" height="38">patterns.DeclareParents + NewParentTypeMunger</td>
  206. </tr>
  207. <tr>
  208. <td width="12%" height="18">inter-type decls</td>
  209. <td width="23%" height="18">ast.InterType*Declaration + lookup.InterType*Binding
  210. + lookup.AjLookupEnvironment</td>
  211. <td width="40%" height="18">New*TypeMunger + bcel.BcelTypeMunger</td>
  212. </tr>
  213. <tr>
  214. <td width="12%" height="19">if pcd</td>
  215. <td width="23%" height="19">ast.IfPseudoToken + ast.IfMethodDeclaration</td>
  216. <td width="40%" height="19">patterns.IfPointcut</td>
  217. </tr>
  218. <tr>
  219. <td width="12%" height="17">pcd</td>
  220. <td width="23%" height="17">ast.PointcutDesignator</td>
  221. <td width="40%" height="17">patterns.Pointcut hierarchy</td>
  222. </tr>
  223. </table>
  224. <p></p>
  225. <h1>Tutorial: implementing a throw join point</h1>
  226. <p>This tutorial will walk step-by-step through the process of adding a new join
  227. point to AspectJ for the moment when an exception is thrown.&nbsp; In Java
  228. source code, the shadow of this point is a throw statement. In Java bytecode,
  229. the shadow is the athrow instruction.</p>
  230. <p>This tutorial is recommended to anyone who wants to get a better feel for how
  231. the implementation of AspectJ really works.&nbsp; Even if you're just working on
  232. a bug fix or minor enhancement, the process of working with the AspectJ
  233. implementation will be similar to that described below.&nbsp; The size of your
  234. actual code changes will likely be smaller, but you are likely to need to be
  235. familiar with all of the pieces of the implementation described below.</p>
  236. <h2>Part 1: Adding the join point and corresponding pcd</h2>
  237. <p>The first part of this tutorial will implement the main features of the throw
  238. join point. We will create a new join point shadow corresponding to the athrow
  239. instruction and also create a new pointcut designator (pcd) for matching it.</p>
  240. <h3>Step 1. Synchronize with repository and run the existing test suite</h3>
  241. <p>Do a Team-&gt;Synchronize With Repository and make sure that your tree is
  242. completely in sync with the existing repository. Make sure to address any
  243. differences before moving on.</p>
  244. <p>Run the existing test suite. I currently do this in four steps:</p>
  245. <ul>
  246. <li>weaver/testsrc/BcWeaverModuleTests.java</li>
  247. <li>org.aspectj.ajdt.core/testsrc/EajcModuleTests.java</li>
  248. <li>ajde/testsrc/AjdeModuleTests.java</li>
  249. <li>Harness on ajctests.xml -- at least under 1.4, preferably under both 1.3 and
  250. 1.4.</li>
  251. </ul>
  252. <p>There should be no failures when you run these tests. If there are
  253. failures, resolve them with the AspectJ developers before moving on.</p>
  254. <h3>Step 2. Write a proto test case</h3>
  255. <p>a. Create a new file in tests/design/pcds/Throw.java</p>
  256. <pre>import org.aspectj.testing.Tester;
  257. public class Throws {
  258. public static void main(String[] args) {
  259. try {
  260. willThrow();
  261. Tester.checkFailed("should have thrown exception");
  262. } catch (RuntimeException re) {
  263. Tester.checkEqual("expected exception", re.getMessage());
  264. }
  265. }
  266. static void willThrow() {
  267. throw new RuntimeException("expected exception");
  268. }
  269. }
  270. aspect A {
  271. before(): withincode(void willThrow()) {
  272. System.out.println("about to execute: " + thisJoinPoint);
  273. }
  274. }</pre>
  275. <p>b. Create a temporary test harness file to run just this test in myTests.xml</p>
  276. <pre>&lt;!DOCTYPE suite SYSTEM "../tests/ajcTestSuite.dtd"&gt;
  277. &lt;suite&gt;
  278. &lt;ajc-test dir="design/pcds"
  279. title="simple throw join point"&gt;
  280. &lt;compile files="Throws.java" /&gt;
  281. &lt;run class="Throws"/&gt;
  282. &lt;/ajc-test&gt;
  283. &lt;/suite&gt;
  284. </pre>
  285. <p>c. Run this test using the harness. You should see:</p>
  286. <pre>about to execute: execution(void Throws.willThrow())
  287. about to execute: call(java.lang.RuntimeException(String))
  288. PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 2 seconds</pre>
  289. <h3>Step 3. Implement the new join point shadow kind</h3>
  290. <p>Modify runtime/org.aspectj.lang/JoinPoint.java to add a name for the Throw
  291. shadow kind.</p>
  292. <pre>static String THROW = "throw";</pre>
  293. <p>Modify weaver/org.aspectj.weaver/Shadow.java to add the Throw shadow kind.
  294. This adds a static typesafe enum for the Throw Kind. The constructor uses the
  295. name from the runtime API to ensure that these names will always match. The '12'
  296. is used for serialization of this kind to classfiles and is part of the binary
  297. API for aspectj. The final 'true' indicates that this joinpoint has its
  298. arguments on the stack. This is because the throw bytecode in Java operates on a
  299. single argument that is a Throwable which must be the top element on the stack.
  300. This argument is removed from the stack by the bytecode.
  301. </p>
  302. <pre>public static final Kind Throw = new Kind(JoinPoint.THROW, 12, true);
  303. </pre>
  304. <p>We also modify the neverHasTarget method to include the Throw kind because in
  305. Java there is no target for the throwing of an exception.</p>
  306. <pre>public boolean neverHasTarget() {
  307. return this == ConstructorCall
  308. || this == ExceptionHandler
  309. || this == PreInitialization
  310. || this == StaticInitialization
  311. || this == Throw;
  312. }
  313. </pre>
  314. <p>In the read method on Shadow.Kind, add another case to read in our new
  315. Shadow.Kind.</p>
  316. <pre>case 12: return Throw;
  317. </pre>
  318. <h3>Step 4. Create this new kind of joinpoint for the throw bytecode</h3>
  319. <p>Modify weaver/org.aspectj.weaver.bcel/BcelClassWeaver.java to recognize this
  320. new joinpoint kind. In the method
  321. <pre>private void match(
  322. LazyMethodGen mg,
  323. InstructionHandle ih,
  324. BcelShadow enclosingShadow,
  325. List shadowAccumulator)
  326. {
  327. </pre>
  328. <p>Add a test for this instruction, i.e.</p>
  329. <pre>} else if (i == InstructionConstants.ATHROW) {
  330. match(BcelShadow.makeThrow(world, mg, ih, enclosingShadow),
  331. shadowAccumulator);
  332. }
  333. </pre>
  334. <p>Then, modify BcelShadow.java to create this new kind of join point shadow:</p>
  335. <pre>public static BcelShadow makeThrow(
  336. BcelWorld world,
  337. LazyMethodGen enclosingMethod,
  338. InstructionHandle throwHandle,
  339. BcelShadow enclosingShadow)
  340. {
  341. final InstructionList body = enclosingMethod.getBody();
  342. TypeX throwType = TypeX.THROWABLE; //!!! not as precise as we'd like
  343. TypeX inType = enclosingMethod.getEnclosingClass().getType();
  344. BcelShadow s =
  345. new BcelShadow(
  346. world,
  347. Throw,
  348. Member.makeThrowSignature(inType, throwType),
  349. enclosingMethod,
  350. enclosingShadow);
  351. ShadowRange r = new ShadowRange(body);
  352. r.associateWithShadow(s);
  353. r.associateWithTargets(
  354. Range.genStart(body, throwHandle),
  355. Range.genEnd(body, throwHandle));
  356. retargetAllBranches(throwHandle, r.getStart());
  357. return s;
  358. } </pre>
  359. <p>Finally modify weaver/org.aspectj.weaver/Member.java to generate the needed
  360. signature</p>
  361. <pre>public static Member makeThrowSignature(TypeX inType, TypeX throwType) {
  362. return new Member(
  363. HANDLER,
  364. inType,
  365. Modifier.STATIC,
  366. "throw",
  367. "(" + throwType.getSignature() + ")V");
  368. }</pre>
  369. <p>Run the proto test again and you should see:</p>
  370. <pre>about to execute: execution(void Throws.willThrow())
  371. about to execute: call(java.lang.RuntimeException(String))
  372. about to execute: throw(catch(Throwable))
  373. PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 3 seconds
  374. </pre>
  375. <p>That last line shows the 'throw(catch(Throwable))'
  376. join point. This is a slightly confusing string form, but it is the first sign
  377. of our brand new join point. The reason for the weird 'catch(Throwable)' part is
  378. that we used Member.HANDLER for the kind of the signature of this join point.
  379. That's clearly not correct. We'll fix that at the end of the lesson as
  380. part of the clean-up. For now, let's go on with the interesting parts.</p>
  381. <h3>Step 5. Extend our proto-test to use a pointcut designator for matching</h3>
  382. <p>Add a second piece of before advice to the test aspect A:</p>
  383. <pre>before(): throw(Throwable) {
  384. System.out.println("about to throw: " + thisJoinPoint);
  385. }</pre>
  386. <p>When we run the test again we'll get a long error message from the harness.
  387. The interesting part of the message is the following:</p>
  388. <pre>[ 0] [error 0]: error can't find referenced pointcut at C:\aspectj\eclipse\tests\design\pcds\Throws.java:23:0
  389. </pre>
  390. <p>This error is not quite what you might have expected. You might have
  391. hoped for a syntax error saying that there is not 'throw' pointcut designator
  392. defined. Unfortunately, this is a weakness in the syntax of AspectJ where
  393. primitive PCDs and named PCDs have the same syntax, so the compiler can't tell
  394. the difference between a misspelled or non-existent primitive PCD and a named
  395. PCD reference that is missing. This also has some impact on extending the
  396. primitive PCDs because it will break existing programs. In this case, when
  397. we add the throw PCD we will break any existing programs that use throw as the
  398. name for a user-defined PCD. Fortunately because throw is a Java keyword
  399. this particular change is very safe.</p>
  400. <h3>Step 6. Extend the PCD parser to handle this new primitive PCD</h3>
  401. <p>Modify the
  402. parseSinglePointcut method in weaver/org.aspectj.weaver.patterns/PatternParser.java to add one more else if clause for the throw pcd:</p>
  403. <pre>} else if (kind.equals("throw")) {
  404. parseIdentifier(); eat("(");
  405. TypePattern typePat = parseTypePattern();
  406. eat(")");
  407. return new KindedPointcut(Shadow.Throw,
  408. new SignaturePattern(Member.HANDLER, ModifiersPattern.ANY,
  409. TypePattern.ANY, TypePattern.ANY, NamePattern.ANY,
  410. new TypePatternList(new TypePattern[] {typePat}),
  411. ThrowsPattern.ANY));</pre>
  412. <p>Modify the matches method in weaver/org.aspectj.weaver.patterns/SignaturePattern.java
  413. to add:</p>
  414. <pre>if (kind == Member.HANDLER) {
  415. return parameterTypes.matches(world.resolve(sig.getParameterTypes()),
  416. TypePattern.STATIC).alwaysTrue();
  417. } </pre>
  418. <p>Run the proto test again and you should see:</p>
  419. <pre>about to execute: execution(void Throws.willThrow())
  420. about to execute: call(java.lang.RuntimeException(String))
  421. about to execute: throw(catch(Throwable))
  422. about to throw: throw(catch(Throwable))
  423. PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 1 seconds
  424. </pre>
  425. Make sure that you see the 'about to throw' printed before moving on.
  426. This shows that the throw PCD is now successfully matching the throw join point
  427. shadow we added earlier.<h3>Step 7. Check that we're properly providing the
  428. single thrown argument (and clean-up the test)</h3>
  429. <p>Now that we have a valid pcd for this advice, we can simplify our test case.
  430. Modify our test aspect A to be the following. In addition to removing the
  431. overly generic withincode pcd, this change also prints the actual
  432. object that is about to be thrown:</p>
  433. <pre>aspect A {
  434. before(Throwable t): throw(*) && args(t) {
  435. System.out.println("about to throw: '" + t+ "' at " + thisJoinPoint);
  436. }
  437. }</pre>
  438. <p>When we run the test again we should see the output below:</p>
  439. <pre>about to throw: 'java.lang.RuntimeException: expected exception' at throw(catch(Throwable))
  440. PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 1 seconds
  441. </pre>
  442. <p>Congratulations! You've just
  443. implemented the throw join point and PCD. This code isn't yet ready to be checked into any repository. It still
  444. has some rough edges that need to be smoothed. However, you've now
  445. added a new join point to the AspectJ language and a corresponding PCD to match
  446. it. This is a good time to take a break before moving on to part two.</p>
  447. <h2>Part 2: Getting the signature of this new join point right</h2>
  448. <p>We know that throw(catch(Throwable)) is not the right thing to be printing
  449. for the signature at this join point. What is the correct signature?
  450. At the beginning of the tutorial, we explained that the preferred design for the
  451. pcd was to have throw(StaticTypeOfExceptionThrown). In step 4, we set the
  452. type of the exception thrown to be 'Throwable'. Can we set this to be more
  453. accurate? Looking at the source code, it seems easy to identify the static
  454. type of the exception that is thrown:</p>
  455. <pre>throw new RuntimeException("expected exception");</pre>
  456. <p>In the source code to a Java program there is a well-defined static type for
  457. the exception that is thrown. This static type is used for various stages
  458. of flow analysis to make sure that checked exceptions are always correctly
  459. handled or declared. The ThrowStatement class in our own compiler has a
  460. special field for exceptionType that stores the static type of the exception
  461. thrown. Unfortunately, this static type is much harder to recover from the
  462. corresponding bytecode. In this case we would need to do flow analysis to
  463. figure out what the static type is for the object on the top of the stack
  464. when the athrow instruction executes. This analysis can certainly be done.
  465. In fact this analysis is a small part of what every JVM must do to verify the
  466. type safety of a loaded classfile.</p>
  467. <p>However, the current AspectJ weaver doesn't do any of this analysis.
  468. There are many good reasons to extend it in this direction in order to optimize
  469. the code produced by the weaver. If we were really implementing this
  470. feature, this would be the time for a long discussion on the aspectj-dev list to
  471. decide if this was the right time to extend the weaver with the code flow
  472. analysis needed to support a static type for the throw join point. For the
  473. purposes of this tutorial, we're going to assume that it isn't the right time to
  474. do this (implementing flow analysis for bytecodes would add another 50 pages to
  475. this tutorial). Instead we're going to change the definition of the throw
  476. join point to state that its argument always has a static type of Throwable.
  477. We still allow dynamic matching in args to select more specific types. In
  478. general, good AspectJ code should use this dynamic matching anyway to correspond
  479. to good OO designs.</p>
  480. <h3>Step 1. Change the signature of the throw pcd</h3>
  481. <p>Since we aren't going to recover the static type of the exception thrown, we
  482. need to fix the parser for the throw pcd to remove this information. We'll
  483. fix the PatternParser code that we added in step 1.6 to read as follows:</p>
  484. <pre>} else if (kind.equals("throw")) {
  485. parseIdentifier(); eat("(");
  486. eat(")");
  487. return new KindedPointcut(Shadow.Throw,
  488. new SignaturePattern(Member.THROW, ModifiersPattern.ANY,
  489. TypePattern.ANY, TypePattern.ANY, NamePattern.ANY,
  490. TypePatternList.ANY,
  491. ThrowsPattern.ANY));</pre>
  492. <p>Notice that this code also starts to fix the member kind to be Member.THROW
  493. instead of the bogus Member.HANDLER that we were using before. To make
  494. this work we have a set of things to do. First, let's create this new kind
  495. in org.aspectj.weaver.Member. Find where the HANDLER kind is defined
  496. there, and add a corresponding throw kind:</p>
  497. <pre>public static final Kind THROW = new Kind("THROW", 8);
  498. </pre>
  499. <p>We also need to fix the serialization kind in
  500. Member.Kind.read(DataInputStream) just above this constant list to add a case
  501. for this new kind:</p>
  502. <pre>case 8: return THROW;
  503. </pre>
  504. <p>Still in this file, we also need to fix Member.makeThrowSignature to use this
  505. new kind:</p>
  506. <pre>public static Member makeThrowSignature(TypeX inType, TypeX throwType) {
  507. return new ResolvedMember(
  508. THROW,
  509. inType,
  510. Modifier.STATIC,
  511. "throw",
  512. "(" + throwType.getSignature() + ")V");
  513. }
  514. </pre>
  515. <p>If you run the test now you'll get an error from the parser reminding us that
  516. the throw pcd now doesn't accept a type pattern:</p>
  517. <pre>------------ FAIL: simple throw join point()
  518. ...
  519. C:\aspectj\eclipse\tests\design\pcds\Throws.java:19:0 Syntax error on token "*", ")" expected
  520. FAIL Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 failed) 1 seconds</pre>
  521. <p>This is an easy fix to the test case as we modify our pcd for the new syntax
  522. in the aspect A in our Throws.java test code:</p>
  523. <pre>before(Throwable t): throw() && args(t) {</pre>
  524. <p> Now when we run the test case it looks like everything's fixed and we're
  525. passing:</p>
  526. <pre>PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 2 seconds</pre>
  527. <h3>Part 2. Make a real test case</h3>
  528. <p>The pass result from running our test should worry you. Unlike previous
  529. runs, this test run doesn't show the output from our System.out.println in the
  530. before advice. So, it's clear this advice is not running. The
  531. problem is that even though the advice is not running, the test case is passing.
  532. We need to make this a real test case to fix this. We'll do that by adding
  533. code that notes when the advice runs and then checks for this event. This
  534. code uses the Tester.event and Tester.checkEvent methods:</p>
  535. <pre>import org.aspectj.testing.Tester;
  536. public class Throws {
  537. public static void main(String[] args) {
  538. try {
  539. willThrow();
  540. Tester.checkFailed("should have thrown exception");
  541. } catch (RuntimeException re) {
  542. Tester.checkEqual("expected exception", re.getMessage());
  543. }
  544. Tester.checkEvents(new String[] { "before throw" });
  545. }
  546. static void willThrow() {
  547. throw new RuntimeException("expected exception");
  548. }
  549. }
  550. aspect A {
  551. before(Throwable t): throw() && args(t) {
  552. Tester.event("before throw");
  553. //System.out.println("about to throw: '" + t+ "' at " + thisJoinPoint);
  554. }
  555. }</pre>
  556. <p>Now when we run our test case it will fail. This failure is good
  557. because we're not matching the throw join point anymore.</p>
  558. <pre>------------ FAIL: simple throw join point()
  559. ...
  560. [ 1] [fail 0]: fail [ expected event &quot;before throw&quot; not found]
  561. FAIL Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 failed) 1 seconds</pre>
  562. <h3>Step 3. Fix signature matching again</h3>
  563. <p>In org.aspectj.weaver.patterns.SignaturePattern.matches, we need to handle
  564. throw signature matching the same way we handle advice signature matching.
  565. Both of these pcds match solely on the kind of join point and use combinations
  566. with other pcds to narrow their matches. So, find the line for kind ==
  567. Member.ADVICE and add the same line below it for Member.THROW.</p>
  568. <pre>if (kind == Member.ADVICE) return true;
  569. if (kind == Member.THROW) return true;</pre>
  570. <p>This change will make our test case pass again. Run it to be sure.</p>
  571. <p>There's an interesting tension between a good automated test and a good test
  572. for development. Our new test case now correctly includes an automated
  573. test to let us know when we are and are not matching the new throw join point.
  574. However, without the println the test doesn't feel as satisfactory to me to run
  575. during development. I often like to turn this kind of printing back on the
  576. see what's happening. If you uncomment to System.out.println in the test
  577. aspect A and rerun the test, you won't be very happy with the results:</p>
  578. <pre>------------ FAIL: simple throw join point()
  579. ...
  580. unimplemented
  581. java.lang.RuntimeException: unimplemented
  582. at org.aspectj.weaver.Member.getSignatureString(Member.java:596)
  583. ...
  584. FAIL Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 failed) 1 seconds</pre>
  585. <p>It looks like there's more work to do to add the new member kind for
  586. Member.THROW. This problem only shows up when we try to print
  587. thisJoinPoint. It's showing that we haven't updated the reflection API to
  588. understand this new signature kind.</p>
  589. <h3>Step 4. Extend org.aspectj.lang.reflect to understand throw signatures</h3>
  590. <p>We need to add a couple of classes to the reflection API to implement the
  591. throw signature. Because we decided at the beginning of this section to
  592. not include the static type of the exception thrown in the throw signature,
  593. these classes are extremely simple. Nevertheless, we have to build them.
  594. Notice that when we add new source files to the system we need to include the
  595. standard eclipse CPL license header.</p>
  596. <pre>/* *******************************************************************
  597. * Copyright (c) 2004 Contributors.
  598. * All rights reserved.
  599. * This program and the accompanying materials are made available
  600. * under the terms of the Common Public License v1.0
  601. * which accompanies this distribution and is available at
  602. * http://www.eclipse.org/legal/cpl-v10.html
  603. *
  604. * Contributors:
  605. * Jim Hugunin initial implementation
  606. * ******************************************************************/
  607. package org.aspectj.lang.reflect;
  608. import org.aspectj.lang.Signature;
  609. public interface ThrowSignature extends Signature { }</pre>
  610. <pre>/* *******************************************************************
  611. * Copyright (c) 2004 Contributors.
  612. * All rights reserved.
  613. * This program and the accompanying materials are made available
  614. * under the terms of the Common Public License v1.0
  615. * which accompanies this distribution and is available at
  616. * http://www.eclipse.org/legal/cpl-v10.html
  617. *
  618. * Contributors:
  619. * Jim Hugunin initial implementation
  620. * ******************************************************************/
  621. package org.aspectj.runtime.reflect;
  622. import org.aspectj.lang.reflect.ThrowSignature;
  623. class ThrowSignatureImpl extends SignatureImpl implements ThrowSignature {
  624. ThrowSignatureImpl(Class declaringType) {
  625. super(0, "throw", declaringType);
  626. }
  627. ThrowSignatureImpl(String stringRep) {
  628. super(stringRep);
  629. }
  630. String toString(StringMaker sm) {
  631. return "throw";
  632. }
  633. }</pre>
  634. <p>To finish up our work in the runtime module, we need to extend
  635. org.aspectj.runtime.reflect.Factory to add a factory method for this new
  636. signature kind:</p>
  637. <pre>public ThrowSignature makeThrowSig(String stringRep) {
  638. ThrowSignatureImpl ret = new ThrowSignatureImpl(stringRep);
  639. ret.setLookupClassLoader(lookupClassLoader);
  640. return ret;
  641. }</pre>
  642. <p>We're not done yet. We still need to fix up the
  643. org.aspectj.weaver.Member class to use these new methods and types and fix the
  644. unimplemented exception that started us down this road in the first place.
  645. First let's add a method to create a string for the throw signature. This
  646. is a very simple method copied from the other create*SignatureString methods.</p>
  647. <pre>private String getThrowSignatureString(World world) {
  648. StringBuffer buf = new StringBuffer();
  649. buf.append('-'); // no modifiers
  650. buf.append('-'); // no name
  651. buf.append(makeString(getDeclaringType()));
  652. buf.append('-');
  653. return buf.toString();
  654. }</pre>
  655. <p>Now we need to modify three methods to add cases for the new Member.THROW
  656. kind. First, Member.getSignatureMakerName add:</p>
  657. <pre>} else if (kind == THROW) {
  658. return "makeThrowSig";
  659. </pre>
  660. <p>Next, to Member.getSignatureType add:</p>
  661. <pre>} else if (kind == THROW) {
  662. return "org.aspectj.lang.reflect.ThrowSignature";
  663. </pre>
  664. <p>Finally, to Member.getSignatureString add:</p>
  665. <pre>} else if (kind == THROW) {
  666. return getThrowSignatureString(world);
  667. </pre>
  668. <p>With all of these changes in place we should have working code for
  669. thisJoinPoint reflection using our new join point and signature kinds.
  670. Rerun the test to confirm:</p>
  671. <pre>about to throw: 'java.lang.RuntimeException: expected exception' at throw(throw)
  672. PASS Suite.Spec(c:\aspectj\eclipse\tests) 1 tests (1 passed) 1 seconds</pre>
  673. <h3>Step 5. Extend the test for automated coverage of reflection</h3>
  674. <p>Modify the before advice to include at least minimal checks of the new
  675. reflective information:</p>
  676. <pre>before(Throwable t): throw() && args(t) {
  677. Tester.event("before throw");
  678. Tester.checkEqual(thisJoinPoint.getSignature().toShortString(), "throw");
  679. Tester.checkEqual(t.getMessage(), "expected exception");
  680. }</pre>
  681. <p> As usual, you should rerun the tests and make sure they pass.</p>
  682. <p> With these changes to the reflection code, it looks like we have a working
  683. version of the throw join point and there are no obvious pieces that we've
  684. skipped. Take a break before proceeding to the final phase of tests.</p>
  685. <h2>Part 3: More serious testing</h2>
  686. <p>Now it's time to get a decent testing story. The test work that we will
  687. do here is probably too little for adding a new join point to the aspectj
  688. language; however, it should at least give you a sense of what's involved.</p>
  689. <h3>Step 1. Run the test suite again</h3>
  690. <p>Rerun the tests you ran at the beginning of part 1. Any failures that
  691. occur should be resolved at this point. At the time of writing this
  692. tutorial, I found 31 failures in the BcWeaverModuleTests. These failures
  693. are for all of the test cases that check the exact set of shadows produces by a
  694. given program. These test cases need to be updated based on the new join
  695. point we're adding. These particular test cases will probably be removed
  696. from the AspectJ test suite very soon because they've shown themselves to be
  697. very fragile over time and they often break for changes that are not introducing
  698. new bugs. However, you should be aware of this kind of failure because you
  699. may find it in other unit tests.</p>
  700. <p>You should expect to see at least one other test case fail when you run
  701. ajcTests.xml. Here's the failure message:</p>
  702. <pre>------------ FAIL: validate (enclosing) join point and source locations()
  703. ...
  704. [ 1] [fail 0]: fail [ unexpected event "before AllTargetJoinPoints throw(throw)" found]</pre>
  705. <p>Most of this message can be ignored. To find out what went wrong you
  706. should look for messages that have &quot;fail&quot; in them. The last line tells you
  707. what happened. There was an unexpected event, &quot;before AllTargetJoinPoints throw(catch(Throwable))&quot;.
  708. This is the signature for one of the new throw join points that we added in part
  709. 1. How could an existing test case match this new join point? The
  710. failing test case uses 'within(TargetClass)' to collect information about ALL
  711. join points that are lexically within a given class. Whenever we add a new
  712. kind of join point to the language we will extend the set of points matched by
  713. pcds like within. This means that these changes need to be very
  714. prominently noted in the release notes for any AspectJ release. Since
  715. we're not writing documentation in this tutorial, we will move on an fix the
  716. test case.</p>
  717. <h3>Step 2. Fix the failing test case</h3>
  718. <p>Now we need to fix this failing test case. The first step is to copy
  719. the test specification into our local myTests.xml file. The easiest way to
  720. do this is to copy the title of the failing test from the output buffer, then
  721. open ajcTests.xml and use find to search for this title. Then copy the xml
  722. spec for this one test into myTests.xml. Finally, run myTests.xml to make
  723. sure you got the failing test. You should see the same failure as before
  724. in step 1, but you should see it a lot faster because we're only running 2
  725. tests.</p>
  726. <p>To fix the test we need to find the source code. If you look at the
  727. test specification, you can see that the source file is the new directory with
  728. the name NegativeSourceLocation.java. Looking at the bottom of this file,
  729. we see a large list of expected events. These are the join points that we
  730. expect to see. If we look back up in TargetClass, we can see that the only
  731. occurence of throw is just before the handler for catch(Error) and right after
  732. the call to new Error. We should add our new expected event between these
  733. two:</p>
  734. <pre>, "before AllTargetJoinPoints call(java.lang.Error(String))"
  735. , "before AllTargetJoinPoints throw(throw)" // added for new throw join point
  736. , "before AllTargetJoinPoints handler(catch(Error))"</pre>
  737. <p>Run the test suite again to see that this test now passes.</p>
  738. <h3>Step 3. Extend test coverage to after advice</h3>
  739. <p>There is a lot we should do now to extend test coverage for this new kind of
  740. join point. For the purpose of this tutorial, we're just going to make
  741. sure that the new join point kind is compatible with all 5 kinds of advice.
  742. Let's extend our current simple Throws test to check for before and the three
  743. kinds of after advice:</p>
  744. <pre>import org.aspectj.testing.Tester;
  745. public class Throws {
  746. public static void main(String[] args) {
  747. try {
  748. willThrow(true);
  749. Tester.checkFailed("should have thrown exception");
  750. } catch (RuntimeException re) {
  751. Tester.checkEqual("expected exception", re.getMessage());
  752. }
  753. Tester.checkEvents(new String[]
  754. { "before throw", "after throwing throw", "after throw" });
  755. }
  756. static void willThrow(boolean shouldThrow) {
  757. int x;
  758. if (shouldThrow) throw new RuntimeException("expected exception");
  759. else x = 42;
  760. System.out.println("x = " + x);
  761. }
  762. }
  763. aspect A {
  764. before(Throwable t): throw() && args(t) {
  765. Tester.event("before throw");
  766. Tester.checkEqual(thisJoinPoint.getSignature().toShortString(), "throw");
  767. Tester.checkEqual(t.getMessage(), "expected exception");
  768. }
  769. after() returning: throw() {
  770. Tester.checkFailed("shouldn't ever return normally from a throw");
  771. }
  772. after() throwing(RuntimeException re): throw() {
  773. Tester.event("after throwing throw");
  774. Tester.checkEqual(re.getMessage(), "expected exception");
  775. }
  776. after(): throw() {
  777. Tester.event("after throw");
  778. }
  779. }</pre>
  780. <p>Run this test to confirm that it still passes. This is a very nice
  781. property of the orthogonality of the implementation of join points and advice.
  782. We never had to do any implementation work to make our new join point kind work
  783. for before and all three kinds of after advice.</p>
  784. <h3>Step 4. Look at around advice on throw join points</h3>
  785. <p>Let's create a new test case to see how this new join point interacts with
  786. around advice.</p>
  787. <pre>import org.aspectj.testing.Tester;
  788. public class AroundThrows {
  789. public static void main(String[] args) {
  790. try {
  791. willThrow(true);
  792. Tester.checkFailed("should have thrown exception");
  793. } catch (RuntimeException re) {
  794. Tester.checkEqual("expected exception", re.getMessage());
  795. }
  796. }
  797. static void willThrow(boolean shouldThrow) {
  798. int x;
  799. if (!shouldThrow) x = 42;
  800. else throw new RuntimeException("expected exception");
  801. System.out.println("x = " + x);
  802. }
  803. }
  804. aspect A {
  805. void around(): throw() {
  806. System.out.println("about to throw something");
  807. proceed();
  808. }
  809. }</pre>
  810. <p>When we run this test case we get a very unpleasant result:</p>
  811. <pre>------------ FAIL: simple throw join point with around()
  812. ...
  813. [ 1] --- thrown
  814. java.lang.VerifyError: (class: AroundThrows, method: willThrow signature: (Z)V) Accessing value from uninitialized register 1
  815. ...
  816. FAIL Suite.Spec(c:\aspectj\eclipse\tests) 3 tests (1 failed, 2 passed) 3 seconds
  817. </pre>
  818. <p>A VerifyError at runtime is the second worst kind of bug the AspectJ compiler
  819. can produce. The worst is silently behaving incorrectly.</p>
  820. <p>Unfortunately, this VerifyError is either impossible or very hard to fix.
  821. Think about what would happen if the around advice body didn't call proceed.
  822. In this case the local variable x would in fact be uninitialized. There is
  823. another serious language design question here, and for a real implementation
  824. this would once again be the time to start a discussion on the aspectj-dev
  825. mailing list to reach consensus on the best design. For the purpose of
  826. this tutorial we're once again going to make the language design choice that is
  827. easiest to implement and press on.</p>
  828. <h3>Step 5. Prohibit around advice on this new join point kind</h3>
  829. <p>The easiest solution to implement is to prohibit around advice on throw join
  830. points. There are already a number of these kinds of rules implemented in
  831. the org.aspectj.weaver.Shadow.match(Shadow, World) method. We can add our
  832. new rule at the beginning of the if(kind == AdviceKind.Around) block:</p>
  833. <pre>} else if (kind == AdviceKind.Around) {
  834. if (shadow.getKind() == Shadow.Throw) {
  835. world.showMessage(IMessage.ERROR,
  836. "around on throw not supported (possibly compiler limitation)",
  837. getSourceLocation(), shadow.getSourceLocation());
  838. return false;
  839. }</pre>
  840. <p> Now if we rerun our test we'll see errors telling us that around
  841. is prohibited on throw join points:</p>
  842. <pre>------------ FAIL: simple throw join point with around()
  843. ...
  844. [ 0] [error 0]: error at C:\aspectj\eclipse\tests\design\pcds\AroundThrows.java:22 around on throw not supported (possibly compiler limitation)
  845. [ 0] [error 1]: error at C:\aspectj\eclipse\tests\design\pcds\AroundThrows.java:16 around on throw not supported (possibly compiler limitation)
  846. ...
  847. FAIL Suite.Spec(c:\aspectj\eclipse\tests) 3 tests (1 failed, 2 passed) 3 seconds</pre>
  848. <p>To finish this test case up we need to modify the specification to be looking
  849. for these errors as the correct behavior. This will produce the following
  850. specification:</p>
  851. <pre>&lt;ajc-test dir=&quot;design/pcds&quot;
  852. title=&quot;simple throw join point with around&quot;&gt;
  853. &lt;compile files=&quot;AroundThrows.java&quot;&gt;
  854. &lt;message kind=&quot;error&quot; line=&quot;16&quot;/&gt;
  855. &lt;message kind=&quot;error&quot; line=&quot;22&quot;/&gt;
  856. &lt;/compile&gt;
  857. &lt;/ajc-test&gt;</pre>
  858. <p>Run myTests.xml one last time to see both tests passing.</p>
  859. <h3>Step 6. Final preparations for a commit or patch</h3>
  860. <p>You probably want to stop here for the purposes of this tutorial. We've
  861. pointed out several language design decisions that would need to be resolved
  862. before actually adding a throw join point to AspectJ. Some of those might
  863. involve a large amount of additional implementation work. If this was
  864. actually going into the tree, it would also be important to add several more
  865. test cases exploring the space of what can be done with throw.</p>
  866. <p>Assuming those issues were resolved and you are ready to commit this new
  867. feature to the tree there are three steps left to follow:</p>
  868. <ol>
  869. <li>Move our new test specifications from myTests.xml to the end of
  870. ajcTests.xml</li>
  871. <li>Rerun ajcTests.xml and the unit tests to ensure everything's okay.</li>
  872. <li>Update from the repository to get any changes from other committers
  873. since you started work on this new feature.</li>
  874. <li>Rerun ajcTests.xml and the unit tests to make sure nothing broke as a
  875. result of the update.</li>
  876. <li>Finally you can commit these changes to the AspectJ tree.</li>
  877. </ol>
  878. </body>
  879. </html>