Dependent Advice
Dependent Advice encode flow-insensitive information about a typestate property. This representation is limited in the sense that it only supports flow-insensitive analyses. This is in contrast to Dependency State Machines, which subsume Dependent Advice and support flow-sensitive analyses as well.
Syntax of Dependent Advice
The figure on the right-hand side shows the full syntax of Dependent Advice in EBNF, as an extension to the usual AspectJ syntax (shown in gray). Dependent Advice modify add the usual AspectJ syntax in three ways:
- AspectJ aspects can have the “dependent” modifier. An advice with a “dependent” modifier is called a “dependent advice”.
- A dependent advice is always named. This is so that dependency declarations can refer to pieces of advice by name. Advice names have no other special semantics.
- Every aspect can have one or more dependency declarations. A dependency declaration expresses that the execution of some pieces of advice depends on the execution of others.
Example
The following is an example aspect that has been annotated with a dependency annotation.
aspect ConnectionClosed { dependency{ strong close, write; weak reconnect; } Set closed = new WeakIdentityHashSet(); dependent after close(Connection c) returning: call(* Connection.close()) && target(c) { closed.add(c); } dependent after reconnect(Connection c) returning: call(* Connection.reconnect()) && target(c) { closed.remove(c); } dependent after write(Connection c) returning: call(* Connection.write(..)) && target(c) { if(closed.contains(c)) error("May not write to "+c+", as it is closed!"); } }
This aspect monitors the “ConnectionClosed” property, which says that one should not write to a closed connection. (Connections are assumed to be open when created and can be closed and re-connected, which transitions them back into a “connected” state in which writes are allowed.) The figure on the right shows the monitored property as a non-deterministic finite-state machine.
When looking at the finite-state machine, one can see that the following implicit dependencies exist between the three pieces of advice:
- “close” only needs to be monitored for connections c that are both closed and written to,
- in the same way, “write” only needs to be monitored for connections c that are both closed and written to, and
- “reconnect” only needs to be monitored for connections c that are both closed and written to.
In other words, the execution of “close” depends on the execution of “write” (on the same connection c) and the other way around, and “reconnect” depends on both “close” and “write” executing but not the other way around.
When we depict the “depends on”-relationship using arrows, this leads to the diagram shown to the right. This graph consists of a strongly-connected component that connects both “close” and “write”, and a weakly-connected component that connects “reconnect” to this strongly-connected component. This yields the dependency declaration:
dependency{ strong close, write; weak reconnect; }
In general, an aspect can have multiple dependency declarations.
Extended Syntax
Note however that the dependency annotation that we give above (line 2) omits the variable name “c” of the Connection. This is because, by default, a dependency annotation infers variable names from the
formal parameters of the advice declarations that it references (e.g. lines 6, 11 and 16). The dependency annotation from above is a short hand for a more verbose form:
dependency{ strong disconn(c), write(c); weak reconnect(c); }
The semantics of variables in dependency declarations is similar to unification semantics in logic programming languages like Prolog: The same variable at multiple locations in the same dependency refers to the same object. For each advice name, the dependency infers variable names in the order in which the parameters for this advice are given at the site of the advice declaration. Variables for return values from “after returning” and “after throwing” advice are appended to the end. For instance, the
following advice declaration would yield the advice reference createIter(c,i):
dependent after createIter(Collection c) returning(Iterator i): call(* Collection.iterator()) {}
We decided to allow for this kind of automatic inference of variable names because both code-generation tools and programmers frequently seem to follow the convention that equally-named advice parameters are meant to refer to the same objects. That way, programmers or code generators can use the simpler short-form as long as they follow this convention.
Semantics of Dependent Advice
We explain the semantics of Dependent Advice by example. Please see our AOSD 09 paper for a formal semantic description.
Assume that we run Clara on the above ConnectionClosed monitoring aspect in combination with the following base program.
public static void main() { Connection c1 = new Connection(); c1.read(); c1.close(); Connection c2 = new Connection(); c2.close(); c2.reconnect(); c2.write(); }
This program instantiates two connection objects, referred to by variables c1 and c2. The semantics of the dependency information in the annotation is that all shadows of pieces of advice that the dependency refers to must execute on an object o if for every strong symbol in the dependency there exists a shadow that may execute on o.
In our example, this means that close, write and reconnect must execute on a connection c when both close and write may execute on c.
For the connection that c1 refers to, there is only a close shadow (line 4) but no write shadow. Hence, Clara’s analyses may disable monitoring for the shadow at line 4. (Whether they will disable the shadow depends on the analysis’s precision. Clara’s Quick Check is too weak to disable the shadow at line 4 in this example, but the Orphan-Shadows Analysis will disable this shadow.)
For the connection that c2 refers to, both close and write shadows exist (lines 7 and 9) and hence the analyses in Clara have to keep all three shadows at lines 7, 8 and 9 enabled.