Test-Guided Proofs for C Programs on LLVM

ion is refined. In 2005 Godefroid and Klarlund [12] predicted that testing and abstraction based verification methods could be combined in useful ways. Yorsh, Ball and Sagiv [39] propose one such approach, which aims to alleviate the cost of constructing the abstraction by abstracting from a set of concrete test executions. In their method a theorem prover is used to check that the concrete tests cover all the reachable abstract states instead of directly computing the abstract transition relation. This can in the best case reduce the number of required theorem prover calls when compared to previous methods. Kroening, Groce and Clarke [27] describe the CRunner tool, which also combines information from concrete executions with counterexample guided abstraction refinement. Their approach combines concrete executions with partial SAT-based simulation of the program to make detecting spurious counterexamples more efficient. Combining DSE-based testing with abstraction refinement is attractive because the two approaches can efficiently handle different types of programs [17]. Abstraction refinement works well for programs that require tracking a relatively small number of predicates to prove a property. Testing, on the other hand, can quickly find feasible paths through code which could otherwise require refining the abstraction many times to discover. The SYNERGY algorithm [17] implemented in Microsoft’s YOGI tool [33] combines abstraction refinement with DSE in such a way that it retains advantages from both. It simultaneously constructs a concrete execution tree of the program and a partition of the program into abstract regions. From a testing point of view, SYNERGY works similarly to DSE with the addition that the branches to expand are selected along error traces found in the abstraction. This ensures that only the parts of the program that may have an error are explored by testing. From an abstraction refinement point of view, the test generation of SYNERGY is how spurious counterexamples are detected. If the counterexample is spurious, then at some point an SMT solver call to generate a test further along the error trace will fail. At this point the abstraction is refined to remove the error trace. The DASH algorithm [4] (also implemented in the YOGI tool) presents several improvements over SYNERGY. The refinement method in DASH for maintaining the abstraction avoids extra solver calls: while SYNERGY uses a solver call to refine the abstraction, DASH uses information from the failed test generation attempt to do the refinement. DASH also presents a technique for efficiently handling programs with pointers by using concrete pointer aliasing information gathered from tests. Finally, while the SYNERGY algorithm only supports single-procedure programs, in DASH programs with multiple procedures are supported via a recursive call to the algorithm for each procedure call. In this work we present an implementation of the DASH algorithm for C programs. The tool, called LCT-D, is a modification to the DSE tool LCT [21–23]. We have implemented our program transformations with the LLVM compiler framework [29]. The main contributions of this work are as follows: 1. We present LCT-D, an open source tool implementing the DASH algorithm for verifying C programs compiled to the LLVM intermediate representation. CHAPTER