15
Detecting aspect-specific code smells using Ekeko for AspectJ Coen De Roover Software Languages Lab Vrije Universiteit Brussel Johan Fabry Pleiad Lab - DCC University of Chile

Detecting aspect-specific code smells using Ekeko for AspectJ

Embed Size (px)

Citation preview

Page 1: 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

Page 2: Detecting aspect-specific code smells using Ekeko for AspectJ

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

Page 3: Detecting aspect-specific code smells using Ekeko for AspectJ

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:

Page 4: Detecting aspect-specific code smells using Ekeko for AspectJ

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

Page 5: Detecting aspect-specific code smells using Ekeko for AspectJ

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

Page 6: Detecting aspect-specific code smells using Ekeko for AspectJ

select programs to

query

start REPL

launch query

browse results

Page 7: Detecting aspect-specific code smells using Ekeko for AspectJ

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

Page 8: Detecting aspect-specific code smells using Ekeko for AspectJ

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))]))

{

Page 9: Detecting aspect-specific code smells using Ekeko for AspectJ

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

Page 10: Detecting aspect-specific code smells using Ekeko for AspectJ

(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

Page 11: Detecting aspect-specific code smells using Ekeko for AspectJ

(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

Page 12: Detecting aspect-specific code smells using Ekeko for AspectJ

(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

Page 13: Detecting aspect-specific code smells using Ekeko for AspectJ

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

Page 14: Detecting aspect-specific code smells using Ekeko for AspectJ

(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))))

Page 15: Detecting aspect-specific code smells using Ekeko for AspectJ

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]