Paper Ok
Paper Ok
Antoine Miné?
1 Introduction
The aim of the article is to give a short overview of the theory underlying
tools such as Astrée and AstréeA. Section 2 focuses on sequential programs and
presents the classic construction of an effective analyzer by abstraction of the
most concrete semantics of a program: its execution traces. Section 3 considers
multithreaded programs and explains how, borrowing ideas from rely/guarantee
proof methods [14], an efficient, thread-modular analysis can be constructed.
Section 4 briefly presents the Astrée and AstréeA analyzers, and Sec. 5 concludes.
The modest contribution of this article is the formulation, in Sec. 3.4, of
Jones’ rely/guarantee method [14] in abstract interpretation form, as an ab-
straction of the execution traces of multithreaded programs by decomposition
into intra-thread invariants and inter-thread interferences. This extends previ-
ous work in the 80’s [7] that formalized earlier Owicki–Gries–Lamport methods
[17,15] as abstract interpretation. It also extends our recent previous work [16],
that only considered coarse abstract interferences, by exhibiting an intermediate
layer of abstraction from which it can be recovered.
i i i
... ...
Fig. 2. Semantics of the program in Fig. 1 at various levels of abstractions: (a) traces,
and (b) reachable states and (c) intervals at program point (3).
such that σ0 ∈ I and ∀i, (σi , σi+1 ) ∈ τ . The semantics we want to observe, which
is called a collecting semantics, is thus defined in our case as the set T ∈ P(Σ ∗ )
of traces spawned by the program. It can be expressed [6] as the least fixpoint
of a continuous operator in the complete lattice of sets of traces:
T = lfp F where
def (1)
F (X) = I ∪ { σ0 → · · · → σi+1 | σ0 → · · · → σi ∈ X ∧ σi → σi+1 }
i.e., T is the smallest set containing traces reduced to an initial state and closed
under the extension of traces by an additional execution step. Note that we
are observing partial execution traces; informally, we allow executions to be
interrupted prematurely; formally, T is closed by prefix (it also includes the finite
prefixes of non-terminating executions). This is sufficient if we are interested in
safety properties, i.e., properties of the form “no bad state is ever encountered,”
which is the case here — more general trace semantics, able to also reason about
liveness properties, are discussed for instance in [4]. A classic result
S [6] is that
T can be restated as the limit of an iteration sequence: T = n∈N F n (∅). It
becomes clear then that computing T is equivalent to testing the program on
all possible executions (albeit with an unusual exhaustive breadth-first strategy)
and that it is not adapted to the effective and efficient verification of programs:
when the program has unbounded or infinite executions, T is infinite.
Example 2. The simple program in Fig. 1.a increments i in a loop, until it
reaches a user-specified value n. Figure 2.a presents its trace semantics starting
in the state set I = { (1, [m 7→ 0, i 7→ 0]) }. The program has infinite executions
(e.g., if i is never incremented).
There is a deep connection [5] between the state semantics and the program logic
of Floyd–Hoare [13] used to prove partial correctness. If we interpret each logic
assertion A` at program point ` as the set of memory states J A` K ⊆ M satisfying
def
it, and note M = { (`, m) | m ∈ J A` K }, then (A` )`∈L is a valid (i.e., inductive)
invariant assertion if and only if G(M ) ⊆ M . Moreover, the best inductive in-
variant assertion stems from lfp G, i.e., it is S. While, in proof methods, the
inductive invariants must be devised by an oracle (the user), abstract interpre-
tation opens the way to automatic invariant inference by providing a constructive
view on invariants (through iteration) and allowing further abstractions.
Example 3. The optimal invariant assertions of the program in Fig. 1.a appear
in Fig. 1.b, and Fig. 2.b presents graphically its state abstraction at point (3).
In order gain in efficiency on bounded state spaces and handle unbounded ones,
we need to abstract further. As many errors (such as overflows and divisions by
zero) are caused by invalid arguments of operators, a natural idea is to observe
1
I.e., ∀X ∈ P(Σ ∗ ), ∀Y ∈ P(Σ), αstate (X) ⊆ Y ⇐⇒ X ⊆ γ state (Y ).
only the set of values each variable can take at each program point. Instead of
def
concrete state sets in P(Σ) = P(L×(V → V)), we manipulate abstract states in
def
ΣC = (L × V) → P(V). The concrete and abstract domains are linked through
the following Galois connection (so-called Cartesian Galois connection):
def
αcart (X) = λ(`, V ) . { v | ∃(`, m) ∈ X, m(V ) = v }
def (3)
γ cart (X ] ) = { (`, m) | ∀V, m(V ) ∈ X ] (`, V ) }
Example 4. Figure 2.c presents the interval abstraction of the program in Fig. 1.a
at point (3). The relational information that i ≤ n when n ≥ 2 is lost.
Following the same method that derived the state semantics from the trace
one, we can derive an interval semantics from the state one: it is expressed as a
fixpoint lfp G] of an interval abstraction G] of G. While it is possible to define
an optimal G] as α ◦ G ◦ γ — where (α, γ) combines (αitv , γ itv ) and (αcart , γ cart )
— this is a complex process when G is large, and it must be performed for
each G, i.e., for each program. To construct a general analyzer, we use the
fact that F , and so, G = αstate ◦ F ◦ γ state , are built by combining operators
for atomic language constructs, as in Ex. 1. It is thus possible to derive G]
as a combination of a small fixed set of abstract operators. More precisely, we
only have to design abstraction versions of assignments, tests, and set union
∪. Generally, the combination of optimal operators is not optimal, and so, the
resulting G] is not optimal — e.g., (α ◦ G1 ◦ G2 ◦ γ)(X) ( ((α ◦ G1 ◦ γ) ◦ (α ◦
G2 ◦ γ))(X). Additionally, due to efficiency and practicality concerns, the base
abstract operators may be chosen non-optimal to begin with. Achieving a sound
analysis, i.e., γ(lfp G] ) ⊇ lfp G, only requires sound abstracts operators, i.e.,
∀X ] , (γ ◦ G] )(X ] ) ⊇ (G ◦ γ)(X ] ). The fact that G] 6= α ◦ G ◦ γ, and even the
existence of an α function, while sometimes desirable, is by no mean required —
for instance, the polyhedra domain [10] does not feature an abstraction function
α as some sets, such as discs, have no best polyhedral abstraction.
0 ]
Example 5. The interval abstraction J ` x := y + z ` K of a simple addition maps
the interval environment [x 7→ [lx ; hx ]; y 7→ [ly ; hy ]; z 7→ [lz ; hz ]] at point ` to
[x 7→ [ly + lz ; hy + hz ]; y 7→ [ly ; hy ]; z 7→ [lz ; hz ]] at point `0 , which is optimal.
0 00 ]
Example 6. The optimal abstraction J ` y := −z; ` x := y + z ` K would map x
0 ] 0 00 ]
to [0; 0]. However, the combination J ` y := −z ` K ◦ J ` x := y + z ` K maps x to
[lz − hz ; hz − lz ] instead, which is coarser when lz < hz .
When analyzing Fig. 1, the iterations with widening at (3) give the following
intervals for i: i]0 = [2; 2] and i]1 = [2; 2] O [2; 3] = [2; +∞], which is stable.
T = lfp F where
def 0 t i t 0 t ti−1 i t (6)
F (X) = I ∪ { σ0 → ··· → σi+1 | σ0 → · · · → σi ∈ X ∧ σi → σi+1 }
(a) (b)
Fig. 3. A simple multithreaded program (a), and its invariant assertions (b).
(a) (b)
Fig. 4. A program requiring flow-sensitivity (a), and its invariant assertions (b).
3.3 Rely/Guarantee
Proof methods for parallel programs, such as Owicki–Gries–Lamport [17,15] and
rely/guarantee
S [14] solve this issue by attaching invariantsQto thread control
points in t∈T Lt instead of combinations of thread points in t∈T Lt . The price
to pay is a refined notion of invariance: the properties attached to a thread t must
also be proved stable by the effect of the other threads t0 6= t. In rely/guarantee
methods, this effect is explicitly provided as extra annotations in t that are
assumed to hold (relied on) when checking the invariants for t and proved correct
(guaranteed) when checking the other threads t0 6= t. It then becomes possible
to check each thread in a modular way, i.e., without looking at the code of the
other threads, relying on the annotations instead.
Example 9. Figure 3 presents a program with two threads: the first one incre-
ments x up to y and the second one increments y up to 10. For conciseness, we
exemplify the rely/guarantee conditions at a single program point. Consider the
problem of proving that x < y holds at (3), just after the test. We need to prove
that the assertion is stable by the action of the second thread. It is sufficient to
rely on the fact that the second thread does not modify x and only increments
y. This property is, in turn, guaranteed by analyzing the second thread, relying
only on the fact that the first thread does not modify y.
to remove these variables and keep only t’s control information to get:
def
¯ πt (`,
αtctrl (S̄[t]) where αtctrl (X) = { (`, m) | ∃`, ¯ m) ∈ X ∧ `[t]
¯ = `} (8)
def 0t tn−1
Ā[t] = { (σi , σi+1 ) | ∃σ0 → · · · → σn ∈ T, ti = t } (9)
Our interference semantic S̄ is very concrete (the state semantics αstate (T ) can
be recovered fully from it), and too large to be computed directly. An effective
and efficient analyzer can be designed by applying abstractions independently
to local invariants and to interferences, while keeping the nested fixpoint form
of (12). Firstly, any memory abstraction, such as intervals (4) or polyhedra [10],
can be applied directly to each local invariant S̄[t]. As the number of variables
is often a critical parameter in the efficiency of abstract domains, a faster but
coarser analysis can be achieved by removing the auxiliary variables from S̄[t]
with αtctrl (8) prior to memory abstraction. Secondly, we abstract interferences
Ā[t]. For instance, it is possible to forget the control state of all threads using a
flow-insensitive abstraction αflow :
¯ `¯0 , ((`,
αflow (X) = { (m, m0 ) | ∃`,
def
¯ m), (`¯0 , m0 )) ∈ X }
Intuitively, this means that, when analyzing a thread t, we consider that any
instruction from any thread t0 6= t may be executed at any point of thread t.
Then, by noting that P((V → V)×(V → V)) ' P(({ 1, 2 }×V) → V), we can see
a set of pairs of maps on V as a set of maps on { 1, 2 } × V, with twice as many
variables; hence, we can apply any memory abstraction to αflow (X). Another
solution consists in abstracting the relation αflow (X) with its image, using the
following abstraction αimg , before applying a memory abstraction:
which is coarser but more efficient as it avoids doubling the number of variables.
By combining αflow , αimg , and αcart (3), we abstract thread interferences in
a non-relational and flow-insensitive way, as the set of values each thread can
store into each variable during the thread execution. Further abstractions, such
as intervals αitv (4), can be applied. This yields an efficient analysis: the abstract
interference is simply a global map from T × V to intervals, and we can build an
abstraction H̄ ] (X̄ ] )[t] of H̄(X̄)[t] by combining abstractions for each instruction
of thread t (as Ex. 5 but slight modified to take interferences into account).
0 ]
Example 10. The interval abstraction J ` x := y + 1` K in the environment [x 7→
[lx ; hx ]; y 7→ [ly ; hy ]] at ` and the global interference map [x 7→ [lx0 ; h0x ]; y 7→
[ly0 ; h0y ]] gives, at `0 , the interval x 7→ [min(lx0 , lx + min(ly , ly0 ) + 1); max(h0x , hx +
max(hy , h0y ) + 1)].
Example 11. We analyze Fig. 3 using intervals, by applying αtctrl , αcart , and αitv
to local invariants, and αflow , αimg , αcart , and αitv to interferences. We find that,
at (3), x ∈ [0; 9]. The information that x < y is lost. The second thread produces
the abstract interferences [x 7→ ∅, y 7→ [0; 10]], i.e., we infer that it cannot modify
x and can only put values from [0; 10] into y. We lose the information that y can
only increase.
Example 12. Consider the program in Fig. 4. When analyzing the first thread,
we note that, at (2), x can be 0 or -2, depending on whether the second thread
is at (6) or (7). Moreover, to prove that x stays in { − 2, 0 } at (2), it is necessary
to infer that the interference from the second thread can only increment x when
it is at (7), and decrement it when it is at (6). A flow-insensitive abstraction
using αtctrl and αflow would instead infer that x can be incremented arbitrarily
and decremented arbitrarily. It would not allow us to prove that x is bounded.
4 Applications
We now briefly describe two static analysis tools that were designed at the École
normale supérieure using abstract interpretation principles.
4.1 Astrée
Astrée is a static analyzer that checks for run-time errors in synchronous (hence
sequential) embedded critical C programs. It covers the full C language, in-
cluding pointers and floats, but excluding dynamic memory allocation and re-
cursivity (forbidden in the realm of critical software). It checks for arithmetic
overflows, and invalid arithmetic and memory operations. It is sound, and so,
never misses any existing error. Due to over-approximations, it can signal false
alarms. Although Astrée can analyze many kinds of codes, it is specialized to
control-command avionic software, on which it aims at efficiency and certifica-
tion, i.e., zero false alarm. It was able to prove quickly (2h to 53h) the absence of
run-time error in large (100 K to 1M lines) industrial avionic codes. This result
could be achieved using a specialization process: starting from a fast and coarse
analyzer based on intervals, we added manually more powerful abstract domains
as they were needed. Some were borrowed from the large library of existing
abstractions, and others were developed specifically for the avionic application
domain (such as an abstraction of digital filters [12]). Astrée is a mature tool:
it is industrialized and made commercially available by AbsInt [1]. More details
can be found in [2].
4.2 AstréeA
5 Conclusion
References
1. AbsInt, Angewandte Informatik. Astrée run-time error analyzer. http://www.
absint.com/astree.
2. J. Bertrane, P. Cousot, R. Cousot, J. Feret, L. Mauborgne, A. Miné, and X. Rival.
Static analysis and verification of aerospace software by abstract interpretation. In
AIAA Infotech@Aerospace, number 2010-3385 in AIAA, pages 1–38. AIAA (Amer-
ican Institute of Aeronautics and Astronautics), Apr. 2010.
3. F. Bourdoncle. Efficient chaotic iteration strategies with widenings. In Proc. of the
Int. Conf. on Formal Methods in Programming and their Applications (FMPA’93),
volume 735 of LNCS, pages 128–141. Springer, June 1993.
4. P. Cousot. Constructive design of a hierarchy of semantics of a transition system
by abstract interpretation. Theoretical Computer Science, 277(1–2):47–103, 2002.
5. P. Cousot and R. Cousot. Abstract interpretation: A unified lattice model for
static analysis of programs by construction or approximation of fixpoints. In Proc.
of the 4th ACM Symp. on Principles of Programming Languages (POPL’77), pages
238–252. ACM, Jan. 1977.
6. P. Cousot and R. Cousot. Constructive versions of Tarski’s fixed point theorems.
Pacific Journal of Mathematics, 81(1):43–57, 1979.
7. P. Cousot and R. Cousot. Invariance proof methods and analysis techniques for
parallel programs. In Automatic Program Construction Techniques, chapter 12,
pages 243–271. Macmillan, New York, NY, USA, 1984.
8. P. Cousot and R. Cousot. Compositional separate modular static analysis of pro-
grams by abstract interpretation. In Procs. of the 2d Int. Conf. on Advances in
Infrastructure for E-Business, E-Science and E-Education on the Internet (SS-
GRR’01). Scuola Superiore G. Reiss Romoli, Aug. 2001.
9. P. Cousot, R. Cousot, J. Feret, L. Mauborgne, A. Miné, D. Monniaux, and X.
Rival. Combination of abstractions in the Astrée static analyzer. In Proc. of the
11th Annual Asian Computing Science Conf. (ASIAN’06), volume 4435 of LNCS,
pages 272–300. Springer, Dec. 2006.
10. P. Cousot and N. Halbwachs. Automatic discovery of linear restraints among
variables of a program. In Conf. Rec. of the 5th ACM SIGPLAN/SIGACT Symp.
on Principles of Programming Languages (POPL’78), pages 84–97. ACM, 1978.
11. R. Cousot. Fondements des méthodes de preuve d’invariance et de fatalité de pro-
grammes parallèles. Thèse d’État ès sciences mathématiques, Institut National
Polytechnique de Lorraine, Nancy, France, 15 Nov. 1985.
12. J. Feret. Static analysis of digital filters. In Proc. of the 13th Europ. Symp. on
Programming (ESOP’04), volume 2986 of LNCS, pages 33–48. Springer, Mar. 2004.
13. C. A. R. Hoare. An axiomatic basis for computer programming. Commun. ACM,
12(10):576–580, Oct. 1969.
14. C. B. Jones. Development Methods for Computer Programs including a Notion of
Interference. Phd thesis, Oxford University, Jun. 1981.
15. L. Lamport. Proving the correctness of multiprocess programs. IEEE Trans. on
Software Engineering, 3(2):125–143, Mar. 1977.
16. A. Miné. Static analysis of run-time errors in embedded real-time parallel C pro-
grams. Logical Methods in Computer Science (LMCS), 8(26):1–63, Mar. 2012.
17. S. Owicki and D. Gries. An axiomatic proof technique for parallel programs I.
Acta Informatica, 6(4):319–340, Dec. 1976.
18. M. Sharir and A. Pnueli. Two approaches to interprocedural data flow analysis.
In Program Flow Analysis: Theory and Applications, pages 189–233. Prentice-Hall,
1981.
19. A. Venet. Nonuniform alias analysis of recursive data structures and arrays. In
Proc. of the 9th Int. Symp. on Static Analysis (SAS’02), volume 2477 of LNCS,
pages 36–51, 2002.
A Proofs
All the results from Sect. 2 are very classic, so, we do not prove them here (see
for instance [4]). Instead, we focus on the new results presented in Sect. 3: the
nested fixpoint characterization of the reachable states of a parallel program
using interferences.
note that each I¯n is continuous. Hence, we have: H̄( ¯ i X̄i ) = ¯ n∈N I¯n ( ¯ i X̄i ) =
S S S
¯
S ¯ I¯n (X̄i ) = S
S ¯ S¯ ¯ ¯ H̄(X̄i ), which proves the continuity of H̄.
S
n∈N i i n∈N In (X̄i ) = i
We can thus apply Keene’s fixpoint theorem to H̄ itself to get lfp H̄ in iterative
form: lfp H̄ = ¯ n∈N H̄ n (¯
S
∅).
¯
Finally, we prove that S̄ ⊆lfp H̄. To do this, we prove by recurrence on n that,
t0 tn−1
if σn is reachable after n steps, i.e., if there exists some trace σ0 → · · · → σn in
T , then, for any t, πt (σn ) ∈ (H̄ n+1 (¯∅))[t]. When n = 0, H̄(¯∅)[t] = lfp Gt (B̄(∅)) =
lfp Gt (∅) ⊇ Πt (I). As σ0 ∈ I, we indeed have πt (σ0 ) ∈ Πt (I). Assume that the
t0 tn
property is true at rank n and consider a trace σ0 → ··· → σn+1 in T . As T is
t0 tn−1
closed by prefix, σ0 → · · · → σn is also in T and we can apply the recurrence
tn+1
hypothesis to get πt (σn ) ∈ (H̄ n+1 (¯∅))[t]. Moreover, we have σn → σn+1 . We
consider first the case tn+1 = t. Then as (H̄ n+1 (¯∅))[t] is closed by reachabil-
ity from thread t, we also have πt (σn+1 ) ∈ (H̄ n+1 (¯∅))[t], and so, πt (σn+1 ) ∈
(H̄ n+2 (¯
∅))[t]. Consider now the case tn+1 6= t. Then, πt (σn ) ∈ (H̄ n+1 (¯∅))[t] im-
plies that (σn , σn+1 ) ∈ B̄(H̄ n+1 (¯∅))[t]. As a consequence, (H̄ n+2 (¯∅))[t] is closed
tn+1
under reachability through the transition σn → σn+1 , and so, πt (σn+1 ) ∈
(H̄ n+2 (¯
∅))[t]. We have proved that, forS any πt (σ) ∈ S̄[t], there exists some n such
that πt (σ) ∈ (H̄ n (¯
∅))[t]. As lfp H̄ = ¯ n∈N H̄ n (¯∅), we get that πt (σ) ∈ (lfp H̄)[t],
hence, S̄ ⊆¯ lfp H̄.