Design and Implementation of an Aspect-Oriented C Programming Language

Aspect-Oriented Programming (AOP) is a programming paradigm that implements crosscutting concerns in a modular way. People have witnessed the prosperity of AOP languages for Java and C++, such as AspectJ and AspectC++, which has propelled AOP to become an important programming paradigm with many interesting application scenarios, e.g., runtime verification. In contrast, the AOP languages for C are still poor and lack compiler support. In this paper, we design a new general-purpose and expressive aspect-oriented C programming language, namely Aclang, and implement a compiler for it, which brings fully-fledged AOP support into the C domain. We have evaluated the effectiveness and performance of our compiler against two state-of-the-art tools, ACC and AspectC++. In terms of effectiveness, Aclang outperforms ACC and AspectC++. In terms of performance, Aclang outperforms ACC in execution time and outperforms AspectC++ in both execution time and memory consumption.


INTRODUCTION
Aspect-Oriented Programming (AOP) [Kiczales et al. 1997] is a programming paradigm that implements crosscutting concerns in a modular way using aspects.An aspect definition is composed of advices.An advice uses a pointcut expression to match a set of join points scattered in a program execution, where a join point is a point in the execution, such as a function call, a function execution, getting and setting the value of a variable.An advice can intercept any matched join points and apply a block of statements around (instead of), before or after these join points.
The Prosperity of AOP Languages for Java and C++.The AOP languages for Java and C++ are rich and have solid compiler support.For example, AspectJ is an AOP extension for Java and has a mature compiler implementation ajc [Kiczales et al. 2001], while AspectC++ is designed for C++, based on the concepts developed in AspectJ, and also has a mature compiler implementation ac++ [Spinczyk 2021a,b;Spinczyk et al. 2002;Spinczyk and Lohmann 2007].
These powerful AOP languages and compilers have propelled AOP to become an important programming paradigm with many interesting application scenarios.For example, AOP has recently been used to migrate traditional monolithic applications in production to microservices by intercepting calls inside the monolith and transforming them into microservice requests using around advices, which leads to zero code changes to the monolithic code in practice [Freire et al. 2021].Likewise, AOP has been used to modularize inherently scattered database accesses into single functional units, which establishes JDBC best practices [Pérez 2021].Besides, AOP has been used to monitor open multi-agent systems for understanding and analyzing the system behaviour and helping developers to review and examine, at run-time, agents' communications and other different events generated during the system execution [Chebout et al. 2019].
Furthermore, AOP has significantly contributed to the runtime verification community, which aims at finding program bugs at runtime [Leucker and Schallhart 2009;Runtime Verification 2023].For example, JavaMOP, the most efficient parametric runtime verification tool for Java, is built on AspectJ [Chen and Rosu 2007;Meredith et al. 2012].More specifically, JavaMOP first transforms monitor definitions, including desired properties, into aspects written in AspectJ and then exploits the ajc compiler to transform these aspects into Java code fragments and weave them into the target program.Running the resulting program can automatically verify the desired properties at runtime.Likewise, AspectC++ has been used to debug and monitor C++ programs by performing program instrumentation [Mahrenholz et al. 2002].We believe that the prosperity of the runtime verification community is closely linked to the maturity of AOP languages and compilers.
The Need of an AOP Language for C. In contrast to Java and C++, the AOP languages for C are still poor and lack compiler support.For example, Coady et al. have proposed "AspectC" (a hypothetical and simple subset of AspectJ) to modularize the implementation of prefetching within page fault handling in the FreeBSD OS kernel and showed significant benefits [Coady et al. 2001b,a].Unfortunately, they used only a paper design for AspectC, supporting only join points of the function call and the control flow, and no implementation of AspectC exists.ACC (AspeCt-oriented C) is currently the most mature compiler implementation of AOP for C [Gong and Jacobsen 2010].Unfortunately, it is no longer maintained, and the latest version is incorrect in many cases due to a problematic implementation.As a result, the AOP languages for C are not commonly used.
A large number of applications are still being developed in C, especially safety-critical embedded software applications that require high dependability.Developing runtime verification tools can help to verify these applications.Unfortunately, the lack of AOP languages and compilers for C has become a major obstacle to developing runtime verification tools for C programs.
Let us look at a simple example, showing that aspects can be used to detect memory leaks in C programs.Suppose a C program calls malloc() to request memory blocks from the heap and free() to delete allocated blocks.A memory leak occurs if a memory block is no longer used but not deleted.Figure 1 shows an aspect memleak, which includes a variable allocated (that counts the number of allocated blocks) and three advices.The first advice increments allocated aspect memleak { unsigned allocated = 0; // The number of allocated memory blocks.advice m after call(% malloc(% %)) { allocated++; } advice f after call(% free(% %)) { allocated--; } advice e after execution(% main(...)) && condition(allocated > 0) { printf("error: %u memory leaks are detected!\n",allocated); } }; Design and Implementation of an Aspect-Oriented C Programming Language 117:3 after a function call to malloc(), where the symbol % is a wildcard character matching any type name and parameter name.The second advice decrements allocated after a function call to free().The last advice prints an error message indicating the number of leaked blocks after the execution of main(), provided that the condition "allocated > 0" holds, where the symbol ... is a wildcard character matching any parameter list.Furthermore, a typical runtime verification tool may express this property as a regular expression ( ) * .A memory leak is detected at runtime if the sequence of function calls on a memory block matches this expression.
Therefore, it is meaningful to develop an AOP language for C and an accompanying compiler.With these infrastructures, the programmer can modularize crosscutting concerns in C programs, which may result in many interesting application scenarios, and the runtime verification community can build runtime verification tools for C programs by exploiting the compiler.
Our Contribution.In this paper, we design a new general-purpose and expressive aspect-oriented C programming language, namely Aclang, and implement a compiler for it, which brings fullyfledged AOP support into the C domain.The major contributions of our work include: • We formalize the syntax and semantics of the core of Aclang based on a simple C language (Sections 2 and 3) and develop a set of weaving rules that can weave aspects written in Aclang into a C program (Section 4).Note that the existing works on the semantics of AOP constructs are designed for functional languages [Walker et al. 2003], -calculus [Wand et al. 2004] or logic languages [Avgustinov et al. 2007].As a result, their formalisms cannot be applied to Aclang, which is designed for an imperative language.Thus, our way of formalizing the semantics and weaving rules of AOP constructs is novel.• We comprehensively design the constructs of Aclang used in practice (Section 5).Our design provides useful new insights on a more systematic classification of AOP notions, e.g., primitive pointcuts and dynamic/static advices, which allows writing aspects in a more systematic, expressive and concise manner.• We implement a compiler for Aclang, which supports all of its constructs and provides convenient user instructions (Section 6).Note that mature AOP compilers such as AspectJ and AspectC++ implement aspects using object-oriented constructs such as classes and templates, which do not exist in C. Thus, building a compiler for an aspect-oriented C language is a challenging and tedious task because we cannot reuse the implementation of mature AOP compilers and have to develop different implementation techniques.Furthermore, we propose a new formalism for deciding the execution order of multiple advices.• We evaluate the effectiveness and performance of our compiler against two state-of-the-art tools, ACC and AspectC++ (Section 7).In terms of effectiveness, Aclang outperforms ACC and AspectC++.In terms of performance, Aclang outperforms ACC in execution time and outperforms AspectC++ in both execution time and memory consumption.
This paper is organized as follows.Section 2 introduces the syntax and semantics of a simple C language, which is used as the base language of Aclang.Section 3 presents the core of Aclang and formalizes its semantics.Section 4 develops a set of weaving rules for the core.Section 5 presents the other constructs beyond the core.Section 6 implements a compiler for Aclang.Section 7 evaluates the effectiveness and performance of our compiler.Section 8 compares Aclang with related AOP languages and compilers.Section 9 concludes the paper and discusses future work.

THE SYNTAX AND SEMANTICS OF A SIMPLE C LANGUAGE
Since our aspect-oriented C language, Aclang, is an extension of the C language, this section formally defines a simple C language, which will later be extended by adding AOP constructs.
Syntax. Figure 2 defines the syntax of a simple C language that includes the basic constructs of variable declaration, function declaration, block, statement and expression.A program is composed of zero or more variable declarations * and function declarations * .A variable declaration comprises a type and an identifier followed by a semicolon, and possibly has an initial value expression .A function declaration includes a return type , an identifier and zero or more parameters * enclosed in parentheses, followed by a block enclosed in braces.A block is made up of zero or more variable declarations * and statements * .A statement can be an expression followed by a semicolon, or a return statement with or without an expression.An expression can be a number , a variable declaration reference , an assignment, an increment, a function call, or a stack frame represented by a block enclosed in braces (only used to replace a function call when the call starts).The dots imply omitting other standard expressions.Note that this simple language is sufficient for building our AOP language, as it includes all the concerned join points: calling and executing a function and getting and setting the value of a variable.

Program
::= * * Semantics.Now, we develop a small-step semantics for this simple language, shown in Figure 3, and later present a helpful demonstration example after introducing the semantic rules.
A program state is a pair ( , ) that consists of a stack and a memory.For conciseness, we directly use a component of a state by omitting the state's name, e.g., denoting .by .Thus, can be denoted by ( , ).A stack ∈ Fr * is an initially empty list of function frames, and each frame fr : ↦ → Adr in Fr is a mapping from a local variable name ∈ to its address ∈ Adr.We denote frame concatenation with the dot symbol, e.g., • fr that pushes a frame fr to the top of a stack .For conciseness, we denote by ( ) the address of the local variable in the top frame, instead of ( )( ).A memory : Adr ↦ → Val is an initially empty mapping from an address to the value stored on this address.We denote by [ ↦ → ] storing a value to an address .The function table : ↦ → Fr is a mapping from a function name ∈ to its frame fr ∈ Fr, where the address of each local variable in the frame is undefined.We introduce a primitive function cframe( , fr) = ( ′ , fr ′ ) which allocates fresh locations for all the local variables in a frame fr, resulting in a new memory ′ and a new frame fr ′ in which all the variables' addresses have been updated to the allocated fresh locations.The global variable table : ↦ → Adr is a mapping from a global variable name ∈ to its address ∈ Adr.Since and are not changed once the program starts, they are not considered parts of the program state.
Rule [VarDecl1] consumes a variable declaration that creates a new variable with type , and assigns the undefined value ⊥ to in the current memory .Rule [VarDecl2Sub] evaluates the initial value expression (i.e., a subexpression) of a variable declaration; if we can take a step from an expression to ′ with the state ( , ) being updated to ( ′ , ′ ), then we can take a step from " = ;" to " = ′ ;", resulting in the same state ( ′ , ′ ).Note that all the rules whose name has the "Sub" suffix are used to evaluate a subexpression.Rule [VarDecl2] consumes a variable declaration and assigns an initial value to .Rule [FuncDecl] consumes a function declaration and stores a function in memory by mapping the function's address (which is the same as its name) to its parameters ( 1 , . . ., ) and block .Rule [SeqStmtSub] states that if we can take a step from a statement 1 to ′ 1 with the state being updated to ′ , then we can take a step from a statement sequence 1 2 to ′ 1 2 , resulting in the same state ′ .Rule [SeqStmt] states that if 1 is consumed in the evaluation, then 1 is removed from the statement sequence 1 2 .
Rule [ExprStmt1] states that evaluating a statement " ;" consumes it without changing the program state.Rule [ExprStmt2] handles the case that is consumed.Rule [RetStmt] states that if a block consists of a return statement followed by a block , then evaluating this block skips and pops the top frame off the stack.Rule [RetVal] states that evaluating "{return ; }" also yields the value , with the subsequent block being skipped.
Rule [RefExpr] states that if a variable has a value in the memory , then evaluating a declaration reference expression yields the value .Rule [AssExpr] states that evaluating " = " updates variable 's value to , and the result is exactly .Rule [IncExpr] evaluates an increment expression ++ by increasing variable 's value by one, and yields variable 's new value + 1.
Rule [CallExpr] evaluates a function call ( 1 , . . ., ); it first looks up the function's parameters/block and frame with its address in the memory and the function table , respectively, and then allocates the function's frame, pushes the frame to the stack, binds the function's formal parameters 1 , . . ., to the values 1 , . . ., of the corresponding actual arguments, and finally evaluates the function's body.
Rule [FrameExprSub] states that if a block is evaluated to a block ′ , then evaluating the block inside a stack frame { } results in the same block ′ .Rule [FrameExpr] states that if a block is consumed, then evaluating { } pops the top frame off the stack.
Note that we assume the standard semantic rules for the omitted expressions, which evaluate operands left-to-right and perform arithmetic operations according to their usual semantics.
Example.Now, we use an example to demonstrate how to evaluate a statement using these semantic rules.Let be a function declared as follows: int (int 1 , int 2 ){int = 1 + 2 ; return ;} The following assignment statement can be evaluated by a sequence of derivations: We can ignore the semicolon and the assignments by [ExprStmtSub] and [AssExprSub], respectively, and first evaluate the call expression (1, 2) inside the assignments.Let ( ) = fr, cframe( , fr) = ( 1 , fr ′ ) and Next, we can ignore the curly braces by [FrameExprSub] and evaluate the first statement inside the curly braces, ignoring the later statements by [SeqStmtSub].Further, we can ignore the variable declaration of by [VarDecl2Sub] and evaluate the reference expressions 1 and 2 .→ 1 , ⊢ 2 = 1 = {int = 1+2; return ;}; by [RefExpr] Note that 1+2 can be evaluated to 3 by the standard semantic rule of the plus operator, which has been omitted here.Then we can evaluate the first statement inside the curly braces by [SeqStmt].
Example.Let us use one more example to demonstrate that these semantic rules correctly work in the case of recursive function calls.Let be a function declared as follows: int (int ){if ( == 0) return 0; else return ( -1);} The following call expression can be evaluated by a sequence of derivations: Note that the if-else statement can be evaluated by the standard semantic rule of if-else, which has been omitted here.
{if ( == 0) return 0; else return ( -1);};};} by [CallExpr] For conciseness, we introduce 3 ⊢ 0 by [RetVal] Note that, for each execution of function in the above derivations, a different address for the same local variable name is used, because variable addresses are retrieved only in the top frame.This avoids wrong value overwrites.For instance: • After the 1st derivation, in the execution of (2), 's address is 1 ( ), which equals fr ′ ( ).

THE CORE OF AN ASPECT-ORIENTED C LANGUAGE
This section adds AOP constructs to the simple C language.Aclang is too large to be completely formalized.Thus, we prefer formalizing the core of Aclang.
Syntax. Figure 4 extends the syntax of the simple C language in Figure 2 by adding the constructs of advice and pointcut.A pattern variable declaration ˆ serves the purpose of matching a variable declaration with two pattern strings: ˆ for matching the variable's type and ˆ for matching its name.A pattern string may contain the wildcard character % that matches any substring, including the empty string.For instance, the pattern string %t matches both int and float.
An advice declaration consists of the advice keyword, a name , a parameter list * , a position modifier (around, before or after), a pointcut expression , and a block .The advice name can be omitted, resulting in anonymous advice.The parameters enclosed in parentheses are separated by commas and can be omitted if not needed.The pointcut expression is used to match join points.The position modifier specifies where the advice shall be triggered relative to the matched Pattern Variable Decl ˆ ::= ˆ ˆ ; ( ˆ , ˆ , ˆ , ˆ are pattern strings) Advice Decl : join points.The block is automatically executed around (instead of), before, or after the matched join points when they are reached in a program execution.
A pointcut expression can be a core pointcut such as exec, call, get and set, to intercept function executions and calls, variable reads and writes, respectively.It can also be a non-core pointcut (which will be discussed later in Section 5.1), e.g., used to restrict the scope of matched join points.A composite pointcut connects multiple primitive pointcuts by using logical operators, e.g., negation (¬) and disjunction (∨), to express complex matches.
Let us elaborate on the use of pattern strings in pointcuts.An exec pointcut can use pattern strings to match return types, function names and parameter declarations, which filters out the executions of a function whose signature is matched by these strings.The pattern strings for matching parameter names can be omitted if the names are not considered in a match.For example, the expression exec(% func%(in%)) matches the executions of a function whose name starts with func, has a parameter whose type starts with in, and returns a value of any type, e.g., "int* func1(int x)".A call pointcut uses pattern strings in the same way.A get pointcut can use pattern strings to match variable types and names, which filters out the join points that get the value of a variable whose declaration is matched by these strings.For example, the expression get(int x%) matches the join points that get the value of an integer variable whose name starts with x, e.g., x and x1.Furthermore, a pattern string may use the scope operator :: to match member variables of record types.For example, the expression get(int "struct st"::x%) concerns the integer variables declared in struct st, e.g., x1 and x2 in "struct st {int x1; int x2;}".A set pointcut uses pattern strings in the same way.
The extended syntax of expressions includes a predefined call expression proceed(), which is used within the body of an around advice to execute the original join point that was intercepted by this advice, e.g., resume the intercepted function call.Note that proceed() can only be used in the body of an around advice as before and after advices do not replace join points.It does not need any actual arguments, even if the original join point needs some, because the required arguments will be automatically added during weaving.
Note that the core language does not include the aspect construct.Instead, we directly use a sequence of advices.Furthermore, we assume that only around advices are used, as a before or after advice can be converted to an equivalent around advice as follows: advice ( * ) before { } =⇒ advice ( * ) around { ; proceed();} advice ( * ) after { } =⇒ advice ( * ) around {proceed(); ;} Semantics.Before presenting the semantics, we first introduce a predicate match that determines whether a pointcut matches a language construct.Figure 5 gives its definition using a set of rules.For instance, a pattern string ˆ matches a string iff is in the language ( ˆ ) generated by ˆ .A pattern variable declaration matches a variable declaration iff its patterns match the variable's type and name.An execution pointcut matches a function declaration iff its patterns match the function's return type, name and parameter list.A call pointcut matches a function call iff its patterns match the called function's return type (denoted by typeof ( )), name and argument types.A get or set pointcut matches a variable read or write iff its patterns match the accessed or modified variable's type and name.Note that the increment expression is matched by both get and set pointcuts as it is equivalent to " = +1".
We denote by an ordered set of advices, and an ordered set of advice names.We write an ordered set as a sequence, e.g., = 1 2 • • • .We denote by ∅ the empty set (sequence).We introduce a function advices, which utilizes the match predicate and returns an order-preserving Proc.ACM Program.Lang., Vol. 8, No. OOPSLA1, Article 117.Publication date: April 2024.
{_} and match( , )} Now, we extend the small-step semantics of the simple C language (cf. Figure 3) by replacing the semantic rules of the language constructs that can be intercepted by an advice, including function declarations, call expressions, reference expressions and assignment expressions.The new semantic rules are shown in Figures 6 and 7. Again, we present a helpful demonstration example later.The program state now additionally includes, besides a stack and a memory, a set of declared advices.
Rule [FuncDecl-Adv] defines adviced function declarations.If a function declaration is matched by a sequence of advices, say 1 • • • , then this rule creates + 1 functions in memory (recall that Rule [FuncDecl] only creates one function, which can still be applied here if the sequence [ is empty).The first function uses the parameters and body of advice 1 , instead of the original function , with proceed() in its body being replaced by a call to the next function 1 .The function −1 uses the parameters and body of advice .The last function uses the parameters and body of the original function .This means when the original program calls , the adviced program makes a chain of calls, say 1 • • • −1 , which executes all the advices' bodies in order before the original function 's body , provided that all the proceed()s are reachable at runtime.
Rule [CallExpr-Red] defines adviced call expressions.If a call is matched by a sequence of advices, say , then this rule redirects the call to function (recall that Rule [CallExpr] directly executes the body of , which can still be applied here if is empty).If contains more than one advice, Rule [CallExpr-Adv1] executes the body of the first advice, with proceed() in its body being replaced by a call to the body of the next advice.Rule [CallExpr-Adv2] executes the body of the original function .Taking the three rules together, when the original program calls , the adviced program makes a chain of calls, which executes all the advices' bodies in order before 's body , provided that all the proceed()s are reachable at runtime.
Figure 7 gives the semantic rules of adviced reference expressions and assignment expressions, which are in a form similar to adviced call expressions.The reason is that a reference expression " " is equivalent to a function call get(& ), while an assignment expression " = " is equivalent to a function call set(& , ), where functions get and set are defined as follows.
get( * ) { return * ; } set( * , ) { return * = ; } Note that these rules can also be applied in the same way to an adviced member expression " ." and its assignment " .= " for a member variable of record .
Example.Now, we demonstrate how to evaluate an adviced statement using these semantic rules.Let be the function in our running example, and be two advices declared as follows: advice 's body instead of the original function , which is renamed to 1 .As a result, the original function 's body is executed after advice 's body.
The following adviced call statement can be evaluated by a sequence of derivations: 4 THE WEAVING RULES FOR THE CORE This section presents the weaving rules that can be used to instrument the source code of a program according to a set of advices.Executing the resulting program after instrumentation by following the C semantics (cf.Section 2) should result in an execution equivalent to executing the original program and advices by following the aspect-oriented C semantics (cf.Section 3).
For each language construct , its related weaving rules include two parts: genfunc( ) that generates a set of new functions and rewrite( ) that rewrites this construct.Given a program and a set of advices, the resulting program can be obtained by computing: , ⊢ genfunc( ) rewrite( ) Figure 8 shows the weaving rules for function declarations and call expressions.If a function declaration is matched by a sequence of advices, say 1 • • • , then Rule [FuncDecl-Gen] generates functions (one for each advice) and recursively generates functions for its body, while Rule [FuncDecl-Rew] rewrites the name of the original function to and recursively rewrites its body.If a call is matched by a sequence of advices, say , then Rule [CallExpr-Gen] leads to generate a function for each advice, while Rule [CallExpr-Rew] rewrites the name of the called function to (no rewrite happens if is empty) and recursively rewrites all its arguments.It is easy to see that the resulting program after applying these rules has the same behaviour as the original adviced program (if one compares these rules with those in Figure 6).Figure 9 shows the weaving rules for reference expressions and assignment expressions.It is easy to understand these rules when we keep in mind that such an expression can be converted to an equivalent function call to get or set.
[FuncDecl-Gen] advices( , Example.Now, we demonstrate how to instrument an adviced statement in our running example using these weaving rules.First, consider the function declaration .Rule [FuncDecl-Gen] generates exactly one new function, as only advice matches this declaration, and does not generate any function for its body as it does not contain any adviced constructs. , ⊢ genfunc(int (int 1 , int 2 ){int = 1 + 2 ; return ;}) = int (int 1 , int 2 ){return 1 ( 1 , 2 )*2;} by Rule [FuncDecl-Gen] Rule [FuncDecl-Rew] rewrites the name of the original function and does not rewrite its body.
Second, consider the call expression (1, 2).Rule [CallExpr-Gen] generates exactly two new functions as only advice matches this call.
Rule [CallExpr-Rew] rewrites the name of the called function and does not rewrite its arguments as they do not contain any adviced constructs.Finally, we can show that evaluating the resulting program after instrumentation using the semantic rules of the simple C language (cf. Figure 3) obtains the same result as evaluating the original program using the semantic rules of the aspect-oriented C language (cf.Figures 6 and 7).
, ⊢ (1, 2); (We are evaluating the call expression after instrumentation.)Let ( ) = fr 1 , cframe( , fr 1 ) = ( 1 , fr ′ 1 ) and Dynamic Linking.These weaving rules support dynamic linking.Consider a program that calls a function that is dynamically linked and matched by a set of execution advices.First, function is renamed to (by Rule [FuncDecl-Rew]) and a set of new functions , 1 , ..., −1 are generated according to the advices (by Rule [FuncDecl-Gen]).Note that the new function has the same signature as the original .These functions are compiled into a dynamic library.Second, the caller program does not need instrumentation as it does not match the used pointcuts.When the program calls at runtime, it actually calls the new function , which in turn calls 1 , ..., −1 in sequence in case of proceed().The last wrapper function −1 finally calls (aka the original ).Thus, all the advices have been correctly executed.

A PRACTICAL LANGUAGE BEYOND THE CORE
We have formally defined the core of Aclang in Section 3, including its syntax and semantics.As a language for practice, Aclang contains many constructs beyond the core.This section presents some constructs related to pointcuts, advices and aspects.

Pointcuts
Figure 10 gives a more comprehensive syntax of pointcut expressions and declarations.To represent negation and disjunction in a program, we use the textual characters ! and || instead of the previously used math symbols ¬ and ∨, e.g., in Figure 4. We first introduce pattern strings and then present primitive pointcuts, composite pointcuts and pointcut declarations.5.1.1Pa ern Strings.They are used to match constructs such as identifiers, type names, variable names, variable declarations, function names, parameter declarations and function signatures.

Pointcut Expr
We have mentioned that a pattern string may contain the wildcard character % that matches any substring.Besides, a pattern string may contain the dots "..." that match a list of any length, including the empty list.For example, the pattern string "% func%(..., int x, ...)" matches any function whose name starts with func and parameter list contains a parameter "int x", but the return type and other parameters are left unspecified, e.g., "int* func1 (float foo, int x)".Furthermore, the dots can be used with the scope operator "::" to match member variables of record types.For example, the pattern string "% "struct st"::...::x%" matches the variables declared in struct st, e.g., x1 and x2 in "struct st {int x1; struct st1 {char x2;} y;}".

Primitive Pointcuts.
Primitive pointcuts are building blocks for constructing composite pointcuts.We propose to use a new systematic classification of primitive pointcuts: core pointcuts, naming pointcuts, dynamic scope pointcuts, and static scope pointcuts.
Core Pointcuts.They are used to match join points.There are five kinds of core pointcuts.We have introduced four of them: execution, call, get and set.The remaining one is the callp pointcut.It can be used to intercept function calls by pointers, i.e., dereferencing function pointers.It uses pattern strings similarly to the call pointcut, with two differences.First, only the return type, the function name and the number of parameters are considered in a match, i.e., parameter types and names are ignored, because it is allowed that the parameter list in the declaration of a function pointer is not the same as the function it points to.Second, the pattern string for matching the function name should not contain wildcard characters due to implementation limits (matching functions by comparing function addresses at runtime).
Naming Pointcuts.They are used to assign names to some objects of a matched join point, e.g., the return value, so that these objects can be referred to by using the assigned names and accessed in the advice body.Note that naming pointcuts must be used together with core pointcuts in a composite pointcut instead of independently used.If the core pointcuts match a join point, then the naming pointcuts can assign names to the corresponding objects.
A typical kind of naming pointcut is the returning pointcut.It assigns a name to the return value of a function call matched by a call or callp pointcut or of a function execution matched by an execution pointcut, or to the value (got or set) of a variable matched by a get or set pointcut.Note that the assigned name is used as an identifier in the advice body instead of a pattern string.Thus, it should not contain wildcard characters.Dynamic Scope Pointcuts.They are used to restrict the scope of matched join points at runtime by considering the dynamic environment when a join point is reached.Similar to naming pointcuts, dynamic scope pointcuts must be used together with core pointcuts in a composite pointcut.
There are two kinds of dynamic scope pointcuts.The first is the inexec pointcut, which can be used to filter out the join points reached during the execution of a matched function.It uses pattern strings similarly to the execution pointcut.The second is the condition pointcut, which can be used to filter out the join points where a boolean expression holds.Note that the boolean expression is not used as a pattern string.Instead, it could be an arbitrary C expression over named objects, e.g., global variables and the names assigned by naming pointcuts or renaming expressions (explained later).
Static Scope Pointcuts.They are used to restrict the scope of matched join points at compile-time by considering the static location of a join point's code.They can be used in both dynamic and static advices (explained later).If used in dynamic advices, then similar to naming pointcuts, static scope pointcuts must be used together with core pointcuts in a composite pointcut.If used in static advices, then they can be independently used.
There are three kinds of static scope pointcuts.The first is the intype pointcut that matches the join points whose code is in the matched type definitions, such as structs, unions and enums.It uses a pattern string to match type names.The second is the infunc pointcut that matches the join points whose code is in the matched function definitions.It uses pattern strings similarly to the execution pointcut.The third is the infile pointcut that matches the join points whose code is in a file whose name matches the pattern string.

Composite Pointcuts.
A composite pointcut is a primitive pointcut or composite pointcuts connected by an operator.The negation operator !excludes the join points matched by its operand (a pointcut).The disjunction and conjunction operators, || and &&, return the union and intersection of the join points matched by its operands (two pointcuts), respectively.The parenthesis ( ) returns the same join points matched by the enclosed pointcut.
For example, the following composite pointcut matches the join points that execute function bar during the execution of function foo.
execution(void bar(% p)) && inexec(% foo(...)) The following composite pointcut matches the join points that call function bar1 beyond the execution of function foo, or call function bar2 at any time.

Parametric Pointcut Declarations.
A named parametric pointcut is a named pointcut that takes formal parameters.The parameters can bind the contextual information of matched join points, such as the value of an actual argument and the return value of a function call.Using these parameters in advice bodies can access this information.For example, the following code declares a parametric pointcut ppc1, of which the two parameters x and ret bind the value of the second actual argument and the return value of a function call to foo, respectively.pointcut ppc1(x, ret) = call(int foo(char, int x)) && returning(ret); Here, the parameter x has the same name as the second parameter of the matched function declaration.If we prefer a different parameter name, e.g., y, we can rename the matched parameters using the renaming expression ":newname" in the pattern strings.For example, the following pointcut ppc2 renames a matched parameter to y. pointcut ppc2(y, b) = call(int foo(char, int x:y)) && returning(b); Parametric pointcut declarations are also implemented like macros.When a parametric pointcut is used, the parameters in its declaration are replaced by the corresponding actual arguments.For example, the following two declarations of pointcut ppc3 are exactly equivalent: pointcut ppc3(f,g) = ppc2(f,g); pointcut ppc3(f,g) = call(int foo(char, int x:f)) && returning(g);

Advices
There are two kinds of advices: dynamic advices executed at runtime and static advices executed at compile-time.Dynamic advices could be executed around (instead of), before or after the matched join points such as function executions and calls, whereas static advices could only be executed to extbeg (extend the beginning of) or extend (extend the end of) constructs such as type definitions, function definitions and files.

Dynamic Advices.
They are used to modify a program execution at runtime.They can use one of the positions around, before and after, and any kind of primitive pointcut in its pointcut expression, but at least one core pointcut.For example, the following named advice ret10 intercepts each call to function foo, prints a message and always returns 10 to the caller.The return statement in the advice body makes the call to function foo be ignored because this advice is executed around (instead of) the original call and does not call proceed().advice ret10 around call(int foo(int)) { printf("call foo\n"); return 10; } A named parametric advice can also take formal parameters.For example, the following parametric advice pact prints a message after the execution of function foo.It takes two parameters: the parameter x and return value ret of function foo.As a result, the values of x and the return value can be accessed and printed in the advice body.
advice pact(x,ret) after execution(int foo(int x)) && returning(ret) { printf("Executing foo(%d) returns %d\n", x, ret); } Recall that, in the C language, an actual argument can be passed to a function by value or address.The advice declaration provides a similar mechanism.In the above examples, all the parameters are declared as pass-by-value.This means, a parameter, e.g., x, accessed in the advice body is a copy of the actual argument passed to a function, e.g., foo; thus, any modifications to this parameter will be discarded after the advice body returns.However, one may want to modify the value of the actual argument in the advice body, or use its address instead of its value.In this case, the parameter should be declared as pass-by-address.For example, the following advice uses a pointer parameter by simply adding a star * before the parameter's name.As a result, the address of the actual argument is passed to the advice body via x, and the actual argument is doubled before foo is executed.advice pact_addr(*x) before execution(int foo(int x)) { printf("The address and value of x are %p and %d\n", x, *x); *x = (*x) * 2; } An advice declaration can use the renaming expression ":newname" to rename a matched parameter.For example, the following advice pact renames the matched parameter x to v. advice pact(v) before execution(int foo(int x:v)) { printf("Before executing foo(%d)\n", v); } An advice declaration can use named pointcuts to simplify its declaration.For example, the following advice uses a named pointcut ppc1.advice pact1(v, r) before ppc1(v, r) && inexec(int bar(int var)) { printf("Before executing foo(%d)\n", v); } 5.2.2 Static Advices.They are used to modify the source code at compile-time, which affects all the join points depending on the modified code.They can use one of the positions extbeg and extend, and only static scope pointcuts in its pointcut expression.For example, the following first advice extends record types foo_t and bar_t by adding a new member variable "int count" to the end of their declarations.The second advice extends all the functions whose name starts with foo by adding a new print statement to the beginning of their definitions, while the third advice extends these functions by adding a statement to the end.As a result, two messages will be printed when a matched function is executed.The fourth advice extfile extends all the files whose name ends with foo.c by adding a new global variable declaration "extern int g_count" to the beginning of these files.
advice extend intype(foo_t) || intype(bar_t) { int count; } advice extbeg infunc(% foo%(...)) { printf("Entering function foo%.\n");} advice extend infunc(% foo%(...)) { printf("Exiting function foo%.\n");} advice extfile extbeg infile(%foo.c){ extern int g_count; } 5.2.3The Tjp Pointer.Let us introduce a useful predefined construct, the tjp pointer, that can be used in advice bodies to access reflective information of join points.Recall that advice parameters provide information about join points, e.g., argument values.However, this may not be sufficient.Thus, our language provides a predefined struct type join_point that includes richer reflective information of a join point, typically including the join point's source location and target (e.g., the name of the executed or called function, the name of the variable being got or set), the type and address of each argument and the return value.Furthermore, such a struct, implicitly created for each matched join point, is pointed to by a predefined pointer tjp.Thus, we are able to access reflective information of join points via tjp inside advice bodies.For example, the following anonymous advice prints some reflective information before the execution of function login.advice before execution(int login(...)) { printf("The join point's target is %s.\n", tjp->target); if (tjp->args_cnt > 0 && strcmp(tjp->args_type[0], "int") == 0) printf("The first argument is %d.\n", *(int*)tjp->args[0]); printf("The return value is %d.\n", *(int*)tjp->ret); }

Aspects
An aspect declaration collects multiple pointcuts and advices together to implement crosscutting concerns in a modular way.As shown in the following syntax rule, an aspect declaration specifies its name after the aspect keyword, followed by its body enclosed in braces, and a semicolon denotes the end of the declaration.The body includes a list of type, variable, pointcut and advice declarations.All the declared types and variables are weaved into the source code as global declarations.

Aspect Decl
: For example, the following aspect counts how many times a function foo is executed.

A PRACTICAL IMPLEMENTATION BEYOND THE WEAVING RULES
We have formally defined the weaving rules for the core of Aclang in Section 4. This section presents the critical implementation of a compiler used in practice, which handles not only the core but also the constructs beyond the core (cf.Section 5).

The Overall Architecture
The compiler implements a source-to-source transformation that automatically weaves aspects written in Aclang into aspect-unaware C source code, and any C compiler, such as GCC and other platform-specific compilers, can compile the resulting source code after instrumentation.That is, the transformation is platform-independent.We prefer such a transformation instead of directly compiling the resulting source code to a binary executable file because the executable file may be incompatible with some platforms (e.g., operating systems and ISAs).Indeed, many embedded system platforms use their own compilers, which may be incompatible with each other.
Our transformation makes it possible to compile the resulting source code for all the target platforms with platform-specific compilers.Figure 11 shows the overall architecture of the compiler.The inputs are C source files and aspect files; the outputs are also C source files.The compiler contains a C parser and an aspect parser, which translate C source files and aspect files into a C source AST and an aspect AST, respectively.The core of the compiler is a weaver that generates code snippets written in ANSI C for the input aspects, inserts some of the code snippets into the source AST, and puts some of them (e.g., the functions generated for advices) into new C source files.Finally, the weaver generates rewritten C source files (by dumping out the modified source AST) and new auxiliary C source files.The rewritten C source files automatically include the auxiliary C source files using #include directives.We implement the C parser by exploiting the Clang library so it can handle GNU extensions beyond ANSI C. Recall that we cannot reuse the implementation of mature AOP compilers (cf.Section 1).Thus, we have devoted much effort to developing the aspect parser and the weaver from scratch.
Let us elaborate on how the weaver rewrites the source AST.The weaver aims at the functionality that a set of advices are automatically invoked when a join point that matches their pointcut expressions is reached at runtime.The difficulty is that the weaver must find and rewrite only at compile-time the program construct (e.g., a function declaration or a call expression) that corresponds to the matched join point.As a solution, the weaver traverses the source AST and uses a recursive AST visitor to perform a static analysis on each program construct, which checks whether this construct corresponds to a matched join point by matching every pointcut expression against this construct.If a construct is matched, then the weaver rewrites it.Note that the instrumentation happens after all macros have been expanded.This is obligatory as rewriting macros on the AST is impossible for Clang.
The remainder of this section is devoted to explaining three critical techniques used in our compiler implementation: (1) a static analysis that matches a pointcut against a language construct, (2) an instrumentation technique that weaves dynamic advices into a program, and (3) a technique that decides the execution order of multiple advices when applied to a single join point.

Matching Pointcuts and Language Constructs
A primitive or composite pointcut may match two language constructs: declarations and expressions.We have introduced a predicate match in Figure 5 to decide whether a pointcut in the core language matches a construct.We need to extend this predicate to other pointcuts.
(1) Matching a core or static scope pointcut against a function or variable declaration.The predicate match in Figure 5 can already decide whether an execution pointcut matches a function declaration and whether a set pointcut matches a variable declaration that takes an initial value.We are left to explain the remaining kinds of pointcuts.
• call, get and callp pointcuts.Such a pointcut can never match a declaration, as it is irrelevant to a declaration.• intype, infunc and infile pointcuts.Such a pointcut matches a declaration iff this declaration is in a type definition (function definition and file, respectively) whose name (signature and file name, respectively) matches this pointcut's pattern strings.
(2) Matching a core or static scope pointcut against an expression.The predicate match in Figure 5 can already decide whether a call, get or set pointcut matches an expression.We are left to explain the remaining kinds of pointcuts.
• execution pointcuts.Such a pointcut can never match an expression, as it is irrelevant to an expression.• callp pointcuts.Such a pointcut matches an expression iff this expression is a call expression that uses a function pointer pointing to a function type whose return type and number of parameters match this pointcut's pattern strings.Note that matching the function name is Figure 12 shows a refinement of these weaving rules.Recall that Rule  in Figure 8 generates a wrapper function for each around advice, which is split into two functions in Figure 12: a wrapper function in Figure 12a or 12b (depending on whether the advice is the first matched around advice) that supports dynamic scope pointcuts and the positions before and after, and an advice function in Figure 12d that supports the tjp pointer.
As shown in Figure 12a, the wrapper function generated for the first matched around advice has the same signature as in Rule .It creates a join point struct, which records the return value's address &retVal, each parameter's address & and the next wrapper function generated for the next matched around advice.It calls the advice function if the guard (generated according to the dynamic scope pointcuts) holds; otherwise, it calls the next wrapper function.It may also call the advice functions generated for the before and after advices that are matched before , i.e., executing before advices before calling and after advices after calling .
As shown in Figure 12b, the wrapper function generated for each remaining matched around advice takes only one parameter tjp, which makes it possible to share the join point information among all the wrapper functions.It returns a pointer pointing to the return value.
As shown in Figure 12c, the wrapper function called at last implements Rule .It only needs to call the original function as it is called by the wrapper function of the last matched around advice when the guard does not hold, or when the guard holds and proceed() is called.
As shown in Figure 12d, an advice function is generated for each advice.It has one more parameter than the corresponding advice, i.e., the tjp pointer.Its body is exactly the advice's body with proceed() being replaced by tjp->fp(tjp), which actually calls the wrapper function of the next matched around advice and thus proceeds to that advice.

The Execution Order of Multiple Advices
The parsing order of multiple advices is the order in which the compiler parses them.Since an aspect file that contains multiple advices is parsed from the beginning to the end, the parsing order of advices is precisely the order in which they are written.For example, if an aspect m1 is written before another m2, all the advices of m1 precede all the advices of m2 in the parsing order.
When a join point is matched by a set of advices, the execution order of these advices is the order in which their bodies are weaved at compile-time for static advices and executed at runtime for dynamic advices.The execution order of static advices is exactly the parsing order.However, the execution order of dynamic advices is related to, but not necessarily the same as, the parsing order due to the use of before and after advices.
We propose a new formalism, i.e., the ordering rules in Figure 13, for deciding the execution order of multiple advices based on their parsing order.The parsing order is denoted by the relation < such that < iff is written, and thus parsed, before .The execution order is denoted by another relation ≺ such that ≺ iff is executed before at runtime.We use superscripts to indicate the position of an advice.For example, we denote by an around advice (replacing join points), a before advice, and an after advice.The predicate ( ) returns whether advice calls proceed() at runtime.
(  Around advices are the critical elements in deciding the execution order.Rules 1 in Figures 13a, 13b and 13c say that if an around advice calls proceed() at runtime, it is executed before all the succeeding advices in the parsing order.In contrast, Rules 2 in Figures 13a, 13b and 13c say that if an around advice does not call proceed() at runtime, all the succeeding advices are ignored.
Rules 3 and 4 in Figure 13a say that an around advice is executed before the original join point, provided that it calls proceed() at runtime; otherwise, the original join point is ignored.As a result, the original join point is executed only if all the matched around advices call proceed() at runtime.Rules 3 in Figures 13b and 13c say that the original join point is executed after all the matched before advices and before all the matched after advices.
Rules 4 and 5 in Figure 13b say that a before advice is executed before all the succeeding advices in the parsing order.Rules 4 and 5 in Figure 13c say that an after advice is executed after all the succeeding advices in the parsing order.

EXPERIMENTAL EVALUATION
We have implemented Aclang in a compiler named Movec, which also provides dynamic analysis of memory safety [Chen et al. 2021[Chen et al. , 2022[Chen et al. , 2019[Chen et al. , 2024[Chen et al. , 2023] ] besides AOP.This section evaluates Movec's AOP module (i.e., an Aclang implementation) against two state-of-the-art tools, ACC (the latest version 0.9) and AspectC++ (version 2.3), in terms of effectiveness (correctly intercepting join points) and performance (the time and memory overheads incurred).All experiments have been conducted on a computer consisting of an Intel Core i5-12500H 3.11 GHz CPU and 16 GB RAM, running 64-bit Ubuntu 22.04 (Linux kernel 5.19.0).Since C is widely used in developing embedded systems, our experiments use the MiBench benchmark suite [Guthaus et al. 2001].This free and commercially representative embedded benchmark suite consists of real applications written in C and covers many domains such as automotive, consumer, network, office, security and telecommunication.The default compiler for building MiBench is GCC.We have slightly revised its source code to make it compatible with G++ so that AspectC++ can work on it.
AspectC++, unlike Movec and ACC, is designed for use with C++.This design leads to two problems.First, it fails to parse or weave some programs in MiBench because it only accepts the standard C++ syntax.As a solution, we have devoted significant effort to revising the source code of nearly all the programs to make them conform to C++.However, we could not convert the ghostscript and typeset programs to C++, as they use too many incompatible C idioms, resulting in too many modifications to complete.Second, the resulting source code cannot be compiled by any C compiler because it only generates C++ code snippets.As a solution, we compile the resulting source code with G++.
For each program in MiBench, we wrote one aspect for each tool to intercept some execution and call join points.These aspects are similar: each has an integer variable and six advices.The variable counts how many times these advices are executed, while the advices are executed around, before and after function executions or calls.Finally, two additional advices print the count after executing function main() or before calling function exit().The printed count is precisely the Table 2 shows the performance of AspectC++ and Movec when get and set advices are used.The TRs of AspectC++ and Movec are 2.31x and 2.15x relative to the original run on average, respectively, while their MRs are 2.98x and 1.68x.Note that the average TRs and MRs are larger than those in Table 1 as significantly more join points are intercepted.Compared with Movec, AspectC++ is 1.07x slower and consumes 1.77x more memory.Note that the percentage extent to which Movec is faster (7-10%) and consumes less memory (72-77%) relative to AspectC++ is consistent with that in Table 1.Therefore, Movec still outperforms AspectC++ in both execution time and memory consumption.
Runtime Verification.We have evaluated our compiler in detecting memory leaks using the aspect in Figure 1.The result is encouraging; our compiler can detect memory leaks in lame, patricia, rsynth, susan and typeset.The TRs of ACC, AspectC++ and Movec are 1x, 1.05x and 1x relative to the original run on average, respectively, while their MRs are 1.62x, 3.55x and 1.65x.Compared with Movec, ACC consumes the same amount of time and memory, and AspectC++ is 1.05x slower and consumes 2.15x more memory.Therefore, Movec still outperforms AspectC++.
Result Analysis.In summary, we can conclude that Movec outperforms ACC and AspectC++ regarding both effectiveness and performance.The reason is that Movec employs different weaving rules.(1) ACC also inserts C code snippets, but it usually inserts more variables and function calls than Movec for the same aspect, and some insertions are unnecessary.For example, ACC inserts some temporary variables and assignments in the inserted functions.These insertions are usually unnecessary but consume memory (for the variables) and time (for the assignments).(2) AspectC++ inserts C++ code snippets, and thus, its weaving rules are entirely different from ours.AspectC++ inserts many complex class templates in which the variables and function calls consume additional memory (for the variables) and time (for the calls).

RELATED WORK
ACC.It is an aspect-oriented C programming language [Gong and Jacobsen 2010].Thus, it is the AOP language most relevant to Aclang.Its main weakness is that the latest version is incorrect in many cases due to a problematic implementation, and it is no longer maintained.For example, it sometimes fails to parse or weave programs and incorrectly intercepts join points (validated in Section 7).Furthermore, ACC is different from Aclang in terms of both language and compiler.
First, in terms of language, Aclang supports all the kinds of join points, pointcuts and advices provided by ACC, but in a more systematic, expressive and concise manner.
(1) Aclang uses a more systematic classification of AOP notions such as pointcut and advice.For example, it classifies pointcuts into four categories according to their uses, namely core, naming, dynamic scope and static scope, and classifies advices into two categories, namely dynamic and static.This makes it easier for the user to understand the pointcut and advice system.In contrast, the notions used in ACC are fragmented and thus confusing.
(2) Aclang provides more expressive pointcuts and advices.For example, it can use a pointcut to match the type and name of a function parameter.In contrast, ACC can only match type but not name.As another example, it can use an extbeg or extend advice to extend the beginning or end of type definitions, function definitions and files.In contrast, ACC can only use an introduce advice to extend the end of structs and unions; it cannot extend function definitions and files.
(3) Aclang uses a more concise syntax.For example, the following two pointcuts written in ACC and Aclang are equivalent.ACC uses an args pointcut to restrict the first parameter's type to char and rename the second parameter to x, and a result pointcut to restrict the return type to int.It is obvious that the args and result pointcuts are written in a redundant way (the types are already written in the execution pointcut), and the args pointcut mixes type matching and parameter renaming.In contrast, Aclang merges them into a single execution pointcut.
ACC: pointcut ef(int x) : execution(int foo(char, int)) && args(char, x) && result(int) Aclang: pointcut ef(x) = execution(int foo(char %, int % : x)) As another example, ACC requires specifying the types of a pointcut declaration's parameters, e.g., x above.In contrast, Aclang does not require this, as it can automatically infer the types from matched join points.Similarly, ACC requires specifying the return type of matched join points in an around advice declaration.In contrast, Aclang does not require this, as it can still infer the type.These improvements make the pointcuts and advices written in Aclang more concise and clean.
Second, in terms of compiler, Movec provides more convenient user instructions.
(1) When performing instrumentation, ACC requires the input C source and aspect files having been preprocessed by a third-party preprocessor (e.g., GCC) before it works and the user explicitly specifying every input file one by one.This may be inconvenient when there are many files in a large program.In contrast, Movec contains a preprocessor and provides the directory-to-directory transformation mode, which can rewrite all the source files recursively found in a directory.
(2) When compiling the rewritten source files after weaving, ACC requires the compile command specifying the names of the generated auxiliary source files and the ACC library -lacc.This may be inconvenient as the user has to modify the program's Makefile.In contrast, Movec generates rewritten source files that automatically include auxiliary source files using #include directives and does not require specifying any additional libraries.Thus, there is no need to modify Makefile.
AspectC++.It is an aspect-oriented C++ programming language [Spinczyk 2021a,b;Spinczyk et al. 2002;Spinczyk and Lohmann 2007].Its main weakness is that it does not satisfactorily support C programs (validated in Section 7).On the one hand, its parser and weaver cannot correctly handle some C idioms because it requires the standard C++ syntax.As a result, the programmer must revise the C source code until it conforms to C++, which is unfortunately time-consuming and sometimes impossible.On the other hand, the resulting source code cannot be compiled by any C compiler because its weaver only generates C++ code snippets (as it implements aspects as classes and inserts many C++ constructs such as templates).As a result, it cannot be employed on many embedded systems that lack a C++ compiler.
In terms of language, Aclang still supports aspects in a more systematic, expressive and concise manner; the above weaknesses of ACC in terms of language also apply to AspectC++.For example, AspectC++ only fully supports two kinds of core pointcuts: call and execution.It also partially

Fig. 4 .
Fig. 4. Syntax of the core of an aspect-oriented C language.

Fig. 6 .
Fig. 6.Semantics of function declarations and call expressions intercepted by exec and call advices.

Fig. 7 .
Fig. 7. Semantics of reference expressions and assignment expressions intercepted by get and set advices.
Evaluating the function declaration by Rule[FuncDecl-Adv]  creates the following two functions in memory, as only advice matches this declaration.Note that function below uses Proc.ACM Program.Lang., Vol. 8, No. OOPSLA1, Article 117.Publication date: April 2024.Design and Implementation of an Aspect-Oriented C Programming Language 117:11

Fig. 10 .
Fig. 10.Syntax of pointcut expressions and declarations used in practice.

Table 2 .
The Performance of AspectC++ and Movec on MiBench.Each program execution is intercepted by get and set advices.ACC is not used as it does not support get and set join points that access arrays.