figure a

1 Introduction

Satisfiability solvers, particularly those based on Satisfiability Modulo Theory (SMT) technology [3, 19], have made tremendous practical advances in the last decade to the point where they are now widely used in tools for applications as diverse as bug finding [27], program analyses [4] to automated verification [2]. However, current SMT solvers are based primarily on first-order logic, and do not yet cater to the needs of resource-oriented logics, such as separation logic [26, 40]. Separation logic has recently established a solid reputation for reasoning about programs that manipulate heap-based data structures. One of its strengths is the ability to concisely and locally describe program states that hold in separate regions of heap memory. In particular, a spatial conjunction (i.e., \(\kappa _1 {*}\kappa _2\)) asserts that a given heap can be decomposed into two disjoint regions and the formulas, \(\kappa _1\) and \(\kappa _2\), hold respectively and separately in the two memory regions. In this work, we investigate the problem of verifying heap-manipulating programs in the framework of SMT. We reduce this problem to solving verification conditions representing precise program semantics [9, 10, 22, 44].

Developing an SMT solver supporting separation logic with inductive predicates and Presburger arithmetic is challenging as the satisfiability problem for this fragment is undecidable [30, 31]. We focus on an expressive fragment which consists of spatial predicates expressing empty heap assertion (\(\mathtt{emp}\)), points-to assertion (\({x}{{\mapsto }}c(\bar{v})\)), and inductive predicate assertions (\({\small \mathtt{P}}(\bar{v})\)). Moreover, it may include pure constraints on data values and capture desired properties of structural heaps (such as size, height, sortedness and even near-balanced tree properties). We thus face the challenge of handling recursive predicates with pure properties, that are inherently infinite. Furthermore, we would like to support both satisfiability (\(\mathtt {SAT}\)) and unsatisfiability (\(\mathtt {UNSAT}\)) checks.

There have been a number of preliminary attempts in this direction. For instance, early proposals fixed the set of shape predicates that may be used, for example, to linked lists (in SeLoger [17, 23], and SLL\(\mathbb {B}\) [34]) or trees (GRIT [36]). There are few approaches supporting user-defined predicates [14, 25, 39]. Brotherston et al. recently made an important contribution by introducing \(\mathtt {SLSAT}\), a decision procedure for a fragment of separation logic with arbitrary shape-only inductive predicates [12]. However, \(\mathtt {SLSAT}\) is limited to the shape domain, whereas shape predicates extended with pure properties are often required for automated verification of functional correctness.

In this paper, we start by proposing a new procedure, called \(\mathtt {S2SAT}\), which combines under-approximation and over-approximation for simultaneously checking \(\mathtt {SAT}\) and \(\mathtt {UNSAT}\) properties for a sound and complete theory augmented with inductive predicates. \(\mathtt {S2SAT}\) takes a set of user-defined predicates and a logic formula as inputs. It iteratively constructs an unfolding tree by unfolding the formula in a breadth-first, flow- and context-sensitive manner until either a symbolic model, or a proof of unsatisfiability or a fixpoint (e.g., a cyclic proof) is identified. In each iteration, it searches over the leaves of the tree (the disjunction of which is equivalent to the input formula) to check whether there is a satisfiable leaf (which proves satisfiability) or whether all leaves are unsatisfiable. In particular, to prove \(\mathtt {SAT}\), it considers base disjuncts which are derived from base-case branches of the predicates. These disjuncts are under-approximations of the input formula and critical for satisfiability. Disjuncts which have no inductive predicates are precisely decided. To prove \(\mathtt {UNSAT}\), \(\mathtt {S2SAT}\) over-approximates the leaves prior to prove \(\mathtt {UNSAT}\). Our procedure systematically enumerates all disjuncts derived from a given inductive formula, so it is terminating for \(\mathtt {SAT}\). However, it may not be terminating for \(\mathtt {UNSAT}\) with those undecidable augmented logic. To facilitate termination, we propose an approach for fixpoint computation. This fixpoint computation is useful for domains with finite model semantics i.e., collecting semantics for a given formula of such domains is finite. In other words, the input formula is unsatisfiable when the unfolding goes on forever without uncovering any models. We have implemented one instantiation of the fixpoint detection for inductive proving based on cyclic proof [13] s.t. the soundness of the cyclic proof guarantees the well-foundedness of all reasoning.

To explicitly handle heap-manipulating programs, we propose a separation logic instantiation of \(\mathtt {S2SAT}\), called \(\mathtt {S2SAT_{SL}}\). Our base theory is a combination of the aforementioned separation logic predicates except inductive predicates. We show that our decision procedure for this base theory is sound and complete. \(\mathtt {S2SAT_{SL}}\) over-approximates formulas with soundly inferred predicate invariants. In addition, we describe some syntax restrictions such that \(\mathtt {S2SAT_{SL}}\) is always able to construct a cyclic proof for a restricted formula so that our procedure is terminating and complete.

To summarize, we make the following technical contributions in this work.

  • We introduce cyclic proof into a satisfiability procedure for a base theory augmented with inductive predicates (refer to Sect. 3).

  • We propose a satisfiability procedure for separation logic with user-defined predicates and Presburger arithmetic (Sect. 4).

  • We prove that \(\mathtt {S2SAT_{SL}}\) is: (i) sound for \(\mathtt {SAT}\) and \(\mathtt {UNSAT}\); (ii) and terminating (i.e., proposing a new decision procedure) for restricted fragments (Sect. 5).

  • We present a mechanism to automatically derive sound (over-approximated) invariants for user-defined predicates (Sect. 6).

  • We have implemented the satisfiability solver \(\mathtt {S2SAT_{SL}}\) and the new verification system, called \(\mathtt {S2_{td}}\). We evaluated \(\mathtt {S2SAT_{SL}}\) and \(\mathtt {S2_{td}}\) with benchmarks from recent competitions. The experimental results show that our system is expressive, robust and efficient (Sect. 7).

Proofs of Lemmas and Theorems presented in this paper are available in the companion technical report [30].

Fig. 1.
figure 1

Motivating example.

2 Illustrative Example

We illustrate how our approach works with the example shown in Fig. 1. Our verification system proves that this program is memory safe and function \(\mathtt {ERROR()}\) (line 5) is never called. Our system uses symbolic execution in [6, 14] and large-block encoding [8] to provide a semantic encoding of verification conditions. For safety, one of the generated verification conditions is: \(\varDelta _0~{\equiv }~ll(n{,}x)_0^0{*}test(x{,}r_1)_0^1{\wedge }n{\ge }0{\wedge }r_1{=}0\). If \(\varDelta _0\) is unsatisfiable, function \(\mathtt {ERROR()}\) is never called. In \(\varDelta _0\), \(\mathtt {ll}\) and \(\mathtt {test}\) are Interprocedural Control Flow Graph (ICFG) of the functions \(\mathtt {ll}\) and \(\mathtt {test}\). Our system eludes these ICFGs as inductive predicates. For each predicate, a parameter \(\mathtt{{res}}\) is appended at the end to model the return value of the function; for instance, the variables \(x\) (in \(\mathtt {ll}\)) and \(r_1\) (in \(\mathtt {test}\)) of \(\varDelta _0\) are the actual parameters corresponding to \(\mathtt{{res}}\). Each inductive predicate instance is also labeled with a subscript for the unfolding number and a superscript for the sequence number, which are used to control the unfolding in a breadth-first and flow-sensitive manner.

To discharge \(\varDelta _0\), \(\mathtt {S2SAT_{SL}}\) iteratively derives a series of unfolding trees \(\mathcal{T}_{i}\). An unfolding tree is a tree such that each node is labeled with an unfolded disjunct, corresponding to a path condition in the program. We say that a leaf of \(\mathcal{T}_{i}\) is closed if it is unsatisfiable; otherwise it is open. During each iteration, \(\mathtt {S2SAT_{SL}}\) either proves \(\mathtt {SAT}\) by identifying a satisfiable leaf of \(\mathcal{T}_{i}\) which contains no user-defined predicate instances or proves \(\mathtt {UNSAT}\) by showing that an over-approximation of all leaves is unsatisfiable. Initially, \(\mathcal{T}_{0}\) contains only one node \(\varDelta _0\). As \(\varDelta _0\) contains inductive predicates, it is not considered for proving \(\mathtt {SAT}\). \(\mathtt {S2SAT_{SL}}\) then over-approximates \(\varDelta _0\) to a first-order logic formula by substituting each predicate instance with its corresponding sound invariants in order to prove \(\mathtt {UNSAT}\). We assume that \(\mathtt {ll}\) (resp. \(\mathtt {test}\)) is annotated with invariant \(i{\ge }0\) (resp. \(0{\le }\mathtt{{res}}{\le }1\)). Hence, the over-approximation of \(\varDelta _0\) is computed as: \(\pi _0{\equiv } n{\ge }0 {\wedge } 0{\le }r_1{\le }1 {\wedge } n {\ge }0 {\wedge }r_1{=}0\). Formula \(\pi _0\) is then passed to an SMT solver, such as Z3 [19], for unsatisfiable checking. As expected, \(\pi _0\) is not unsatisfiable.

Next, \(\mathtt {S2SAT_{SL}}\) selects an open leaf for unfolding to derive \(\mathcal{T}_{1}\). A leaf is selected in a breadth-first manner; furthermore a predicate instance of the selected leaf is selected for unfolding if its sequence number is the smallest. With \(\varDelta _0\), the \(\mathtt {ll}\) instance is selected. As so, \(\mathcal{T}_{1}\) has two open leaves corresponding to two derived disjuncts:

$$ \begin{array}{l} \varDelta _{11}{\equiv }test(x{,}r_1)_0^1{\wedge }n{\ge }0{\wedge }r_1{=}0{\wedge }n{=}0{\wedge }x{=}{{\small \mathtt{{null}}}} \\ \varDelta _{12}{\equiv }x{\mapsto }node(n{,}r_2){*}ll(n_1{,}r_2)_1^0{*}test(x{,}r_1)_0^1 {\wedge }n{\ge }0{\wedge }r_1{=}0{\wedge }n{\ne }0{\wedge }n_1{=}n{-}1 \\ \end{array} $$

Since \(\varDelta _{11}\) and \(\varDelta _{12}\) include predicate instances, they are not considered for \(\mathtt {SAT}\). To prove \(\mathtt {UNSAT}\), \(\mathtt {S2SAT_{SL}}\) computes their over-approximated invariants:

$$ \begin{array}{l} \pi _{11}{\equiv }0{\le }r_1{\le }1{\wedge }n{\ge }0 {\wedge }r_1{=}0{\wedge }n{=}0{\wedge }x{=}{{\small \mathtt{{null}}}}\\ \pi _{12}{\equiv }x{\ne }{{\small \mathtt{{null}}}}{\wedge }n_1{\ge }0{\wedge }0{\le }r_1{\le }1{\wedge }n{\ge }0 {\wedge }r_1{=}0{\wedge }n{\ne }0{\wedge }n_1{=}n{-}1 \end{array} $$
Fig. 2.
figure 2

Unfolding tree \(\mathcal{T}_{3}\).

As neither \(\pi _{11}\) nor \(\pi _{12}\) is unsatisfiable, \(\mathtt {S2SAT_{SL}}\) selects \(\mathtt {test}\) of \(\varDelta _{11}\) for unfolding to construct \(\mathcal{T}_{2}\). For efficiency, unfolding is performed in a context-sensitive manner. A branch is infeasible (and pruned in advance) if its invariant is inconsistent with the (over-approximated) context. For instance, the invariant of the \(\mathtt {then}\) branch at line 12 of \(\mathtt {test}\) is \({\small \mathtt{inv}}_{test_o}{\equiv }p{=}{{\small \mathtt{{null}}}}{\wedge }\mathtt{{res}}{=}1\). As \({\small \mathtt{inv}}_{test_o}\) (after proper renaming) is inconsistent with \(\pi _{11}\), this branch is infeasible. Similarly, both \(\mathtt {else}\) branches of \(\mathtt {test}\) are infeasible. For \(\mathcal{T}_{3}\), the remaining leaf \(\varDelta _{12}\) is selected for unfolding. As the \(\mathtt {test}\)’s unfolding number is smaller than \(\mathtt {ll}\)’s, \(\mathtt {test}\) is selected. After the \(\mathtt {then}\) branch is identified as infeasible and pruned, \(\mathcal{T}_{3}\) is left with two open leaves as shown in Fig. 2, where infeasible leaves are dotted-lined. \(\varDelta _{32}\) and \(\varDelta _{33}\) are as below.

$$ \begin{array}{l} \varDelta _{32}{\equiv }{x{\mapsto }node({n}{,}r_2)}{*}ll(n_1{,}r_2)_1^0 {\wedge } {\underline{n{\ge }0}}{\wedge }r_1{=}0{\wedge }\underline{n{\ne }0}{\wedge }n_1{=}n{-}1 {\wedge } x{\ne }{{\small \mathtt{{null}}}}{\wedge } {\underline{{n}{<}0}} {\wedge }r_1{=}0\\ \varDelta _{33}{\equiv }x{\mapsto }node({n}{,}{r_2}){*}ll(n_1{,}r_2)_1^0{*} test({r_2}{,}r_1)_1^1 {\wedge } {n{\ge }0} \wedge r_1{=}0{\wedge }n{\ne }0{\wedge }n_1{=}n{-}1 \\ \quad { {\wedge } x{\ne }{{\small \mathtt{{null}}}}{\wedge } {n}{\ge }0 } \\ \end{array} $$

As \(\varDelta _{32}\) and \(\varDelta _{33}\) include inductive predicate instances, \(\mathtt {SAT}\) checking is not applicable. For \(\mathtt {UNSAT}\) checking, \(\mathtt {S2SAT_{SL}}\) proves that \(\varDelta _{32}\) is unsatisfiable (its unsatisfiable cores are underlined as above); and shows that \(\varDelta _{33}\) can be linked back to \(\varDelta _0\) (i.e., subsumed by \(\varDelta _0\)). The latter is shown based on some weakening and substitution principles (see Sect. 4.2). In particular: (i) Substituting \(\varDelta _{33}\) with \(\theta {=}[n_2/n{,}x_1/x{,}n/n_1{,}x/r_2]\) such that predicate instances in the substituted formula, i.e., \(\varDelta _{{33}_a}\), and \(\varDelta _0\) are identical; as such, \(\varDelta _{{33}_a}\) is computed as below.

$$ \begin{array}{l} { \varDelta _{{33}_a}{\equiv }x_1{\mapsto }node({n_2}{,}{x}){*}} ll(n{,}x)_1^0{*} test({x}{,}r_1)_1^1 {\wedge } {n_2{\ge }0}{\wedge }r_1{=}0{\wedge }n_2{\ne }0{\wedge }n{=}n_2{-}1 \\ \quad {\wedge } x_1{\ne }{{\small \mathtt{{null}}}}{\wedge } {n_2}{\ge }0 \end{array} $$

(ii) subtracting identical inductive predicates between \(\varDelta _{{33}_a}\) and \(\varDelta _0\); (iii) weakening the remainder of \(\varDelta _{{33}_a}\) (i.e., \(x_1{\mapsto }node({n_2}{,}{x})\) is eliminated); (iv) checking validity of the implication between pure of the remainder of \(\varDelta _{{33}_a}\) with the pure part of the remainder of \(\varDelta _0\), i.e., \(n_2{\ge }0{\wedge }r_1{=}0{\wedge }n_2{\ne }0{\wedge }n{=}n_2{-}1{\wedge } x_1{\ne }{{\small \mathtt{{null}}}}{\wedge } {n_2}{\ge }0 {\implies } n{\ge }0{\wedge }r_1{=}0\). The back-link between \(\varDelta _{33}\) and \(\varDelta _{0}\) establishes a cyclic proof which then proves \(\varDelta _0\) is unsatisfiable.

figure b

3 \(\mathtt {S2SAT}\) Algorithm

In this section, we present \(\mathtt {S2SAT}\), a procedure for checking satisfiability of formula with inductive predicates. We start by defining our target formulas. Let \(\mathcal {L}\) be a base theory (logic) with the following properties: (i) \(\mathcal {L}\) is closed under propositional combination and supports boolean variables; (ii) there exists a complete decision procedure for \(\mathcal {L}\). Let \({\mathcal {L}}^{ind}\) be the extension of \(\mathcal {L}\) with inductive predicate instances defined in a system with a set of predicates \(\mathcal {P}{=} \{P_1, ..., P_k \}\). Each predicate may be annotated with a sound invariant. We use \({\lambda }\) to denote a formula in \(\mathcal {L}\) and \({\lambda ^{ind}}\) to denote a formula in the extended theory. Semantically, \({\lambda ^{ind}}{\equiv }\bigvee _{i{=}0}^n {\lambda }_i, ~ n{\ge }0\).

\(\mathtt {S2SAT}\) is presented in Algorithm 1. \(\mathtt {S2SAT}\) takes a formula \({\lambda ^{ind}}\) as input, systematically enumerates disjuncts \({\lambda }_i\) and can produce two possible outcomes if it terminates: \(\mathtt {SAT}\) with a satisfiable formula \({\lambda }_i\) or \(\mathtt {UNSAT}\) with a proof. We remark that non-termination is classified as \(\mathtt {UNKNOWN}\).

\(\mathtt {S2SAT}\) maintains a set of open leaves of the unfolding tree \(\mathcal{T}_{i}\) that is derived from \({\lambda ^{ind}}\). In each iteration, \(\mathtt {S2SAT}\) selects and unfolds an open leaf so as either to include more reachable base formulas (with the hope to produce a \(\mathtt {SAT}\) answer), or to refine inductive formulas (with the hope to produce an \(\mathtt {UNSAT}\) answer). Specially, in each iteration, \(\mathtt {S2SAT}\) checks whether the formula is \(\mathtt {SAT}\) at line 3; whether it is \(\mathtt {UNSAT}\) at line 6; whether a fixpoint can be established at line 7. Function \(\mathtt {\small \mathtt{{UA\_test}}}\) searches for a satisfiable base disjunct (i.e., \(\mathtt {is\_sat}\) is set to true). Simultaneously, it marks all unsatisfiable base disjuncts closed. Next, function \(\mathtt {\small \mathtt{{OA\_test}}}\) uses predicate invariants to over-approximate open leaves of \(\mathcal{T}_{i}\), and marks those with an unsatisfiable over-approximation closed. After that, function \(\mathtt {link\_back}\) attempts to link remaining open leaves back to interior nodes so as to form a fixpoint (i.e., a (partial) pre-proof for induction proving). The leaves which have been linked back are also marked as closed. Whenever all leaves are closed, \(\mathtt {S2SAT}\) decides \({\lambda ^{ind}}\) as \(\mathtt {UNSAT}\) (line 8). Otherwise, the \(\mathtt {choose\_bfs}\) (line 10) chooses an open leave in breadth-first manner for unfolding.

Procedure \(\mathtt {link\_back}\) takes the unfolding tree \(\mathcal{T}_{i}\) as input and checks whether each open leaf \({\lambda ^{ind}}^{bud} {\in } \mathcal{T}_{i}\) matches with one interior node \({\lambda ^{ind}}^{comp}\) in \(\mathcal{T}_{i}\) via a matching function \(\mathtt {f_{fix}}\). \(\mathtt {f_{fix}}\) is based on weakening and substitution principles [13]. Intuitively, \(\mathtt {f_{fix}}\) detects the case of (i) the unfolding goes forever if we keep unfolding \({\lambda ^{ind}}^{bud}\); and (ii) \({\lambda ^{ind}}^{bud}\) has no model when \({\lambda ^{ind}}^{comp}\) has no model. If \(\mathtt {f_{fix}}\) \(({\lambda ^{ind}}^{bud},\varDelta ^{comp}){=}{\small \mathtt{{true}}}\,\), \(\varDelta ^{bud}\) is marked closed.

Our procedure systematically enumerates all disjuncts derived from a given inductive formula, so it is terminating for \(\mathtt {SAT}\). However, it may not be terminating for \(\mathtt {UNSAT}\) with those undecidable augmented logic. In the next paragraph, we discuss the soundness of the algorithm.

Soundness. When \(\mathtt {S2SAT}\) terminates, there are the following three cases.

  • (case A) \(\mathtt {S2SAT}\) produces \(\mathtt {SAT}\) with a base satisfiable \({\lambda ^{ind}}_i\);

  • (case B) \(\mathtt {S2SAT}\) produces \(\mathtt {UNSAT}\) with a proof that all leaves of \(\mathcal{T}_{i}\) are unsatisfiable;

  • (case C) \(\mathtt {S2SAT}\) produces \(\mathtt {UNSAT}\) with a fixpoint: a proof that some leaves of \(\mathcal{T}_{i}\) are unsatisfiable and the remaining leaves are linked back.

Under the assumption that \(\mathcal {L}\) is both sound and complete, case A can be shown to be sound straightforwardly. Soundness of case B immediately follows the soundness of \(\mathtt {\small \mathtt{{OA\_test}}}\). In the following, we describe the cyclic proof instantiation of \(\mathtt {link\_back}\) for fixpoint detection and prove the soundness of case C.

We use CYCLIC to denote the cyclic proof for entailment procedure adapted from [13]. The following definitions are adapted from their analogues of CYCLIC.

Definition 1

(Pre-Proof). A pre-proof derived for a formula \({\lambda ^{ind}}\) is a pair (\(\mathcal{T}_{i}\), \(\mathcal L\)) where \(\mathcal{T}_{i}\) is an unfolding tree whose root labelled by \({\lambda ^{ind}}\) and \(\mathcal L\) is a back-link function assigning every open leaf \({\lambda ^{ind}}_{l}\) of \(\mathcal{T}_{i}\) to an interior node \({\lambda ^{ind}}_{c}= \mathcal{L}({{\lambda ^{ind}}_{l}})\) such that there exists some substitution \(\theta \) i.e., \({\lambda ^{ind}}_{c}={\lambda ^{ind}}_{l}[\theta ]\). \({\lambda ^{ind}}_{l}\) is referred as a bud and \({\lambda ^{ind}}_{c}\) is referred as its companion.

A path in a pre-proof is a sequence of nodes \(({\lambda ^{ind}}_{i})_{i{\ge }0}\).

Definition 2

(Trace).

Let \(({{\lambda ^{ind}}}_{i})_{i{\ge }0}\) be a path in a pre-proof \(\mathcal PP\). A trace following \(({\lambda ^{ind}}_{i})_{i{\ge }0}\) is a sequence \((\alpha _i)_{i{\ge }0}\) such that, for all \(i{\ge }0\), \(\alpha _i\) is a predicate instance \({\small \mathtt{P}}(\bar{t})\) in the formula \({\lambda ^{ind}}_{i}\), and either:

  1. 1.

    \(\alpha _{i{+}1}\) is the subformula according to \({\small \mathtt{P}}(\bar{t})\) occurrence in \({\lambda ^{ind}}_{i{+}1}\), or

  2. 2.

    \({\lambda ^{ind}}_{i}\)[\(\bar{t}\)/\(\bar{v}\)] where \({\lambda ^{ind}}_{i}\) is branches of inductive predicate \({\small \mathtt{P}}(\bar{v})\). i is a progressing point of the trace.

To ensure that pre-proofs correspond to sound proofs, a global soundness condition must be imposed on such pre-proofs as follows.

Definition 3

(Cyclic Proof). A pre-proof is a cyclic proof if, for every infinite path \(({\lambda ^{ind}}_{i})_{i{\ge }0}\), there is a tail of the path \(p{=}({\lambda ^{ind}}_{i})_{i{\ge }n}\) such that there is an infinitely progressing trace following p.

Theorem 1

(Soundness). If there is a cyclic proof of \({\lambda ^{ind}}_{0}\), \({\lambda ^{ind}}_{0}\) is \(\mathtt {UNSAT}\).

Proof

We reduce our cyclic proof problem for satisfiability to the cyclic proof problem for entailment check, i.e., \({\lambda ^{ind}}_{0} ~{{\vdash }}~{\small \mathtt{{false}}}\,\) of CYCLIC. Assume there is a cyclic proof \(\mathcal PP\) of \({\lambda ^{ind}}_{0}\). From \(\mathcal PP\) we construct the pre-proof \(\mathcal PP_{{\vdash }}\) for the sequent \({\lambda ^{ind}}_{0} ~{{\vdash }}~ {\small \mathtt{{false}}}\,\) as follows. For each node \(({\lambda ^{ind}}_{i})_{i{\ge }0}\) in \(\mathcal PP\), we replace the formula \({\lambda ^{ind}}_{i}\) by the sequent \({\lambda ^{ind}}_{i}~{{\vdash }}~{\small \mathtt{{false}}}\,\). Since \(\mathcal PP\) is a cyclic proof, it follows that for every infinite path \(({\lambda ^{ind}}_{i})_{i{\ge }0}\), there is a tail of the path, \(p{=}({\lambda ^{ind}}_{i})_{i{\ge }n}\), such that there is an infinitely progressing trace following p (Definition 3). Since formulas in [13] are only traced through the LHS of the sequent and not its RHS, it is implied that for every infinite path \(({\lambda ^{ind}}_{i} {{\vdash }}{\small \mathtt{{false}}}\,)_{i{\ge }0}\), there is a tail of the path, \(p{=}({\lambda ^{ind}}_{i}~{{\vdash }}~{\small \mathtt{{false}}}\,)_{i{\ge }n}\), such that there is an infinitely progressing trace following p. Thus, \(\mathcal PP_{{\vdash }}\) is a cyclic proof (Definition 3 of [13]). As such \({\lambda ^{ind}}_{0} ~{\models } {\small \mathtt{{false}}}\,\) (Theorem 6 of [13]). In other words, \({\lambda ^{ind}}_{0}\) is \(\mathtt {UNSAT}\).    \(\square \)

To sum up, to implement a sound cyclic proof system besides the matching function, a global soundness condition must be established on pre-proofs to guarantee well-foundedness of all reasoning.

4 Separation Logic Instantiation of \(\mathtt {S2SAT}\)

In this section, to explicitly handle heap-manipulating programs, we propose a separation logic instantiation of \(\mathtt {S2SAT}\), called \(\mathtt {S2SAT_{SL}}\). We start by presenting \({\small \mathtt{SLPA}}\), a fragment of separation logic with inductive predicates and arithmetic.

Fig. 3.
figure 3

Syntax.

4.1 A Fragment of Separation Logic

Syntax. The syntax of \({\small \mathtt{SLPA}}\) formulas is presented in Fig. 3. We use \(\bar{x}\) to denote a sequence (e.g., \(\bar{v}\) for sequence of variables), and \({x_i}\) to denote the \(i^{th}\) element. Whenever possible, we discard \(f_i\) of the points-to predicate and use its short form as \({x}{{\mapsto }}c(v_i)\). Note that \(v_1 {\ne } v_2\) and \(v {\ne } {{\small \mathtt{{null}}}}\) are short forms for \(\lnot (v_1{=}v_2)\) and \(\lnot (v{=}{{\small \mathtt{{null}}}})\), respectively. All free variables are implicitly universally quantified at the outermost level. To express different scenarios for shape predicates, the fragment supports disjunction \(\varPhi \) over formulas. Each predicate instance is of the form \({\small \mathtt{P}}(\bar{v})_u^o\) where \(o\) and \(u\) are labels used for context- and flow- sensitive unfolding. In particular, \(o\) captures the sequence number and \(u\) is the number of unfolding. For simplicity, we occasionally omit these two numbers if there is no ambiguity. A formula \(\varDelta \) is a base formula if it does not have any user-defined predicate instances. Otherwise, \(\varDelta \) is an inductive formula.

User-Defined Predicate. A user-defined predicate \(\mathtt P\) is of the following general form

$$ \mathtt{pred}~ {\small \mathtt{P}}(\bar{t}) ~{\equiv }~ \bigvee ^n_{i=1} ({\exists } \bar{w_i}{\cdot }~ \varDelta _i ~~|~~ \pi _i^b) \quad { \overline{inv}}{:}~ \pi ; $$

where \(\mathtt P\) is predicate name; \(\bar{t}\) is a set of formal parameters; and \(\exists \bar{w_i}\cdot ~\varDelta _i\) (i \(\in \) 1...n) is a branch. Each branch is optionally annotated with a sound invariant \(\pi _i^b\) which is a pure formula that over-approximates the branch. \(\pi \) is an optionally sound predicate invariant. It must be a superset of all possible models of the predicate \(\mathtt P\) via a pure constraint on stack. The default invariant of each inductive predicate is \(\mathtt {true}\) . For efficiency, we infer more precise invariants automatically (see Sect. 6). Inductive branches may be recursive. We assume that the recursion is direct, i.e., a recursive branch of predicate \(\mathtt P\) includes at least one predicate instance \(\mathtt P\). In each branch, we require that variables which are not formal parameters must be existentially quantified i.e., \(\forall i\in 1...n {\cdot }\textit{FV}({\varDelta _i}){=} \bar{t}\) and \(\bar{w_i} {\cap } \bar{t}{=}\emptyset \) where \(\textit{FV}({\varDelta })\) are all free variables in the formula \(\varDelta \).

In the following, we apply \({\small \mathtt{SLPA}}\) to model two data structures: sorted lists (\(\mathtt {sortll}\)) without an annotated invariant and AVL trees (\(\mathtt {avl}\)) with annotated-invariant.

$$ \begin{array}{l} {\small \mathtt{pred}}~{\small \mathtt{{sortll}}}(\mathtt{root}{,}n{,}m) \equiv \mathtt{root}{\mapsto }node(m,{{\small \mathtt{{null}}}}) \wedge {\small \mathtt{n{=}1}} \\ ~~~ {\vee }~ ~{\exists } ~q{,}n_1{,}m_1 {\cdot } \mathtt{root}{\mapsto }node(m,q) *{\small \mathtt{{sortll}}}(q,n_1,m_1){\wedge }n{=}n_1{+}1{\wedge } m{\le }m_1 \\ \end{array} $$
figure c

Semantics. In the following, we discuss the semantics of \({\small \mathtt{SLPA}}\). Concrete heap models assume a fixed finite collection Node, a fixed finite collection Fields, a disjoint set Loc of locations (heap addresses), a set of non-address values Val, such that \({{\small \mathtt{{null}}}}~{\in }~ {\textit{Val}}\) and Val \(\cap \) Loc = \(\emptyset \). Further, we define:

The semantics is given by a forcing relation: \(s{,}h~{\models }~ \varPhi \) that forces the stack \(s\) and heap \(h\) to satisfy the constraint \(\varPhi \) where \(h\in {\textit{Heaps}} \), \(s\in {\textit{Stacks}}\), and \(\varPhi \) is a formula.

Fig. 4.
figure 4

Semantics.

The semantics is presented in Fig. 4. \(dom(f)\) is the domain of function \(f\); \(h_1 {\#} h_2\) denotes that heaps \(h_1\) and \(h_2\) are disjoint, i.e., \(\text {dom}(h_1) ~{\cap }~ \text {dom}(h_2) ~{=}~ \emptyset \); and \(h_1 {\cdot } h_2\) denotes the union of two disjoint heaps. Inductive predicates are interpreted using the least model semantics [42]. Semantics of pure formulas depend on stack valuations; it is straightforward and omitted in Fig. 4, for simplicity.

4.2 Implementation of Separation Logic Instantiation

In the following, we describe how \(\mathtt {S2SAT_{SL}}\) is realized. In particular, we show how the functions \(\mathtt {\small \mathtt{{UA\_test}}}\), \(\mathtt {\small \mathtt{{OA\_test}}}\), \(\mathtt {unfold}\), and \(\mathtt {link\_back}\) are implemented.

Deciding Separation Logic Formula. Given an \({\small \mathtt{SLPA}}\) formula, the functions \(\mathtt {\small \mathtt{{UA\_test}}}\) and \(\mathtt {\small \mathtt{{OA\_test}}}\) in \(\mathtt {S2SAT_{SL}}\) work similarly, by reducing the formula to a first-order formula systematically and deciding the first-order formula. In the following, we define a function called , which transforms a separation logic formula into a first-order formula. is defined over the symbolic heap as follows:

$$ \begin{array}[t]{l} \mathbf{{\scriptstyle eXPure}}(\exists \bar{w}{\cdot }~{x_1}{{\mapsto }}c_1(\bar{v}_1){*} ...{*} {x_n}{{\mapsto }}c_n(\bar{v}_n) ~{*}~ {\small \mathtt{P_1}}(\bar{t}_1){*} ...{*}{\small \mathtt{P_m}}(\bar{t}_m)~{\wedge }~\pi ) ~{\equiv } \\ \quad {\small \mathtt{\exists }} ~ \bar{w}{\cdot }~ \bigwedge \{x_i{\ne }{{\small \mathtt{{null}}}}\mid i{\in }1...n \} \wedge \bigwedge \{x_i{\ne }x_j \mid i,j{\in }1...n \text { and } i{\ne }j \} ~{\wedge }\\ \quad \bigwedge \{ {\small \mathtt{{ inv}}}({\mathcal {P}},{\small \mathtt{P_j}}, {\small \mathtt{\bar{t_j}}}) \mid j\in 1...m \} ~{\wedge }\\ \quad \pi \\ \end{array} $$

where the reduction at the first line (after \(\equiv \)) is for points-to predicates, and the second line is for user-defined predicates. The auxiliary function \(\mathtt { inv}({\mathcal {P}},P,\bar{v})\) returns the invariant of the predicate \(\mathtt P\) with a proper renaming.

Next, the auxiliary procedure \(\mathtt {sat_p}\)(\(\exists \bar{w} {\cdot }~ \pi \)) takes a quantified first-order formula as input. It preprocesses the formula and then invokes an SMT solver to solve it. The preprocessing consists of two steps. First, the existential quantifiers \(\bar{w}\) are eliminated through a projection \({\varPi }(\pi ,\bar{w})\). Second, remaining existential quantifiers are skolemized and \({{\small \mathtt{{null}}}}\) is substituted by special number (i.e., zero). The preprocessed formulas are of the form of linear arithmetic with free function symbols. These formulas may contain existential (\(\exists \)) and universal (\(\forall \)) quantifiers but no \(\exists \forall \) alternation. Hence, they are naively supported by SMT solvers.

Deriving Unfolding Tree. Next, we describe how function \(\mathtt {unfold}\) works in \(\mathtt {S2SAT_{SL}}\). Given a formula, \(\mathtt {unfold}\) selects one predicate instance for unfolding as follows.

$$ \begin{array}{c} \pi _{c} {\equiv } \mathbf{{\scriptstyle eXPure}}(\kappa *{\small \mathtt{P}}(\bar{v}) \wedge \pi ) \qquad \varGamma _i{=}\mathtt{unfoldP}({\small \mathtt{P}}(\bar{v})_u^o,\pi _{c}) \quad \\ \hline \mathtt{unfold}( {\exists } \bar{w}_0 {\cdot }~ \kappa {*} {\small \mathtt{P}}(\bar{v})_u^o {\wedge } \pi ) ~{\leadsto }~ \{{ {\exists } \bar{w}_0 {\cdot }~ \kappa {*} \varDelta _i {\wedge } \pi } ~|~ \varDelta _i {\in } \varGamma _i \} \end{array} $$

Predicate instances in \(\kappa \) are sorted by a pair of unfolding number and ordering number where the former has higher priority. The instance \({\small \mathtt{P}}(\bar{v})_u^o\) is selected if \(\mathtt u\) is the smallest number of unfoldings and \(\mathtt o\) is the smallest number among instances which have the same unfolding number \(\mathtt u\). The procedure \(\mathtt{unfold}\) outputs a set of disjuncts which are combined from branches of the predicate \(\mathtt P\) with the remainder \(\kappa {\wedge }\pi \). At the middle, the predicate instance is unfolded by the procedure \(\mathtt {unfoldP}\). This auxiliary procedure \(\mathtt{unfoldP}({\small \mathtt{P}}(\bar{t})^o_u,\pi _{c})\) unfolds the user-defined predicate \(\mathtt P\) with actual parameter \(\bar{t}\) under the context \(\pi _{c}\). It outputs branches of the predicate \(\mathtt P\) that are not inconsistent with the context. It is formalized as follows.

$$ \begin{array}{c} \pi _{c}^{P}\equiv {\varPi }(\pi _{c},\bar{v}) \qquad (\bigvee ^m_{i=1} (\exists \bar{w}_i{\cdot }~\kappa _i {\wedge } \pi _i~|~\pi _i^b), \bar{t}) {=} {\small \mathtt{lookup}}({\mathcal {P}, {\small \mathtt{P}}}) \qquad \bar{w'_i}{=} \textit{fresh}(\bar{w_i}) \\ (\bar{v'},\pi _{eq}){=} {\small \mathtt{freshEQ}}(\bar{v}) \qquad \rho _p {=} [\bar{v'} / \bar{t}] \qquad \rho ^{\exists }_{i} {=} [\bar{w'_i} / \bar{w_i}] \qquad \rho _i {=}\rho _p \circ \rho ^{\exists }_i\\ \hline \mathtt{unfoldP}({\small \mathtt{P}}(\bar{v})_u^o,\pi _{c}) ~{\leadsto }~ \{{\exists } \bar{w}'_i {\cdot }~ [\rho _i] \kappa _i {\wedge } [\rho _i] \pi _i {\wedge } \pi _{eq} ~|~ {\small \mathtt{\mathbf sat_p}}(\pi _{c}^{P}{\wedge }[\rho _i]\pi _i^b{\wedge }\pi _{eq}){\ne }{\small \mathtt{unsat}}, i{\in }1...m \} \end{array} $$

In the first line, the procedure looks up the definition of \(\mathtt P\) and refreshes the existential quantifiers (using the function \(\textit{fresh}(...)\)). In the second line, formal parameters are substituted by the corresponding actual arguments. Finally, the substituted definition is combined and pruned as shown in the RHS of \(\leadsto \). Function \({\small \mathtt{freshEQ}}(\bar{v})\) above refreshes the sequence of variables \(\bar{v}\) and produces the equality constraints \(\pi _{eq}\) between the old and new ones, i.e. \(\pi _{eq}{\equiv }\bigwedge v_i{=}v'_i\). Let \({\small \mathtt{Q}}(\bar{t})^{o_l}_{\_}\) denote a predicate instance of the derived \(\kappa _i\), its unfolding number is set to \(\mathtt u{+}1\) if its corresponding branch \(\varDelta _i\) is recursive. Otherwise, it is \(\mathtt u\). Its sequence number is set to \(o_l{+}o\).

The branch invariant is used as a necessary condition to unfold a branch. The formalism underlying the pruning process is as follows: given a context \(\varDelta _c\) with its over-approximation \(\pi _c\) and a branch \(\varDelta _i\) with its over-approximation \(\pi ^b_i\), if \(\pi _c {\wedge } \pi ^b_i\) is unsatisfiable, so is \(\varDelta _c {*} \varDelta _i\). Similar to the specialization calculus [15], our unfolding mechanism also prunes infeasible disjuncts while unfolding user-defined predicates. However, the specialization calculus performs exhaustive pruning with multiple unfolding that may be highly costly and redundant compared with our one-step unfolding.

Fig. 5.
figure 5

Rules for back-link.

Detecting Cyclic Proof. In the following, we implement the matching function \(\mathtt {f_{cyclic}}\), an instantiation of \(\mathtt {f_{fix}}\), to form a cyclic proof for fixpoint detection. \(\mathtt {f_{cyclic}}\) checks whether there exists a well-founded ordering relation \(R\) between \(\varDelta ^{comp}\) and \(\varDelta ^{bud}\) so as to form an infinite path following the path between these two nodes. If \(\varDelta ^{bud}\) matches with \(\varDelta ^{comp}\), \(\varDelta ^{bud}\) is marked as closed. For global infinitary soundness, \(\mathtt {f_{cyclic}}\) only considers those \(\varDelta ^{bud}\) and \(\varDelta ^{comp}\) of the restricted form as: \(\varDelta ^{comp}{\equiv }\varDelta _{b_1} {*} {\small \mathtt{P_1}}(\bar{t}_1)_m^0{*} ...{*}{\small \mathtt{P_i}}(\bar{t_i})_m^i\), and \(\varDelta ^{bud}{\equiv }\varDelta _{b_2} {*} {\small \mathtt{P_1}}(\bar{t}_1')_n^0{*} ...{*}{\small \mathtt{P_k}}(\bar{t_k'})_n^k\), where \(\mathtt k{\ge }i\), \(\mathtt n{>}m\), \(\varDelta _{b_1}\) and \(\varDelta _{b_2}\) are base formulas.

Like [13], \(\mathtt {f_{cyclic}}\) is implemented using the weakening and substitution principle. In particular, it looks for a substitution \(\theta \) s.t. \(\varDelta ^{bud}\theta ~{\implies }~ \varDelta ^{comp}\). \( {{\small \mathtt{{f_{cyclic}}}}}(\varDelta ^{bud}{,}\varDelta ^{comp})\) is formalized as the procedure \(\varDelta ^{bud}{~\vdash _{lb}~}\varDelta ^{comp}\) whose rules are presented in Fig. 5. These rules are applied as follows.

  • First, existential variables are refreshed (\([\underline{\mathbf{\scriptstyle EX-L}}]\), \([\underline{\mathbf{\scriptstyle EX-R}}]\) rules).

  • Second, inductive variables in \(\varDelta ^{bud}\) are substituted (\([\underline{\mathbf{\scriptstyle SUBST}}]\) rule). This substitution is based on well-ordering relations \(R\). Let \({\small \mathtt{P}}({t})_m^k\) be a predicate instance in \(\varDelta ^{comp}\) and its corresponding subformula in \(\varDelta ^{bud}\) be \(R(s{,}{t})\), then \(s\), \(t\) are inductive variables. Two examples of well-founded relations \(R\) are structural induction for pointer types where \(R(s{,}t)\) iff s is a subterm of t and natural number induction on integers where \(R(s{,}t)\) iff \(0{<}s{<}t\).

  • Third, heaps are exhaustively matched (\([\underline{\mathbf{\scriptstyle PRED-MATCH}}]\) and \([\underline{\mathbf{\scriptstyle PTO-MATCH}}]\) rules) and weakened (\([\underline{\mathbf{\scriptstyle PRED-WEAKEN}}]\) and \([\underline{\mathbf{\scriptstyle PTO-WEAKEN}}]\) rules). Soundness of these rules directly follows from the frame rule [26, 40].

  • Last, back-link is decided via the implication between pure formulas (\([\underline{\mathbf{\scriptstyle PURE}}]\) rule).

5 Soundness and Termination of \(\mathtt {S2SAT_{SL}}\)

In the following, we establish the correctness of \(\mathtt {S2SAT_{SL}}\).

5.1 Soundness

We show that (i) \(\mathtt {S2SAT_{SL}}\) is sound and complete for base formulas; and (ii) the functions \(\mathtt {\small \mathtt{{UA\_test}}}\), \(\mathtt {\small \mathtt{{OA\_test}}}\) and \(\mathtt {link\_back}\) in \(\mathtt {S2SAT_{SL}}\) are sound. These two tasks rely on soundness and completeness of the function \(\mathbf{{\scriptstyle eXPure}}\) over base formulas, soundness of \(\mathbf{{\scriptstyle eXPure}}\) over inductive formulas, and soundness of the function \(\mathtt {f_{cyclic}}\).

Lemma 1

(Equiv-Satisfiable Reduction). Let \(\varDelta {\equiv }\exists \bar{w}{\cdot }{x_1}{{\mapsto }}c_1(\bar{v}_1){*} ...{*} {x_n}{{\mapsto }}c_n(\bar{v}_n){\wedge }\alpha {\wedge } \phi \) be a base formula. \(\varDelta \) is satisfiable iff \({\mathbf{{\scriptstyle eXPure}}}({\varDelta })\) is satisfiable.

The proof is based on structural induction on \(\varDelta \).

Lemma 2

(Over-Approximated Reduction). Given a formula \(\varDelta \) such that the invariants of user-defined predicates appearing in \(\varDelta \) are sound, then

$$ \forall s, h\cdot s,h{\models } \varDelta \implies s{\models } \mathbf{{\scriptstyle eXPure}}(\varDelta ) $$

In the following lemma, we consider the case \(\varGamma {=}\{ \}\) at line 8 of Algorithm 1.

Lemma 3

Given a formula \(\varDelta _0\) and the matching function \(\mathtt {f_{cyclic}}\) as presented in the previous section, \(\varDelta _0\) is \(\mathtt {UNSAT}\) if \(\varGamma {=}\{ \}\) (line 8).

To prove this Lemma, in [30] we show that there is a “trace manifold” which implies the global infinitary soundness (see [11], ch. 7) when a bud is linked back.

Theorem 2

(Soundness). Given a formula \(\varDelta \) and a set of user-defined predicates \(\mathcal {P}\),

  • \(\varDelta \) is satisfiable if \(\mathtt {S2SAT_{SL}}\) returns \(\mathtt {SAT}\).

  • if \(\mathtt {S2SAT_{SL}}\) terminates and returns \(\mathtt {UNSAT}\), \(\varDelta \) is unsatisfiable.

While the soundness of \(\mathtt {SAT}\) queries follows Lemma 1, the soundness of \(\mathtt {UNSAT}\) queries follows Lemmas 2 and 3. As satisfiability for \({\small \mathtt{SLPA}}\) is undecidable [30, 31], there is no guarantee that \(\mathtt {S2SAT_{SL}}\) terminates on all inputs. In the next subsection, we show that \(\mathtt {S2SAT_{SL}}\) terminates for satisfiable formulas in \({\small \mathtt{SLPA}}\) and with certain restrictions on the fragment, \(\mathtt {S2SAT_{SL}}\) always terminates.

5.2 Termination

Termination for SAT. In this paragraph, we show that \(\mathtt {S2SAT_{SL}}\) always terminates when it decides a satisfiable formula. Given a satisfiable formula

$$ \varDelta {\equiv } \exists \bar{w}{\cdot }~{x_1}{{\mapsto }}c_1(\bar{v}_1){*} ...{*} {x_n}{{\mapsto }}c_n(\bar{v}_n) ~{*} {\small \mathtt{P_0}}(\bar{t}_0)_0^0{*} ...{*}{\small \mathtt{P_n}}(\bar{t}_n)_0^n~{\wedge }~\pi $$

There exists a satisfiable base formula \(\varDelta _k\) such as:

$$ \varDelta _k{\equiv } {x_1}{{\mapsto }}c_1(\bar{v}_1){*} ...{*} {x_n}{{\mapsto }}c_n(\bar{v}_n) ~{*}~ \varDelta ^{\small \mathtt{P_0}}_{k_0}{*} ...{*}\varDelta ^{{\small \mathtt{P_n}}}_{k_n}~{\wedge }~\pi $$

where \(\varDelta ^P_k\) (\(k{\ge }0\)) denotes a base formula derived by unfolding the predicate \(\mathtt P\) \(k\) times and then substituting all predicate instances \(\mathtt P\) by \(\mathtt P\)’s base branch. Let \(k_m\) be the maximal number among \(k_0\),..,\(k_n\). The breadth-first unfolding manner in the algorithm \(\mathtt {S2SAT}\) ensures that \(\mathtt {S2SAT_{SL}}\) identifies \(\varDelta _k\) before it encounters the following leaf:

$$ {y_1}{{\mapsto }}c_1(\bar{t}_1){*} ...{*} {y_i}{{\mapsto }}c_i(\bar{t}_i) ~{*}~ {\small \mathtt{P_0}}(\bar{t}_0)_{k_m{+}1}^{\_}{*} ...{*}{\small \mathtt{P_j}}(\bar{t}_j)_{k_m{+}1}^{\_}~{\wedge }~\pi $$

We remark that the soundness of cyclic proof ensures that our \(\mathtt {link\_back}\) function only considers infinitely many unfolding traces. Thus, it never links finite many unfolding traces, i.e., traces connecting the root to satisfiable base leaves, like \(\varDelta _k\).

Decidable Fragment. In the following, we describe universal \({\small \mathtt{SLPA_{ind}}}\), a fragment of \({\small \mathtt{SLPA}}\), for which we prove that \(\mathtt {S2SAT_{SL}}\) always terminates. Compared to \({\small \mathtt{SLPA}}\), universal \({\small \mathtt{SLPA_{ind}}}\) restricts the set of inductive predicates \(\mathcal {P}\) as well as the inputs of \(\mathtt {S2SAT_{SL}}\).

Definition 4

( \({\small \mathtt{SLPA_{ind}}}\) ). An inductive predicate \(\mathtt{pred}~{\small \mathtt{P}}(\bar{t}){\equiv }\varPhi \) is well-founded \({\small \mathtt{SLPA_{ind}}}\) if it has one induction case with \(N\) occurrences of \(\mathtt P\), and it has the shape as follows.

$$\varPhi ~{\equiv }~\varPhi _0 \vee \exists \bar{w} {\cdot } {x_1}{{\mapsto }}c_1(\bar{v}_1){*} ...{*} {x_n}{{\mapsto }}c_n(\bar{v}_n) {*} {\small \mathtt{P}}(\bar{w}_1) {*} ...{*} {\small \mathtt{P}}(\bar{w}_N) {\wedge }\pi $$

where \(\varPhi _0\) is disjunction of base formulas and the two following restrictions.

  1. 1.

    \(\forall n{\in } 1...N ~ \bar{w}_n {\subseteq } \bar{w} {\cup } \{{{\small \mathtt{{null}}}}\}\) and \(\bar{w}_n\) do not appear in the equalities of \(\pi \),

  2. 2.

    if \(t_i\) is a numerical parameter and there exists a well-ordering relation \(R\) such that \(R(s{,}{t_i}{,}w_{1_i}{,}..{,}w_{m_i})\) (\(1{\le }m{\le }N\)) is a subformula of \(\pi \), the following conditions hold.

    • \(t_i\) is constrained separately (i.e., there does not exist \(j{\ne }i\) and a subformula \(\phi \) of \(\pi \) such that \(\{t_i,t_j\} {\subseteq } FV(\phi )\) or \(\{t_i,{w}_{n_j}\} {\subseteq } FV(\phi )\) or \(\{w_{m_i},{w}_{n_j}\} {\subseteq } FV(\phi )\) \(\forall m{,}n{\in } 1...N\), and

    • \(\forall n{\in } 1...N\), \(\pi \implies t_i{>}{w}_{n_i}\) or \(\pi \implies t_i{<}{w}_{n_i}\).

    • if \(t_i {\in } FV(\varPhi _0)\) then \(\varPhi _0 \implies t_i{=}k\), for some integer \(k\)

      \(t_i\) is denoted as inductive parameters.

Restriction 1 guarantees that \(\mathtt {f_{cyclic}}\) can soundly weaken the heap by discarding irrelevant points-to predicates and \(N{-}1\) occurrences of \(\mathtt P\) (when \(N{\ge }2\)) while it links back. Restriction 2 implies that \(t_i{>}w_i{\ge }k_1\) or \(t_i{<}w_i{\le }k_2\) for some integer \(k_1\), \(k_2\). This ensures that leaf nodes of unfolding trees of an unsatisfiable input must be \(\mathtt {UNSAT}\) or linked back.

The above \({\small \mathtt{SLPA_{ind}}}\) fragment is expressive enough to describe a range of data structures, e.g. sorted lists \(\mathtt {sortll}\), lists/trees with size properties, or even AVL trees \(\mathtt {avl}\).

Definition 5

(Universal \({\small \mathtt{SLPA_{ind}}}\) ). Given a separation logic formula

$$ \varDelta _0{\equiv } {x_1}{{\mapsto }}c_1(\bar{v}_1){*} ...{*} {x_n}{{\mapsto }}c_n(\bar{v}_n) {*} {\small \mathtt{P_1}}(\bar{t}_1) {*} ...{*} {\small \mathtt{P_n}}(\bar{t}_n){\wedge }\phi _0 $$

\(\varDelta _0\) is universal \({\small \mathtt{SLPA_{ind}}}\) if all predicates \({\small \mathtt{P_1}}\),..,\({\small \mathtt{P_n}}\) are well-founded \({\small \mathtt{SLPA_{ind}}}\), and if all \(\bar{x}\) of free, arithmetical, inductive variables, with \(\bar{x} {\subseteq } (\bar{t}_1 {\cup } ... {\cup }\bar{t}_n)\), \(\phi _0\) is a conjunction of \(\phi _{0,i}\) where \(\phi _{0,i}\) is either of the following form: (i) \({\small \mathtt{{true}}}\,\); or (ii) \(x_i{\ge }k_1\) for some integer \(k_1\); or (iii) \(x_i{\le }k_2\) for some integer \(k_2\).

Theorem 3

(Termination). \(\mathtt {S2SAT_{SL}}\) terminates for universal \({\small \mathtt{SLPA_{ind}}}\) formulas.

6 Sound Invariant Inference

In order to perform fully automatic verification without user-provided invariants, \(\mathtt {S2SAT_{SL}}\) supports automatic invariant inference. In this section, we describe invariant inference from user-defined predicates and predicate branches. While the former is used for over-approximation, the latter is used for context-sensitive predicate unfolding. To infer invariants for a set of user-defined predicates, we first build a dependency graph among the predicates. After that, we process each group of mutual dependent predicates following a bottom-up manner. For simplicity, we present the inference for one directly recursive predicate. The inference for a group of mutual inductive predicates is similar.

Inferring Predicate Invariant. Our invariant inference is based on the principle of second-order abduction [28, 45]. Given the predicate \(\mathtt P\) defined by m branches as \({\small \mathtt{P}}(\bar{t}) \equiv \bigvee ^m_{i=1} \varDelta _i\), we assume a sound invariant of \(\mathtt P\) as an unknown (second-order) variable \({\small \mathtt{I}}({\bar{t}})\). After that we prove the lemma \({\small \mathtt{P}}(\bar{v}) {{\vdash }} {\small \mathtt{I}}({\bar{v}})\) via induction; and simultaneously generate a set of pure relational assumptions using second-order abduction. The steps to prove the above lemma and generate a set of \(m\) relational assumptions over \(\mathtt I\) are as follows.

  1. 1.

    Unfold LHS of the lemma to generate a set of \(m\) subgoals i.e. \(\varDelta _i[\bar{v}/\bar{t}] {{\vdash }} {\small \mathtt{I}}({\bar{v}})\) where \(i \in 1...m\). The original lemma is taken as the induction hypothesis.

  2. 2.

    For each subgoal \(i\), over-approximate its LHS to a pure formula \(\pi _i\) and form an assumption relation \(\pi _i ~{\implies }~ {\small \mathtt{I}}({\bar{v}})\). There are two cases to compute \(\pi _i\).

    • if \(\varDelta _i\) is a base formula, then \(\pi _i{\equiv }\mathbf{{\scriptstyle eXPure}}({ \varDelta _{i}})\).

    • if \(\varDelta _i\) includes k instances \(\mathtt P\) such that \(\varDelta _i{\equiv }\varDelta _{rest_i}{*} {\small \mathtt{P}}({\bar{v_1}}){*}...{*} {\small \mathtt{P}}({\bar{v_k}})\), then we compute \(\pi _{i_0}{\equiv } \mathbf{{\scriptstyle eXPure}}({ \varDelta _{rest_i}})\), \(\pi _{i_j}{\equiv }{\small \mathtt{I}}({\bar{v_j}})\), for all \(j \in 1...k\), and \(\pi _i{\equiv } \bigwedge _{j{=}1}^k\pi _{i_k}\).

  3. 3.

    Our system applies a least fixed point analysis to the set of gathered relational assumptions. We use the analyzer \(\mathtt LFP\) presented in [45] to compute these invariants.

We illustrate this procedure to infer an invariant for \(\mathtt {sortll}\). First, our system introduces an unknown relation \({\small \mathtt{I}}(\mathtt{root}{,}n{,}m)\). Second, it generates the below relational constraints.

$$ \begin{array}{lcl} \mathtt{root}{\ne }{{\small \mathtt{{null}}}}{\wedge } n{=}1 &{}{\implies }&{} {\small \mathtt{I}}({\mathtt{root}{,}n{,}m}) \\ \mathtt{root}{\ne } {{\small \mathtt{{null}}}}{\wedge }{\small \mathtt{I}}({Q{,}N_1{,}M_1}) {\wedge } n{=}{N_1}{+}1 {\wedge } m{\le }M_1 &{}{\implies }&{} {\small \mathtt{I}}(\mathtt{root}{,}{n}{,}m) \\ \end{array} $$

Finally, it analyzes these two constraints and produces the following result:

$$ {\small \mathtt{I}}(\mathtt{root}{,}{n}{,}m){\equiv }\mathtt{root}{\ne }{{\small \mathtt{{null}}}}{\wedge }n{\ge }1 $$

Lemma 4

(Sound Invariant Inference). Given a predicate \({\small \mathtt{P}}(\bar{t}) ~{\equiv }~ \varPhi \), and \(\mathcal {R}\) be a set of relational assumptions generated by the steps above. If \(\mathcal {R}\) has a solution, i.e., \({\small \mathtt{I}}(\bar{v}){\equiv }\pi \), then we have \( \forall s, h\cdot s, h~{\models }~ {\small \mathtt{P}}(\bar{v})\), \(s~{\models } \pi \).

Proof Sketch: Soundness of Lemma 2 implies that for all \(i \in 1...m\), \(\pi _i\) is an over-approximated abstraction of \(\varDelta _i\). As such, the soundness of this lemma immediately follows from the soundness of second-order abduction [28, 45]. \(\square \)

Inferring Branch Invariant. Given a predicate \(\mathtt P\) defined by m branches as \({\small \mathtt{P}}(\bar{t}) \equiv \bigvee ^m_{i=1} ({\exists } \bar{w_i}{\cdot }~ \varDelta _i) ~~{ \overline{inv}}{:}~ \pi \), we compute invariants for each branch of \(\mathtt P\) as \({\varPi }(\mathbf{{\scriptstyle eXPure}}(\varDelta _i),\bar{w_i})\) \({\forall }~i{=}1...m\). For example, with the invariant inferred for the predicate \(sortll\) as above, our system computes its branch invariants \(\pi ^b_1\) for the base branch and \(\pi ^b_2\) for the inductive branch as below.

$$ \begin{array}{ll} \pi ^b_1\!&{} {\equiv } {\varPi }(\mathbf{{\scriptstyle eXPure}}(\mathtt{root}{\mapsto }node(m,{{\small \mathtt{{null}}}}) \wedge {\small \mathtt{n{=}1}}),\{\}) {\equiv }~{\small \mathtt{\mathtt{root}{\ne }{{\small \mathtt{{null}}}}}} \wedge {\small \mathtt{n{=}1}}\\ \pi ^b_2\!&{} {\equiv } \varPi ({\mathbf{{\scriptstyle eXPure}}( \mathtt{root}{\mapsto }node(m,q) *{\small \mathtt{{sortll}}}(q,n_1,m_1){\wedge }n{=}n_1{+}1{\wedge } m{\le }m_1)},\\ &{} \qquad \!\! {\{q{,}n_1{,}m_1\}}) ~\equiv ~\mathtt{root}{\ne }{{\small \mathtt{{null}}}}\wedge n{\ge }1 \end{array} $$

Soundness of implies that the branch invariant over-approximates its branch.

Table 1. Exponential time and space satisfiability checks.

7 Implementation and Evaluation

We have implemented the proposed solver \(\mathtt {S2SAT_{SL}}\) and a new interprocedural (top-down) program verification tool, called \(\mathtt {S2_{td}}\), which uses \(\mathtt {S2SAT_{SL}}\). We make use of Omega Calculator [38] to eliminate existential quantifiers, Z3 [19] as a back-end SMT solver, and \(\mathtt {FixCalc}\) [37] to find closure form in inferring invariants for user-defined predicates.

In the following, we evaluate \(\mathtt {S2SAT_{SL}}\) and \(\mathtt {S2_{td}}\)’s robustness and efficiency on a set of benchmarks from the software verification competition SV-COMP [7]. We also present an evaluation of \(\mathtt {S2SAT_{SL}}\) in compositional (modular) program verification with the \(\mathtt {HIP/S2}\) system [14, 28] for a range of data structures.

7.1 Robustness and Efficiency

In [12], Brotherston et al. introduced a new and challenging set of satisfiability benchmarks discussed in Proposition 5.13 of [12]. In this Proposition, Brotherston et al. stated that there exists a family of predicates of size \(O\)(n) and that \(\mathtt {SLSAT}\) runs in \(\varOmega (2^n)\) time and space regardless of search strategies. Since \(\mathtt {SLSAT}\) relies on bottom-up and context-insensitive fixed point computation, it has to explore all possible models before answering a query. Their approach is designed for computing invariants of shape predicates rather than satisfiability checks. In contrast, \(\mathtt {S2SAT_{SL}}\) performs top-down and context-sensitive searches, as it is dedicated for satisfiability solving. Moreover, it prunes infeasible disjuncts, significantly reduces the search space, and provides better support for model discovery.

We conducted an experiment on comparing \(\mathtt {SLSAT}\)’s and \(\mathtt {S2SAT_{SL}}\)’s performance on this set of benchmarks. The results are shown in Table 1. The size \(\mathtt n\) of \(\mathtt {succ{-}circuit*}\) (\(\mathtt {succ{-}rec*}\)) benchmarks expresses the breadth (depth, resp.) of dependency. This set of benchmarks is a part of the User-Defined Predicate Satisfiability (UDB_sat) suite of SL-COMP 2014 [41]. The output is either a definite answer (sat, unsat) with running time (in milliseconds (ms), or seconds (s)), or an error. In particular, SO denotes stack overflow; TO denotes timeout (i.e., tools run longer than 1800 s); and X denotes a fatal error. The experimental results show that \(\mathtt {S2SAT_{SL}}\) is much more robust and also more efficient than \(\mathtt {SLSAT}\). While \(\mathtt {S2SAT_{SL}}\) successfully solved 24 (out of 40) benchmarks, \(\mathtt {SLSAT}\) was capable of handling 17 benchmarks. Furthermore, on 17 benchmarks that \(\mathtt {SLSAT}\) discharged successfully, \(\mathtt {S2SAT_{SL}}\) outperforms \(\mathtt {SLSAT}\), i.e., about 6.75 (3126 s/462 s) times faster. As shown in the table, \(\mathtt {S2SAT_{SL}}\) ran with neither stack overflow nor fatal errors over all these challenging benchmarks.

Table 2. Experimental results on complex data structures.

7.2 Modular Verification with \(\mathtt {S2SAT_{SL}}\)

In this subsection, we evaluate \(\mathtt {S2SAT_{SL}}\) in the context of modular program verification. \(\mathtt {S2SAT_{SL}}\) solver is integrated into the \(\mathtt {HIP/S2}\) [14, 28, 29] system to prune infeasible program paths in symbolic execution. Furthermore, \(\mathtt {S2SAT_{SL}}\) is also used by the entailment procedure \(\mathtt {SLEEK}\) to discharge verification conditions (VC) generated. In particular, when \(\mathtt {SLEEK}\) deduces a VC to the following form: \(\varDelta ~ {{\vdash }}~ \mathtt{emp}{\wedge }\pi _r\), the error calculus in \(\mathtt {SLEEK}\) [29] invokes \(\mathtt {S2SAT_{SL}}\) to discharge the following queries: \(\varDelta \) and \(\varDelta {\wedge }\lnot \pi _r\) for safety and \(\varDelta {\wedge }\pi _r\) for must errors. In experiments, we have extracted those VCs generated while \(\mathtt {HIP/S2}\) verified heap-manipulating programs.

We have evaluated \(\mathtt {S2SAT_{SL}}\) deciding the VCs discussed above. The experimental results are described in Table 2. Each line shows a test on one program. The first column lists data structures and their pure properties. are trees with nodes that are allowed to have a variable number of children, stored as doubly-linked lists. TLL is a binary tree whose nodes point to their parents and all leaf nodes are linked as a singly-linked list. #Query is the number of satisfiability queries sent to \(\mathtt {S2SAT_{SL}}\) for each data structure. The next two columns report the outputs from \(\mathtt {S2SAT_{SL}}\). The last column shows the time (in seconds) taken by the \(\mathtt {{{\small \mathtt{{S2SAT_{SL}}}}}}\) solver. In this experiment, \(\mathtt {S2SAT_{SL}}\) terminated on all queries. Furthermore it exactly decided all \(\mathtt {SAT}\) and \(\mathtt {UNSAT}\) queries. These experimental results affirm the correctness of our algorithm \(\mathtt {S2SAT_{SL}}\). They also show that \(\mathtt {S2SAT_{SL}}\) is expressive, effective, and can be integrated into program verification systems for discharging satisfiability problems of separation logic formulas.

7.3 Recursive Program Verification with \(\mathtt {S2SAT_{SL}}\)

Table 3. Experimental results on recursive programs.

We have evaluated and compared our verification system \(\mathtt {S2_{td}}\) with state-of-the-art verification tools on a set of SV-COMP benchmarksFootnote 1. The results are presented in Table 3. There are 102 recursive/loop programs taken from Recursive and HeapReach sub-categories in the benchmark; timeout is set to 180 s. In each program, there is at least one user-supplied assertion to model safety properties. The first column identities the subset of verification systems which competed in both the above sub-categories. The next three columns count the instances of correct safe (s\(\surd \)), correct error (e\(\surd \)) and unknown (e.g., timeout). The next two columns capture the number of false positives (s✗) and false negatives (e✗). We rank these tools based on their points. Following the SV-COMP competition, we gave +2 for one s\(\surd \), +1 for one e\(\surd \), 0 for unk, \(-16\) for one s✗, and \(-32\) for one e✗. The last column expresses the total time in minutes. The results show that the proposed verification approach is promising; indeed, our system is effective and efficient: it produces the best correctness with zero false answers within the nearly-shortest time.

8 Related Work

Close to our work is the SeaHorn verification system [22]. While SeaHorn relies on Z3-PDR to handle inductive predicates on non-heap domains, it is unclear (to us) how SeaHorn supports induction reasoning for heap-based programs (which is one contribution of our present work).

Our \(\mathtt {S2SAT}\) satisfiability procedure is based on unfolding which is similar to the algorithm in the Leon system [43, 44]. Leon, a verifier for functional programs, adds an unfolding mechanism for inductive predicates into complete theories. However, Leon only supports classic logic and not structural logic (i.e., separation logic). Neither does Leon support inductive reasoning. Furthermore, our system infers sound invariants for inductive predicates to facilitate over-approximation.

Our work is related to work on developing satisfiability solvers in separation logic. In the following, we summarize the development in this area. Smallfoot [5] has the first implemented decision procedure for a fragment of separation logic. This solver was originally customized to work with spatial formulas over list segments. Based on a fixed equality (disequality) constraint branches of the list segment, the proposals presented by [17, 32] further enhanced decision procedure for this fragment with equality reasoning. They provided normalization rules with a graph technique [17] and a superposition calculus [32] to infer (dis)equality constraints on pointers and used these constraints to prune infeasible branches of predicate instances during unfolding. Although these proposals can decide the formula of that fragment in polynomial time, it is not easy to extend them to a fragment with general inductive predicates (i.e., the fragment \({\small \mathtt{SLPA}}\)). Decision procedures in [3336] support decidable fragments of separation logic with inter-reachable data structures using SMT. Our proposal extends these procedures to those fragments with general inductively-defined predicates. Indeed, our decidable fragment can include more complex data structures, such as AVL trees.

\(\mathtt {S2SAT_{SL}}\) is closely related to the satisfiability solvers [12, 25] which are capable of handling separation logic formulas with general user-defined predicates. Decision procedures [12, 25] are able to handle predicates without pure properties. The former described a decidable fragment of user-defined predicates with bounded tree width. The problem of deciding separation logic formulas is then reduced to monadic second-order logic over graphs. The latter, \(\mathtt {SLSAT}\), decides formulas with user-defined predicates via a equi-satisfiable fixed point calculation. The main disadvantage of \(\mathtt {SLSAT}\) is that it is currently restricted to the domain of pointer equality and disequality, so that it cannot be used to support predicates with pure properties from infinite abstract domains.

Using over-approximation in decision procedures is not new. For example, D’Silva et al. have recently made use of abstract domains inside satisfiability solvers [20, 21]. In separation logic, satisfiability procedures in \(\mathtt {HIP/SLEEK}\) [14] and Dryad [39] decide formulas via a sound reduction that over-approximates predicate instances. \(\mathtt {HIP/SLEEK}\) and Dryad are capable of proving the validity of a wide range of expressive formulas with arbitrary predicates. However, expressivity comes with cost; as these procedures are incomplete, and they do not address the satisfiability problem. We believe that \(\mathtt {S2SAT}\) can be integrated into these systems to improve upon these two shortcomings.

9 Conclusion and Future Work

We have presented a satisfiability procedure for an expressive fragment of separation logic. Given a formula, our procedure examines both under-approximation (so as to prove \(\mathtt {SAT}\)) and over-approximation (so as to prove \(\mathtt {UNSAT}\)). Our procedure was strengthened with invariant generation and cyclic proof detection. We have also implemented a solver and a new verification system for heap-manipulating programs. We have evaluated them on a range of competition problems with either complex heap usage patterns or exponential complexity of time and space.

For future work, we might investigate \(\mathtt {S2SAT}\)-based decision procedures for other complete theories (i.e., Presburger, string, bag/set) augmented with inductive predicates. We would also study a more general decidable fragment of separation logic by relaxing the restrictions for termination. Finally, we would like to improve \(\mathtt {S2_{td}}\) for array, string and pointer arithmetic reasoning as well as witness generation for erroneous programs.