Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[aspectj-dev] The old list of language design issues for 1.1

I've attached a document that I made in the earliest 1.1 planning stages listing proposed language features with varying degrees of commentary.  It turned out that 1.1 was focused on the new implementation based on the eclipse compiler and bytecode weaving, and very few new language features were implemented.  That means that most of this is still valuable for 1.2 planning.

This list needs to be updated to reflect the 1.1 release.  A small number of these proposals were implemented, such as "C.9. make a kinded pcd for all kinds of join points".  A few others were implemented in very different forms, such as "C.1. the opposite of dominates" which was one piece of the motivation for the new "declare precedence" in 1.1.

I think that the structure that this document suggests is very valuable for having a language design discussion -- particularly the separation between specifying the problem in as concrete terms as possible and specifying potential solutions.  I've also learned that a hand-edited form like this one can very easily become stale and would recommend that we use either bugzilla or some other automated means of tracking these issues in the future.

-Jim

Language features to consider for AspectJ-1.1

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, ...

Discussion of these features should take place on the AspectJ users mailing list . Please use the following subject line for messages about these features, DESIGN: <exact title of feature from list below>. i.e.

Subject: DESIGN: the opposite of dominates

This would solve the horrible problem I've been having with the
interaction between my logging and profiling aspects.  I have the
following system, ...
        

A. Accepted for 1.1

B. Ready to consider for 1.1

B.1. typename pattern matching in throws clauses
B.2. after returning advice on handler join points

C. Need more work, but expect to consider for 1.1

C.1. the opposite of dominates
C.2. extensible pointcuts
C.3. -Xlint flag status (compiler warnings)
C.4. address the potentially unsafe casts for proceed (compiler warnings)
C.5. args doesn't behave like instanceof for null
C.6. extend reflection model for new AspectJ constructs
C.7. add a join point corresponding to synchronized blocks
C.8. add a join point corresponding to throw statements
C.9. make a kinded pcd for all kinds of join points
C.10. allow programs to extend the throws clause of existing methods
C.11. resolve the status of the -XserializableAspects flag
C.12. clean support for standard logging idiom using static fields
C.13. pcd's for converting from one kind of join point to another

D. Don't expect to spend time on for 1.1

D.1. allow super.m() to access concrete methods introduced on interfaces
D.2. support a short form of proceed with args unchanged
D.3. provide a pcd for simply selecting joinpoints within the current aspect
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
D.10. Allow finer-grained control of privileged
D.11. Provide an aspectsOf method on abstract aspects
D.12. provide join points and/or pcds for conditionals and/or loops

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

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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

  1. Leave things as they are: disallowed (compiler limitation)
  2. Add two new kinds of after advice: after fallthrough and after break/continue
  3. 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

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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)

Click here to discuss this feature on the users mailing list

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)

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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.12. clean support for standard logging idiom using static fields

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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

Click here to discuss this feature on the users mailing list

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.


Back to the top