Keywords

figure ba
figure bb
figure bc

1 Introduction

CPAchecker  [35] is a framework for configurable software verification with a focus on the verification of C programs. It is based on the concept of configurable program analysis [26, 28, 29] and provides an extensive collection of verification algorithms and abstract domains. Throughout the past years, CPAchecker has been a top contender in the International Competition on Software Verification [11, 12, 13] and has helped identify over 240 bugs in Linux device drivers [45, 64, 84].

Fig. 1:
figure 1

Inputs and outputs of CPAchecker when it is used as a verifier, witness validator, or test-case generator

CPAchecker is open source and written in Java. Founded in 2007 at Simon Fraser University, it is now maintained by an active community (project statistics can be found on OpenHub.net). It puts a high priority on extensibility and flexible reuse of components for developers. The architecture and features of the framework are described in other articles [35, 48]. More information about the achievements, history, and license of CPAchecker are available in the extended version [7].

1.1 Use Cases of CPAchecker

There are three main use cases of CPAchecker, with their inputs and outputs summarized in Fig. 1: (1) As a verifier, CPAchecker takes as input a program and a specification, and returns a verdict, a verification report, and a verification witness. The verdict specifies whether the given program adheres to the specification, the verification report allows users to examine the verification result, and the witness contains a machine-readable justification for the returned verdict. (2) As a witness validator [5, 19], CPAchecker takes as input a program, a specification, and a witness, and returns a verdict that indicates whether the witness could be confirmed by CPAchecker. (3) As a test-case generator [32, 52, 75], CPAchecker takes as input a program and a test-coverage specification, and returns a set of test cases that cover the program according to the specification.

CPAchecker is also used for program transformation [31, 33, 34, 41, 42], to explore decompositions of verification problems [4, 27, 37], and to parallelize verification approaches [23, 37]. This tutorial focuses on using CPAchecker as a verifier. Information about CPAchecker as a witness validator and test-case generator is present in the extended version [7].

1.2 Configurable Program Analysis

CPAchecker uses configurable program analysis (CPA) [26, 28, 29] to compute a program’s reachable states. A CPA specifies an abstract domain and a precision used to explore a program’s reachable states. The abstract domain defines the representation of a program’s state, while the precision defines how precise the abstraction should be. Various CPAs have been implemented in CPAchecker, each tailored to handle specific program features and perform a dedicated analysis. CPAs can also be combined to achieve synergy. Furthermore, precisions can be adjusted dynamically [29], making an analysis coarse but efficient, or precise but resource-consuming. CPAchecker automatically adjusts the precisions via counterexample-guided abstraction refinement (CEGAR) [24, 43, 44, 53] or some carefully-designed procedures [15].

1.3 Documentation and Communication

The README and directory doc/ in the CPAchecker project provide useful information for users and developers. For an overview on the architecture, we recommend the tool paper [35] on CPAchecker and the publications regarding the CPA concept [26, 28, 29]. CPAchecker supports various verification algorithms and techniques. The most important techniques in CPAchecker are explained in separate publications, including data-flow and value analysis [15, 26, 43], SMT-based verification algorithms [22, 38, 39], block-abstraction memoization [23, 24, 25, 83], program transformations [31, 33, 34, 41, 42], cooperative verification [16, 20], witness certification and validation [5, 19], and test-case generation [32, 52, 75]. The configurations of CPAchecker that were submitted to competitions are described in the competition contribution papers of SV-COMP  [2, 3, 8, 55, 57, 66, 68, 69, 70, 74, 81, 82], Test-Comp  [30, 60, 61, 62], and RERS  [46, 47]. These publications give an indication of the breadth of analyses available in CPAchecker and its power and flexibility as a verification framework.

Questions, bug reports, and feature requests for CPAchecker are always welcome on its mailing list (https://groups.google.com/g/cpachecker-users) and the issue tracker (https://gitlab.com/sosy-lab/software/cpachecker/-/issues).

1.4 CPAchecker in Education

Due to the many algorithms and abstract domains, and the clean and extensible architecture, CPAchecker is an ideal tool for teaching of program-analysis techniques. The techniques can be explored in comparison and their effects observed. Visualizations of abstract states and error paths help understand the reasons for correctness or violation of the specification. We use CPAchecker in various courses on software engineering, software verification, software testing, and program semantics.

1.5 Outline

This tutorial starts in Sect. 2 with installation instructions and a first example of running CPAchecker. Section 3 explains the inputs and outputs of CPAchecker. Finally, Sect. 4 gives an overview on the most important analysis techniques that CPAchecker provides for software verification.

The extended version [7] includes further information on CPAchecker, provides an overview of all concrete example command lines together with references to the respective part of the tutorial, provides more information about the CPAchecker project, its development history, achievements, and licensing, provides some more detailed examples for the presented analysis techniques, and explains how to use CPAchecker for witness validation and test-case generation.

2 Getting Started with CPAchecker

In the following, we explain the installation and a few alternatives for executing CPAchecker on individual verification tasks.

For trying out CPAchecker and following this tutorial we provide a few example programs in a reproduction package [6]. We assume this package was downloaded and unpacked, and that the current working directory is its root directory (where directory examples/ is visible). The execution of each example in this tutorial should take less than 10 seconds.

2.1 Local Installation

Installation Requirements. Most features of CPAchecker require a 64-bit GNU/Linux machine, unless users build the required libraries themselves. A limited feature set is usable on other platforms. We recommend a current LTS version of Ubuntu; recent versions of other distributions can be expected to work as well.

Installation. For users on Debian or Ubuntu we provide a package repository at https://apt.sosy-lab.org. Please follow the instructions on that webpage to enable the repository. Afterwards, the latest version of CPAchecker can be installed with sudo apt install cpachecker.

For users without root access or on other distributions, we also provide CPAchecker as pre-built binary releases via Zenodo [49] and our download page. Please ensure that a Java Runtime Environment (JRE) is available (for CPAchecker  3.0, Java version 17 or newer is required). Unpack the archive for CPAchecker after the download. We recommend adding CPAchecker’s bin/ directory to the PATH environment variable. This way the examples provided in this tutorial work as is, without having to specify the full path to the cpachecker executable every time. If CPAchecker was installed via the package repository, changing the PATH variable is not necessary.

Execution. To try out CPAchecker, run the following command from the reproduction package’s [6] root directory:

figure a

This will verify that there is no assertion violation in program example-safe.c, and report that the program satisfies the specification. Further information is provided in Sect. 2.4.

2.2 Execution via Container

CPAchecker is available as an image in OCI format, for use with container runtimes like Podman and Docker. The identifiers of the images are sosylab/cpachecker (always the latest release) and sosylab/cpachecker:3.0 for version 3.0. The following command line executes CPAchecker  3.0 from a container (may require sudo, depending on the Docker installation):

figure b

Command-line argument -v "$(pwd)":/workdir makes the current working directory ($(pwd)) available in the started container at path /workdir. This is the default entrypoint of the CPAchecker images. Command-line argument -u $UID:$GID might be added after docker run to set the user and group ID of the container to the current user and group ID: output files produced by CPAchecker are then owned by the current user instead of root. Argument examples/example-safe.c is passed to CPAchecker and will be explained in Sect. 2.4. The command-line arguments and input files can be adjusted as usual.

2.3 Remote Execution via Website

We provide a web interface for CPAchecker at https://vcloud.sosy-lab.org/cpachecker/webclient/run/. The examples of this paper are available as Examples on the left of the page.

2.4 Example Verification Task

figure c

Program Description. We use the program in Fig. 2a. This program initializes variables n and x to two nondeterministic but concrete values of type unsigned int (modeled by calls to \( \texttt {\_\_VERIFIER\_nondet\_uint()} \)) and then initializes y to the difference between n and x. As long as x is larger than y, the while loop decrements x and increments y by one. If the sum of x and y does not equal n at the end of a loop iteration, \( \texttt {\_\_assert\_fail} \) at line 10 triggers a program error (arguments omitted for simplicity). The program is correct with respect to the specification that \( \texttt {\_\_assert\_fail} \) is unreachable, because the sum of x and y always equals n at the end of every loop iteration. A variant of this program is shown in Fig. 2b. The variant follows the same execution except at line 9. Here an error is triggered if x is smaller than y. This error is reachable by initializing n to 3 and x to 2 (among many other possibilities).

Fig. 2:
figure 2

Example C programs

Verification Run. To verify the example program in Fig. 2a with CPAchecker, execute the below command in a terminal (cf. example default on the web service):

figure d

This command line does not specify an explicit configuration. In this case CPAchecker uses the default configuration, which is the currently recommended configuration. Like most configurations shipped with CPAchecker, the default configuration uses the default specification, which specifies that no C assertion error \( \texttt {\_\_assert\_fail} \) and no label named ERROR should be reachable. The specifications, configurations, and the available analyses are described in more detail in Sect. 3.2, Sect. 3.3, and Sect. 4.

At the end of its execution, CPAchecker produces the following messages:

figure e

The verification result TRUE indicates that the error (line 10 in Fig. 2a) is not reachable. We can also change the input program to example-unsafe.c in the command line. In this case, the verification result is FALSE, meaning that CPAchecker finds an execution path that triggers the error. The meanings of verification results and how to navigate through the generated report is the topic of Sect. 3.4 and Sect. 3.5, respectively.

3 Input and Output Interface of CPAchecker

Figure 1 shows the inputs and outputs of CPAchecker. CPAchecker always takes a program, a specification, and a configuration as input. It always produces a verdict and a report. Depending on how the user intends to use it, either as a verifier, a witness validator, or a test-case generator, CPAchecker may also take a verification witness as input, or produce witnesses or test cases as output.

3.1 Input Program

CPAchecker supports a large subset of the GNU-C11 features. Normally, the verifier expects pre-processed input files. CPAchecker supports compiler directives (e.g., #include or #define) if the command-line argument --preprocess is given, in which case CPAchecker pre-processes the input C program. To guarantee a meaningful verification of programs that use external functions, including functions in the C standard library, the implementations of the functions have to be provided in the input programs. Otherwise, CPAchecker overapproximates their behavior, potentially leading to false alarms. Two exceptions are the function \( \texttt {pthread\_create} \) for creating a new thread and functions malloc, memset, etc., for manipulating memory, which are handled out-of-the-box by CPAchecker’s concurrency and memory analyses, respectively. To verify a software project that consists of multiple C files, all relevant files must be listed on the command-line. By default, CPAchecker starts the analysis from the function main. Another entry function can be specified with the command-line argument \( \texttt {- -entry{-}function} \,\texttt {{<}entry}\, \texttt {function{>}} \).

The semantics of a C program depends on the runtime platform, which consists of a machine architecture, a data model, and an operating system. CPAchecker assumes a single platform during verification. The command-line argument --32 (default) sets the platform to 32-bit x86 Linux (ILP32) and --64 sets the platform to 64-bit x86 Linux (LP64) [78].

3.2 Program Specification

Besides the input program, a specification is needed as input for CPAchecker. The specification defines what property of the program should be checked. CPAchecker supports an automaton-based specification language (similar to Blast  [17] and Slam  [9]) to define program specifications (documented in doc/SpecificationAutomata.md). CPAchecker ships with several common specifications in the directory config/specification/. A selection is listed in Table 1. CPAchecker also supports property files written in the specification language that was standardized by the International Competition on Software Verification (SV-COMP) [13].

Table 1: Provided specifications (files in config/specification/)

The command-line argument \( \texttt {- -spec <specification>} \) defines the specification to use. It accepts the path to a specification-automaton file, an SV-COMP property file, or the name of one of the specifications that ship with CPAchecker. For example, to verify a program against the provided specification Assertion with CPAchecker’s default analysis, we run (cf. example assert on the web service):

figure f

The square brackets in the above command indicate that argument --preprocess may be omitted if the program does not contain compiler directives (cf. Sect. 3.1).

Figure 3 shows a simplified version of the Assertion specification. The specification is violated if a call to function \( \texttt {\_\_assert\_fail} \) is reachable in the given input program, which matches how assert statements appear in a C program after pre-processing. The automaton starts in the initial state Init and observes the analyzed program operations until an operation matches a call to \( \texttt {\_\_assert\_fail} \) (line 5) with an arbitrary number of function-call arguments (denoted by $?). In this case, the automaton transitions to the special state ERROR (line 6) that signals a specification violation with the given explanation.

Fig. 3:
figure 3

Example of automaton-based specification for checking assert statements

3.3 CPAchecker Configuration

CPAchecker is highly configurable via a set of configuration options, which are documented in the file doc/ConfigurationOptions.txt. Configuration options are specified as key-value pairs in a configuration file or on the command line. An extensive set of bundled configuration files is available in directory config/. Most of these bundled configurations specify default values for common configuration options, e.g., the specification config/specification/default.spc and a time limit of 900 s. Command-line arguments overwrite these defaults.

It is possible to write and provide own configuration files. Their format is inspired by Windows INI files with some extensions like include directives. A full description is available in doc/Configuration.md. Configuration files may use relative paths. CPAchecker interprets these relative paths relative to the directory of the respective configuration file.

Command-line argument \( \texttt {- -config \, CONFIG\_FILE} \) selects a configuration file. The bundled configuration files can also be selected with short-hand arguments that consist of the base name of the configuration file, e.g., --kInduction for the configuration file config/kInduction.properties or --svcomp24 for the configuration file config/svcomp24.properties. When no configuration file is explicitly specified, CPAchecker runs in its default configuration (defined by the configuration file config/default.properties).

The command-line argument --option key=value sets a single configuration option. The order of command-line arguments is irrelevant. If an option is set both in the configuration file and through --option, the --option value takes precedence and overwrites any value from the configuration file.

CPAchecker provides shortcuts for the most common configuration options, for example --64 to specify the platform as 64-bit x86 Linux (LP64), or --timelimit to set an analysis time limit. A full list of shortcuts is available via cpachecker -h and in doc/Configuration.md. For technical reasons, a few command-line arguments exist that can only be specified through command-line arguments and not via configuration files. These arguments include --benchmark (which leads to better performance by disabling CPAchecker-internal assertions, writing no output files, and much more) and --heap (which adjusts the amount of memory used by the JVM for CPAchecker).

As an example, consider the following command line (cf. example settingOptions on the web service):

figure g

This invokes CPAchecker with the configuration for k-induction, sets the configuration option limits.time.cpu for the time limit to 900 s, tells the JVM to use 2000 MiB of heap memory, chooses the specification file ErrorLabel, the program program.c as input file, and sets the configuration option solver.solver to MATHSAT5.

3.4 Verification Verdict

CPAchecker may report three different verification verdicts: (1) TRUE, if it proves that the program satisfies the specification; (2) FALSE, if it proves that the program does not satisfy the specification; (3) UNKNOWN, if it cannot decide the verification task using the given resource limits and configuration.

3.5 Interactive Report in HTML Format

In addition to a verification verdict, CPAchecker produces detailed information about the performed analysis in directory output/ in the current working directory. This usually includes an interactive report in HTML format. Note that different configurations may produce different output files.

Fig. 4:
figure 4

Screenshot of the HTML report for program example-unsafe.c

The interactive report offers a graphical interface for users to inspect the results of CPAchecker. It allows to inspect, among others: the control-flow automata (CFA) of the input program, the abstract reachability graph (ARG) that was constructed by the chosen configuration, statistics, and an error path that violates the specification (if the verdict is FALSE).

In the following we explain the most important parts of this report. A screenshot of the report is shown in Fig. 4. An example report is provided online. If CPAchecker reports the verdict FALSE and the used analysis provides detailed counterexample information, the report file is output/Counterexample.0.html (number 0 may differ). Otherwise, the report file is output/Report.html.

Control-Flow Automata. The tab in the report shows the input program in the internal representation of CPAchecker, the control-flow automata (CFA). A CFA consists of program locations (nodes of the graph) and program statements (edges of the graph). In the report, a double-click on a CFA edge navigates to the source-code line it represents. The drop-down menu “Displayed CFA” can be used to display a single CFA for a single program function.

Abstract-Reachability Graph. The tab in the report shows a graphical representation of the program states that were explored by CPAchecker in the form of an abstract-reachability graph (ARG). The right-hand side of Fig. 4 shows an ARG. Each node in the ARG represents an abstract state of the input program. CPAchecker constructs abstract states according to the selected configuration. An abstract state usually represents a set of concrete program states in order to overapproximate the reachable state space. Two abstract states are connected by a directed edge if one state is the successor to the other. The directed edge goes from predecessor to successor and is labeled with a program operation that induced the predecessor-successor relation during analysis.

If CPAchecker reported the verdict TRUE, the ARG represents all reachable abstract program states. If CPAchecker reported the verdict FALSE, nodes and edges that are part of the error path are marked in red (as in Fig. 4).

Error Path. If the verification verdict is FALSE and the analysis provides detailed counterexample information, the report includes a textual error-path section as separate panel on the left (toggle with button “Show Error-Path Section”). This allows users to step through the error path that CPAchecker computed. The textual error path is a list of program statements, accompanied by concrete assignments to all variables on the error path. A button is displayed next to each statement, which indicates the concrete variable assignments at the respective location. To replay the error path step-by-step, users can click on the button on the top left. Then, two buttons and can be used to navigate through the error path.

3.6 Statistics

CPAchecker collects a variety of statistics, depending on the chosen analysis. These are presented in the interactive report under tab and are also written to file output/Statistics.txt. With the command-line argument --stats, CPAchecker prints the statistics to the console at the end of the verification run.

The statistics help users to evaluate the performance of the analysis. Below is an example excerpt of a run’s statistics that shows the time spent on SMT solving, the total number of computed reachable abstract states, and the consumed CPU time.

figure o

A separate tutorial covers how to interpret CPAchecker statistics in more detail.

3.7 Verification Witnesses

Verification witnesses [5, 19] help users and tools to reason about verification results and allow independent validation of the verification result. Validation is usually easier than verification, thanks to the additional information the witness provides. CPAchecker can both export witnesses for verification results and validate witnesses that other tools produce. The extended version [7] explains witness validation in detail.

Fig. 5:
figure 5

Example verification witnesses (format version 2.0, slightly shortened for readability)

Correctness Witnesses. Correctness witnesses are defined for reachability of error locations and detection of signed-integer overflows in sequential programs. CPAchecker produces such a witness not only if the verdict is TRUE, but also if it is UNKNOWN (in this case with partial information). The witness contains information about the explored program state space in the form of loop and location invariants. In case the analysis result is TRUE, the invariants hold whenever the program execution passes through the respective location.

Figure 5a shows an excerpt of a correctness witness for the safe program in Fig. 2a. It reports the loop invariant x + y == n for the loop head in line 7.

Violation Witnesses. Violation witnesses represent one or more program paths that lead to a specification violation. This is achieved by specifying assumptions about the program inputs and the control flow of the program.

Figure 5b shows an excerpt of a violation witness for the unsafe program in Fig. 2b. It shows the program path that leads to the assertion failure at line 10 when x is assigned value 0 and n is assigned value 1.

3.8 Test Harnesses

If CPAchecker finds a specification violation (verdict FALSE), it produces a test harness that triggers this violation through test execution. A test harness contains a sequence of external inputs (e.g., for inputs modeled by \( \texttt {\_\_VERIFIER\_nondet*} \)) to the program that trigger an execution path to the specification violation. Figure 6 shows an excerpt of a test harness for the example program in Fig. 2b. The two return values 2U (lines 5 and 6) initialize, in the program under analysis (Fig. 2b), both variables n and x with value 2. This triggers the assertion failure at line 10 of the program.

Fig. 6:
figure 6

Test harness generated for the example program in Fig. 2b

The test harness can be compiled with the program under analysis:

figure p

This produces a binary a.out. The execution of ./a.out exhibits that the claimed specification violation is actually reachable. It reports:

figure q

The extended version [7] gives more details on test generation with CPAchecker.

4 Verification Analyses and How to Select Them

This section shows how to execute various commonly-used verification analyses in CPAchecker. These analyses can be divided into three groups depending on the kind of specifications they can check. First, there are analyses that perform a reachability analysis. These support common specifications, for example, reachability of an error location or an assertion violation. Second, there are analyses that support a particular special-purpose specification. Third, there are meta analyses that implement strategy selection and delegate to one of the above depending on the provided specification. Table 2 lists common configurations and the respective specifications they support. Apart from the configuration - -dataRaceAnalysis, which performs partial order reduction [73] over memory accesses in combination with value analysis [43], the following sections explain these configurations in more detail.

Table 2: Commonly-used configurations and supported specifications

4.1 Selecting an Analysis

Selecting an analysis of CPAchecker primarily depends on the kind of specification that should be verified. Memory safety, overflows, and data races can each be verified by exactly one recommended analysis, which is listed in Table 2. For termination, there are two recommendations, described in Sect. 4.9. If SV-COMP property files are used to encode the specification, meta configurations of CPAchecker automatically select a recommended analysis depending on the specification.

For standard reachability specifications a wide range of different analyses and techniques is available in CPAchecker. Each of them has their strengths and weaknesses, and while some of them are more powerful or efficient in general, none of them always outperforms all of the others, so it can be worthwhile experimenting with several analyses.

The general recommendation for most use cases is the default analysis of CPAchecker (used if no other configuration is selected on the command line). It is a meta configuration that uses k-induction (- -kInduction, most effective overall in our experience) for reachability specifications.

CPAchecker’s value analysis (- -valueAnalysis-NoCegar-join), symbolic execution (- -symbolicExecution-NoCegar), and bounded model checking (BMC, - -bmc-incremental) are mostly suited for finding specification violations. While they are often quite efficient in finding bugs, they are often inefficient for proving correctness for large programs. In our experience these configurations usually either succeed quickly or will not produce a result at all.

To prove the absence of specification violations in larger programs, either abstraction of the program state space or a proof technique such as induction needs to be used. Value analysis and symbolic execution support a limited form of abstraction (ignoring irrelevant program variables and clauses) if their configuration variants with precision refinement are chosen as described in the respective sections below. Predicate abstraction (- -predicateAnalysis) is stronger and can in principle find arbitrary loop invariants as long as the loop invariants do not require quantifiers nor floating-point arithmetic. k-Induction (- -kInduction) on the other hand requires that an induction proof can be found for the program.

Another aspect that needs to be considered is that value analysis and symbolic execution in CPAchecker do not support precise reasoning about dynamically allocated memory and data structures on the heap, whereas BMC, predicate abstraction, and k-induction do support this. However, the latter three are based on solving (sometimes large) formulas with an SMT solver, which may not scale. Value analysis has the advantage that it does not require SMT solving, but the disadvantage that it cannot reason about non-deterministic values. Symbolic execution uses an SMT solver, but only when required for non-deterministic values.

The value analysis can be considered comparatively easy to understand conceptually, which makes it a good starting point for the use of CPAchecker.

Table 3: Main configuration flavors of value analysis

4.2 Value Analysis

CPAchecker’s value analysis tracks concrete value assignments. There are two main configuration choices for the value analysis: (1) whether to use precision refinement, and (2) whether to be path sensitive. Table 3 lists the available command-line arguments to run CPAchecker with the corresponding configuration of value analysis. For example, the following command runs a configuration of value analysis that implements constant propagation [1] (no precision refinement, no path sensitivity) on the program in Fig. 7 (cf. example valueAnalysis-NoCegar-join on the web service):

figure r
Fig. 7:
figure 7

Program example-const.c

Fig. 8:
figure 8

Program example-sym.c

This configuration tracks only value assignments that always hold on a given location, because abstract states are joined when control flow meets. This is efficient, but in most cases not powerful enough to verify programs. For Fig. 7, it suffices because only the value of variable z is needed to prove the program safe, and this is always 0. The extended version [7] shows the state-space exploration of the value analysis for this example in more detail. If, however, the program safety would also depend on the values of x or y after the loop, the verification result would be UNKNOWN because the analysis does not track these non-constant variable values.

The value analysis with path sensitivity tracks value assignments per program path and location. For the example in Fig. 7, it would keep track of all variable values and fully unroll the loop. This leads to path explosion when many paths with distinct value assignments exist, because the analysis tracks all of them separately.

Value analysis with path sensitivity and precision refinement mitigates this path explosion by tracking only those value assignments that are necessary for the analysis to prove the program safe. This is more efficient than value analysis without precision refinement in the common case where not all variables in the program are relevant for safety, like in Fig. 7. The relevant variables are detected automatically through counterexample-guided abstraction refinement (CEGAR) with Craig interpolation [43].

Because the value analysis always tracks concrete value assignments and overapproximates nondeterministic values, it may find false alarms. To mitigate this, CPAchecker runs a precise, SMT-based feasibility check on every found potential error path and only reports confirmed specification violations. This can be seen in the output of CPAchecker, which is provided in the extended version [7].

4.3 Interval-Based Data-Flow Analysis

The data-flow analysis (DF) of CPAchecker is a lightweight proof-finding technique that uses arithmetic expressions over intervals as its abstract domain [15, 21]. It tracks, for an automatically-selected set of program variables, the range of values that each variable can take in the form of interval expressions, e.g., \([l_1, u_1] \cup [l_2, u_2]\), where  \(l_i\) ( resp. \(u_i\)) is a numerical value representing the lower (resp. upper) bound of an interval. DF supports dynamic precision refinement. At the beginning of the analysis, it performs a coarse but efficient program exploration. If some abstract state reachable in the exploration violates the safety specification, DF incrementally increases its precision by tracking more program variables, allowing more complex expressions of intervals, and disabling widening [54]. To run DF in CPAchecker, provide the configuration - -dataFlowAnalysis on the command line (cf. example dataFlowAnalysis on the web service):

figure s

For the above example, CPAchecker produces the verdict TRUE. A limitation of DF is that its abstract program exploration cannot identify concrete error paths when there are specification violations and may sometimes be too imprecise to find a safety proof. For example, when CPAchecker analyzes example-safe.c or example-unsafe.c in Fig. 2 with DF, it produces the verdict UNKNOWN. DF cannot only run standalone but also serve as an auxiliary invariant generator that assists other analyses, e.g., k-induction  [20] (cf. Sect. 4.7).

4.4 Symbolic Execution

The symbolic execution [40] of CPAchecker tracks concrete value assignments the same way as the value analysis. But for every value that cannot be tracked concretely, for example because it is assigned non-deterministically, symbolic execution introduces a new symbolic value \(s_i\). Whenever a symbolic value is used in an expression, symbolic execution stores the expression over this symbolic value without evaluating it. In addition, symbolic execution tracks the constraints over these symbolic values for each program path. This produces a symbolic-execution tree (cf. the extended version [7] for details). From this, concrete variable assignments can be derived for any program path. The symbolic execution of CPAchecker also supports precision refinement through CEGAR with Craig interpolation [39]. This determines which variables and constraints must be tracked through the program.

The below command runs a configuration of symbolic execution [65] without precision refinement (cf. example symbolicExecution-NoCegar on the web service):

figure t

Because symbolic execution tracks the expressions over symbolic values without further abstraction, it is well suited for collecting constraints on inputs for certain program paths. But this precision also leads to path explosion: The analysis of symbolic execution on program example-safe.c (Fig. 2a) does not terminate. To prove the program safe, it is important to know that the sum of x and y equals n at line 9. Symbolic execution tracks this by storing the expressions \(n = s_1, x = s_2, y = s_1 - s_2,\,x = s_2 - 1, y = s_1 - s_2 + 1,\,x = s_2 - 1 - 1, y = s_1 - s_2 + 1 + 1\), and so on. This produces ever more complicated expressions and does not scale.

The following command runs a configuration of symbolic execution with precision refinement (cf. example symbolicExecution-Cegar on the web service):

figure u

On the program of Fig. 8, this only tracks assignments and constraints over x and y, which are necessary to prove the program safe. Assignments to \(z\) are not tracked.

4.5 Predicate Abstraction

Predicate abstraction [36, 59, 63] abstracts the program’s state space with predicates that it learns using CEGAR [53] and Craig interpolation [59]. Compared to symbolic execution, predicate abstraction is not limited to tracking (symbolic) values and constraints in the program, but can derive more powerful abstractions. The computation of abstractions can be costly, thus predicate abstraction uses large-block encoding [18, 36] to compute abstractions only at certain program locations, which by default are the loop-head locations. This reduces the number of abstractions calculated and, hence, the overall cost. To run predicate abstraction, use the command (cf. example predicateAnalysis on the web service):

figure v

In this example, predicate abstraction derives the loop invariant x + y == n, which proves that \( \texttt {\_\_assert\_fail} \) in Fig. 2a is unreachable, and hence returns the verdict TRUE. Learned predicates at these locations are written down in a format based on SMT-LIB2 [10] into the file output/predmap.txt of the current working directory. Take the program in Fig. 2a for example. Predicate abstraction can derive the invariant x + y == n for the while loop at line 7 in function main that suffices to prove the safety specification that the assertion error is unreachable. In predmap.txt, this is represented as follows:

figure w

Predicate abstraction can abstract the program state space concisely in a way that proves the program safe, if it learns the right predicates. Unfortunately, there is no mechanism forcing predicate abstraction to find predicates that abstract well. Especially for concrete value assignments in the program, the learned predicates might enumerate all possible states. For instance, predicate abstraction may unnecessarily learn the predicates x == 0, x == 1, and x == 2 at line 6 of Fig. 7, instead of z == 0. Alternatively, Impact  [72] is another analysis that abstracts a program’s state space with predicates. It computes and refines abstractions in a lazier way compared to predicate abstraction, and can be initiated using the configuration - -predicateAnalysis-ImpactRefiner-ABEl. The two analyses have shown different and complementing strengths in our empirical evaluations [22]: Predicate abstraction is more effective at deriving proofs, whereas Impact is more efficient at finding specification violations.

4.6 Bounded Model Checking

Bounded model checking (BMC) [22, 51] is an analysis specialized in finding specification violations. Given a bound n, BMC symbolically unrolls the loops in the program n times, encodes all execution paths and specification viloations (within the unrolling bound n) into an SMT formula, and checks the satisfiability of the formula with an SMT solver. The satisfiability of the formula directly corresponds to the feasibility of the encoded error paths. If the formula is satisfiable, then a specification-violating execution path (with n loop unrollings) exists and can be extracted from the satisfying assignment. A bounded model checker then reports the verification verdict FALSE. In case the formula is unsatisfiable, the program is considered safe up to the bound \(n\). A bounded model checker reports the verification verdict TRUE if the loops in the program have finite bounds and are fully unrolled by the bound \(n\). Otherwise, the verdict is UNKNOWN, as the behavior of the program at higher unrolling bounds is still unknown.

CPAchecker automatically determines the required unrolling bound by incrementally increasing the bound using configuration - -bmc-incremental. Incremental BMC starts with an unrolling bound of 0 and increments the bound by 1 after each iteration. The analysis terminates once an error path is found, the safety specification is proven (by fully unrolling all loops in the program), or a resource limit is reached. For instance, the following command runs BMC with incrementally increasing loop bound on the program in Fig. 2b (cf. example bmc-unsafe on the web service):

figure x

CPAchecker finds the bug inside the loop body of the program in Fig. 2b on its first encounter of the assertion, with zero complete unrollings of the loop. Running incremental BMC on the correct program in Fig. 2a does not succeed (cf. example bmc-safe on the web service). During the process, CPAchecker produces log messages that show the current unrolling bound:

figure y

CPAchecker eventually reaches the time limit and the verdict is UNKNOWN, since a really large unrolling bound (roughly \(2^{31} \)) is required to fully explore the program. If the loop condition at line 7 changes to \( \texttt {x > 0 \& \& x < 3} \) in Fig. 2a, incremental BMC can prove the program safe with 2 complete loop unrollings.

4.7 Extensions of BMC for Unbounded Verification

BMC can be extended for unbounded verification of programs by employing the k-induction principle [20, 77] or constructing fixed points, i.e., inductive invariants, via Craig interpolation [38, 71, 79, 80]. To run k-induction in CPAchecker, use the configuration - -kInduction, which combines k-induction  with an auxiliary invariant generator based on data-flow analysis [15, 20] (described in Sect. 4.3). The invariants produced by the latter are used to strengthen the induction hypotheses of the former. This is more effective than plain k-induction  [20]. As opposed to incremental BMC, k-induction could easily prove the safety of the example programs in Fig. 2a with the command (cf. example kInduction on the web service):

figure z

CPAchecker has three verification algorithms based on BMC and Craig interpolation: interpolation-based model checking (IMC) [38, 71], interpolation-sequence-based model checking (ISMC) [14, 79], and dual approximated reachability (DAR) [14, 80]. From unsatisfiable BMC queries, the three algorithms derive interpolants to construct inductive invariants at loop heads. Such an invariant overapproximates the reachable states of the program that conforms to the safety specification, and hence could serve as a proof for the program’s correctness. IMC, ISMC, and DAR are enabled via the configurations - -bmc-interpolation, - -bmc-interpolationSequence, and - -bmc-interpolationDualSequence, respectively, and currently support only programs with at most one loop. The tool CPAchecker verifies the program in Fig. 2a with IMC (- -bmc-interpolation) via the command (cf. example bmc-interpolation on the web service):

figure aa

It produces the below log message:

figure ab

The message indicates that IMC has found an inductive invariant for the while loop at line 7 and proved the safety specification of the program.

Fig. 9:
figure 9

example-unsafe-memsafety.c with two distinct memory-safety violations

4.8 Symbolic Memory Graphs with Symbolic Execution

CPAchecker’s symbolic-memory-graph (SMG) analysis [56] combines symbolic execution [65] with a graph-based domain that tracks all memory. It is usable in CPAchecker with the configuration - -smg. In addition to common state-space exploration, the SMG analysis can check for memory safety. The analysis can detect memory leaks, invalid memory access, and invalid freeing of memory.

SMGs accurately track most memory operations, including pointer arithmetics and bit-precise reading of memory. They also store memory boundaries and can thus be used to reason about the validity of pointer dereferences. A distinguishing feature of SMGs is that linked lists of arbitrary length can be abstracted under certain circumstances. This is currently limited to lists that terminate in indefinitely repeating equal values. If the analysis fails to abstract lists of arbitrary length, it enumerates all possible list lengths. This may lead to a path explosion, but can still find violations to safety specification.

We can see some capabilities of the SMG analysis on the example program in Fig. 9. The program first allocates some memory at line 7, then uses this memory to store some distinct but non-deterministic values in a loop at line 9, filling the entire memory allocated in arr. Then, in a reversed loop, the saved values are compared to their expected values at line 13. Please note that this example is not pre-processed and thus the command-line argument --preprocess is needed. To start the verification of memory safety with the configuration - -smg on this program, run the following command:

figure ac

This detects that the first memory access of the second loop at line 12 is unsafe (i.e., the verdict is FALSE), as the pointer dereference exceeds the bounds of the allocated memory. Another error can be found before line 16, as the memory allocated in arr is never freed. This second memory-safety violation can be found either by fixing the invalid dereference at line 13, or by using the dedicated specification memorycleanup:

figure ad
Fig. 10:
figure 10

Example C programs for demonstration of termination analyses

4.9 Termination Analysis

The specification termination requires a program to always terminate. A program that can execute infinitely is called non-terminating.

CPAchecker provides two approaches for termination analysis: the termination-as-safety analysis [76] - -terminationToSafety and the lasso-based analysis [58] - -lassoRankerAnalysis. Analysis - -terminationToSafety is based on loop unrolling (similar to BMC, cf. Sect. 4.6). It can prove termination only if all loops in the program can be fully unrolled, but is often efficient in finding specification violations, i.e., counterexamples that show non-termination. Analysis - -lassoRankerAnalysis constructs ranking functions and does not need to unroll all loops in the program for termination proofs.

Termination-as-Safety Analysis. The termination-as-safety analysis transforms a verification task for a termination specification into a verification task for reachability. It stores the values of variables that were seen at the programs’ loop heads. For example, the loop head for the two programs in Fig. 10 is the location that corresponds to line 5. Similar to BMC (cf. Sect. 4.6), when the analysis visits a loop head for the \(n\) + 1-st time, it constructs an SMT formula that symbolically represents \(n\)-th loop unrollings. Via satisfiability queries, the analysis checks whether there exists a reachable state that is visited twice within \(n\) loop iterations. If such a state is found, the program is non-terminating.

The following command line runs the analysis on the program in Fig. 10b (cf. example terminationToSafety on the web service):

figure ae

CPAchecker reports the verdict FALSE and produces a counterexample that shows the following three unrollings of the loop (visible in the output file output/Counterexample.1.core.txt):

figure af

The unrolling represents an execution with assignment z = 2 at line 4. By inspecting the values of n and z at the loop-head location of each iteration, we see that the state (n,z) = (0,0) is visited twice. This represents a non-terminating loop.

Lasso-Based Analysis. The main idea of the lasso-based analysis is to extract potentially non-terminating structures called lassos and then pass each of them to the library LassoRanker [67]. This library constructs ranking functions, which are arguments for termination. Simultaneously, it is looking for a non-termination argument. If it finds a non-termination argument for at least one lasso, CPAchecker claims that the program is non-terminating.

The lasso-based analysis complements the termination-as-safety analysis. The analysis can verify that program example-terminating.c in Fig. 10a terminates, but not that program example-nonterminating.c in Fig. 10b might not terminate. The following command line runs it the lasso-based analysis on the program in Fig. 10a (cf. example lassoRankerAnalysis on the web service):

figure ag

CPAchecker reports the verdict TRUE and produces the output file output/terminationAnalysisResult.txt. This contains a termination argument in the form of the ranking function \(3*z - 3*n + 4\). As n is always positive, if the loop condition \(n \le z\)  is satisfied,  \(3*z - 3*n + 4 \ge 0\) holds. In addition, after each loop iteration, the resulting value of the ranking function strictly decreases. After a finite number of iterations, the value will eventually become smaller than zero,which implies the negation of the loop condition and thus termination.

4.10 Integer-Overflow Detection

To detect integer overflows, CPAchecker uses a standard reachability analysis, such as those explained in Sects. 4.2, 4.5 and 4.6, together with an internal encoding of overflow conditions as error locations (CPAchecker’s overflow analysis also checks for underflows). The configurations supporting overflow detection have the suffix -​-​overflow in their names. By default, CPAchecker only checks for signed integer overflows, as these are declared undefined behavior by the C standard. To additionally check for unsigned integer overflows, set the option overflow.checkUnsigned to true. For instance, to determine whether the example program in Fig. 2a is free of signed and unsigned integer overflows while using predicate abstraction (cf. Sect. 4.5), run the command (cf. example predicateAnalysis-unsigned-overflow on the web service):

figure ah

The verification verdict is FALSE, because an overflow could happen at line 6  if n and x are initialized to 0 and 1, respectively.

5 Conclusion

This tutorial gives an introduction to the CPAchecker framework and how to use it to verify programs. It gives an overview of the main analysis techniques that CPAchecker offers, together with their strengths and weaknesses, and provides guidance on how to use CPAchecker in several analysis situations.

We hope that our tutorial is useful for researchers, practitioners, and educators, and that we stimulate interest and curiosity to dig deeper into the full potential of software model checking. Interested readers can find more information on the CPAchecker project web page, in the research publications on CPAchecker, the CPAchecker GitLab repository, and the CPAchecker mailing list.