Applications of Traversal Strategies
6/13/02
The traversal theory that we developed for
the Demeter system is applicable to both adaptive programming as well as
aspect-oriented programming, as shown in this paper. While in Aspect-Oriented
Concepts in Demeter we describe several applications of the basic AOP idea
in Demeter, here we describe how an important part of Demeter can be applied to
further Aspect-Oriented Programming.
The traversal theory relies on three
layers of graphs: top, middle and bottom (see Table 1). The bottom layer consists of graphs that we want to
traverse or ask questions about whether certain traversals can possibly exist.
Each bottom layer graph has a graph from the middle layer associated with it
that contains meta-information about the bottom layer graph. The
meta-information expresses that certain edges must or may exist. Each middle
layer graph is associated with at least one top layer graph. The top layer
graph is basically a sub graph of the transitive closure of the middle layer
graph, decorated with additional information attached to the edges. The purpose
of the top layer graph is to
1. define
sub graphs of the bottom layer graph. In other words, when the bottom layer
graph is traversed, the top layer graph tells us at each node which outgoing
edges to traverse.
2. find
all occurrences of the top layer graph in the bottom layer graph.
The union of all occurrences of the top
layer graph defines a sub graph of the bottom layer graph. The purpose of the
middle layer graph is to act as an abstraction barrier between the top and
bottom layers. At the middle layer we program the specification given by the
top layer and we use the middle layer to reduce the search space.
The top layer graphs are an abstraction A
of the associated middle layer graphs and the middle layer graphs are an
abstraction B of the associated bottom layer graphs. The abstractions A and B,
however, are different. Abstraction A involves the transitive closure and
abstraction B involves compatibility rules where relations at the middle layer
imply relations at the bottom layer.
In the following we use static and dynamic
call graphs.
Static call graph: It is derived from the
program source as follows. The nodes correspond to methods and the edges to
method invocation expressions (e.g. a.foo(b,c) contained in the methods). There
are two types of methods: concrete and abstract. A concrete
method has an outgoing edge for every method the method calls or might call.
Some of the outgoing edges are marked required and others are marked optional.
The required edges are to calls of other methods that are reached
unconditionally while optional edges correspond to calls that are reached
conditionally (some conditional statement might prevent the execution of the
call; e.g. an if, loop or switch statement). An abstract method has several
outgoing edges marked as virtual edges. Each leads to one of the method calls
that might happen as a result of the virtual method call. A static call graph
has the shape of a class graph as shown by the correspondences in the following
table:
|
|
static call graph |
class graph |
|
nodes |
concrete |
concrete |
|
|
abstract |
abstract |
|
edges |
required/optional |
part-of(required/optional) |
|
|
virtual |
subclass (reversed inheritance) |
Dynamic call graph: It is a tree
conforming to a static call graph. The nodes are calls of methods and the edges
represent immediate method call nesting. Conformance means that
1. the
dynamic call graph can only contain instances of call sites appearing in the
static call graph.
2. the
dynamic call graph can only contain edges prescribed by the static call graph.
3. if
in the static call graph a required edge exits a call site then the edge must
be in the dynamic call graph.
4. a
call of a virtual method is not shown in the dynamic call graph; instead it
shows the normal method that actually gets called.
A dynamic call graph has the shape of an
object graph where call nodes correspond to object nodes and immediately nested
calls correspond to immediately nested objects.
Make a figure for an expression traversal
program. Show source, static and dynamic call graphs.
|
layer |
object graph application |
call graph application |
|
top |
strategy graph |
pattern graph |
|
middle |
class graph |
static call graph |
|
bottom |
object graph |
dynamic
call graph |
Table 1: AOP applications of top-middle-bottom layers
2
abstractions, but details differ.
In the object graph application the intent
of the top layer graph is to define a set of collaborating introductions and to
define a pointcut for the join points that are created during the execution of
the introductions. In the call graph application the intent of the top layer
graph is to define a set of join points in the in an already defined dynamic
call graph. The call graph application lacks the code defining qualities of the
object graph application.
In aspect-oriented programming with a
dynamic join point model, the join points are points in the dynamic call graph
of the base program. The static call graph determines the overall shape of the
dynamic call graph. For example, if in the static call graph method f does not
call method g, then in the dynamic call graph f will never call g and if in the
static call graph method f calls method g, then in the dynamic call graph, f
may call g (there could be a condition involved that prevents the call of g at
run-time).
The object graph application defines a
pointcut traversal(s): all join points of the object graph traversal defined by
s. The call graph application defines a pointcut reachable(s): all join points
defined by s for a given dynamic call graph.
John Sung has implemented the object graph
application in the DAJ
tool.
The call graph application is introduced
in this paper. We start with a comparison to AspectJ and show why the cflow
construct of AspectJ is not sufficient to simulate the call graph application.
AspectJ has a cflow pointcut that can be used together with the set operations
to define useful sets of join points in a structure-shy way. For example, cflow
can be used to propagate variables from one method to another method
disregarding the number of intermediate method calls.
All method calls that might happen between
a call to f and a call to g. For each of those method calls we would like to
print some information about the join point and we would like to know how the
join points are related. In other words, we want to know the structure of the
dynamic call graph between calls to f and calls to g. We would like to describe
this slice of the dynamic call graph as “from jp1 to jp2”, where jp1 = call(*
f(*)) and jp2 = call(* g(*)). Both f and g may return any type and both may
have one argument of any type.
In AspectJ we cannot conveniently express
such subgraphs of the dynamic call graph. We could try cflow(jp1) &&
jp2 but this gives only a subset of the calls of g.
Consider the situation where we use
AspectJ for testing architectural rules. We would like to ensure that between a
call to f and call to g there is a call to x. In other words, there should be
no path “from jp1 bypassing jp3 to jp2”, where jp3 corresponds to a call to x.
Can do this with cflow: cflow(jp1) && jp2 ???