Grammar

ExtDecl ::= ... / AspFuncDef / GIntroAdv

DeclOrFuncDef ::= Decl / FuncDef

GIntroAdv ::= intro(Id) { (DeclOrFuncDef)* } ;

AspStructTDef ::= struct (AttrSpecL)? (StructTag)? { AspStructDeclL }

AspStructDeclL ::= (AnnotAspStructDecl)+ Annotations / Annotations

AnnotAspStructDecl ::= AspStructDecl

AspStructDecl ::= (__extension__)? StructAdvDecl; / (__extension__)? SpecQualL OptStructDeclL;

StructAdvDecl ::= ... / AspStructTDef

AspFuncDef ::= (__extension__)? (DeclSpecs)? Declarator FuncDeclContext (DeclL)? AspCompStmt

AspCompStmt ::= { (LocalLabelDeclaration)* ExtBlock (DeclOrStmt)* Annotations }

ExtBlock ::= extension(Id) { (Declaration)* (BeforeStmt)? (AfterStmt)? }

BeforeStmt ::= before(Ids) { (DeclOrStmt)* }

AfterStmt ::= after(Id) { (DeclOrStmt)* }

Ids ::= Id(, Id)*

Code

The before block enables to specify some statements that should be executed prior the execution of the function containing this extension.

Basic Before Extension

int 
main(void) 
{
 extension(c4_extension) {
   before ()
   {
     printf("Before\n");
   }
 }
 int i=0;
 for(; i<5; i++) {
  printf("i: %d\n", i);
 }
 return 0;
}

This program will print:

Before
0
1
2
3
4

Basic After Extension

The after block enables to specify some statements that should be executed after the execution of the function containing this extension. The after block must return the same type as the original function.

int 
main(void) 
{
 extension(c4_extension) {
   after()
   {
    printf("After\n");
    return 0;
   }
 }
 int i=0;
 for(; i<5; i++) {
  printf("i: %d\n", i);
 }
 return 0;
}

This program will print:

0
1
2
3
4
After

Around Simulation

There is no longer an around block in C4. before and after block can emulate the around behavior.

State Sharing

before and after Extensions should be able to share some state. To do so, variables must be declared in the extension block to be made visible to both before and after extensions.

void 
trans_func(void) 
{
 extension(c4_extension) {
   LOCK * lock;
   before() 
   {
     lock = acquire_lock();
   }
   after()
   {
     release_lock(lock)
   }
 }
 do_something();
}

In this example, the lock variable is shared between the before and the after block.

Proceeding

In regular AOSD, the around pointcut enables to wrap the whole execution of a function. From the advice code, the original code can be called using the proceed() function. In C4, no need to call proceed(), the original function will be executed after the execution of the statements in the before block. The before block reproduces exactly the prototype of the including function (in terms of the number of parameters, the type is inferred). Optionally, the parameters names can be modified for more flexibility.

int 
trans_func(int parameter) 
{
 extension(c4_extension) {
   before(parameter) 
   {
    printf("Before Parameter %d\n", parameter);
    parameter++;
   }
   after()
   {
    parameter--;
    printf("After Parameter %d\n", parameter);
    return parameter;
   }
 }
 printf("Main Body Parameter %d\n", parameter);   
 return parameter;
}

If called with 0, the program should print:

Before Parameter 0
Main Body Parameter 1
After Parameter 0

What cannot be implemented using this approach: \begin{itemize} \item proceed in a loop \item proceed in one branch of an if \end{itemize}

No Proceeding

Sometimes the around advice is used to replace the body of a whole function. In regular AOSD approaches, the advice never calls the proceed() method. In C4, if the before block contains a return statement, the execution does not proceed to the original function body but goes straight to the beginning of the after block. The before block returns void. If some variables are supposed to be shared between the before and after advice, they must be declared in the extension block.

int 
trans_func(int parameter) 
{
 extension(c4_extension) {
   before(parameter) 
   {
    printf("Before Parameter %d\n", parameter);
    parameter++;
    return;
   }
   after()
   {
    parameter--;
    printf("After Parameter %d\n", parameter);
    return parameter;
   }
 }
 printf("Main Body Parameter %d\n", parameter);
 return parameter;
}

If called with 0, the program should print:

Before Parameter 0
After Parameter 0

Playing with Return Values in After Blocks

When entering an after advice, the value returned by the original function or by the corresponding before block is mapped with the variable name given as a parameter of the after block. If no variable name is given

int 
trans_func(int parameter) 
{
 extension(c4_extension) {
   before(parameter) 
   {
    printf("Before Parameter %d\n", parameter);
    parameter++;
   }
   after(__returned__)
   {
    int result = __returned__ + 42;
    printf("After Parameter %d Returned %d\n", parameter, __returned__);
    return result;
   }
 }
 printf("Main Body Parameter %d\n", parameter);
 return parameter + 1;
}

If called with 0, the program should print:

Before Parameter 0
Main Body Parameter 1
After Parameter 1 Returned 2

The trans_func function should return 44.

Compilation

Before

Before blocks are translated as a regular block in the function including the extension. The ordering is important since It reflects the execution order.

After

After blocks are translated as nested functions. Those nested functions are pushed onto a stack. When returning from the including function, an orchestration function is called (just before returning to the calling function) that pops out all the after advice that were pushed on the stack. The last after advice to be executed returns from the function.