Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

compiler-weaver.adoc 41KB

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