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.

compiler-weaver.adoc 41KB

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