2.1 Coverage Property Instrumentation
Instrumentation. In symbolic execution of state systems, entering a branch requires satisfying a precondition, i.e., a branch constraint [
1]. For instance, to execute a branch guarded by a non-constant condition like
if(cond)
, there must exist an assignment that satisfies
cond
. This is equivalent to checking if a counterexample satisfies
assert(!cond)
. Based on this insight, we introduce a method that models branch entry by transforming branch constraints into properties, represented as assertions, and instrumented into the program for verification. The instrumentation is applied to the GOTO program rather than the source code, as at this stage, control-flow constructs such as
if-else
statements,
while
loops,
for
loops, and
switch-case
statements are normalized into
if-goto
structures. This normalization simplifies subsequent operations and ensures the instrumentation is applied to all decision points, thereby maintaining soundness. Specifically, constraints from
if
statements (
cond
in statement
if(cond)
) are extracted and converted into two assertions: one representing the false condition (
assert(!cond)
) and the other representing its negation (
assert(!(!cond))
). These two assertions reflect the requirement for executing the
if
and
else
branches, respectively. Ultimately, they are inserted in sequential order before that
if
statement. Performing instrumentation before the
if
statement, rather than within its branches, enhances performance. Since the BMC engine unwinds loops, placing coverage properties inside loop bodies would result in duplicate assertion instances during unwinding, distorting coverage statistics, and hindering test generation. Additionally, this “instrument-before” strategy facilitates the elimination of redundant code, i.e.,
if-goto
blocks, during the program slicing stage.
Isolation. Potential interferences are excluded to isolate the analysis of instrumented properties from others. First, all original program properties, e.g. user-defined assertions, are converted into tautologies (
assert(True)
), which are removed during the program slicing [
5]. Second, internal safety checks within ESBMC are disabled to prevent unnecessary assertions from being introduced during analysis.
2.2 Multi-Property Verification
Multiple branch coverage properties are inserted during the CFG instrumentation. To reason and generate test suites for each property, we propose a method to verify multi-property in incremental, following these steps:
Symbolic execution & Slicing. The GOTO program is first symbolically executed with loops unwound up to a predefined threshold k, generating verification condition VC. Next, program slicing minimizes constraints by removing irrelevant parts based on the property under verification, reducing the state space.
Property Splitting. Given the sliced
VC, the global property is decomposed into atomic properties, and the satisfiability of each assertion is verified within the bounded execution. Eq. (
1) demonstrates the key process for this checking procedure, where
C and
P are used to denote the global constraints and properties of the state system (within bound
k), respectively. The global property
P is decomposed into a set of unit properties
\(P_i\). Thus, the verification condition
VC is divided into a set of
\(VC_i\).
Base Case. The base case evaluates whether each property \(P_i\) holds within k steps. For each \(VC_i\), an SMT solver is set up, checking for satisfiability within a fixed bound k in isolation. Outcomes dictate subsequent actions: (1) Violation: A counterexample is identified and reported. Furthermore, the GOTO program is updated by converting the violated property \(P_i\) into a tautology (assert(True)
). (2) Hold: Forward reasoning is performed to verify behaviour beyond k.
Forward Reasoning. This step evaluates program behavior for
\(k+1\) steps and beyond, verifying that no violations occur beyond the
k-bound (
Forward Condition) and proving that if the property holds for
k steps, it also holds for
\(k+1\) steps (
Inductive Step) [
10]. Outcomes dictate subsequent actions: (1) Violation: A counterexample is reported, and the property
\(P_i\) is converted into a tautology. (2) Hold: The property
\(P_i\) is proven correct in an unbounded context and converted into a tautology. This indicates that the branch is unreachable. (3) Unknown: This indicates the bound
k is insufficient to evaluate the property (e.g.,
k is too small to explore deep loop iterations), making forward reasoning undecidable. In such cases, the bound
k is incremented for further re-verification.
Termination & Re-verification. The whole verification process terminates under the following conditions: (1) all remaining coverage properties are proven during forward reasoning, or (2) all properties are reduced to tautologies and removed through slicing, leaving no properties for further verification. Conversely, if any property remains unknown, a verification re-run is initiated. In this process, the bound
k is incremented, and the updated GOTO program undergoes re-verification from symbolic execution until either a maximum
k-threshold or a timeout is reached. Eq.
1 shows the reduction in
VCs via slicing (
\(m\le n\)). The repeated program slicing removes the converted tautologies and their property-relevant code, mitigating state space explosion as the bound
k increases. Additionally, to further accelerate
k-increment verification, we implement a jump strategy starting
k from a larger
i and incrementing it by
j, where
\(i,j > 1\).
Test Generation. Test generation occurs whenever a property \(P_i\) violation is reported during the Base Case or Forward Reasoning stage. Assignments with nondeterministic initial values are extracted from the counterexample traces and transformed into corresponding test suites.