Apostle: Aspect Programming for Smalltalk
v 0.2 (super-alpha pre-release)

Brian de Alwis
bsd@cs.ubc.ca


Welcome to Apostle, an extension providing Aspect-Oriented Programming (AOP) functionality to the Smalltalk language. This work is the basis of my master's thesis at the University of British Columbia, being done under the supervision of Gregor Kiczales. It's currently a mostly-faithful `port' of AspectJ to Smalltalk, sporting a similar join point model, aspects and advice.

This pre-release is being done to get feedback and direction of the language and system; being so new, much of Apostle is either subject to change or undefined. Thus this document is filled with notes of the form:

[TBI: ...]
indicating points To Be Investigated. Any comments you may have relating to these would be appreciated. Actually, any comments are appreciated! Please direct them to me.

Apostle was previously called the yawn-inducing Aspect/Smalltalk. This old name is still very present, especially in the source code. This will change over time.


Table of Contents


Availability

Apostle is currently only available for IBM's VisualAge for Smalltalk 4.5. Why? Because neither the ideas nor the engineering in this pre-release are considered stable enough to consider porting it elsewhere. It may `just work' under VAST 5.5, but I haven't tried it. I expect to upgrade to 5.5 at some point, and I'd especially like to see it ported to Squeak.

This release is available at http://www.cs.ubc.ca/labs/spl/projects/apostle/v0.2/apostle-0.2.tar.gz

Be sure to check the patches section below; some are required for successful operation of Apostle.

CAVEATS / WARNINGS

Please notice this pre-release's very styling super-mega-alpha2 designation. Realize that this is an extremely young piece of software. It is also a very powerful tool: small changes can have very large effects. To reiterate:

This software has and provides great potential for destroying your image
This cannot be emphasised enough: this software rewrites many components of the image. While it behaves in a predictable fashion, it can be applied in ways not imagined by the user. Consider the following piece of advice as an example:
    around<receptions(#at:)>

	self hasCachedContents ifFalse: [aspect refreshCacheFor: self].
	PROCEED
This example has two major problems:
  1. The pointcut identifies all types understanding the selector #at:; this is effectively all types, as #at: is defined in Object. Do all types understand hasCachedContent? If not, this will cause walkbacks: this can be devastating since this will be applied to almost all Collection classes.
  2. Secondly, the advice is missing a return (the caret, `^'). The return result will never be given; this could be avoided by making this before advice.
Independently or together, this advice can be devastating.

That said, I've been using it live for the last couple of days without a hitch. You can disable it with the following doit:

    AspectSmalltalk disable
and remove all applied aspects/advice with: with the following
    AsLoom current reset

The TODO section below has more detail on missing or buggy functionality. Please remember that this isn't real yet: play with the software, look at the examples (however trivial), try something. But please don't rely on it yet.

Copyrights

All of this code is copyrighted in some form or another.

The SUnit* application is copyrighted by its respective owners.

The MtTools and MtToolsTests applications were previously written by me under contract, and are copyrighted 2000-2001 by Brian de Alwis. They were copyrighted under a BSD-like license, but as I've misplaced my copyright notice, I'll make release these for now with all rights reserved. If you'd like to use them, e-mail me; I'll re-release them with a different copyright and even with documentation!

The remaining code is copyright by the University of British Columbia, as dictated by UBC's Policy 88 on Patents and Licensing. Again, all rights are reserved: you may use the code for evaluation of this software.

Phew!


Documentation

If Apostle is still very much in its infancy (and it is), then this documentation is even more so. But Apostle's being based on AspectJ means that it can leverage from AspectJ's documentation too. Recommended in particular are:

This document includes some brief notes on the Apostle language. But I recommend that you UTSL too (Use the Source, Luke!). The implementation section below describes the structure of the implementation. Also included is a lightly aspectified application; see the notes below on using the simulation.


The Apostle Language

Apostle is very new and very open to change. While based on the model of AspectJ, it can (and is expected to) diverge. This pre-release is being done to get feedback and direction of the language.

Having read the AspectJ documentation (you have read them, right?), the remainder of this section presents the details of the Apostle language.

Defining Aspects

Aspects are created as subclasses of the AsAspect class, using a similar protocol as that for creating classes:

    <aspectClass>
	subaspect: 'aspectName'
	instanceVariableNames: 'inst var names '
	classVariableNames: 'class var names'
	poolDictionaries: 'pool names'
Or #subaspect:classInstanceVariableNames:... to declare class-instance variables. [TBI: These messages should not be considered fixed, as they must somehow change to incorporate the aspect's type: singleton, per-object, cflow, etc. Plus, should they be called aspectVariableNames: or aspectInstanceVariableNames:?]

Currently only `singleton' aspects are supported. Singleton aspects have only a single aspect instance in the system at any time. These were called of eachJVM in AspectJ, and have now become the default. Aspect instances are explained in the next section.

Acquiring and Inquiring About Aspect Instances

Aspect instances are created upon some condition, depending on the aspect-type; they cannot be created explicitly. The instances are managed automatically, being released when appropriate. The aspect instance for a particular object can be obtained with the AsAspect class>>#aspectFor: message.

[TBI: Should there be shortcuts for singleton aspects? Or should they use

    <aspectClass> aspectFor: nil
?]

Declaring Pointcuts

Join points identify points in the execution of the program. They are identified by pointcut designators, declared and saved just like methods. They can be defined either on classes or aspects. Pointcuts have the following format:

    pointcut name(): pointcut-expression
pointcut-expressions are similar to those in AspectJ: join points, named pointcuts, or sub-expressions connected by the set operators `&&' or `||', or complemented with `!'. The currently defined join points are:
kindOf([namespace-pattern.]type-pattern [class])
Identifies receivers who are of a type matches by the given patterns. Patterns may use `*' for any sequence of characters, or a `#' to match any single character. For VAST, namespaces correspond to ENVY Applications or SubApplications; a class's namespace is considered to be its defining application (found by executing the doit: `<class> controller'). [TBI: This may strike the reader as strange as it may seem more logical to have `kindOf(CLIM.Bag)' identify the methods of Bag from its extension in the application CLIM. But kindOf() identifies types, not methods; besides which, implementing this behaviour this is difficult!] Class methods are identified by providing the class modifier.
(Corresponds to AspectJ's instanceof() join point)
receptions(selector)
Identify points where selector is received before being dispatched to the appropriate implementation. Or in english, identifies places where sending selector won't cause a #doesNotUnderstand:.
(Corresponds to AspectJ's receptions() join point)
executions(selector)
Identify points where selector is dispatched. Or in english, the implementations of selector.
(Corresponds to AspectJ's executions() join point)
cflow(pointcut-expression)
Identifies points in the program's execution as a result of having executed through a point identified by pointcut-expression. cflow() is better explained with an example:
    kindOf(Foo) && receptions(#hash) &&
	cflow(kindOf(Bag) && (receptions(#add*) || receptions(#remove*)))
identifies all join points where #hash is sent to an instance of Foo as a result of being added to or removed from a Bag.
(Corresponds to AspectJ's cflow() join point)
sends(selector)
Identify methods sending selector to some object. This join point is currently pretty useless as it does not identify the location of the call, but only the surrounding method issuing the call. cflow() can be used to do this better; for example:
    kindOf(Bag) && sends(#hash)
becomes
    receptions(#hash) && cflow(kindOf(Bag))
[TBI: This may be a better overall solution anyways; sends() cannot be used with constructed selectors, and there's no way of narrowing it for a particular type, since kindOf() only applies against the receiver...]
(Corresponds to AspectJ's calls() join point)

Named pointcuts can be referenced from other pointcuts. For example:

    pointcut foo(): kindOf(Aardvark) && receptions(add:)
    pointcut bar(): cflow(foo()) && kindOf(Block) && receptions(value*)
Currently there is only a single-level namespace for pointcuts; this is expected to change [TBI: Automatically incorporate namespace and class names? So foo() defined in CLDT.Bag becomes CLDT.Bag.foo()?]

Pointcuts cannot currently capture arguments; support for this is forthcoming.

Declaring Advice

Advice attaches code to identified join points; it annotates the execution of the program. Similar to pointcuts, advice is declared and defined in a browser; unlike pointcuts, advice can only be defined on aspects, and must always be defined as instance methods.

Advice is of the form:
    <advice-specification>

	<advice-body>
There are currently three forms of advice-specification:
before<pointcut-designator>
Executed before the identified execution point. Short of using an out-of-context return, cannot affect the identified execution point.
after<pointcut-designator>
Executed after the identified execution point. If the execution point is reached, then the advice-body is guaranteed to execute. Cannot affect return value of execution point. [TBI: Should incorporate AspectJ's additional differentiations: after-throwing and after-returning]
around<pointcut-designator>
Executed around the identified execution point. The actual execution point is indicated by using the PROCEED keyword; by omitting it, it is possible to prevent executing the original point. The return-value of the execution point is taken to be the return-value of the advice-body.

Advice code executes in a rather different context than might be expected, and is very different from that in AspectJ. self identified the executing object instance of the execution context (as opposed to AspectJ's this being the aspect instance), but inst-var accesses are in the context of the aspect instance. Methods on the aspect can be sent to aspect. I believe this makes sense as I view the aspect as extending the class, implementing a different concern from that of the original class. But the aspect expresses a different concern from that in the class, and thus shouldn't have direct access to the instance's innards. [TBI: This is one aspect of Apostle and AOP that is of great interest. I find that providing a representation of aspect instances as first-class objects is a difficult concept for users. This is open to change.]

An example piece of advice:

    before<kindOf(Foo) && receptions(add*)>

	aspect isFull ifTrue: [aspect dropLeastRecentlyUsed].
	aspect rememberNewestReceiver: self.


Implementation

The implementation is grouped as a set of ENVY applications and subapplications:

    AspectSmalltalk
       AspectSmalltalkImplementation
       AspectSmalltalkPointcutExpressions
       AspectSmalltalkES
    AspectSmalltalkTesting
AspectSmalltalk contains the classes expected to be `public' (i.e. with some public API). AspectSmalltalkImplementation and AspectSmalltalkPointcutExpressions exist to hide implementation code irrelevant to users. Some effort has been made to isolate ENVY/VAST-specific code into AspectSmalltalkES, to aid in future porting work. Lastly, AspectSmalltalkTesting has some unit-tests built with SUnit, nice debugging printStrings for various Apostle objects, and some helper methods for development.

Features currently working in this pre-release are:

To enable defining pointcuts from within classes, you'll need to explicity instruct VAST to use the provided aspect compiler, AsCompiler, by:

    Compiler := AsCompiler
This is reasonably safe: I've been running live with this for weeks now.

To define pointcut objects, use the AsPointcutCompiler. You can combine pointcuts with the `&&' and `||' protocols, and with the #not method. For example:

    | pcut1 pcut2 |
    pcut1 := AsPointcutCompiler compile: 'kindOf(Foo)'.
    pcut2 := AsPointcutCompiler compile: 'kindOf(Bar)'.
    ^pcut1 || pcut2


Aspectified Example: Percolation Simulation Package

Included is a lightly aspectified application. This simulation was written to demonstrate that the performance of a system made up of asynchronous elements may actually be determined by the time of its slowest operations due to a phenomenon called percolation. This is discusses in more detail in:

Mark Greenstreet, Brian de Alwis. How to Achieve Worst-Case Performance, accepted to the Seventh International Symposium on Asynchronous Circuits and Systems (ASYNC 2001), Salt Lake City, USA. (pdf)

The simulation is split into two ENVY applications: PercolationSimulation embodied the simulation model and logic, and PercolationSimulationGraphing provided the graphical presentation. The start the simulation, use the following doit:

    SimulationWindow new open
and use Simulation -> New Simulation to start a new simulation.

There is currently only one aspect in the simulation:

SimDrawingOptimization
Optimize drawing and redrawing of nodes and graphs; don't schedule multiple redraws while one is in progress. Ensure redraws are performed by the UIProcess, as required under VAST/ENVY.
There are many other potential aspects; these will be extracted with time!


TODOs, Future Considerations, Bugs, Etc.

Patches

Patch 1: ENVY/Manager should use #literalSize

Apostle does a lot of source-rewriting and method-manipulation, and thus uses CompiledMethod>>#setSource: quite extensively. This seems to trigger a bug in ENVY/Manager in VAST 4.5, specifically ClassDescription>>rebindVariablesIn:using:. Replace the bolded code in the following, found near the beginning of the method:

    1 to: methodOrCopy size do: [:index |
with
    1 to: methodOrCopy literalSize do: [:index |
This may have been fixed in VAST 5.5.

Bugs

Beyond unimplemented features, no bugs are known as yet. Please report any to bsd@cs.ubc.ca.
Brian de Alwis / bsd@cs.ubc.ca