400 likes | 553 Views
Static Analysis of Aspects. Evgeniy Gabrilovich http://www.cs.technion.ac.il/~gabr CS Department, Technion. Problems with AOP. Aspect applicability is usually decided dynamically Matching join points with pointcut designators incurs s ignificant run-time overhead
E N D
Static Analysis of Aspects Evgeniy Gabrilovich http://www.cs.technion.ac.il/~gabr CS Department, Technion Static Analysis of Aspects
Problems with AOP • Aspect applicability is usually decided dynamically • Matching join points with pointcut designators incurs significant run-time overhead • Multiple pieces of advice may apply at the same program point Programs are difficult to maintain and debug ComposeJ, JasCo/JAC Static Analysis of Aspects
60 seconds about Dynamic Matching • Aspect code monitors the execution of the base program • Certain (sequences of) events trigger the aspect code (aka advice) • Monitored events are defined as patterns in the call stack • An aspect can be applied recursively, monitoring its own execution • Dynamic matching is expensive ! Observer Pattern Static Analysis of Aspects
Outline • New syntax for pointcut designators • Running example • Compiling aspects in AspectJ • Static analysis • Running example revisited • Limitations of the approach • Conclusions Static Analysis of Aspects
Reminder: Join points and pointcuts in AspectJ • Join point – a machine configuration where advice might intervene • Pointcut – a set of join points where a given advice should be executed • Examples:call(…), execution(…), cflow(…), cflowbelow(…) • Complex pointcuts use boolean operators: • !p, p1 || p2, p1 && p2 Static Analysis of Aspects
New syntax – regular expressions over control stack • Intuition: join points as abstractions over control stack • modeling the sequence of procedure calls as a graph • A join point is a sequence of • Procedure calls (call) • Procedure executions (exec) • Advice executions (aexec) Demeter used object graphs Invocation of a procedure at the call site Entry into the procedure’s body Static Analysis of Aspects
Grammar for the language of possible join points jp ::= jp_element* jp_element ::= call(name, name, actual_param*) | exec name | aexec name Calling context Called procedure Static Analysis of Aspects
Pointcut designators (PCDs) • Regular expressions over an alphabet of element designators: ed ::= pcall pname | pwithin name | args var* | ed or ed | ed and ed | not ed | true Matches join point elements of the form call(n,_,_) Matches calls made from the context n: call(_,n,_) args(x1,…,xn) matches call(_,_,[a1,…,an]), binding xi to ai Static Analysis of Aspects
Profiling Quicksort • linesbuf is a global variable holding an array of strings Static Analysis of Aspects
Gathering Quicksort statistics • Count the number of calls to partition • Count the number of swap operations • Note that • there may be other uses of swap apart from the obvious ones within the partition routine • both swap and partition may also be used outside quicksort Static Analysis of Aspects
Profiling aspect for Quicksort (1/4) aspectCounts var swaps, partitions: int; advice PCount before: {pcall(partition) /\ pwithin(quicksort)}; {true}* begin partitions := partitions + 1 end Top of the call stack A call to partition from the body of quicksort Static Analysis of Aspects
Profiling aspect for Quicksort (2/4) advice SCount before: {pcall(swap);{true}*;{pcall(quicksort)};{true}* begin swaps := swaps + 1 end A call to swap within the context of a call to quicksort pcall(swap) /\ cflow(quicksort) Static Analysis of Aspects
Profiling aspect for Quicksort (3/4) advice Init before: {pcall(quicksort)}; {not pcall(quicksort)}* begin partitions := 0; swaps := 0 end Only matches non-recursive calls to quicksort pcall(quicksort) /\ not cflowbelow(quicksort) Static Analysis of Aspects
Profiling aspect for Quicksort (4/4) advice Print after: {pcall(quicksort)}; {not pcall(quicksort)}* begin println “Partitions: “ ++ partitions; println “Swaps: “ ++ swaps; end endCounts Only matches non-recursive calls to quicksort Static Analysis of Aspects
Example {pcall(quicksort)}; {not pcall(quicksort)}* main() f() quicksort() X quicksort() Static Analysis of Aspects
More examples {pcall(swap);{true}*;{pcall(quicksort)};{true}* main() f() quicksort() partition() swap() X main() readln() swap() Static Analysis of Aspects
Interpreting aspects • Localizing the code that gathers quicksort statistics is nice … provided the run-time cost is negligible • Dynamic matching • Whenever a new join point is created, it’s matched against all PCDs • If a match is found, the corresponding advice is executed, with PCD variables bound in matching Static Analysis of Aspects
Interpreting aspects: example • Initialization trigger: {pcall(quicksort)}; {not pcall(quicksort)}* • To check that a join point satisfies this PCD, we must traverse the whole join point (= call stack) • Upon each creation of a new join point, the interpreter may have to traverse the whole join point for each PCD Static Analysis of Aspects
The AspectJ approach • Each PCD corresponds to a DFA • The compiled program maintains a set of such automata • Inspecting the automaton state determines in O(1) whether the corresponding aspect code should be executed Maintaining these automata is still a significant overhead, proportional to the number of PCDs (= pieces of advice) Static Analysis of Aspects
Static analysis of aspects • Our goal: • Completely eliminate the matching process • Determine for each point in the program exactly what PCDs will apply at run-time • Then, a compiler can generate a tangled program that the programmer might have written before aspects were invented Static Analysis of Aspects
The approach • Given the program call graph • determine for each procedure call the set of all join points possible at that call • statically determine all matching PCDs • Perform source-to-source transformation by weaving all applicable advice at compile-time • No PCD matching is needed at run-time! • This is not always possible Static Analysis of Aspects
Analysis overview • Each piece of advice (a) is associated with a PCD (pcd), which denotes a (usually infinite) set of join points: join_points(pcd) • Advice a is executed if the current join point (= sequence of procedure calls in the call stack) belongs to join_points(pcd) • For each procedure call p in the program, compute a set L(p) of all possible join points at evaluation of the call p. Static Analysis of Aspects
Static analysis by computing language containment • L(p) is defined as a regular language over the call graph of the program • join_points(pcd) is a regular language defined by the regular expression of pcd Then: • a always applies at p • a never applies at p • Otherwise, the analysis is inconclusive Static Analysis of Aspects
main() partition() swap() quicksort() SCount Examples {pcall(quicksort)}; {not pcall(quicksort)}* main() quicksort() Init {pcall(swap);{true}*;{pcall(quicksort)};{true}* Static Analysis of Aspects
Constructing the call graph • Nodes – procedure calls + aspects • Edges – join point elements • elementary operations affecting the control stack • Join points – sequences of join point elements = paths in the call graph • L(p) = a set of paths from the source vertex v (main) to the vertex representing p Static Analysis of Aspects
Constructing the call graph (2) • Every path from v to p is a valid join point at p • although it doesn’t have to occur in actual program runs – overestimation ! • Every valid join point is represented by a path • 3 kinds of edges (= join point elements): • procedure calls • procedure executions • advice executions Static Analysis of Aspects
Direct (unadvised) path Advised paths (possibly chaining multiple aspects) Static Analysis of Aspects
Pruning the call graph • The construction yields a huge call graph with many infeasible edges • Reducing the graph size by considering the topmost element of the call stack: • PCDs of the form {pcall(f) /\ …}; … can never apply to a call to procedure g ≠ f nodes for corresponding advice need not be included for such calls, and may be pruned Static Analysis of Aspects
Meet-over-all-paths analysis • Given the source program, construct its call graph • The set of paths from v to p is a superset of the set of possible call stacks at point p during program execution • For each procedure call p • obtain a regular expression L(p) • Tarjan’s algorithm – O(|E| · log (|V|) • Test it for inclusion w.r.t. each PCD • O(|E| + |V|) The set of join points at p Static Analysis of Aspects
Analyzing the Quicksort example • Legend (for a procedure call p and an advice a): • √ - a always applies at p • x – a can never apply at p • (blank) – the analysis is inconclusive Static Analysis of Aspects
main() partition() swap() quicksort() SCount Examples {pcall(quicksort)}; {not pcall(quicksort)}* main() quicksort() Init {pcall(swap);{true}*;{pcall(quicksort)};{true}* Static Analysis of Aspects
Quicksort example (cont’d) • PCount advice is in a sense static – it only depends on the textual location of the call • Only the topmost item of the stack is matched: {pcall(partition) /\ pwithin(quicksort)}; {true}* jp_element = call(partition,quicksort,_) quicksort() { … partition(); … } partition() { … } Static Analysis of Aspects
Quicksort example (cont’d) • The other two PCDs are truly dynamic and depend on the call stack: • {pcall(swap);{true}*;{pcall(quicksort)};{true}* - a call to swap is within dynamic scope of quicksort • {pcall(quicksort)}; {not pcall(quicksort)}* - a call to quicksort is not recursive Static Analysis of Aspects
Quicksort example (cont’d) • The analysis was successful for each advice and each procedure call Dynamic PCDs are in fact static in the context of the base program Static matching is possible! • Separation of concerns (sorting per se and profiling) does not sacrifice run-time efficiency Static Analysis of Aspects
Limitations of the approach • Static analysis is not always possible for arbitrary PCDs • Consider a simple procedure f with a single recursive call to itself • {pcall(f)}; {true}*; {pcall(f)}; {true}*; {pcall(f)}; {true}*; • recursion depth ≥ 3 • This PCD applies to some – but not all – recursive calls from f to itself • For example, it does not apply to the first such call pcall(f) /\ cflowbelow ( pcall(f) /\ cflowbelow(pcall(f)) Static Analysis of Aspects
Limitations (cont’d) • PCDs cannot make tests that depend on dynamic values of variables (args) • Otherwise, the matching code cannot be eliminated – we still must maintain a stack of variable bindings • The construction of the call graph is complicated by virtual methods • At each virtual method call, we need to determine what instances might be called • Open issue Static Analysis of Aspects
Conclusions • Primitive language for describing patterns in the call stack using regular expressions • Meet-over-all-paths analysis enables the compiler to determine aspect applicability statically • Run-time overhead of matching PCDs can be reduced, and sometimes completely eliminated Static Analysis of Aspects
Conclusions (cont’d) • In practice, static undecidability is limited to a few procedure calls in the program • Large portions of the call graph can still be woven statically • In large AO programs, it is important to warn programmers of potential interactions between aspects • Static analysis can detect when different pieces of advice may both be executed at the same program point Static Analysis of Aspects
Bibliography • D. Sereni and Oege de Moor. “Static Analysis of Aspects”, AOSD 2003 • H. Masuhara, G. Kiczales and C. Dutchyn. “Compilation Semantics of Aspect-Oriented Programs”, FOAL Workshop at AOSD 2002 • M. Wand, G. Kiczales and C. Dutchyn. “A Semantics for Advice and Dynamic Join Points in AOP”, FOAL Workshop at AOSD 2002 • E. Gamma, R. Helm, R. Johnson and J. Vlissides. “Design Patterns”, Addison-Wesley, 1995 • R.E. Tarjan. “Fast Algorithms for Solving Path Problems”, JACM 28(3), 1981 Static Analysis of Aspects
Questions? Static Analysis of Aspects