Authors Nicolas Stouls, Virgile Prevosto,
License CC-BY-SA-4.0
Aoraï Plugin Tutorial (A.k.a. LTL to ACSL) For Frama-C 27.1 (Cobalt) Nicolas Stouls and Virgile Prevosto Nicolas.Stouls@insa-lyon.fr,virgile.prevosto@cea.fr Work licensed under Creative Commons BY-SA licence https://creativecommons.org/licenses/by-sa/4.0/ © 2009-2023 CEA LIST Foreword Aoraï is a Frama-C plugin that provides a method to automatically annotate a C program according to an automaton F such that, if the annotations are verified, we ensure that the program respects F . A classical method to validate annotations then is to use Eva or WP. This document requires basic knowledge about the Frama-C platform itself (See https://frama-c. com for more information), in particular the notions of plug-ins and project. Notes: – to the question "Why this name: Aoraï ?" my answer is: why not? Aoraï is the name of the tallest reachable mount in the Tahiti island and its reachability is not always obvious. Official website: https://www.frama-c.com/fc-plugins/aorai.html 2 Contents 1 Introduction 5 1.1 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2 Interest of Aoraï . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.3 Documentation’s description . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2 Quick overview 6 2.1 First use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.1 Launching the test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.2 Automata and verification . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2 Help Command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.3 Known Restrictions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3 Aoraï’s Language 10 3.1 YA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.1.1 YA file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.1.2 Basic YA guards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.1.3 YA extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 4 Advanced Features 15 4.1 Generated Annotated Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.1.1 Auxiliary Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.1.2 Deterministic lemmas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.1.3 Update functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.1.4 Functions behaviors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.1.5 Loop Invariants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.2 Analyzing Annotated Files with Eva or WP . . . . . . . . . . . . . . . . . . . . . 19 5 Going Further 21 5.1 Theoretical Base of the Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 5.1.1 Safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 5.1.2 Liveness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3 CONTENTS 5.2 Adding from the Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 5.2.1 Automata Modeling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 5.2.2 Memorization of last Transitions . . . . . . . . . . . . . . . . . . . . . . 22 5.2.3 Use of Specifications instead of Invariant . . . . . . . . . . . . . . . . . 22 5.3 Abstract Interpretation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 5.3.1 Generation of Abstract Specifications . . . . . . . . . . . . . . . . . . . 23 5.3.2 Static Simplification . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 5.4 Plugin Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 5.5 Recent updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.5.1 Frama-C Iron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.5.2 Frama-C Vanadium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.5.3 Frama-C Titanium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.5.4 Frama-C Aluminium . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.5.5 Frama-C Nitrogen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.5.6 Frama-C Boron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.5.7 Frama-C Beryllium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 6 Conclusion 25 4 Introduction 1 1.1 Installation Aoraï is part of the main Frama-C distribution and is installed by default with Frama-C. Installation instructions for Frama-C are available at https://frama-c.com/html/get-frama-c.html. 1.2 Interest of Aoraï As explained before, Aoraï’s goal is to prove that the C program works like a given automaton. The approach used by Aoraï has two advantages: – the high level of abstraction helps to write simple automata and avoid the necessity to compute all possibilities of a function1 – thanks to the collaboration between human and plugin principle, you can easily check complex C programs (see section 4.2) 1.3 Documentation’s description This document is divided into four parts: – First part is a quick overview of Aoraï. It will enable you to verify basic properties and explain the general principle of the software. – The second part defines the Aoraï input language with which it is possible to describe a given property. – The third part explains how to prove a program annotated with Aoraï using the WP plug-in. – Finally, the last part details Aoraï’s underlying theory, and its internal architecture in order to help people who would like to contribute to the plug-in itself. 1 for more information, see chapter 5 5 Quick overview 2 In this chapter we will see how to use Frama-C and the couple WP-Aoraï to prove that a C program has the same behavior as an automaton. 2.1 First use The goal is to launch the examples1 and read results. 2.1.1 Launching the test First, we will forget about the specification of the automaton, which will be described in the second part. In fact, we consider that we have already written the file which describes the automaton. WP verification2 can only be done on C code augmented with ACSL annotations. Thus, Aoraï creates a new C file where the automaton is encoded into ACSL annotations. Section 4.1 will give more information about the annotations generated by Aoraï If you look at the example’s archive, you will find two files: – example.ya which gives a description of the automaton’s specifications. – example.c is the implementation which will be checked. With these two files (automaton’s description and C file), we can create an annotated file. This is done by the following command: $ frama-c example.c -aorai-automata example.ya \ -then-last -ocode example_annot.c -print This generates a new C file example_annot.c. In order to decide if the original program is correct with respect to the automaton, it is sufficient to establish that the generated C code and its associated ACSL annotations are valid. For instance, the following command uses the WP plugin over the generated file: $ frama-c example_annot.c -wp -wp-rte Of course, any option of WP itself can be used, notably -wp-rte to check for the absence of runtime error. Finally, it is possible to instruct Frama-C to do a sequence of analyzes over various projects, via the -then-on options and -then-last options. Thus, we do not need to use an intermediate file and to run Frama-C twice. Instead, we just instruct WP to operate on the aorai project that contains the code annotated by Aoraï: 1 From http://frama-c.com/aorai.html 2 For more information about WP and code verification, please refer to http://frama-c.com/wp.html 6 2.1. FIRST USE $ frama-c example.c -aorai-automata example.ya \ -then-last -wp -wp-rte 2.1.2 Automata and verification The main interest of Aoraï is to prove that the program can be described by an automaton. Please keep in mind that solutions to write automata in Aoraï are listed in the next chapter. The automaton of our running example is described by figure 2.1. 0 Call(main) 1 Call(opa) 2 not Return(opb) 3 not Call(opa) 4 Return(opb) 5 Return(main) 6 True Figure 2.1: Automaton From the description contained in .ya file, a specification — in terms of automata states and transitions — is computed for each operation. For instance, the following specification corresponds to the previous automaton: Pre : state = {2} ∧ trans = {1} opa Post : \old(state) = {2} ⇒ state = {3} ∧ trans = {2} Pre : state = {4} ∧ trans = {3} opb Post : \old(state) = {4} ⇒ state = {5} ∧ trans = {4} Pre : state = ∅ ∧ trans = ∅ opc Post : \old(state) = ∅ ⇒ state = ∅ ∧ trans = ∅ Pre : state = {1} ∧ trans = {0} main Post : \old(state) = {1} ⇒ state = {6} ∧ trans = {5} Finally, the C-code which will be checked is given in figure 2.2. Actually, the mapping between state and code is made thanks to the transitions properties like CALL(opa). Note that the pre- and post-conditions of the C functions are defined by the set of states authorized just before (resp. after) the call. Aoraï generates a new C program, including the automaton axiomatization, some coherence invariants, and annotations on operations, such that if this annotated program can be validated with the WP plugin, then we ensure that it respects the given properties. Sometimes, the automaton has not enough information to check the validity of the C-program, and the problem is only related to the implementation which is used. In this case you can add some properties in the automaton or in the generated files. For more information about that, please read section 4.2. 7 2.2. HELP COMMAND int rr=1; //@ global invariant inv:0<=rr<=5000; /*@ requires r<5000; @ behavior j : @ ensures \result==r+1; */ int opa(int r) {return r+1;} /*@ requires rr>=1 && rr <=5000; @behavior f: @ ensures rr>=3 && rr<=5000; */ void opb () {if(rr<4998) {rr+=2;}} /*@ behavior d: @ ensures rr==600; */ void opc () {rr=600;} /*@ requires rr==1; */ int main() { if (rr<5000) rr=opa(rr); opb(); goto L6; opc(); L6: return 1; } Figure 2.2: Example of C File 2.2 Help Command The frama-c -aorai-help command returns the list of options for the Aoraï plug-in. Here are the most common ones: -aorai-automata <f> considers the property described by the ya automata (in Ya language) from file <f> -aorai-verbose <n> gives some information during computation, such as used/produced files and heuristics applied -aorai-show-op-spec displays, at the end of the process, the computed specification of each operation, in terms of states and transitions. -aorai-dot generates a dot file of the automata. Dot is a graph format used by the GraphViz tools3 . 8 2.3. KNOWN RESTRICTIONS 2.3 Known Restrictions The current version of Aoraï is under development. Hence, there are some restrictions. – Only safety properties can be checked. The liveness part is not truly considered. Namely, the acceptance condition is a post-condition of the main function, so that we need a terminating program to actually validate it: for a non-terminating program, the point where it is supposed to be checked is unreachable. – Function pointers are only supported if each indirect call comes with an calls ACSL annotation (see Frama-C user manual for more information) – In the init state from the automaton, conditions on C-array or C-structure are not statically evaluated (it’s an optimization) but are supported. 3 http://www.graphviz.org 9 Aoraï’s Language 3 Aoraï’s verification principle is built from the automaton. Automata can be described in the YA language, which was created for Aoraï. 3.1 YA 3.1.1 YA file A YA file follows the grammar described in Fig. 3.1. The directives specify the initial and accepting file ::= directive ∗ state + directive ::= %init : id + ; name of initial state(s) | %accept : id + ; name of accepting state(s) | %deterministic ; deterministic automaton state ::= id : transition (| transition)∗ ; state name and transitions from state transition ::= guard -> id guard and end state of the transition | -> id transition that is always taken | other -> id default transition. must appear last guard ::= { condition } Figure 3.1: Structure of a YA file state(s). There must be at least one initial state (exactly one if the automaton is supposed to be deterministic). All initial and accepting state must appear in the list of states afterwards. A state is simply described by its name and the list of transitions starting from this state with their guard. The specific other guard indicates that this transition is taken if none of the other ones can be taken. If it appears, it must be last in the list of transitions. 10 3.1. YA condition ::= CALL ( id ) | RETURN ( id ) | COR ( id ) | true | false | ! condition | condition && condition | condition || condition | ( condition ) | relation relation ::= expr relop expr relop ::= < | <= | == | != | >= | > expr ::= lval | cst | expr + expr | expr - expr | expr * expr | expr / expr | expr % expr | ( expr ) cst ::= integer lval ::= id ( ) . id | id ( ) . \result | id | lval . id | lval -> id | lval [ expr ] | * lval Figure 3.2: Basic YA guards Conditions that can occur in guards are described in the next section. 3.1.2 Basic YA guards The syntax for basic YA conditions is described in figure 3.2. Basically, a condition is a logical expression obtained from the following atoms: – CALL, RETURN or COR event, indicating respectively the call, the return, the call or the return of the corresponding function; – A relation over the variables of the programs. In addition to global variables, that are directly accessed through their id, it is possible to consider the value returned by a function or the value of its formal parameters. This is done through f().return and f().a respectively. In order to be closer to ACSL’s syntax, f().\result is accepted as a synonym of f().return. Whenever f().prm appears in a relation, the related guard has an implicit CALL(f) event, while f().return and f().\result trigger a RETURN(f) event. Note that this might result in an always-false guard if several such expressions occur in the same guard, as in f().x <= g().y In order for this guard to hold, we should be calling at the same time f and g, which is not possible. In addition, if such expression occurs in a negative occurrence, that is under a negation, as in ! f().x <= 4 11 3.1. YA the related CALL(f) event itself is not negated. In other words, the guard above is true if and only if we call f with an argument greater than 4. Usage of these expressions might be deprecated in future versions of Aoraï in favor of the less ambiguous constructions presented in the next subsection. For instance, the automaton used in the chapter 2.1 contains the following transitions: %init: S0; %accept: S0, S1, S2,S3,S4,S5,S6; S0 : { CALL(main) } -> S1; ; S1 : { opa().r<5000 } -> S2 ; S2 : { opa().return<=5000 } -> S3 ; S3 : { !RETURN(opa) } -> S4 ; S4 : { RETURN(opb) } -> S5 ; S5 : { RETURN(main)} -> S6 ; S6 : -> S6 ; 3.1.3 YA extensions Extended YA guards In order to describe more easily whole sequences of calls, some extensions to the basic conditions above are available. They are described in figure 3.3. Note however that these extensions are very experimental yet A guard can now be the succession of several atomic events, possibly optional or on the contrary repeated more than one time. The repetition modifier follows the syntax and semantics of POSIX regexps: the most general are {e1,e2} that indicates at least e1 repetitions and at most e2 and {e1,} that indicates at least e1 repetitions without upper bound. There are then shortcuts for the most common patterns: – no modifier indicates exactly one execution (equivalent to {1,1}) – + indicates 1 or more repetitions (equivalent to {1,}) – * indicates any number of repetitions, including 0 (equivalent to {0,}) – ? is equivalent to {0,1} – {e} is equivalent to {e,e} – {,e} is equivalent to {0,e} Note that a repetition modifier that allows a non-fixed number of repetitions prevents the automaton to be %deterministic. id(seq) indicates that we have a CALL(id) event, followed by the internal sequence of event, and a RETURN(id), i.e. it describes a complete call to id, including the calls that id itself performs. In particular, f() indicates that f does not perform any call. When in a sequence internal to a call to f, the identifiers found in the expressions are first searched among the formals of f, starting with the innermost call and then among globals. It is still possible to use f().x to refer to parameter x of f, but if f is already in the call stack, this will not trigger a new CALL(f) event at this point. Instead, the value of x for the last call to f will be used. In addition, the CALL(id) event may be further guarded by a pre-condition, that is either the name of an ACSL behavior of id, or a basic YA condition (in which we have access to the formals of id as explained above). Similarly, the final RETURN(id) event can come with a post-condition, in which one can access the \result returned by id. 12 3.1. YA guard ::= seq-elt seq-elt ::= condition | basic-elt repetition basic-elt ::= [ non-empty-seq ] | id pre-cond ( seq ) post-cond seq ::= ϵ | non-empty-seq non-empty-seq ::= seq-elt | seq-elt ; seq repetition ::= ϵ | + | * | ? | { expr , expr } | { expr } | { expr , } | { , expr } pre-cond ::= ϵ | :: id | {{ condition }} post-cond ::= ϵ | {{ condition }} Figure 3.3: Extended YA guards For instance, the following automaton describes a function main that does not call anything when called in behavior bhv and performs a single call to f, when called with a parameter c less than or equal to 0, returning 0 in this latter case: %init: S0; %accept: Sf; S0: { main::bhv() } -> Sf | { main {{ c <= 0 }} (f()) {{ \result == 0 }} } -> Sf; Sf: -> Sf; YA variables Extended guards do not allow specifying relations between the parameters of distinct, non-nested calls. In order to be more flexible, it is possible to declare variables in a Ya file, to assign them values when crossing a transition, and to use them in guards. The syntax for that is described in Fig. 3.4. Only char, int, long variables are currently supported. Furthermore, variables can only be introduced in deterministic automata, which do not use extended guards. A variable $x must have been declared in the directives of the file to be used in a guard. Furthermore if it is used in a transition starting from state S, then all possible paths from the initial state to S must contain at least one assignment to $x. Note that assignments are performed sequentially, so that if $x has already been assigned in a given sequence of actions, it can automatically be used in subsequent assignments (on the other hand, since conditions are evaluated before actions, it must have been initialized elsewhere if it were to be used in the condition part of the guard. 13 3.1. YA directive ::= ... | $ id : type type ::= char | int | long guard ::= { condition } action∗ action ::= $ id := lval ; lval ::= ... | $ id Figure 3.4: Syntax for declaring and using YA variables in the right hand side of an action, it is possible to refer to the value of a formal parameter of f when the transition is triggered over a CALL to f and to its return value when hangling a RETURN event, as described in section 3.1.2 for conditions. An example of YA automaton with variables is given below. It uses variables $x and $y that are updated when calling f and returning from i, while $x is used when calling h. %init: a; %accept: i; %deterministic; $x : int; $y : int; a : { CALL(main) } -> b; b : { CALL(f) } $x:=f().x; $y := $x; -> c | { CALL(g) } -> d ; c : { RETURN(f) } -> e; d : { RETURN(g) } -> g; e : { CALL(h) && $x > 0 } -> f | { RETURN(main) } -> i ; f : { RETURN(h) } -> g; g : { CALL(i) } -> h; h : { RETURN(i) } $y := 0; $x := 1; -> e; i : -> i; 14 Advanced Features 4 4.1 Generated Annotated Program The instrumented program is the original program (with its annotations1 ) completed with the following: – Some auxiliary C declarations representing the automaton itself and information needed to decide if a given transition should be taken or not; – If the automaton has been marked as deterministic, a set of lemmas state that it is indeed the case; – For each original C function, two functions are defined with their specification. They take care of updating the automaton’s state when entering and exiting the function respectively; – Each original C function gets additional ACSL behaviors, expressing how the automaton is supposed to evolve when the function is called – Each loop gets additional loop invariants stating in which states the automaton might be during the loop. These annotations are detailed in the rest of this section. 4.1.1 Auxiliary Variables We have to represent the current state of the automaton. It can take two forms. First, if the automaton is marked as %deterministic, an enum type representing the states of the automaton is generated. It makes it easier to read the generated annotations when they come from a Ya file with explicitly named states. We use then a single variable, aorai_CurStates which is simply a value of the enum type corresponding to the current (unique) active state of the automaton. Otherwise, we use a set of boolean variables, whose value is 1 when the automaton is in the corresponding state and 0 otherwise. Furthermore, the use of extended YA constructions (section 3.1.3) might introduce additional variables: – Repetitions introduce a counter, aorai_counter (with a numeric suffix if needed), except if their lower bound is 0 or 1 and they don’t have an upper bound or their upper bound is 0 or 1 (in these cases, there is no need to test the number of repetition done so far at the end of the sequence). – The value of a parameter prm of function f that is accessed in another event than CALL(f) is stored in a global variable aorai_prm in order to be accessible in the remainder of the sequence. 4.1.2 Deterministic lemmas When a YA automaton is marked as %deterministic, some lemmas are generated whose verification will ensure that the automaton is indeed deterministic. Namely, for each state of the automaton, a 1 ACSL language for annotation is described at https://github.com/acsl-language/acsl 15 4.1. GENERATED ANNOTATED PROGRAM lemma states that at any given event, there is at most one transition exiting from this state that is active. 4.1.3 Update functions In order to update the automaton’s status, a pair of function is defined for each function f defined in the original C code. f_pre_func is then called when entering f, while f_post_func is called just before f returns. Both come with a specification that indicates what actions may occur for the automaton at the corresponding event. For instance, we can have a look at the specification generated for opa_pre_func in our running example, presented in figure 4.1. Similarly, the corresponding body is shown in figure 4.2. For each state of the automaton, we have one or two behaviors, describing /*@ ensures aorai_CurOpStatus == aorai_Called; ensures aorai_CurOperation == op_opa; assigns aorai_CurOpStatus, aorai_CurOperation, S1, S2, S3, S4, S5, S6, S7; behavior buch_state_S1_out: ensures 0 == S1; behavior buch_state_S2_out: ensures 0 == S2; behavior buch_state_S3_in: assumes 1 == S2 && r >= 0; ensures 1 == S3; behavior buch_state_S3_out: assumes 0 == S2 || !(r >= 0); ensures 0 == S3; behavior buch_state_S4_out: ensures 0 == S4; behavior buch_state_S5_out: ensures 0 == S5; behavior buch_state_S6_out: ensures 0 == S6; behavior buch_state_S7_out: ensures 0 == S7; */ void opa_pre_func(int r); Figure 4.1: Specification of opa_pre_func whether the state can be active or not. In addition, when there are counters or other auxiliary variables that must be updated, other ensures clauses define their new value according to the transition that is activated. It is also possible to only activate the generation of the body of the transition functions, without their specification (e.g. to analyze the instrumented code with the Eva plug-in, which does not need the con- 16 4.1. GENERATED ANNOTATED PROGRAM int S1_tmp; int S2_tmp; int S3_tmp; int S4_tmp; int S5_tmp; int S6_tmp; int S7_tmp; aorai_CurOpStatus = aorai_Called; aorai_CurOperation = op_opa; S1_tmp = S1; S2_tmp = S2; S3_tmp = S3; S4_tmp = S4; S5_tmp = S5; S6_tmp = S6; S7_tmp = S7; S7_tmp = 0; S6_tmp = 0; S5_tmp = 0; S4_tmp = 0; if (S2 == 1 && r >= 0) S3_tmp = 1; else S3_tmp = 0; S2_tmp = 0; S1_tmp = 0; S1 = S1_tmp; S2 = S2_tmp; S3 = S3_tmp; S4 = S4_tmp; S5 = S5_tmp; S6 = S6_tmp; S7 = S7_tmp; Figure 4.2: Body of opa_pre_func 17 4.1. GENERATED ANNOTATED PROGRAM tracts and loop invariants). This is done through option -aorai-no-generate-annotations. In that case, it might be the case that the automaton end up in a rejecting state (for a deterministic automa- ton) or without any active state (for a non-deterministic automaton). Option -aorai-smoke-tests can thus be used to generate assertions at the end of all update functions, stating that the automaton is still in an appropriate state. 4.1.4 Functions behaviors Each function f defined in the original C code gets its specification augmented with behaviors describing how the automaton’s status changes during a call to f. The specification of the opa function in our running example is shown in figure 4.3. /*@ requires 1 == S2 && 0 == S1 && 0 == S3 && 0 == S4 && 0 == S5 && 0 == S6 && 0 == S7; requires 1 == S2 ==> r >= 0; requires r < 5000; behavior j: ensures \result == \old(r)+1; behavior Buchi_property_behavior: ensures 1 == S4 ==> \result <= 5000; ensures 0 == S1 && 0 == S2 && 0 == S3 && 0 == S5 && 0 == S6 && 0 == S7; ensures 1 == S4; */ int opa(int r); Figure 4.3: Generated specification for an existing C function The first requires clause indicates which state(s) can be active before entering the function. Then, for each of these states, we have a requirement that at least one of the guard of a transition exiting from this state is true. After the global requires, we find some behaviors corresponding to the possible states of the automaton when the function returns. Again, we might also find some post-conditions on the auxiliary variables used by Aoraï. Note however that these conditions are computed through abstract interpretation and may thus be over-approximated. 4.1.5 Loop Invariants For each loop, Aoraï defines an invariant stating in which states the automaton can be during the loop. Since the states of the automaton when entering the loop the first time and the states found during the executions of the loop can be quite different, Aoraï introduces in addition a new variable, that is initially set to 1 and reset to 0 when the loop is entered. This allows to make a distinction between the first run and the other ones and to refine the invariant according to value of the variable. Possible values for the auxiliary variables are also described by loop invariants (again, the values found might be over-approximated). An example of loop invariant can be found using the following example. Figures 4.4 and 4.5 describe the automaton and the C code (a function main is supposed to call f and g between 0 and 5 times). Figure 4.6 presents the generated invariants for the while loop. 18 4.2. ANALYZING ANNOTATED FILES WITH EVA OR WP %init: S0; %accept: Sf; S0: { [main([f();g()]{0,5})] } -> Sf; Sf: -> Sf; Figure 4.4: Example of YA automaton describing a loop int f() {} int g() {} int main(int c) { if (c<0) { c = 0; } if (c>5) { c = 5; } /*@ assert 0<=c<=5; */ while (c) { f(); g(); c--; } return 0; } Figure 4.5: Original C code with a loop 4.2 Analyzing Annotated Files with Eva or WP Once the annotated file has been generated, it remains to verify that all the annotations hold. This section describes briefly how this can be done and some common issues that may arise during verification. In addition to annotations, Aoraï generates an implementation for the transition functions, so that it is possible to use the Eva plug-in of Frama-C on the instrumented code. However, there’s no guarantee that Eva will be able to perform an analyzis that stays precise enough to verify that the automaton always ends in an accepting state. Aoraï will set up automatically a certain number of parameters in Eva to help make the analyzis more precise, but, in the case of deterministic automata it is also possible to use option -aorai-instrumentation-history n to have the instrumentation retain the n previous states of the automaton (in addition to the current state), that will be used for splitting Eva’s abstract states. Furthermore, each time Eva encounters a call to the built-in function Frama_C_show_aorai_state, it will display the current state (together with the previous ones up to -aoraiinstrumentation-history). If the function is called with some arguments, their abstract value will also be displayed. Another possibility is to use the deductive verification plug-in WP. Note however that the generated annotations are not guaranteed to be complete, i.e. to it might be necessary to add further annotations in order to discharge all proof obligations. In particular, in presence of loops, Aoraï generates loop invariants for its own auxiliary variables, but it is likely that these variables (especially the counters) will need to be related to the variables of the original programs. For instance, we must add to the loop invariants of figure 4.6 that c+aorai_counter remains constant throughout the loop (c gets decremented at each step, while aorai_counter gets incremented), but such a relation is well beyond the scope of Aoraï itself. 19 4.2. ANALYZING ANNOTATED FILES WITH EVA OR WP /*@ loop invariant Aorai: 0 == S0; loop invariant Aorai: 0 == Sf; loop invariant Aorai: 1 == aorai_intermediate_state || 0 == aorai_intermediate_state; loop invariant Aorai: 1 == aorai_intermediate_state_0 || 0 == aorai_intermediate_state_0; loop invariant Aorai: 0 == aorai_intermediate_state_1; loop invariant Aorai: 0 == aorai_intermediate_state_2; loop invariant Aorai: 0 == aorai_intermediate_state_3; loop invariant Aorai: 1 == aorai_intermediate_state || 1 == aorai_intermediate_state_0; loop invariant Aorai: aorai_Loop_Init_43 != 0 ==> \at(1 == S0,Pre) ==> 0 == aorai_intermediate_state_0; loop invariant Aorai: aorai_Loop_Init_43 == 0 ==> 0 == aorai_intermediate_state; loop invariant Aorai: \at(1 == aorai_intermediate_state,aorai_loop_43) && 1 == aorai_intermediate_state_0 ==> 1 <= aorai_counter <= 5; * / Figure 4.6: Example of Generated Loop Invariants 20 Going Further 5 The objective of the Aoraï plug-in is to generate an annotated C program such that, if it is validated, then the original program respect the automaton. In this chapter we first introduce some theoretical bases on the approach by annotation generation. Next we describe the two parts of the computing module: – the specification generator (from the automaton) – the constraints propagation for static simplification. 5.1 Theoretical Base of the Approach A program can be defined by a set of execution traces PATHProg and similarly, an automaton can be defined by a set of accepted traces PATHBüchi . Hence, to verify that a program is correct with respect to an automaton, we need to verify two aspects: Safety for each program trace t, there exists a Büchi path c, such that, for each i, the cross-condition Pi from the c is verified in the context of the state ti (figure 5.1). More formally, we have: ∀t ∈ PATHProg · ∃c ∈ PATHBüchi · ∀i ∈ 0..(size(t) − 1) · ti |= Pi (c) Liveness for each program trace t, there is an infinite number of states synchronized with a Büchi acceptance state. We propose to restrict these constraints to the weaker one : there is no deadlock (always a crossable transition from a non-acceptance state) and no live-lock (always a finite number of states between 2 acceptance states). Note: At this time the liveness aspect is not included in the tool. t0 t1 t2 ti−1 ti ti+1 q0 q1 q2 qi−1 qi qi+1 P0 P1 P2 Pi−1 Pi Pi+1 Figure 5.1: Synchronization of Paths from automata and from Program 5.1.1 Safety In order to encode this approach in an approach by annotations and to consider all program traces, our solution is to use a synchronization function. Such a function associates the set of states synchronized with the nth state from an execution trace. It is the sufficient to prove that at least one state is synchronized with each state of the execution to establish the safety of the property. 21 5.2. ADDING FROM THE THEORY Definition 1 (Synchronization function) Let A = ⟨Q, q0 , R⟩ ∈ BUCHI and σ ∈ PATHProg . The synchronization function Sync ∈ BUCHI × PATH × N → 2Q is defined by: – Sync(A, σ, 0) = {q0 } – For each i > 0: ∃⟨q, P, q ′ ⟩ ∈ R · ∧ Sync(A, σ, i) = q′ σi−1 |= P ∧ q ∈ SyncA, σ, i − 1) Definition 2 (Acceptance condition) (CSync ) ∀i ∈ 0..(len(σ) − 1) · Sync(A, σ, i) ̸= ∅ This verification is encoded into annotations by generating the following assertions: Declaration Let {q0 , . . . , qn } be a set of boolean variables associated to the states. qi is true if the system is synchronized with the state i. Initially, only q0 is true. Transitions A set of ghost instructions has to be generated just before each call and return statement. These instructions have to update the set of states synchronized with the current state. Synchronization The synchronization condition can be expressed with an invariant verifying that at least one state is always synchronized. 5.1.2 Liveness This part is not developed at this time, but the method consists in verifying a global variant between each couple of acceptance states and also the inclusion of the set of reachable states in the set of accepting states. 5.2 Adding from the Theory The previous section described a sufficient framework. However, in order to verify the correction with theorem provers, we need to use more efficient modeling and to add some hypothesis in order to link the models from C program and the automaton. 5.2.1 Automata Modeling In order to link models from the program and the property, we describe the automaton as constants in the generated C file. This axiomatization is combined with a set of invariants that give some properties of the automaton. For instance, the non-reachability of a state s can be deduced from the absence of transitions from an active state to s such that its cross-condition is true. This cross-condition is then expressed in terms of program information. This is the link program-automata. 5.2.2 Memorization of last Transitions In order to memorize the last synchronization link, we keep the set of last crossed transitions in addition with the set of old active states. 5.2.3 Use of Specifications instead of Invariant Finally, the synchronization condition is not implemented as an invariant, but as a pre- and post-condition on each operation. This choice is more flexible if we can statically decide that some states cannot be synchronized with some operation. In the following section, our objective is to describe how to automate this simplification by using abstract interpretation. 22 5.3. ABSTRACT INTERPRETATION 5.3 Abstract Interpretation Current Implementation : behavioral Property as Widening Operator In this section we describe our method to generate the specification of each operation. In a first part, we deduce an over-approximation of specifications by using automata, and next we propagate the generated constraints in order to converge to a fixpoint of specifications. 5.3.1 Generation of Abstract Specifications Initially, each operation’s specification states that each state and transition can be active before and after an operation. We then fix a first constraint: the main operation starts in the initial state. Next, we verify, for each operation, if its call or its return is always forbidden in a particular transition’s cross-condition. If any, the associated transition is removed from the operation’s specification. This process is done once on each operation. Finally, this computed constraint has to be propagated. 5.3.2 Static Simplification Starting from specified operations, each of them is analyzed by forward and backward abstract interpre- tation. The abstraction consists in abstracting all expressions. We only consider control statements and call and return statements. The post-condition is defined by intersecting its old value with the reachable post-condition computed by forward propagation. Similarly, the pre-condition is defined by intersecting its old value with the reachable pre-condition computed by backward propagation. If a loop is reached during this process, we compute its loop invariant in terms of automata from its computed pre- and post-conditions. During each pass of the program the list of use-cases of each operation is kept. Hence, if we observe that an operation is still called from a strict subset of its authorized input states, then we restrict its specification. Finally, a fixpoint is computed in order to minimize the specifications. Note that during this process, the post-conditions are described as behaviors. Indeed, this approach allow to give a particular post-condition for each possible pre-condition. Hence, the caller, which cannot observe the control-flow inside a called operation, has more precise information about current active states, since it knows each previous active states. 5.4 Plugin Architecture Frama-C Annotations Annotated C C Program pre-processor calculus program WP plugin Eva plugin Büchi Property automaton Provers Ya automaton Figure 5.2: Plug-in Structure 23 5.5. RECENT UPDATES The plug-in is composed of three parts: 1. a front-end (translator); 2. a computing module for specification of operations; 3. a back-end (C generator, including annotations). 5.5 Recent updates 5.5.1 Frama-C Iron – Remove obsolete support for LTL and Promela input language 5.5.2 Frama-C Vanadium – Documentation for options -aorai-no-generate-annotations and -aorai-smoke-tests – Documentation for option -aorai-instrumentation-history and built-in Frama_C_show_aorai_state – Aoraï does not generate a C file by default anymore, relying on kernel options -print and -ocode for that, like all plug-ins. Remove corresponding ad’hoc options. – update syntax for YA sequence to avoid ambiguities with + and * repetition operators 5.5.3 Frama-C Titanium – Various bug fixes – Introduction of YA variables 5.5.4 Frama-C Aluminium – Generated functions now have a body in addition to a specification 5.5.5 Frama-C Nitrogen – New translation mechanism for the automaton – Extended Ya guards 5.5.6 Frama-C Boron – A function that is used in a C program, but that is not defined is stubbed by Frama-C and ignored in Aorai. – For each function and each loop, if no state can be enabled before or after it (not reachable), then a warning is displayed. It is usually either a dead code, or a code violating the specification. – In the YA and Promela formats, it is now possible to speak about call parameters and returned value. f().a denotes the call parameter a of f and f().return denotes the returned value of f. – In the annotated C file generated, array of states are indexed by the name of the state (defined as an enum structure) 5.5.7 Frama-C Beryllium – YA format for properties 24 Conclusion 6 This manual is not always up-to-date and only gives some hints on the Aoraï plug-in. If you want more information, please send me a mail at: nicolas.stouls@insa-lyon.fr or visit the website: https://www.frama-c.com/fc-plugins/aorai.html 25