Recent News

  • [August 7, 2009] Website published!

Functionality

Aspicere is an aspect language for C with the following characteristics:

  • an aspect module is a normal C compilation unit enhanced with an advice construct
  • expressive pointcut language based on Prolog, enabling:
    • generic advice (reuse!)
    • access to and manipulation of join point context
    • access to weave-time metadata and -rules such as the current build configuration and annotations (/*@some_tag(field1,field2)*/)
  • supported join points:
    • call ("invocation"), execution and delimited continuation join points
    • preliminary support for cflow
    • sequence-like temporal pointcuts
    • join point properties can be attached to join points (similar to inter-type declaration)
  • advice code is similar to procedures, except that:
    • it can use typed variables bound in the pointcut (so-called "bindings")
    • around advice can contain a proceed-call
  • two weaver implementations:

Code Examples

We give two simple code examples, followed by a more complex one.

1. Recovery aspect

This first example aspect checks whether a critical SQL query (call to _iqcftch) has been interrupted by a database error. If the particular error which popped up is recoverable, the aspect tries to fix the error up to a maximum number of attempts. If the error still persists, an error-specific message is prompted. This example works for Aspicere1 and Aspicere2, but with a slightly different syntax.

#include <stdio.h>
#include "sql.h"

int recover(int ErrorCode, int Iterations, char* ErrorString) around Jp:
    invocation(Jp,"_iqcftch",_)
    && critical_call(Jp)
    && sql_redo(ErrorCode,Iterations)
    && sql_code(ErrorCode,ErrorString) {

    int res=0;
    int i;
    
    for(i=-1;i<Iterations;i++){
    res=proceed();
    
    if(sql_code==ErrorCode){
            if(i<Iterations-1) {
                if (i == -1)
                    fprintf(stderr,"!! Error (%d): %s\n", ErrorCode, ErrorString);
                fprintf(stderr,"!! Retrying (attempt %d/%d)...\n",(i+2), Iterations);
            } else {
                    fprintf(stderr,"Retry limit reached. Bailing out.\n");
            }
        } else {
            if (i > -1)
                fprintf(stderr,"!! Retry %d was successful.\n", (i+1));
            break;
        }
    }

    return res;
}

Of course, the notion of "a critical SQL query" is subject to interpretation. Developers can annotate queries, or use Prolog facts and rules for this. Here, we use the latter approach:

:- load_library('KnowledgeLibrary').

sql_code(-666,'The statement cannot be executed, because a time limit was exceeded.').
sql_code(-910,'The object cannot be used, because an operation is pending.').
sql_code(-542,'Invalid SQL code.').
sql_code(-357,'The file server is currently not available.').

sql_redo(-666,2).
sql_redo(-357,3).
sql_redo(-542,0).

critical_method('monthlyPayment').

critical_call(Jp) :-
    enclosingMethod(Jp,EncMethod),
    execution(EncMethod, Name),
    critical_method(Name),
    print('CRITICAL call found!!!'),nl.

2. Optimizer aspect

In this example, we optimize an SQL-query by modifying the argument of the call to _iqcprep.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sql.h"

#define CRITICAL_FREQ 2

/* Usage patterns */
static int usage=0;
static char* last_query=0;

static void cleanup(void){
  if(last_query) free(last_query);
}

static char* getLastQuery(void){
  if(!last_query) atexit(cleanup);
  return last_query;
}

static void setLastQuery(char* new_query){
  if(last_query) free(last_query);
  last_query=(char*)malloc((strlen(new_query)+1)*sizeof(char));
  strcpy(last_query,new_query);
}

/* Optimize SQL queries based on usage patterns */
void optimise_query (char* Query) before Jp:
 invocation(Jp,"_iqcprep")
 && args(Jp,[Query|_])
{
  char* optimised_query=0;
  if(getLastQuery()&&!strcmp(getLastQuery(),Query)){ /* Same query */

    usage++;

    if(usage<=CRITICAL_FREQ) { /* Suboptimal optimizing */
      optimised_query=(char*)malloc((strlen(Query)+9+1)*sizeof(char));
      strcpy(optimised_query,"OPTIMISED");
      strcat(optimised_query,Query);
    }else{ /* Optimal optimizing */
      optimised_query=(char*)malloc((strlen(Query)+14+1)*sizeof(char));
      strcpy(optimised_query,"SUPEROPTIMISED");
      strcat(optimised_query,Query);
    }

    printf("--Unoptimised query:\t%s\n",Query);
    Query=optimised_query;
    printf("++Optimised query:\t%s\n",Query);
    
  }else{

    setLastQueryQ(Query);
    usage=1;
    printf("--Could not optimise query yet.\n");

  }
}

3. Delimited Continuation Join Point

This more complex example illustrates the concept of delimited continuation join points and Aspicere2's annotation support.

First, we show the base code.

#include <stdio.h>

int g(void){
	return 0;
}

void f(void){
	printf("B");
	/*@halting()*/
	g();
	printf("C");
}

int main(void){
	printf("A");
	f();
	printf("D\n");
}

The /*@halting()*/-annotation means that we want to stop the execution of procedure f if the call to g returns zero (which is always true here for the sake of simplicity). In other words, printf("C") must NOT be executed. For this, we need the following advice:

int handling (int* ReturnVal) around Jp:
  halting_invocation(JpCall,ReturnVal)
  && delimited_continuation(Jp,JpCall) {
   if(*ReturnVal==0)
     return *ReturnVal;
   else
     return proceed();
}

This advice does the following: if the return value of a /*@halting()*/-annotated call equals zero, the delimited continuation (i.e. the remaining execution within procedure f after the call to g) is skipped (not proceeded). Otherwise, the remaining execution is resumed (proceeded). In other words, we get "ABD" as output.

How do we define the halting_invocation/2 predicate? For this, we need to attach a join point property to procedure f in order to temporarily store the return value of /*@halting()*/-annotated calls:

int* return_var () on Jp:
  execution(Jp,_);

void var_update(int* ReturnVal) after Jp returning (int* Result):
  halting_invocation(Jp,ReturnVal){
   *ReturnVal=*Result;
}

Finally, this is the actual definition of the halting_invocation/2 predicate:

halting_invocation(JpCall,ReturnVal):-
	invocation(JpCall,_),
	annotation(JpCall,halting,_)
	enclosingMethod(JpCall,JpEncl),
	property(JpEncl,return_var,ReturnVal)
	.

Basically, there is a check whether JpCall is a call annotated with a /*@halting()*/ annotation. If so, the current value of the return_var property attached to the calling execution join point property is returned.

In this paper, we apply delimited continuation join points to extract an idiom-based exception handling idiom into aspects.

Releases

There are two implementations of Aspicere. Aspicere2 fully implements Aspicere, whereas Aspicere1 only provides a small subset.

Aspicere1

Beta 1 is the aspect weaver we used in the Kava case study. This means that it is able to weave in K&R- as well as in ANSI C-code. Unfortunately, in the former case, there are times when method prototypes don't declare the types of their arguments. Aspicere1 infers these types from a procedure's call site, but does not support all possible C types. In those cases, our weaver merely "skips" the relevant join points without halting the full weaving process. The weaver shows statistics about the relative percentage of "skipped" join points.

Aspicere1 has the following limitations:

  • only supports call join points
  • (with GCC 4.x on non-OSX platforms) modifying captured arguments of advised procedures gives a compilation error
  • does not advize function pointers
  • multiple advices on the same join point are allowed, but advice on advice is not possible yet

As a manual workaround for the GCC 4.x bug, one can do the following:

  1. open the file which refuses to compile
  2. regexp-replace all "# .*" (mind the space) by ""
  3. retry the offended GCC command and note down the erring line numbers
  4. reopen the file which refuses to compile
  5. go to the relevant lines and replace the casts like (char*)voidptr=charptr; by voidptr=(void*)charptr;
  6. recompile the corrected file

Aspicere1 is covered under a Tri License scheme: MPL/LGLP/GPL. It depends on Yerna Lindale and Lillambi, but these are included in the download. Furthermore, you need a Java 1.4-compatible VM, a C compiler, Perl interpreter and Bash shell. The included INSTALL file contains detailed installation instructions for Aspicere1.

Aspicere2

Aspicere2 is also covered under a Tri License scheme: MPL/LGLP/GPL. It has the following dependencies:

This is the most recent version of Aspicere2, used in multiple papers. The best documentation can be found here. The included INSTALL file contains installation instructions for all of these and for Aspicere2 itself.

Publications

Download

Jump to ...

My Other Tools