Detecting aspect-specific code smells using Ekeko for AspectJ
-
Upload
coen-de-roover -
Category
Documents
-
view
1.162 -
download
0
Transcript of Detecting aspect-specific code smells using Ekeko for AspectJ
Detecting aspect-specific code smellsusing Ekeko for AspectJ
Coen De RooverSoftware Languages LabVrije Universiteit Brussel
Johan FabryPleiad Lab - DCC
University of Chile
Aspect-oriented programming... modularizes cross-cutting concerns
pointcut specifies program points at which the aspect intervenesadvice behavior to invoke at points selected by a pointcut
AspectWeaver
woven output code
core application functionalitycore application functionality
aspect 2aspect 3 aspect 1
Code smells in aspect-oriented programs... symptoms of suboptimal implementation
e.g., [Piveta07]
advices with anonymous pointcutslarge aspects
extrapolated from OO smellsrather informal specification
1 public class LargeAspectASTVisitor extendsBadSmellsASTVisitor {
2 public void endVis i t ( TypeDeclaration node ) {3 super . endVis i t ( node ) ;4 i f ( ( ( AjTypeDeclaration ) node ) . i sAspec t ( ) )5 i f ( getNumberOfMembers( ) >= Consts .TAU) {6 BadSmellsEvent event = new BadSmellsEvent ( ) ;7 event . setType ( ”Large Aspect” ) ;8 . . .9 }
10 }11 }
Listing 3: AST visitor responsible for the detection of the Large Aspect bad smell
2.3 Detecting Lazy Aspects
This bad smell, initially defined in [M.P. Monteiro, 2005], occurs if an aspect hasfew responsibilities, and its elimination could result in benefits during mainte-nance activities. Sometimes, this responsibility reduction is related to previousrefactoring or to unexpected changes in requirements (planned changes that didnot occur, for instance).
Definition 03 Consider an aspect !. The crosscutting members of ! are the
collection of all advice, pointcuts, declare constructions and inter type declara-
tions directly defined in !. Consider " as the number of crosscutting members of
!. An aspect is considered a lazy one whenever the predicate " == 0 holds. The
function could be defined as: f(!) = " == 0
To detect lazy aspects, a similar approach to the Large Aspect bad smell istaken. The LazyAspectASTVisitor creates bad smell events whenever an aspectwithout crosscutting members is found (see Listing 4).
1 public class LazyAspectASTVisitor extendsBadSmellsASTVisitor {
2 public void endVis i t ( TypeDeclaration node ) {3 super . endVis i t ( node ) ;4 i f ( ( ( AjTypeDeclaration ) node ) . i sAspec t ( ) )5 i f ( getNumberOfMembers( ) == 0) {6 BadSmellsEvent event = new BadSmellsEvent ( ) ;7 event . setType ( ”Lazy Aspect” ) ;8 . . .9 }
10 }11 }
Listing 4: AST visitor responsible for the detection of the Lazy Aspect bad smell
815Kessler Piveta E., Hecht M., Soares Pimenta M., Price R.T.: Detecting ...
lazy aspects
but:
Assumption-based code smells
[Zschaler11] AspectAssumptions
Aspect–Base Coordination
Synchronisation Architecture Coding Patterns
Managedby Context
MonitorSharing
Communication Data CodeAdvised
CodeCalled
Aspect–Aspect Coordination
Inter-Aspect Inter-Advice Inter-Process
ITDsDeployment Super-AspectStructure
Sub-AspectStructure Precedence
Wormhole Advice Execution Sequence
Figure 1: Top-level aspect assumption categories identified
behaviour A1 introduces or to introduce base behaviour that A1
can then modify. If A1 requires A2 to be deployed because it usesinter-type definitions (ITDs) defined in A2, this is classified withthe other ITD assumptions below.
A special case is when an aspect A1 has been introduced to re-solve a feature interaction between two other aspects A2 and A3
(or more). In this case, there will be an inclusion assumption!A1 ! d(A2) " d(A3).Examples. MobileMedia uses aspects to configure different prod-ucts of a product line. For example, deploying PhotoAndMu-sicAspect implies features for both photo and music manage-ment have been selected. As the features themselves have beenimplemented in separate aspects (PhotoSelector and Music-Selector in this example), there is an inclusion requirement thatthese implementation aspects also be deployed.
HealthWatcher shows an example of an aspect introduced be-cause of a feature interaction: Aspect HWTimeStamp had to beadjusted to provide special behaviour for the case when Update-StateObserver (implementing the observer protocol) is de-ployed. In particular, HWTimeStamp maintains a modificationcounter for objects stored in the database. Before the deploymentof the observer protocol it was sufficient for this aspect to updatethe modification counter for every request coming into the system.Deployment of the observer pattern invalidated this assumption asa request may now lead to a chain of modifications triggered byobservers. The modification counter may need to be updated morethan once for each request coming into the system. Here, the in-clusion assumption is on the deployment of HWTimeStamp andUpdateStateObserver. However, the former is trivially truebecause the advice has been directly included into HWTimeStamp.
Some aspects are very specific and must be woven in one par-ticular way; other aspects capture more generic patterns and canbe woven in a potentially large number of places. Where anaspect provides special behaviour depending on an aspect cap-turing generic patterns, whether to provide the special behaviour(and possibly also what behaviour to provide) may depend onwhere exactly the generic pattern is woven. An aspect provid-ing such special behaviour not only makes an assumption aboutthe generic-pattern aspect being deployed, but also about where inthe system it is woven. For example, HealthWatcher has an aspectHWUpdateObserverExceptionHandler, documented as“[. . . ] this aspect needs a lot of knowledge about how the observerpattern affects the application and how the application should dealwith its exceptions” [14]. Further analysis of the code shows that,
among other things, this aspect assumes that the Observer-Patternaspect is only woven into the system for data updates (and not, forexample, for data creation).
Beyond the general case, we have identified a specific variation,where one aspect defines a marker interface that is then used byanother aspect in a declare parents clause. In this case, thesecond aspect assumes deployment of the first, so that the semanticsof the marker interface are operationalised.
For example, Glassbox defines interface MonitoredType.This is defined as a top-level interface, but is documented to “start[the] agent when loading any such type. Should be added to anycontrolled code whose loading would trigger monitoring [. . . ]”[33]; that is, it is used as a marker interface. The starting-the-agentfunctionality is implemented in aspect AutoInitialization.Any aspect declaring MonitoredType as the parent of someclass (for example, BerkeleyDbMonitor, CommonsHttp-Monitor, or ServletRequestMonitor) implicitly requiresAutoInitialization to be deployed.Discussion. Aspect deployment is specified separately from the ac-tual aspect definitions, either on the ajc command line or in pa-rameter files for ajc. When inclusion dependencies are not madeexplicit in an aspect definition, the presence of a required aspectcannot be checked by ajc at weaving time, potentially leading toruntime errors or unexpected system behaviour.
The inclusion assumptions we have found, seem to occur mostlywhere aspects are used to encapsulate individual features to beadded to or removed from the system as required. In this sense,they seem closely related to the ordering constraints between so-called derivatives as described in [25].
Name. Mutual exclusion assumptionsDescription. Aspects may also be mutually exclusive; that is,!A1 ! ¬d(A2). In some cases, at least one aspect from a mu-tually exclusive set of aspects has to be deployed to provide therequired system functionality.Examples. HealthWatcher defines two aspects HWLocalSyn-chronization and HWManagedSynchronization provid-ing alternative implementations for the synchronisation of accessto data classes of HealthWatcher. HWLocalSynchronizationis intended to be used in a debugging version of HealthWatcher,which uses an in-memory representation of data. In contrast, HW-ManagedSynchronization is used in the live system, whichuses a relational database.
A special case is the mutual exclusion between sub-aspects
96
assumption: Examples. The Glassbox aspect JxtaSocketMonitor assumesto have precedence over its super-aspect AbstractMonitor.This is the default semantics of AspectJ. However, an assumptionremains that this is not changed by any declare precedenceclauses anywhere in the code.Discussion. Precedence may be influenced by declare prece-dence clauses directly referencing a set of aspects, but also quiteindirectly. For example, AbstractMonitor defines an interfaceLowerPrecedence. Any aspect implementing this interfacewill automatically receive lower precedence than AbstractMo-nitor.
However, AspectJ’s declare precedence clause can alsobe used to make precedence assumptions explicit. Then, if they areviolated, ajc will produce an error. This has been done for theGlassbox example as a result of the discussion of our findings withGlassbox’ lead developer.
3.1.2 Inter-Advice AssumptionsThese assumptions refer to the interactions between individual
pieces of advice rather than aspects as a whole.
Name. “Wormhole” assumptionsDescription. A common pattern for advice interaction is the so-called “wormhole” [28]. This involves collecting context informa-tion that is available at different points throughout a control flowand making it available at a different point in this (or another) con-trol flow. The original form of the pattern involves exposing all con-text information as parameters of a single pointcut. However, thisis not always convenient (especially when not all points involvedare within one cflow). A common alternative is to temporarilystore some context data in aspect-local variables to be used at alater point. In both scenarios, there is a requirement that the valuespicked up by one advice / at one joinpoint remain valid (and un-changed) until another piece of advice is invoked. This can implya synchronisation assumption for the aspect.Examples. MobileMedia implements a mechanism for maintain-ing a collection of favourite media that can be accessed directlyfrom a list of favourites. This feature is implemented in aspectFavouritesAspect using a set of heterogeneous advice on dif-ferent parts of the overall system. All of these advice need to main-tain state about whether the favourites view is currently activated.They do this using an aspect member favorite. Hence, theaspect requires that this variable is maintained consistently in ac-cordance with the application state. This requirement is satisfiedin the current MobileMedia implementation, because there is onlyone application thread. It may break when the aspect is reused in amulti-threaded context.Discussion. Probably, wormhole assumptions should be classifiedas a bug that should be fixed by using appropriate synchronisationmechanisms and possibly thread-local variables (or, of course theoriginal form of the pattern, which may avoid some of the assump-tions because AspectJ would implement it internally using a thread-local variable). Hence, this assumption category may be most use-ful for assumption elicitation and as an anti-pattern. There may alsobe situations in which the above corrections would not be applica-ble. In these cases it will still be useful to make the assumptionexplicit so that it can be checked when the context changes.
Name. Assumptions on sequential execution of adviceDescription. Advice may modify the state of base objects. Otheradvice may rely on this state. In effect, such advice relies on otheradvice having been executed before it for a particular applicationinstance. Let es(a, t) indicate that advice a has started executing in
thread t. Conversely, let ef (a, t) indicate that advice a has finishedexecuting in thread t.3 Then, we might formalise the assumptionthat advice a1 ! A assumes previous execution of advice a2 as!A " #t1.G¬es(a1, t1)U$t2.ef (a2, t2).4
Examples. For example, MobileMedia has an aspect PhotoAnd-MusicAndVideo, which is deployed when all three named fea-tures are selected. This provides two pieces of advice: one per-forming some setup code, the other reacting to selections of menuitems installed by the first advice. Clearly, there is an assumptionhere that the first advice has run before the second one is invoked.Discussion. If broken, this type of assumption can cause unex-pected behaviour or errors at runtime. For example, in the Mo-bileMedia case, if the setup code were not to be executed, the newfeature would not be accessible to the user, because all of its user-interface elements would not have been created.
If a1 and a2 are located in different aspects, this assumptioncould also be rendered as an inclusion assumption about aspectdeployment. In this respect, it is a more semantic representationof the cause for such an inclusion assumption. This category isalso closely related to the synchronisation assumptions and the as-sumptions on coding patterns discussed later. The key differenceis that here we discuss an assumption on other aspects regardlessof whether they are being advised by the aspect making the as-sumption. Also, this assumption is independent of threading issues,which is different from the synchronisation assumptions.
3.1.3 Inter-processual Aspect Interaction Assump-tions
Name. Inter-processual aspect interaction assumptionsDescription. Some aspects may affect communication between ap-plication processes. This may lead to assumptions about the inter-action of aspects deployed in different application processes.Examples. These assumptions may relate, for example, to data per-sistence and to aspects deployed in serial invocations of the sameapplication (or versions thereof). Alternatively, they may relate todistribution and to aspects deployed in (potentially different) ap-plications running simultaneously, but possibly on different nodes.While in principle, all types of assumptions discussed above mightalso exist in an inter-processual variant, we have specifically foundexamples of the following types: 1) Inter-processual deploymentassumptions, and 2) Inter-processual precedence assumptions.
For example, HealthWatcher defines RMIClientDistribu-tion to be deployed in the client version of the software. Thisattempts to link to the server version at a hard-coded address. An-other aspect (RMIServerDistribution) is deployed in theserver version of the software and makes sure that the server is actu-ally registered at that address. RMIClientDistribution as-sumes RMIServerDistribution to be deployed in the serverversion of the software.
As another example, MobileMedia provides a number of aspectscontributing data fields to the serialisation (i.e., persistence) featureof the software. Because these data are serialised in an order de-pending on the precedence between these aspects, there is an inter-processual precedence assumption that subsequent invocations of3The association to a specific thread is not actually relevant here.We introduce these forms of the predicates, because we can reusethem in Sect. 3.2.1.4Using the usual operators of LTL [35]:Gq – globally q; that is q holds for all states from nowFq – eventually q; that is q will hold in some future stateXq – next q; that is q holds in the next system stateq1Uq2 – q1 until q2; that is q1 will hold in all states until q2 holdsand q2 will eventually hold.
99
tool support:
... aspect/aspect aspect/base assumptions
warn when advice precedence DAG is not completely connected
warn about aspects that override implicit precedence
continuously check assumptions made explicit in code
logic meta-programming
Ekeko
Eclipse plugin
applicationsprogram querying
program transformation
corpus mining
meta-programming library for Clojure’s core.logic
causally connected
applicative meta-programming
script queries over workspace
specify code characteristics declaratively, leave search to logic engine
manipulate workspace
select programs to
query
start REPL
launch query
browse results
Applicative logic meta-programming... core.logic in a nutshell
core
.logi
cembedding of logic programming
port of Kanren [Friedman, Kiselyov, Bird] to Clojure by David Nolen
no operational impurities (e.g., cut), yet high performance features tabling, extensible constraints, extensible unification protocols
composing goals
functions that take a substitution either return a possibly infinite stream of substitutions (success) or nil (failure)
logic goals
constructors: always success: s#, always failure: f#, success if arguments unify: ==
(fresh [?x ?y] (== ?x ?y) (== ?x 5)))
(fresh [?x ?y] (conde [(== ?x 1) ..] [(== ?x 5) ..]))
(fresh [?x] (g ?x))
(defn g [?y] (fresh [] (== ?y 5)))
introduces lexically scoped variableschains goals together
interleaves substitutions from goals
abstraction
Applicative logic meta-programming... using relations provided by the Ekeko library
concerncharacteristics data flow
control flowstructural{syntactic
JDT DOMJDT Modelcontrol flow graphSOOT data flow analyses
derived from
AspectJ weaver(defn aspect-declaredsuper+ "Relation between an aspect and one of its declared ancestors.” [?aspect ?ancestor] (conde [(aspect-declaredsuper ?aspect ?ancestor) ] [(fresh [?super] (aspect-declaredsuper ?aspect ?super) (aspect-declaredsuper+ ?super ?ancestor))]))
{
Example queries
(ekeko* [?advice1 ?advice2 ?shadow] (advice-shadow ?advice1 ?shadow) (advice-shadow ?advice2 ?shadow) (!= ?advice1 ?advice2))
... using Ekeko for AspectJ
two advices operating on the same join point shadow
(ekeko* [?declaringaspect ?intertype ?member ?targettype] (aspect-intertype ?declaringaspect ?intertype) (intertype-member-type ?intertype ?member ?targettype) (aspect ?targettype))
aspect adding a member to another aspect through an intertype declaration
(ekeko* [?advice ?shadow ?intertype] (intertype-element ?intertype ?shadow) (advice-shadow ?advice ?shadow))
advice operating on a join point shadow stemming from an intertype declaration
(defn incomplete-precedence [?first ?second] (all (aspect ?first) (aspect ?second) (!= ?first ?second) (fails (aspect-dominates-aspect ?first ?second)) (fails (aspect-dominates-aspect ?second ?first))))
Code smells... related to precedence graph
precedence DAG is not fully connected
aspect overrides implicit precedence
(defn overriden-implicit-precedence [?first ?second] (all (aspect-dominates-aspect ?second ?first) (aspect-dominates-aspect-implicitly+ ?first ?second)))
an aspect may assume default precedence is never changed
inverse assumption made explicit
(defn pointcut-concretizedby [?abstractpc ?concretepc] (fresh [?abaspect ?concaspect ?name] (aspect-super+ ?concaspect ?abaspect) (aspect-pointcutdefinition ?abaspect ?abstractpc) (aspect-pointcutdefinition ?concaspect ?concretepc) (pointcut-name ?abstractpc ?name) (pointcut-name ?concretepc ?name)))
Code smells... related to aspect hierarchy
pointcut concretizing abstract pointcut from
super aspect
(defn abstractpointcut-concretized-reconcretized [?abpointcut ?concpointcut1 ?concpointcut2] (all (pointcut-concretizedby ?abpointcut ?concpointcut1) (pointcut-concretizedby ?concpointcut1 ?concpointcut2)))
pointcut concretized
twice
aspect introducing pointcut assumes it is not already defined unless behavior of super does not need to be maintained
(defn modifies-aspect [?modifier ?modified] (fresh [?advice ?shadow] (aspect-advice ?modifier ?advice) (advice-shadow ?advice ?shadow) (shadow-enclosingtypedeclaration ?shadow ?modified) (aspect ?modified)))
Code smells... related to intertype declarations
aspect modifies another aspect
(defn intertypemethod-unused [?itmethod] (fresh [?caller] (intertypemethod ?itmethod) (fails
(soot-method-called-by-method ?itmethod ?caller)))))
an intertype declaration introduces a method that is never called
Code smells... related to behavioral patternspublic aspect WormholeAspect {
pointcut entry(int save): execution(* BaseClass.method1(int)) && args(save); pointcut exit() : execution(* BaseClass.method2()); int store; before(int savedarg) : entry (savedarg) { this.store = savedarg; } before() : exit() { System.out.println("Wormholed value is: "+ store); }} assumes it is the
wormholed value
(defn brokenwormhole-entry-exit-field [?aspect ?entryadvice ?exitadvice ?field] (aspect-field ?aspect ?field) (aspect-advice ?aspect ?entryadvice) (aspect-advice ?aspect ?exitadvice) (fresh [?icfg] (soot-method-icfg ?entryadvice ?icfg) (qwal ?icfg ?entryadvice ?exitadvice [] (q=>*) (qcurrent [[?entryadvice ?unit]] (assigns-field ?unit ?field)) (q=>+) (qcurrent [[?method ?unit]] (differs ?entryadvice ?method) (assigns-field ?unit ?field)) (q=>+) (qcurrent [[?exitadvice ?unit]] (reads-field ?unit ?field)))))
Code smells... related to behavioral patterns
regexp describing path through the cfg of a broken
wormhole
(defn assigns-field [?unit ?field] (fresh [?lhs] (soot-unit-assign-leftop ?unit ?lhs) (soot-value :JInstanceFieldRef ?lhs) (equals ?field (.getField ?lhs))))
Conclusions
of smells related to implicit aspect assumptions
moving from structurally to behaviorally characterized
empirical study
designing annotation library for making assumptions explicit
continuously cross-check explicit and derived assumptions
script Ekeko against AspectJ corpus
logic-based specifications
Ekeko provides actual tool support
Ongoing work
https://github.com/cderoove/damp.ekeko [.aspectj]