For a feature to be considered for 1.1 it needs to have
both a convincing example of where it is a real and important
problem as well as a fairly concrete proposed solution.
Problems that have easy work-around are unlikely to make that
cut. Similarly, features that would involve a large amount of
design and/or implementation work are also unlikely. For cases
where there is a good problem, but the solution looks hard, carefully
worked out proposed solutions can tilt this balance.
The most valuable contribution is a clear description of a
problem that you have in building programs with AspectJ that can
be solved by one of these features (or by a yet unlisted
feature). Always bear in mind when lobbying for a particular
feature that any time spent working on language design and new
feature implementation is time taken away from basic engineering
work on things like compiler performance, error messages,
bytecode weaving, ...
B. Ready to consider for 1.1
These are issues that have both a clear definition of the
problem that they solve and a fairly well worked out solution
with no obvious holes.
B.1. typename pattern matching in throws clauses
The problem (from Ron Bodkin)
I am applying AspectJ to improve error handling, specifically
compile-time enforcement of policies for exception types. I would like
AspectJ to add support for type patterns in the the throws clause of
method signatures. For example, this code would enforce the policy that
the model layer should never throw checked exceptions to its clients:
declare error:
within(com.financial.portal.model..*) &&
execution(public * *(..) throws (Exception+ && !RuntimeException+)):
"do not throw checked exceptions from this layer";
This would require throwing checked exceptions only from
a given hierarchy:
execution(public * *(..) throws (Exception+ && !RuntimeException+ && !ModelException+))
This would prevent passing on exceptions from
implementation libraries:
execution(public * *(..) throws (IOException+ || SQLException+))
There is no known work-around for this problem.
The proposed solution
Extend the current syntax and matching rules for throws
clauses to allow type name patterns where only type names
are currently allowed. This would make all of the
examples above work as written. More detailed semantics
follow.
Grammar:
<ThrowsClausePattern>:
<ThrowsClausePatternItem> (","<ThrowsClausePatternItem>)*
<ThrowsClausePatternItem>:
["!"]<TypeNamePattern>
A <ThrowsClausePattern> matches the
<ThrowsClause> of any code member signature. The
matching rules are that each
<ThrowsClausePatternItem> must match the throws
clause. If any item doesn't match, then the whole pattern
doesn't match. This rule is unchanged from AspectJ-1.0.
If a <ThrowsClausePatternItem> does not begin
with "!", then it matches a throws clause if the
contained <TypeNamePattern> matches ANY of
the types named in the throws clause.
If a <ThrowsClausePatternItem> does begin
with "!", then it matches a throws clause if the
contained <TypeNamePattern> does not match ANY of
the types named in the throws clause.
These rules are completely backwards compatible with
AspectJ-1.0. The rule for "!" matching has one
potentially surprising property, in that the two PCDs
shown below will have different matching rules.
[1] call(* *(..) throws !IOException)
[2] call(* *(..) throws (!IOException)
void m() throws RuntimeException, IOException {}
[1] will NOT match the method m(), because method m's
throws clause declares that it throws IOException. [2]
WILL match the method m(), because method m's throws
clause declares the it throws some exception which does
not match IOException, i.e. RuntimeException.
B.2. after returning advice on handler join points
The problem (from Ron Bodkin)
I want to write an aspect that will complain if any
catch clauses in my code absorb an exception. That is,
they catch an exception but don't throw one. This is
straightforward in the current AspectJ language, i.e.
aspect PreventExceptionAbsorption {
pointcut allHandlers() :
handler(Throwable+) && !within(PreventExceptionAbsorption);
after() returning: allHandlers() {
throw new RuntimeException("Exception absorbed!");
}
}
However, AspectJ-1.0 doesn't allow after returning advice
on handler join points, so I would have to write the
following code instead as a work-around.
aspect PreventExceptionAbsorption {
ThreadLocal absorbed = new ThreadLocal();
pointcut allHandlers() :
handler(Throwable+) && !within(PreventExceptionAbsorption);
before() : allHandlers() {
Stack s = (Stack)absorbed.get();
if (s == null) {
s = new Stack();
absorbed.set(s);
}
boolean[] b = new boolean[1];
b[0] = false;
s.push(b);
}
after() throwing : allHandlers() {
Stack s = (Stack)absorbed.get();
boolean[] b = (boolean[])s.peek();
b[0] = true;
}
after() : allHandlers() {
Stack s = (Stack)absorbed.get();
boolean[] b = (boolean[])s.pop();
if (!b[0]) {
throw new RuntimeException("Exception absorbed!");
}
}
}
The proposed solution
There are three ways to handle the issue of after
returning advice on handlers
- Leave things as they are: disallowed (compiler
limitation)
- Add two new kinds of after advice: after fallthrough
and after break/continue
-
Have after returning (and after) capture fallthrough and break/continue
We believe the best decision would be the last one, for
three reasons:
- We should do something, since the current situation is the last
non-orthogonality between join points and advice
- 3 is the cleanest option
- it's not possible to reliably tell the difference between
returning, fallthrough, and break/continue in bytecode, so 2 is
problematic.
Background:
An exception handler join point is a different thing than many of our
other join points, because it is inherently statement level. [This is
our only current statement-level join point, but when we do
'synchronized' we'll have the same issues].
There are two says of exiting the computation corresponding to an
_expression_: by returning a value (or normally returning no value, in
void contexts), or by throwing an exception. That's why we have the
three kinds of after we do.
However, in statement level contexts, there are four ways of exiting
the computation:
* by throwing an exception
void foo() {
try { ... }
catch (MyException e) {
throw e;
}
}
* by returning
void foo() {
try { ... }
catch (MyException e) {
return;
}
...
}
* by falling through to the next statement
void foo() {
try { ... }
catch (MyException e) {
}
...
}
* or through a break or continue statement
void foo() {
X: {
try { ... }
catch (MyException e) {
break X;
}
...
}
...
}
So here are our options:
1. Leave things as they are: disallowed (compiler limitation)
Cons: this is the only non-orthogonality remaining between join
points and advice
Pros: backward compatible, saves the decision for future. Any
other decision will make it so we cannot back out.
2. Add two new kinds of after advice: after fallthrough and after break/continue
Cons: crosses everything else, it's unclear that anybody
actually wants after fallthrough or after break/continue
Pros: ooh, look at all the cool options!
3. Have after returning (and after) capture fallthrough and break/continue
Cons: The word 'returning' may be somewhat confusing, since it
would capture things that have no 'return' statement.
If we ever wanted to capture when someone actually tried to
return a value from an exception handler, as opposed to
falling through, we couldn't.
Pros: It's unclear that anyone actually wants to do it. And, more
importantly, it's unclear that we could even tell the
difference in bytecode:
void foo() {
X: {
try { ... }
catch (MyException e) {
return; [a]
break X; [b]
/*nothing */ [c]
}
}
}
The exact same bytecode can be (and generally is) generated
if the catch block includes one of [a], [b], or [c].
C. Need more work, but expect to consider for 1.1
C.1. the opposite of dominates
In jitterbug as
PR#607
The problem (from Arno Schmidmeier)
I often have a general aspect A, with a low priority, which does not
know anything about other aspects, but ensures some important
preconditions. (In my case opens a transaction)
Then there are quiet a lot of aspects (b,c,d,e) whith a higher
priority, which must be executed after A. They know about A.
So it would be more natural having b,c,d,e to tell, that they must
be executed after A.
The proposed solution
The devotes construct should work inverse than the dominates
construct
C.2. extensible pointcuts
The problem (from Many people)
Consider the standard FigureElement.moves() pointcut from the
AspectJ tutorial and standard talk. What happens to this pointcut
when I add a new kind of FigureElement?
public abstract class FigureElement {
public pointcut moves():
call(void FigureElement.move(int, int)) ||
call(void Line.set*(..)) ||
call(void Point.set*(..));
...
}
If I add a new kind of FigureElement to this system, I'm probably
going to have to update the moves pointcut to know about this new
class. Sometime this might be the right modularity, but certainly not
always. It would be very nice to be able to capture this information
locally with the new class.
The proposed solution
We can add a new kind of pointcut declaration that is "extensible".
The syntax is completely unclear at the moment. That proposed below
is just to make the proposal concrete.
public abstract class FigureElement {
public extensible pointcut moves():
call(void FigureElement.move(int, int));
...
}
public class Point extends FigureElement {
extends FigureElement.moves(): call(void Point.set*(..));
...
}
public class Line extends FigureElement {
extends FigureElement.moves(): call(void Line.set*(..));
...
}
C.3. -Xlint flag status (compiler warnings)
The problem (from Nicholas Lesiecki)
-Xlint seems so useful that maybe it should ship as the default,
and there would be a -Xnolintwarnings that would suppress them?
C.4. address the potentially unsafe casts for proceed (compiler warnings)
The problem (from ???)
The following program compiles under AspectJ-1.0 without any
warnings or errors. Even though it doesn't include any cast
operators, it will produce a ClassCastException at
Runtime.
public class Main {
public static void main(String[] args) {
}
}
aspect A {
Object around(Object p):
execution(static void main(String[])) && args(p)
{
proceed("hello");
}
}
C.5. args doesn't behave like instanceof for null
The problem (from Wes Isberg)
fyi, args() behaves differently than instanceof: args() matches a null reference.
String s = null;
boolean isFalse = (s instanceof String); // false, per JLS 15.20.2
but args(Type) will pick out join points where the arguments are null but of the correct type.
class C {
void g() { f(null); }
void f(String s) { }
before() : args(String) && call(* f(..)) {
// run before g's call to C.f(String) with null args
}
}
C.6. extend reflection model for new AspectJ constructs
The problem (from Robin Green)
Non-public inter-member declarations have mangled names
that make them very hard to use with Java's standard
reflection API. Non-static fields placed on interfaces
don't even appear as fields but are visible as methods with
mangled names. Finally, there is no reflective support for
any of the new AspectJ declarations such as aspects, advice,
pointcuts and declarations.
The proposed solution
Provide a parallel API to java.lang.reflect that will
expose AspectJ structure correctly. This is waiting on
writing about 10 new interfaces with good javadoc to
specify what the solution is.
C.7. add a join point corresponding to synchronized blocks
The problem (from Robert Barry)
Entering and exiting a synchronized block is a
well-defined and important join point in the execution of a
Java program. AspectJ-1.0 allows you to select those
synchronized blocks that correspond to methods with
call(synchronized * *(..)); however, it doesn't let you
capture synchronized blocks within a method.
The proposed solution
Add a new join point to the language corresponding to
synchronized blocks. The behavior will be very similar to
the current exception handler join points. This will also
require adding an appropriate pointcut designator.
C.8. add a join point corresponding to throw statements
The problem (from Adrian Colyer)
The point at which an exception is thrown is a
well-defined and important join point in the execution of a
Java program. AspectJ-1.0 doesn't let you directly capture
these join points. Current programs work-around this
problem by capturing calls the the constructors for
Throwables, i.e.
after() returning(Throwable t): call(Throwable+.new(..)) {
log("about to throw: " + t);
}
This will capture the vast majority of exceptions throw
events in a program. All of those of the form "throw new
Exception(..);". However, sometimes programs create new
Throwables that they don't throw immediately, or they throw
exceptions that they receive from somewhere else. This
idiom breaks for those cases.
The proposed solution
Add a new join point and corresponding pcd for throws.
C.9. make a kinded pcd for all kinds of join points
The problem (from Jim and Erik)
We have several join points that are matched by the
non-kinded pcds (within, this, ...) but that can't be
selected by any kinded pcd, including advice execution.
Lacking this feature seriously breaks the orthogonality
of AspectJ. It also makes it impossible to write the
following useful programs TBD.
The proposed solution
Add a kinded PCD for each kind of join point.
C.10. allow programs to extend the throws clause of existing
methods
In jitterbug as
PR#662
The problem (from Sergio Soares)
the feature request is an introduction constructor, which adds an
exception in a method throws clause, having the following "candidate
sintaxe"
declare throws: TypePattern throws TypeList;
In our case, this kind of feature is necessary because the RMI API
demands all methods of the remote interface must declare the
RemoteException in its throws clause! This demanding is checked by the
RMI post-processing tool (rmic). Another application of this feature
is allowing static checking, therefore woven programs will be more
robust than using softened exceptions.
C.11. resolve the status of the -XserializableAspects flag
The problem (from Arno Schmidmeier)
C.12. clean support for standard logging idiom using static fields
The problem (from Adrian Colyer)
I want to capture the following logging design in an aspect.
class Foo {
private static Log log = new Log(Foo.class);
void m() { log.enter(); ... log.exit(); }
}
There doesn't appear to be a clean way either to introduce the
static member and its initializer from an aspect or to access it
efficiently from the advice.
The proposed solution
Add perclass to the various forms of aspect instance
binding. We would need to work out the detailed semantics
of this, including how it would work with inner
classes.
There might be a different solution that addresses class
member variables in a more general way...
C.13. pcd's for converting from one kind of join point to
another
This is the most complicated item, and might have to be
broken out into several. There are a number of problems
that look like they could be solved by adding pcd's that
would convert from one kind of join point to another.
The problem (from Arno Schmidmeier)
I want to be able to convert from pointcuts specified
using calls to ones specified using executions. My
current code base would be dramatically simplified if I
could do this.
The proposed solution
enclosingExecution(pcd), callsTo(pcd), enclosedBy(pcd), ...
D. Don't expect to spend time on for 1.1
D.1. allow super.m() to access concrete methods introduced on
interfaces
The problem (from Ramnivas Laddad)
For example, consider:
interface MyIf { }
class MyClass implements MyIf { }
aspect IntroduceFooToMyIf {
public void MyIf.foo() {
System.out.println("This is MyIf.foo()");
}
}
This results in a foo() in MyIf and foo() with implementation in
MyClass. So far so good.
Now I change MyClass to:
class MyClass implements MyIf {
public void foo() {
System.out.println("This is MyClass.foo()");
}
}
This result in not replacing MyClass.foo() by one specified in aspect.
This is ok too because, MyClass.foo() overrides introduced MyIf.foo().
Now, I change MyClass to:
class MyClass implements MyIf {
public void foo() {
super.foo(); //!!!
System.out.println("This is MyClass.foo()");
}
}
}
I get a compiler error, saying there is no foo() in super (MyIf).
Now, this would be correct from pure Java point-of-view. But with
AspectJ, I expected to result in _equivalent_ of
class MyClass implements MyIf {
public void foo() {
System.out.println("This is MyIf.foo()");
System.out.println("This is MyClass.foo()");
}
}
That is, super.foo() should have been replaced with the introduced
code. In other words, introducing a method in MyIf should have behaved
as if that method was indeed there.
D.2. support a short form of proceed with args unchanged
D.3. provide a pcd for simply selecting joinpoints within the
current aspect
withinMyDefinition() instanceofMyType()
D.4. allow pointcuts definitions to be overloaded
D.5. allow the synchronized modifier to be used on advice
D.6. allow extension of concrete aspects
D.7. allow non-static inner aspects
D.8. support declare delegate
D.9. XPath-like support of field references
Also includes
Frank Sauer's hasa(A,B) proposal.
D.10. Allow finer-grained control of privileged
D.11. Provide an aspectsOf method on abstract aspects
In jitterbug as
PR#582
D.12. provide join points and/or pcds for conditionals and/or
loops