4
4
\author Martin Brain, Peter Schrammel, Johannes Kloos
5
5
6
6
The purpose of this section is to explain several key concepts used throughout
7
- CBMC on a high level, ignoring the details of the actual implementation.
7
+ the CPROVER framework on a high level, ignoring the details of the actual implementation.
8
8
In particular, we will discuss different ways to represent programs in memory,
9
9
three important analysis methods and some commonly used terms.
10
10
@@ -21,16 +21,14 @@ representation, and it is easy to go from representations that are
21
21
close to the source code to representations that focus on specific
22
22
semantic aspects of the program.
23
23
24
- The representations that CBMC uses mirror commonly-used representations
24
+ The representations that the CPROVER framework uses mirror those
25
25
used in modern compilers such as LLVM and gcc. I will point out those
26
- places where CBMC does things differently, attempting to give rationales
27
- wherever possible.
26
+ places where the CPROVER framework does things differently, attempting to give
27
+ rationales wherever possible.
28
28
29
- #More in-depth information can be found in ...
30
- #TODO: Find a good online compiler construction course and reference
31
- #here.
32
- # TODO: There doesn't seem to be a good online course that covers
33
- # all of these topics in one go. Maybe buy the Dragon Book?
29
+ One in-depth resource for most of this section is the classic
30
+ (compiler construction text book)[ https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools ]
31
+ by Aho, Lam, Sethi and Ullman.
34
32
35
33
To illustrate the different concepts, we will consider a small example
36
34
program. While the program is in C, the general ideas apply to other
@@ -76,10 +74,12 @@ representation.
76
74
77
75
The first step in representing a program in memory is to parse the
78
76
program, at the same time checking for syntax errors, and store the
79
- result of parsing the program in memory.
77
+ parsing result in memory.
80
78
81
79
The key data structure that stores the result of this step is known as an
82
- **Abstract Syntax Tree**, or **AST** for short. ASTs are still relatively
80
+ **Abstract Syntax Tree**, or **AST** for short
81
+ (cf. [Wikipedia](https://en.wikipedia.org/wiki/Abstract_syntax_tree)).
82
+ ASTs are still relatively
83
83
close to the source code, and represent the structure of the source code
84
84
while abstracting away from irrelevant details, e.g., dropping
85
85
parentheses, semicolons and braces as long as those are only used for
@@ -93,7 +93,7 @@ declarations of `atoi` and `printf`, and the function definitions of
93
93
`atoi`. This gives rise to a subtree modeling that we have a function
94
94
`atoi` with return type `int` and unnamed argument of type `const char *`.
95
95
We can represent this using a tree that has, for instance, the
96
- following structure (this is a simplified version of the tree that CBMC
96
+ following structure (this is a simplified version of the tree that the CPROVER framework
97
97
uses internally):
98
98
99
99
\dot "AST for the atoi declaration"
@@ -352,11 +352,15 @@ In general, for analyses based around Abstract Interpretation (see below), it
352
352
is usually preferable to use a CFG representation, while other analyses,
353
353
such as variable scope detection, may be easier to perform on ASTs.
354
354
355
- The general idea is to break the program into **basic blocks** - a basic
356
- block is a sequence of statements that is always executed linearly from
357
- beginning to end. The basic blocks of them program are then used as the
358
- nodes of a graph. The edges of the graph describe how the program
359
- execution may move from one basic block to the next.
355
+ The general idea is to present the program as a graph. The nodes of the graph
356
+ are instructions or sequences of instructions. In general, the nodes are
357
+ **basic blocks**: A basic block is a sequence of statements that is always
358
+ executed linearly from beginning to end. The edges of the graph describe how
359
+ the program execution may move from one basic block to the next.
360
+ Note that single statements are always basic blocks; this is the representation
361
+ used inside the CPROVER framework. In the examples below, we try to use maximal basic blocks
362
+ (i.e., basic blocks that are as large as possible); this can be advantageous
363
+ for some analyses.
360
364
361
365
Let us consider the factorial function as an example. As a reminder,
362
366
here is the code:
@@ -506,11 +510,11 @@ digraph ast {
506
510
}
507
511
\enddot
508
512
509
- In CBMC , we provide a precise implemenation of &Phi ; , using explicitly tracked
513
+ In the CPROVER framework , we provide a precise implemenation of &Phi ; , using explicitly tracked
510
514
information about which branches were taken by the program.
511
515
There are also some differences in how loops are
512
- handled (finite unrolling in CBMC , versus a &Phi ; -based approach in
513
- compilers); the CBMC approach will be discussed in a later chapter.
516
+ handled (finite unrolling in CPROVER , versus a &Phi ; -based approach in
517
+ compilers); this approach will be discussed in a later chapter.
514
518
515
519
For the time being, let us come back to ` factorial ` . We can now give
516
520
an SSA using &Phi ; functions:
@@ -537,60 +541,154 @@ The SSA is an extremely helpful representation when one
537
541
wishes to perform model checking on the program (see next section),
538
542
since it is much easier to extract the logic formulas used in this
539
543
technique from an SSA compared to a CFG (or, even worse, an AST). That
540
- being said, CBMC takes a different route, opting to convert to
544
+ being said, the CPROVER framework takes a different route, opting to convert to
541
545
intermediate representation known as GOTO programs instead.
542
546
543
- \subsection Trace_section Trace
544
-
545
- TODO: I am not quite sure what is supposed to go here.
546
-
547
- From GitHub: "I think this was intended to describe how a (counterexample) trace is represented."
548
-
549
547
\section analysis_techniques_section Analysis techniques
550
548
551
549
\subsection BMC_section Bounded model checking
552
550
553
- To be documented (can copy from the CBMC manual).
551
+ One of the most important analysis techniques by the CPROVER framework,
552
+ implemented using the CBMC (and JBMC) tools, is ** bounded model checking** ,
553
+ a specific instance of a method known as
554
+ [ Model Checking] ( https://en.wikipedia.org/wiki/Model_checking ) .
555
+
556
+ The basic question that model checking tries to answer is: Given some system
557
+ (in our case, a program) and some property, can we find an execution of the
558
+ system such that it reaches a state where the property holds?
559
+ If yes, we would like to know how the program reaches this state - at the very
560
+ least, we want to see what inputs are required, but in general, we would prefer
561
+ having a ** trace** , which shows what statements are executed and in which order.
562
+
563
+ As it turns out, model checking for program is, in general, a hard problem.
564
+ Part of the reason for this is that many model checking algorithms strive for
565
+ a form of ''completeness'' where they either find a trace or return a proof
566
+ that such a trace cannot possibly exist.
567
+
568
+ Since we are interested in generating test cases, we prefer a different
569
+ approach: It may be that a certain target state is reachable only after
570
+ a very long execution, or not at all, but this information does not help
571
+ us in constructing test cases. For this reason, we introduce an
572
+ ** execution bound** that describes how deep we go when analyzing a program.
573
+
574
+ Model checking techniques using such execution bounds are known as
575
+ ** bounded model checking** ; they will return either a trace, or a statement
576
+ that says ''the target state could not be reached in n steps'', for a
577
+ given bound n. Thus, for a given bound, we always get an
578
+ ** underapproximation** of all states that can be reached: We
579
+ can certainly find those reachable within the given bound, but we may
580
+ miss states that can be reached only with more steps. Conversely, we will
581
+ never claim that a state is not reachable within a certain bound if there is,
582
+ in fact, a way of reaching this state.
583
+
584
+ The bounded model checking techniques used by the CPROVER framework are
585
+ based on * symbolic model checking* , a family of model checking techniques that
586
+ work on sets of program states and use advanced tools such as SAT solvers (more
587
+ on that below) to calculate the set of reachable states. The key step here
588
+ is to encode both the program and the set of states using an appropriate logic,
589
+ mostly * propositional logic* and (fragments of) * first-order logic* .
590
+
591
+ In the following, we will quickly discuss propositional logic, in combination
592
+ with SAT solving, and show how to build a simple bounded model checker for
593
+ a finite-state program. Actual bounded model checking for software requires
594
+ a number of additional steps and concepts, which will be introduced as required
595
+ later on.
596
+
597
+ \subsubsection propositional_logic_subsubsection Propositional logic and SAT solving
598
+
599
+ Many of the concepts in this section can be found in more detail in the
600
+ Wikipedia article on [ Propositional logic] ( https://en.wikipedia.org/wiki/Propositional_calculus ) .
601
+
602
+ Let us start by looking at ** propositional formulas** . A propositional formula
603
+ consists of ** propositional variables** , say * x* , * y* and * z* , that can
604
+ take the Boolean values ** true** and ** false** , connected together with
605
+ logical operators (often called * junctors* ), namely * and* , * or* and * not* .
606
+ Sometimes, one introduces additional junctors, such as * xor* or * implies* ,
607
+ but these can be defined in terms of the three basic junctors just described.
608
+
609
+ Examples of propositional formulas would be ''* x* and * y* '' or
610
+ ''not * x* or * y* or * z* ''. We can ** evaluate** formulas by setting each
611
+ variable to a Boolean value and reducing using the follows rules:
612
+ - * x* and ** false** = ** false** and * x* = ** false** or ** false** = not ** true** = ** false**
613
+ - * x* or ** true** = ** true** or * x* = ** true** and ** true** = not ** false** = ** true**
614
+
615
+ An important related question is: Given a propositional formula,
616
+ is there a variable assignment that makes it evaluate to true?
617
+ This is known as the [ ** SAT problem** ] ( https://en.wikipedia.org/wiki/Boolean_satisfiability_problem ) .
618
+ The most important things to know about SAT are:
619
+ 1 . It forms the basis for bounded model checking algorithms;
620
+ 2 . It is a very hard problem to solve in general: It is NP-complete,
621
+ meaning that it is easy to check a solution, but (as far as we know) hard
622
+ to find one;
623
+ 3 . There has been impressive research in
624
+ [ ** SAT solvers** ] ( https://en.wikipedia.org/wiki/Boolean_satisfiability_problem#Algorithms_for_solving_SAT )
625
+ that work well in practice for the kinds of formulas that we encounter in
626
+ model checking. A commonly-used SAT solver is [ minisat] ( http://minisat.se ) .
627
+ 4 . SAT solvers use a specific input format for propositional formulas, known
628
+ as the [ ** conjunctive normal form** ] ( https://en.wikipedia.org/wiki/Conjunctive_normal_form ) .
629
+ For details, see the linked Wikipedia page;
630
+ roughly, a conjunctive normal form formula is a propositional
631
+ formula with a specific shape: At the lowest level are
632
+ * atoms* , which are propositional variables ''* x* '' and
633
+ negated propositional variables ''not * x* '';
634
+ the next layer above are * clauses* , which are sequences
635
+ of atoms connected with ''or'', e.g. ''not * x* or * y* or * z* ''.
636
+ The top layer consists sequences of clauses,
637
+ connected with ''and''.
638
+
639
+ As an example in how to use a SAT solver, consider the following
640
+ formula (in conjunctive normal form):
641
+
642
+ ''(* x* or * y* ) and (* x* or not * y* ) and * x* and * y* ''
643
+
644
+ We can represent this formula in the minisat input format as:
645
+
646
+ ```
647
+ p cnf 2 3
648
+ 1 2 0
649
+ 1 -2 0
650
+ 1 0
651
+ 2 0
652
+ ```
653
+ Compare the [ Minisat user guide] ( https://www.dwheeler.com/essays/minisat-user-guide.html ) .
654
+ Try to run minisat on this example. What would you expect, and what result do you get?
655
+
656
+ Next, try running minisat on the following formula:
657
+ ''(* x* or * y* ) and (* x* or not * y* ) and (not * x* ) and * y* ''
658
+ What changed? Why?
554
659
555
- TODO: There seems to be no sufficiently comprehensive section in the
556
- CBMC manual. Find some other sources to collect information from.
660
+ \subsubsection bmc_subsubsection How bounded model checking works
557
661
558
- - Idea of model checking: Given some property, check if we can reach
559
- it; if yes, under what conditions?
560
- - Bounded model-checking: Cut off search at some point; either prove
561
- that property must occur before, or accept underapproximation.
562
662
- How does it work? Encoding of state as propositions/first-order
563
663
propositions, step function and composition of step functions.
564
664
- Solve question: Is there a model for this formula?
565
-
566
- \subsection SAT_section SAT and SMT
567
-
568
- - Goal: Solve model-finding problem.
569
- - Answers: Here is a model, no model possible
570
- - Complexity issues
571
- - SAT and SMT as extremely advanced black boxes that solve
572
- a good part of problems efficiently. Point out MiniSat,
573
- Z3 as good examples that we use.
574
- - Pointer to literature: CDCL, Lazy/Eager instantiation
575
-
576
- From our wiki:
577
- In a Boolean formula, variables can only be assigned values from the set
578
- {true, false}, and they are connected using and, or and not operators.
579
- The Boolean satisfiability problem is to find a valuation of the variables
580
- of a given formula so that the formula evaluates to true, or to prove that
581
- there is no such valuation. SAT is an NP-complete problem, meaning
582
- a given solution can be always verified in an amount of time that is polynomial
583
- in terms of the size of the input formula, and
584
- there is no known algorithm that computes a solution to any given formula
585
- in polynomial time (and the existence of such an algorithm would prove P = NP).
665
+ - Traces
666
+
667
+ \subsubsection smt_etc_subsubsection Where to go from here
668
+
669
+ The above section gives only a superficial overview on how SAT solving and
670
+ bounded model checking work. Inside the CPROVER framework, we use a
671
+ significantly more advanced engine, with numerous optimizations to the
672
+ basic algorithms presented above. One features that stands out is that
673
+ we do not reduce everything to propositional logic, but instead use a more
674
+ powerful logic, namely quantifier-free first-order logic. The main
675
+ difference is that instead of propositional variables, we allow expressions
676
+ that return Boolean values, such as comparisons between numbers or string
677
+ matching expressions. This gives us a richer logic to express properties.
678
+ Of course, a simple SAT solver cannot deal with such formulas, which is why we
679
+ go to [ * SMT solvers* ] ( https://en.wikipedia.org/wiki/Satisfiability_modulo_theories )
680
+ instead - these solvers can deal with specific classes
681
+ of first-order formulas (like the ones we produce).
682
+ One well-known SMT solver is [ Z3] ( https://github.com/Z3Prover/z3 ) .
586
683
587
684
\subsection static_analysis_section Static analysis
588
685
589
686
While BMC analyses the program by transforming everything to logic
590
687
formulas and, essentially, running the program on sets of concrete
591
688
states, another approach to learn about a program is based on the idea
592
689
of interpreting an abstract version of the program. This is known
593
- as ** abstract interpretation** . Abstract interpretation is one of the
690
+ as [ ** abstract interpretation** ] ( https://en.wikipedia.org/wiki/Abstract_interpretation ) .
691
+ Abstract interpretation is one of the
594
692
main methods in the area of ** static analysis** .
595
693
596
694
The key idea is that instead of looking at concrete program states
@@ -600,8 +698,17 @@ sufficiently-precise abstraction (e.g., ''variable x is odd'', or
600
698
using such abstract values. Coming back to our running example, we wish
601
699
to prove that the factorial function never returns 0.
602
700
701
+ An abstract interpretation is made up from four ingredients:
702
+ 1 . An ** abstract domain** , which represents the analysis results.
703
+ 2 . A family of ** transformers** , which describe how basic programming
704
+ languge constructs modify the state.
705
+ 3 . A way to map from pairs of program locations (e.g., positions in the
706
+ program code) and variable names to values in the abstract domain.
707
+ 4 . An algorithm to compute a ''fixed point'', computing a map as described
708
+ in the previous step that describes the behavior of the program.
709
+
603
710
The first ingredient we need for abstract interpretation is the
604
- so-called ** abstract domain** . An abstract domain is a set $D$ (or, if you
711
+ ** abstract domain** . An abstract domain is a set $D$ (or, if you
605
712
prefer, a data type) with the following properties:
606
713
- There is a function merge that takes two elements of $D$ and returns
607
714
an element of $D$. This function is associative (merge(x, merge(y,z))
@@ -622,14 +729,14 @@ For our example, we use the following domain:
622
729
It is easy but tedious to check that all conditions hold.
623
730
624
731
The domain allows us to express what we know about a given variable or
625
- value; in our example, whether it is zero or not.
732
+ value at a given program location ; in our example, whether it is zero or not.
626
733
The way we use the abstract domain is for each program point, we have a
627
734
map from visible variables to elements of the abstract domain,
628
735
describing what we know about the values of the variables at this point.
629
736
630
737
For instance, consider the ` factorial ` example again. After running the
631
738
first basic block, we know that ` fac ` and ` i ` both contain 1, so we have
632
- a map to maps both ` fac ` and ` i ` to "not 0".
739
+ a map that associates both ` fac ` and ` i ` to "not 0".
633
740
634
741
The second ingredient we need are the ** abstract state transformers** .
635
742
An abstract state transformer describes how a specific expression or
@@ -666,7 +773,13 @@ underlying program instructions. There is a formal description of this
666
773
property, using * Galois connections* ; for the details, it is best to
667
774
look at the literature.
668
775
669
- At this point, we have all the ingredients we need to set up an abstract
776
+ The third ingredient is straightforward: We use a simple map
777
+ from program locations and variable names to values in the abstract
778
+ domain. In more complex analyses, more involved forms of maps may be
779
+ used (e.g., to handle arbitrary procedure calls, or to account for the
780
+ heap).
781
+
782
+ At this point, we have almost all the ingredients we need to set up an abstract
670
783
interpretation. To actually analyze a function, we take its CFG and
671
784
perform a * fixpoint algorithm* .
672
785
@@ -764,7 +877,7 @@ topics can be found in the literature as well.
764
877
765
878
\section Glossary_section Glossary
766
879
767
- # Instrument
880
+ \subsectino instrument_subsection Instrument
768
881
769
882
To instrument a piece of code means to modify it by (usually) inserting new
770
883
fragments of code that, when executed, tell us something useful about the code
@@ -811,14 +924,31 @@ makes it easier for a given analysis to do its job, regardless of whether that
811
924
is achieved by executing the instrumented code or by just analyzing it in some
812
925
other way.
813
926
814
- # Flattening and Lowering
815
-
816
- TODO: Explain that this means going from a more high-level
817
- representation to a more low-level representation, with the goal of
818
- enabling a different set of transformations, analyses or optimizations.
819
-
820
- # Verification Condition
821
-
822
- TODO: Sketch how a trivial program maps to VCs
927
+ \subsection flattening_lowering_subsection Flattening and Lowering
928
+
929
+ As we have seen above, we often operate on many different representations
930
+ of programs, such as ASTs, control flow graphs, SSA programs, logical formulas
931
+ in BMC and so on. Each of these forms is good for certain kinds of analyses,
932
+ transformations or optimizations.
933
+
934
+ One important kind of step in dealing with program representations is
935
+ going from one representation to the other. Often, such steps are going
936
+ from a more ''high-level'' representation (closer to the source code)
937
+ to a more ''low-level'' representation. Such transformation steps are
938
+ known as ** flattening** or ** lowering** steps, and tend to be more-or-less
939
+ irreversible.
940
+
941
+ \subsection verification_condition_subsection Verification Condition
942
+
943
+ In the CPROVER framework, the term ** verification condition** is used
944
+ in a somewhat non-standard way. Let a program and a set of assertions
945
+ be given. We transform the program into an SSA and turn it into a
946
+ logical formula, as described above. Note that in this case, the
947
+ formula will also contain information about what the program does
948
+ after the assertion is reached: This part of the formula, is, in fact,
949
+ irrelevant for deciding whether the program can satisfy the assertion or
950
+ not. The * verification condition* is the part of the formula that only
951
+ covers the program execution until the line that checks the assertion
952
+ has been executed, with everything that comes after it removed.
823
953
824
954
0 commit comments