Hostname: page-component-586b7cd67f-gb8f7 Total loading time: 0 Render date: 2024-12-07T22:07:40.358Z Has data issue: false hasContentIssue false

Three improvements to the top-down solver

Published online by Cambridge University Press:  03 February 2022

Helmut Seidl*
Affiliation:
TU München, Garching, Germany
Ralf Vogler
Affiliation:
TU München, Garching, Germany
*
*Corresponding author. Email: seidl@in.tum.de
Rights & Permissions [Opens in a new window]

Abstract

The local solver TD is a generic fixpoint engine which explores a given system of equations on demand. It has been successfully applied to the interprocedural analysis of procedural languages. The solver TD gains efficiency by detecting dependencies between unknowns on the fly. This algorithm has been recently extended to deal with widening and narrowing as well. In particular, it has been equipped with an automatic detection of widening and narrowing points. That version, however, is only guaranteed to terminate under two conditions: only finitely many unknowns are encountered, and all right-hand sides are monotonic. While the first condition is unavoidable, the second limits the applicability of the solver. Another limitation is that the solver maintains the current abstract values of all encountered unknowns instead of a minimal set sufficient for performing the iteration. By consuming unnecessarily much space, interprocedural analyses may not succeed on seemingly small programs. In the present paper, we therefore extend the top-down solver TD in three ways. First, we indicate how the restriction to monotonic right-hand sides can be lifted without compromising termination. We then show how the solver can be tuned to store abstract values only when their preservation is inevitable. Finally, we also show how the solver can be extended to side-effecting equation systems. Right-hand sides of these may not only provide values for the corresponding left-hand side unknowns but at the same time produce contributions to other unknowns. This practical extension has successfully been used for a seamless combination of context-sensitive analyses (e.g., of local states) with flow-insensitive analyses (e.g., of globals).

Type
Paper
Creative Commons
Creative Common License - CCCreative Common License - BY
This is an Open Access article, distributed under the terms of the Creative Commons Attribution licence (http://creativecommons.org/licenses/by/4.0/), which permits unrestricted re-use, distribution and reproduction, provided the original article is properly cited.
Copyright
© The Author(s), 2022. Published by Cambridge University Press

1. Introduction

Static analysis tools based on abstract interpretation are complicated software systems. They are complicated due to the complications of programming language semantics and the subtle invariants required to achieve meaningful results. They are also complicated when dedicated analysis algorithms are required to deal with certain types of properties, for example, one algorithm for inferring points-to information and another for checking array-out-of-bounds accesses. Thus, static analysis tools themselves are subject to subtle programming errors. From a software engineering perspective, it is therefore meaningful to separate the specification of the analysis as much as possible from the algorithm solving the analysis problem for a given program. In order to be widely applicable, this algorithm therefore should be as generic as possible.

In abstract interpretation-based static analysis, the analysis of a program, notably an imperative or object-oriented program, can naturally be compiled into a system of abstract equations. The unknowns represent places where invariants regarding the reaching program states are desired. Such unknowns could be, for example, plain program points or, in case of interprocedural analysis, program points together with abstract calling contexts. The sets of possible abstract values for these unknowns then correspond to classes of possible invariants and typically form complete lattices. Since for infinite complete lattices of abstract values the number of calling contexts is also possibly infinite, interprocedural analysis has generally to deal with infinite systems of equations. It turns out, however, that in this particular application, only the values of those unknowns are of interest that directly or indirectly influence some initial unknown. Here, local solving comes as a rescue: a local solver can be started with one initial unknown of interest. It then explores only those unknowns whose values contribute to the value of this initial unknown.

One such generic local solver is the top-down solver TD (Charlier and Van Hentenryck Reference Charlier and Van Hentenryck1992; Muthukumar and Hermenegildo Reference Muthukumar and Hermenegildo1990). Originally, the TD solver has been conceived for goal-directed static analysis of Prolog programs (Hermenegildo and Muthukumar Reference Hermenegildo and Muthukumar1989; Hermenegildo and Muthukumar Reference Hermenegildo and Muthukumar1992) while some basic ideas can be traced back to Bruynooghe et al. (Reference Bruynooghe, Janssens, Callebaut and Demoen1987). The same technology as developed for Prolog later turned out to be useful for imperative programs as well (Hermenegildo Reference Hermenegildo2000) and also was applied to other languages via translation to CLP programs (Gallagher and Henriksen Reference Gallagher and Henriksen2006; Hermenegildo et al. Reference Hermenegildo, Mendez-Lojo and Navas2007). A variant of it is still at the heart of the program analysis system Ciao (Hermenegildo et al. Reference Hermenegildo, Bueno, Carro, LÓpez-GarcÍa, Mera, Morales and Puebla2012; Hermenegildo et al. Reference Hermenegildo, Puebla, Bueno and LÓpez-GarcÍa2005). The TD solver is interesting in that it completely abandons specific data structures such as work-lists, but solely relies on recursive evaluation of (right-hand sides of) unknowns.

Subsequently, the idea of using generic local solvers has also been adopted for the design and implementation of the static analysis tool Goblint (Vojdani et al. Reference Vojdani, Apinis, RÕtov, Seidl, Vene and Vogler2016) – now targeted at the abstract interpretation of multi-threaded C. Since the precise analysis of programming languages requires to employ abstract domains with possibly infinite ascending or descending chains, the solvers provided by Goblint were extended with support for widening as well as narrowing (Amato et al. Reference Amato, Scozzari, Seidl, Apinis and Vojdani2016). Recall that widening and narrowing operators have been introduced in Cousot and Cousot (Reference Cousot, Cousot, Graham, Harrison and Sethi1977); Cousot and Cousot (Reference Cousot and Cousot1992) as an iteration strategy for solving finite systems of equations: in a first phase, an upward Kleene fixpoint iteration is accelerated to obtain some post-solution, which then, in a second phase, is improved by an accelerated downward iteration. This strict separation into phases is given up by the algorithms from Amato et al. (Reference Amato, Scozzari, Seidl, Apinis and Vojdani2016). Instead, the ascending iteration on one unknown is possibly intertwined with the descending iteration on another. The idea is to avoid unnecessary loss of precision by starting an improving iteration on an unknown as soon as an over-approximation of the least solution for this unknown has been reached. On the downside, the solvers developed in Amato et al. (Reference Amato, Scozzari, Seidl, Apinis and Vojdani2016) are only guaranteed to terminate for monotonic systems of equations. Systems for interprocedural analysis, however, are not necessarily monotonic. The problems concerning termination as encountered by the non-standard local iteration strategy from Amato et al. (Reference Amato, Scozzari, Seidl, Apinis and Vojdani2016) were resolved in Frielinghaus et al. (Reference Frielinghaus, Seidl and Vogler2016) where the switch from a widening to a narrowing iteration for an unknown was carefully redesigned.

When reviewing advantages and disadvantages of local generic solvers for Goblint, we observed in Apinis et al. (Reference Apinis, Seidl, Vojdani, Probst, Hankin and Hansen2016) that the extension with widening and narrowing from Amato et al. (Reference Amato, Scozzari, Seidl, Apinis and Vojdani2016) could nicely be applied to the solver TD as well – it was left open, though, how termination can be enforced not only for monotonic, but for arbitrary systems of equations. In this paper, we settle this issue and present a variant of the TD solver with widening and narrowing which is guaranteed to terminate for all systems of equations – whenever only finitely many unknowns are encountered.

Besides termination, another obstacle for the practical application of static analysis is the excessive space consumption incurred by storing abstract values for all encountered unknowns. Storing all these values allows interprocedural static analysis tools like Goblint only to scale to medium-sized programs of about 100k LOC. This is in stark contrast to the tool AstrÉe, which – while also being implemented in OCaml – succeeds in analyzing much larger input programs (Cousot et al. Reference Cousot, Cousot, Feret, Mauborgne, MinÉ and Rival2009). One reason is that AstrÉe only keeps the abstract values of a subset of unknowns in memory, which is sufficient for proceeding with the current fixpoint iteration. As our second contribution, we therefore show how to realize a space-efficient iteration strategy within the framework of generic local solvers. Unlike AstrÉe which iterates over the syntax, generic local solvers are application-independent. They operate on systems of equations – no matter where these are extracted from. As right-hand sides of equations are treated as black boxes, inspection of the syntax of the input program is out of reach. Since for local solvers the set of unknowns to be considered is only determined during the solving iteration, also the subset of unknowns sufficient for reconstructing the analysis result must be identified on the fly. Our idea for that purpose is to instrument the solver to observe when the value of an unknown is queried, for which an iteration is already on the way. For equation systems corresponding to standard control-flow graphs, these unknowns correspond to the loop heads and are therefore ideal candidates for widening and narrowing to be applied. That observation was already exploited in Apinis et al. (Reference Apinis, Seidl, Vojdani, Probst, Hankin and Hansen2016) to identify a set of unknowns where widening and narrowing should be applied. The values of these unknowns also suffice for reconstructing the values of all remaining unknowns without further iteration. Finally, we present an extension of the TD solver with side effects. Side effects during solving means that, while evaluating the right-hand side for some unknown, contributions to other unknowns may be triggered. This extension allows to nicely formulate partial context-sensitivity at procedure calls and also to combine flow-insensitive analysis, for example, of global data, with context-sensitive analysis of the local program state (Apinis et al. Reference Apinis, Seidl, Vojdani and Igarashi2012). The presented solvers have been implemented as part of Goblint, a static analysis framework written in OCaml.

This paper is organized as follows: First, we recall the basics of abstract interpretation. In Section 2, we show how the concrete semantics of a program can be defined using a system of equations with monotonic right-hand sides. In Section 3, we describe how abstract equation systems can be used to compute sound approximations of the collecting semantics. Since the sets of unknowns of concrete and abstract systems of equations may differ, we argue that soundness can only be proven relative to a description relation between concrete and abstract unknowns. We also recall the concepts of widening and narrowing and indicate in which sense these can be used to effectively solve abstract systems of equations. In Section 4, we present the generic local solver $\mathbf{TD}_{\mathbf{term}}$ . In Section 5, we show that it is guaranteed to terminate even for abstract equation systems with non-monotonic right-hand sides. In Section 6, we prove that it will always compute a solution that is a sound description of the least solution of the corresponding concrete system. In Section 7, we present the solver $\mathbf{TD}_{\mathbf{space}}$ , a space-efficient variation of $\mathbf{TD}_{\mathbf{term}}$ that only keeps values at widening points. In Section 8, we prove that it terminates and similar to $\mathbf{TD}_{\mathbf{term}}$ computes a sound description of the least solution of the corresponding concrete system. In Section 9, we introduce side-effecting systems of equations, and the solver $\mathbf{TD}_{\mathbf{side}}$ , a variation of $\mathbf{TD}_{\mathbf{term}}$ . In order to argue about soundness in presence of side effects, it is now convenient to consider description relations between concrete and abstract unknowns which are not static, but dynamically computed during fixpoint iteration, that is, themselves depend on the analysis results. In Section 10, we discuss the results of evaluating the solvers on a set of programs. In Section 11, we summarize our main contributions. Throughout the presentation, we exemplify our notions and concepts by small examples from interprocedural program analysis.

2. Concrete Systems of Equations

Solvers are meant to provide solutions to systems of equations over some complete lattice. Assume that $\mathcal{X}$ is a (not necessarily finite) set of unknowns and $\mathbb{D}$ a complete lattice. Then, a system of equations $\mathbb{E}$ (with unknowns from $\mathcal{X}$ and values in $\mathbb{D}$ ) assigns a right-hand side $F_x$ to each unknown $x\in\mathcal{X}$ . Since we not only interested in the values assigned to unknowns but also in the dependencies between these, we assume that each right-hand side $F_x$ is a function of type $(\mathcal{X} \to \mathbb{D}) \to \mathbb{D} \times \mathcal{P} (\mathcal{X})$ .

  • The first component of the result of $F_x$ is the value for x;

  • The second component is a set of unknowns which is sufficient to determine the value.

More formally, we assume for the second component of $F_x$ that for every assignment $\sigma:\mathcal{X}\to\mathbb{D}$ , $F_x\,\sigma = (d,X)$ implies that for any other assignment $\sigma':\mathcal{X}\to\mathbb{D}$ with $\sigma|_X = \sigma'|_X$ , that is, which agrees with $\sigma$ on all unknowns from $\mathcal{X}$ , $F_x\,\sigma = F_x\,\sigma'$ holds as well.

The set X thus can be considered as a superset of all unknowns onto which the unknown x depends – w.r.t. the assignment $\sigma$ . We remark this set very well may be different for different assignments. For convenience, we denote these two components of the result $F_x\,\sigma$ as $(F_x\,\sigma)_1$ and $(F_x\,\sigma)_2$ , respectively.

Systems of equations can be used to formulate the concrete (accumulating) semantics of a program. In this case, the complete lattice $\mathbb{D}$ is of the form $\mathcal{P} (\mathcal{Q})$ where $\mathcal{Q}$ is the set of states possibly attained during program execution. Furthermore, all right-hand sides of the concrete semantics should be monotonic w.r.t. the natural ordering on pairs. This means that on larger assignments, the sets of states for x as well as the set of contributing unknowns may only increase.

Example 1 For $\mathcal{X} = \mathcal{Q} = \mathbb{Z},$ a system of equations could have right-hand sides $F_x:(\mathbb{Z} \to \mathcal{P} (\mathbb{Z})) \to (\mathcal{P} (\mathbb{Z}) \times \mathcal{P} (\mathbb{Z}))$ where, for example,

\begin{align*} F_1\,\sigma &= (\{0\}, \unicode{x00D8}) \\ F_2\,\sigma &= (\sigma\,1 \cup \sigma\,3, \{1,3\})\end{align*}

Thus, $F_1$ always returns the value $\{0\}$ and accordingly depends on no other unknown, $F_2$ on the other hand returns the union of the values of unknowns 1 and 3. Therefore, it depends on both of them.

Example 2 As a running example, consider the program from Figure 1 which consists of the procedures main and p. Assume that these operate on a set $\mathcal{Q}$ of program states where the functions $h_1, h_2: \mathcal{Q} \to \mathcal{P} (\mathcal{Q})$ represent the semantics of basic computation steps. The collecting semantics for the program provides subsets of states for each program point $u \in {0, \ldots, 7}$ and each possible calling context $q \in \mathcal{Q}$ . The right-hand side function $F_{{\langle{u,q}\rangle}}$ for each such pair ${\langle{u,q}\rangle}$ is given by:

\begin{equation*}\begin{array}{lll}F_{{\langle{0,q}\rangle}}\,\sigma &=& (\{q\},\unicode{x00D8}) \\[0.5ex]F_{{\langle{1,q}\rangle}}\,\sigma &=& (\cup{h_1(q')\mid q'\in\sigma\,{\langle{0,q}\rangle}},\;{{\langle{0,q}\rangle}}) \\[0.5ex]F_{{\langle{2,q}\rangle}}\,\sigma &=& ({\textsf{combine}\,q_1\,q_2\mid q_1\in\sigma\,{\langle{1,q}\rangle}, q_2\in\sigma\,{\langle{7,q_1}\rangle}}, \\ & &\phantom{(}{{\langle{1,q}\rangle}}\cup{{\langle{7,q_1}\rangle}\mid q_1\in\sigma\,{\langle{1,q}\rangle}}) \\[0.5ex] F_{{\langle{3,q}\rangle}}\,\sigma &=& ({q},\unicode{x00D8}) \\[0.5ex]F_{{\langle{4,q}\rangle}}\,\sigma &=& (\sigma\,{\langle{3,q}\rangle},\;{{\langle{3,q}\rangle}}) \\[0.5ex]\end{array}\end{equation*}
\begin{equation*}\begin{array}{lll}F_{{\langle{5,q}\rangle}}\,\sigma &=& (\cup{h_1(q_1)\mid q_1\in\sigma\,{\langle{4,q}\rangle}},{{\langle{4,q}\rangle}}) \\[0.5ex]F_{{\langle{6,q}\rangle}}\,\sigma &=& (\sigma\,{\langle{3,q}\rangle},\;{{\langle{3,q}\rangle}}) \\[0.5ex]F_{{\langle{7,q}\rangle}}\,\sigma &=& ({\textsf{combine}\,q_1\,q_2\mid q_1\in\sigma\,{\langle{5,q}\rangle}, q_2\in\sigma\,{\langle{7,q_1}\rangle}} \\ & &\phantom{(} \cup\,{h_2(q_1)\mid q_1\in\sigma\,{\langle{6,q}\rangle}}, \\ & &\phantom{(} {{\langle{5,q}\rangle}}\cup{{\langle{7,q_1}\rangle}\mid q_1\in\sigma\,{\langle{5,q}\rangle}}\cup {{\langle{6,q}\rangle}}) \end{array}\end{equation*}

Recall that in presence of local scopes of program variables, the state after a call may also depend on the state before the call. Accordingly, we use an auxiliary function $\textsf{combine}:\mathcal{Q}\to \mathcal{Q}\to \mathcal{Q}$ which determines the program state after a call from the state before the call and the state attained at the end of the procedure body. For simplicity, we have abandoned an extra function enter for modeling passing of parameters as considered, e.g., in Apinis et al. (Reference Apinis, Seidl, Vojdani and Igarashi2012) and thus assume that the full program state of the caller before the call is passed to the callee. If the set $\mathcal{Q}$ is of the form $\mathcal{Q} = \mathcal{A}\times\mathcal{B}$ where $\mathcal{A},\mathcal{B}$ are the sets of local and global states, respectively, the function $\textsf{combine}$ could, for example, be defined as

\begin{equation*}\textsf{combine}\,(a_1,b_1)\,(a_2,b_2) = (a_1,b_2)\end{equation*}

We remark that the sets of unknowns onto which the right-hand sides for ${\langle{2,q}\rangle}$ as well as ${\langle{7,q}\rangle}$ depend themselves depend on the assignment $\sigma$ .

Figure 1. An example program with procedures.

Besides the concrete values provided for the unknowns, we also would like to determine the subset of unknowns which contribute to a particular subset of unknowns of interest. Restricting to this subset has the practical advantage that calculation may be restricted to a perhaps quite small number of unknowns only. Also, that subset in itself contains some form of reachability information. For interprocedural analysis, for example, of the program in Example 2, we are interested in all pairs ${\langle\textsf{ret}_{\textit{main}},q\rangle},q\in Q_0$ , that is, the endpoint of the initially called procedure main for every initial calling context $q\in Q_0$ . In the Example 2, these would be of the form ${\langle{2,q}\rangle}, q\in Q_0$ . In order to determine the sets of program states for the unknowns of interest, it suffices to consider only those calling contexts for each procedure p (and thus each program point within p) in which p is possibly called. The set of all such pairs is given as the least subset of unknowns which (directly or indirectly) influences any of the unknowns of interest.

More technically for a system $\mathbb{E}$ of equations, consider an assignment $\sigma : \mathcal{X}\to \mathbb{D}$ and a subset $\textit{dom}\subseteq\mathcal{X}$ of unknowns. Then, $\textit{dom}$ is called $(\sigma,\mathbb{E})$ -closed if $(F_x\,\sigma)_2\subseteq\textit{dom}$ is satisfied for all $x\in\textit{dom}$ . The pair $(\sigma,\textit{dom})$ is called a partial post-solution of $\mathbb{E}$ , if $\textit{dom}$ is $(\sigma,\mathbb{E})$ -closed, and for each $x\in\textit{dom}$ , $\sigma\,x\sqsupseteq(F_x\,\sigma)_1$ holds.

The partial post-solution $(\sigma,\textit{dom})$ of $\mathbb{E}$ is total (or just a post-solution of $\mathbb{E}$ ) if $\textit{dom}=\mathcal{X}$ . In this case, we also skip the second component in this pair.

Example 3 Consider the following system of equations with $\mathcal{X} = \mathcal{Q} = \mathbb{Z}$ where

\begin{align*} F_1\,\sigma &= (\sigma\,2, \{2\}) \\ F_2\,\sigma &= (\{2\}, \unicode{x00D8}) \\ F_3\,\sigma &= (\{3\}, \unicode{x00D8}) \\ F_x\,\sigma &= (\unicode{x00D8},\unicode{x00D8})\qquad\text{otherwise}\end{align*}

Assume we are given the set of unknowns of interest $X = \{1\}$ . Then, $(\sigma,\textit{dom})$ with $\textit{dom}=\{1,2\}$ , $\sigma\,1 = \{2\}$ and $\sigma\,2= \{2\}$ is the least partial post-solution with $X\subseteq\textit{dom}$ . For $X' = \{1,3\}$ on the other hand, the least partial solution $(\sigma',\textit{dom}')$ with $X'\subseteq\textit{dom}'$ is given by $\textit{dom}'=\{1,2,3\}$ and $\sigma'\,1 = \{2\}, \sigma'\,2 = \{2\}$ and $\sigma'\,3 = \{3\}$ . For monotonic systems such as those used for representing the collecting semantics, and any set $X\subseteq\mathcal{X}$ of unknowns of interest, there always exists a least partial solution comprising X.

Proposition 1 Assume that the system $\mathbb{E}$ of equations is monotonic. Then for each subset $X\subseteq{\mathcal{X}}$ of unknowns, consider the set P of all partial post-solutions $(\sigma',{\textit{dom}}')\in (({\mathcal{X}}\to \mathbb{D})\times \mathcal{P} (\mathcal{X}))$ so that $X\subseteq{\textit{dom}}'$ . Then, P has a unique least element $(\sigma{}_X,{\textit{dom}}_X)$ . In particular, $\sigma{}_X\,x'=\bot$ for all $x'\not\in\textit{dom}{}_X$ .

Proof. Consider the complete lattice $\mathbb{L} = (\mathcal{X}\to\mathbb{D})\times \mathcal{P} (\mathcal{X})$ . The system $\mathbb{E}$ defines a function $F:\mathbb{L}\to\mathbb{L}$ by $F(\sigma{}_1,X_1) = (\sigma{}_2,X_2)$ where

\begin{equation*}\begin{array}{lll}X_2 &=& X\cup X_1 \cup\bigcup\{ (F_x\,\sigma{}_1)_2 \mid x\in X_1\} \\\sigma{}_2\,x &=& (F_x\,\sigma{}_1)_1 \qquad\text{for}\;x\in X_1\;\text{and}\;\bot\;\text{otherwise}\end{array}\end{equation*}

Since each right-hand side $F_x$ in $\mathbb{E}$ is monotonic, so is the function F. Moreover, $(\sigma,\textit{dom})$ is a post-fixpoint of F iff $(\sigma,\textit{dom})$ is a partial post-solution of $\mathbb{E}$ with $X\subseteq\textit{dom}$ . By the fixpoint theorem of Knaster-Tarski, F has a unique least post-fixpoint – which happens to be also the least fixpoint of F.

For a given set X, there thus is a least partial solution of $\mathbb{E}$ with a least domain $\textit{dom}{}_X$ comprising X. Moreover for $X\subseteq X'$ and least partial solutions $(\sigma_X,\textit{dom}{}_X),(\sigma_{X'},\textit{dom}{}_{X'})$ comprising X and X’, respectively, we have $\textit{dom}{}_{X}\subseteq\textit{dom}{}_{X'}$ and $\sigma_{X'}\,x = \sigma_X\,x$ for all $x\in\textit{dom}{}_X$ . In particular, this means for the least total solution $\sigma$ that $\sigma_X\,x = \sigma\,x$ whenever $x\in\textit{dom}{}_X$ .

Example 4. Consider the program from Example 2. Assume that $\mathcal{Q}=\{q_0,q_1,q_2\}$ where the set of initial calling contexts is given by $\{q_1\}$ . Accordingly, the set of unknowns of interest is given by $X=\{{\langle2,q_1\rangle}\}$ (2 being the return point of main). Assume that the functions $h_1,h_2$ are given by

\begin{equation*}\begin{array}{lll}h_1 &=& \{ q_0\mapsto\unicode{x00D8}, q_1\mapsto\{q_2\}, q_2\mapsto\{q_0\}\} \\h_2 &=& \{ q_0\mapsto\{q_0\}, q_1\mapsto\unicode{x00D8}, q_2\mapsto\unicode{x00D8}\}\end{array}\end{equation*}

while the function combine always returns its second argument, that is, $\textsf{combine}\,q\,q' = q'$ .

Let $\textit{dom}$ denote the set

\begin{equation*}\begin{array}{l}\{ {\langle0,q_1\rangle}, {\langle1,q_1\rangle}, {\langle2,q_1\rangle}, \\\;\;{\langle3,q_0\rangle}, {\langle4,q_0\rangle}, {\langle5,q_0\rangle}, {\langle6,q_0\rangle}, {\langle7,q_0\rangle}, \\\;\;{\langle3,q_2\rangle}, {\langle4,q_2\rangle}, {\langle5,q_2\rangle}, {\langle6,q_2\rangle}, {\langle7,q_2\rangle} \}. \\\end{array}\end{equation*}

Together with the assignment $\sigma : \textit{dom}\to \mathcal{P} (\mathcal{Q})\times \mathcal{P} (\mathcal{X})$ as shown in Figure 2, we obtain the least partial solution of the given system of equations which we refer to as the collecting semantics of the program. We remark that $\textit{dom}$ only has calls of procedure p for the calling contexts $q_0$ and $q_2$ .

Figure 2. The collecting semantics.

3. Abstract Systems of Equations

Systems of abstract equations are meant to provide sound information for concrete systems. In order to distinguish abstract systems from concrete ones, we usually use superscripts $\sharp$ at all corresponding entities. Abstract systems of equations differ from concrete ones in several aspects:

  • Right-hand side functions need no longer be monotonic.

  • Right-hand side functions should be effectively computable and thus may access the values only of finitely many other unknowns in the system (which need not be the case for concrete systems).

Example 5. Consider again the program from Figure 1 consisting of the procedures main and p. Assume that the abstract domain is given by some complete lattice $\mathbb{D}$ where the functions $h^\sharp_1 , h^\sharp_2 : \mathbb{D}\to\mathbb{D}$ represent the semantics of basic computation steps. The abstract semantics for the program provides an abstract state in $\mathbb{D}$ for each pair ${\langle u,a\rangle}$ (u program point from $\{ 0,\ldots,7\}$ , a possible abstract calling context from $\mathbb{D}$ ). The right-hand sides $F^\sharp{}_{{\langle u,a\rangle}}$ then are given by:

\begin{equation*}\begin{array}{lll}F^\sharp{}_{{\langle0,a\rangle}}\,\sigma &=& (a,\unicode{x00D8}) \\[0.5ex]F^\sharp{}_{{\langle1,a\rangle}}\,\sigma &=& (h^\sharp{}_1\,(\sigma{\langle0,a\rangle}),\;{{\langle0,a\rangle}}) \\[0.5ex]F^\sharp{}_{{\langle2,a\rangle}}\,\sigma &=& (\textsf{combine}^\sharp\, (\sigma{\langle1,a\rangle})\, (\sigma{\langle7,\sigma{\langle1,a\rangle}\rangle}),\\[0.5ex] & & \quad{{\langle1,a\rangle},{\langle7,\sigma{\langle1,a\rangle}\rangle}}) \\[0.5ex]F^\sharp{}_{{\langle3,a\rangle}}\,\sigma &=& (a,\unicode{x00D8}) \\[0.5ex]F^\sharp{}_{{\langle4,a\rangle}}\,\sigma &=& (\sigma{\langle3,a\rangle},\;{{\langle3,a\rangle}}) \\[0.5ex]F^\sharp{}_{{\langle5,a\rangle}}\,\sigma &=& (h^\sharp{}_1\,(\sigma{\langle4,a\rangle}),{{\langle4,a\rangle}}) \\[0.5ex]F^\sharp{}_{{\langle6,a\rangle}}\,\sigma &=& (\sigma\,{\langle3,a\rangle},\,{{\langle3,a\rangle}}) \\[0.5ex]F^\sharp{}_{{\langle7,a\rangle}}\,\sigma &=& (\textsf{combine}^\sharp\,(\sigma{\langle5,a\rangle})\, (\sigma{\langle7, \sigma{\langle5,a\rangle}\rangle}) \sqcup\,h^\sharp{}_2\,(\sigma{\langle6,a\rangle}), \\ & &\phantom{(} {{\langle5,a\rangle}, {\langle7,\sigma{\langle5,a\rangle}\rangle}, {\langle6,a\rangle}}) \end{array}\end{equation*}

Corresponding to the function combine required by the collecting semantics, the auxiliary function $\textsf{combine}^\sharp:\mathbb{D}\to\mathbb{D}\to\mathbb{D}$ determines the abstract program state after a call from the abstract state before the call and the abstract state at the end of the procedure body.

Right-hand functions in practically given abstract systems of equations, however, usually do not explicitly provide a set of unknowns onto which the evaluation depends. Instead, the latter set is given implicitly via the implementation of the function computing the value for the left-hand side unknown. If necessary, this set must be determined by the solver, for example, as in case of TD, by keeping track of the unknowns accessed during the evaluation of the function.

Evaluation of the right-hand side function during solving may thus affect the internal state of the solver. Such operational behavior can conveniently be made explicit by means of state transformer monads. For a set S of (solver) states, the state transformer monad $\mathcal{M}_S(A)$ for values of type A consists of all functions $S\to S\times A$ . As a special case of a monad, the state transformer monad $\mathcal{M}_S(A)$ provides functions $\textsf{return}: A \to \mathcal{M}_S(A)$ and $\textsf{bind}: \mathcal{M}_S(A) \to (A\to\mathcal{M}_S(B)) \to \mathcal{M}_S(B)$ . These are defined by

\begin{equation*}\begin{array}{lll} \textsf{return}\,a &=& \textbf{fun}\,s\to (s,a) \\ \textsf{bind}\,m\,f &=& \textbf{fun}\,s\to \begin{array}[t]{l} \textbf{let}\,(s',a) = m\,s \\ \textbf{in}\, f\,a\,s' \end{array}\end{array}\end{equation*}

The solvers we consider only take actions when the current values of unknowns are accessed during the evaluation of right-hand sides. In the monadic formulation, the right-hand side functions $f^\sharp_x, x\in\mathcal{X}$ of the abstract system of equations $\mathbb{E}^\sharp$ therefore are of type $(\mathcal{X}\to\mathcal{M}(\mathbb{D}))\to\mathcal{M}(\mathbb{D})$ for any monad $\mathcal{M}$ , that is, are parametric in $\mathcal{M}$ (the system of equations should be ignorant of the internals of the solver!). Such functions $f^\sharp{}_x$ have been called pure in Karbyshev (Reference Karbyshev2013).

Example 6. For $\sigma^\sharp:\mathcal{X}\to\mathcal{M}_S(\mathbb{D})$ , the right-hand side functions in the monadic formulation of the abstract system of equations for the program from Figure 1 now are given by

\begin{equation*}\begin{array}{lll}f^\sharp_{{\langle0,a\rangle}}\,\sigma^\sharp &=&\textsf{return}\,a \\[0.5ex]f^\sharp_{{\langle1,a\rangle}}\,\sigma &=& \textsf{bind}\,(\sigma\,{\langle0,a\rangle})\,(\textbf{fun}\,b\to \\ & & \textsf{return}\,(h^\sharp_1\,b)) \\[0.5ex]f^\sharp_{{\langle2,a\rangle}}\,\sigma &=& \textsf{bind}\,(\sigma\,{\langle1,a\rangle})\,(\textbf{fun}\,b_1\to\\ & & \textsf{bind}\,(\sigma\,{\langle7,b_1\rangle})\,(\textbf{fun} b_2 \to\\ & & \textsf{return}\,(\textsf{combine}^\sharp\,b_1\,b_2))) \\[0.5ex]f^\sharp_{{\langle3,a\rangle}}\,\sigma &=& \textsf{return}\,a \\[0.5ex]f^\sharp_{{\langle4,a\rangle}}\,\sigma &=& \sigma\,{\langle3,a\rangle} \\[0.5ex]f^\sharp_{{\langle5,a\rangle}}\,\sigma &=& \textsf{bind}\,(\sigma\,{\langle4,a\rangle})\,(\textbf{fun}\,b\to \\ & & \textsf{return}\,(h^\sharp_1\,b)) \\[0.5ex]f^\sharp_{{\langle6,a\rangle}}\,\sigma &=& \sigma\,{\langle3,a\rangle} \\[0.5ex]f^\sharp_{{\langle7,a\rangle}}\,\sigma &=& \textsf{bind}\,(\sigma\,{\langle5,a\rangle})\,(\textbf{fun}\,b_1\to \\ & & \textsf{bind}\,(\sigma\,{\langle7,b_1\rangle})\,(\textbf{fun}\,b_2\to \\ & & \textsf{bind}\,(\sigma\,{\langle6,a\rangle})\,(\textbf{fun}\,b_3\to \\ & & \textsf{return}\,(\textsf{combine}^\sharp\,b_1\,b2\;\sqcup\;h^\sharp_2\,b_3))))\end{array}\end{equation*}

According to the considerations in Karbyshev (Reference Karbyshev2013), each pure function f of type $(\mathcal{X}\to\mathcal{M}(\mathbb{D}))\to\mathcal{M}(\mathbb{D})$ equals the semantics ${t}$ of some computation tree t. Computation trees make explicit in which order the values of unknowns are queried when computing the result values of a function. The set of all computation trees (over the unknowns $\mathcal{X}^\sharp$ and the set of values $\mathbb{D}$ ) is the least set $\mathcal{T}$ with

\begin{equation*}\mathcal{T}\quad{::=}\quad \textsf{A}\,\mathbb{D} \mid \textsf{Q}\,(\mathcal{X}^\sharp,\mathbb{D}\to\mathcal{T})\end{equation*}

The computation tree $\textsf{A}\,d$ immediately returns the answer d, while the computation tree $\textsf{Q}\,(x,f)$ queries the value of the unknown x in order to apply the continuation f to the obtained value.

Example 7 Consider again the program from Figure 1 consisting of the procedures main and p and the abstract system of equations as provided in Example 6. The computation trees $t_{{\langle u,a\rangle}}$ for the right-hand side functions $f^\sharp_{{\langle u,a\rangle}}$ then are given by:

\begin{equation*}\begin{array}{lll}t_{{\langle0,a\rangle}} &=& \textsf{A}\,a \\[0.5ex]t_{{\langle1,a\rangle}} &=& \textsf{Q}\,({\langle0,a\rangle},\textbf{fun}\,b\to \textsf{A}\,(h^\sharp{}_1\,b)) \\[0.5ex]t_{{\langle2,a\rangle}} &=& \textsf{Q}\,({\langle1,a\rangle},\textbf{fun}\,b\to \\[0.5ex] & & \textsf{Q}\,({\langle7,b\rangle},\textbf{fun}\,b'\to \\[0.5ex] & & \textsf{A}\,(\textsf{combine}^\sharp\,b\,b'))) \\[0.5ex]t_{{\langle3,a\rangle}} &=& \textsf{A}\,a \\[0.5ex]t_{{\langle4,a\rangle}} &=& \textsf{Q}\,({\langle3,a\rangle},\textbf{fun}\,b\to \textsf{A}\,b) \\[0.5ex]t_{{\langle5,a\rangle}} &=& \textsf{Q}\,({\langle4,a\rangle},\textbf{fun}\,b\to \textsf{A}\,(h^\sharp{}_1\,b)) \\[0.5ex]t_{{\langle6,a\rangle}} &=& \textsf{Q}\,({\langle3,a\rangle},\textbf{fun}\,b\to \textsf{A}\,b) \\[0.5ex]t_{{\langle7,a\rangle}} &=& \textsf{Q}\,({\langle5,a\rangle},\textbf{fun}\,b\to \\[0.5ex] & & \textsf{Q}\,({\langle7,b\rangle},\textbf{fun}\,b'\to \\[0.5ex] & & \textsf{Q}\,({\langle6,a\rangle},\textbf{fun}\,b''\to\\[0.5ex] & & \textsf{A}\,(\textsf{combine}^\sharp\,b\,b'\sqcup h^\sharp{}_2\,b''))))\end{array}\end{equation*}

The semantics of a computation tree t is a function ${t}:(\mathcal{X}^\sharp\to\mathcal{M}(\mathbb{D}))\to \mathcal{M}(\mathbb{D})$ where for $\textsf{get}:\mathcal{X}\to\mathcal{M}(\mathbb{D})$ ,

\begin{equation*}\begin{array}{lll} {\textsf{A}\,d}\,\textsf{get} &=& \textsf{return}\,d \\ {\textsf{Q}\,(x,f)}\,\textsf{get} &=& \textsf{bind}\,(\textsf{get}\,x)\,(\textsf{fun}\,d\to{f\,d}\,\textsf{get}) \\ \end{array}\end{equation*}

In the particular case that $\mathcal{M}$ is the state transformer monad for a set of states S, we have:

\begin{equation*}\begin{array}{lll} {\textsf{A}\,d}\,\textsf{get}\,s &=& (s,d) \\ {\textsf{Q}\,(x,f)}\,\textsf{get}\,s &=& \textbf{let}\;(s',d) = \textsf{get}\,x\,s \\ & & \textbf{in}\;{f\,d}\,\textsf{get}\,s' \\\end{array}\end{equation*}

When reasoning about (partial post-)solutions of abstract systems of equations, we prefer to have right-hand side functions where (a superset of) the set of accessed unknowns is explicit, as we used for concrete systems of equations. These functions, however, can be recovered from right-hand side functions in monadic form, as we indicate now.

One instance of state transformer monads is a monad which tracks the variables accessed during the evaluation. Consider the set of states $S = (\mathcal{X}^\sharp\to\mathbb{D})\times \mathcal{P} ({\mathcal{X}^\sharp})$ together with the function

\begin{equation*}\begin{array}{lll} \textsf{get}\,x\,(\sigma,X) &=& \textbf{let}\;d = \sigma\,x \\ & & \textbf{in}\;((\sigma,X\cup\{x\}),d) \\\end{array}\end{equation*}

Proposition 2. For a mapping $\sigma : \mathcal{X}^\sharp\to\mathbb{D}$ and $s=(\sigma,\unicode{x00D8})$ , assume that ${t}\;\textsf{get}\;s = (s_1,d)$ . Then for $s_1=(\sigma{}_1,X)$ the following holds:

  1. 1. $\sigma=\sigma{}_1$ ;

  2. 2. Assume that $\sigma':\mathcal{X}^\sharp\to\mathbb{D}$ is another mapping and $s'=(\sigma',\unicode{x00D8})$ . Let ${t}\;\textsf{get}\;s' = ((\sigma',X'),d')$ . If $\sigma'$ agrees with $\sigma$ on X, that is, $\sigma|_X = \sigma'|_X$ , then $X'=X$ and $d'=d$ holds.

We strengthen the statement by claiming that the conclusions also hold when s and s’ are given by $(\sigma,X_0)$ and $(\sigma',X_0)$ , respectively, for the same set $X_0$ . Then, the proof is by induction on the structure of t.

Now assume that for each abstract unknown $x\in\mathcal{X}^\sharp$ , the system $\mathbb{E}^\sharp$ provides us with a right-hand side function $f^\sharp_x:(\mathcal{X}\to\mathcal{M}(\mathbb{D}))\to\mathcal{M}(\mathbb{D})$ . Then, the elaborated abstract right-hand side function $F_x^\sharp : (\mathcal{X}^\sharp \to \mathbb{D}) \to \mathbb{D} \times \mathcal{P} ({\mathcal{X}^\sharp})$ of x is given by:

\begin{equation*}\begin{array}{lll}F_x^\sharp\,\sigma &=& \textbf{let}\;((\_,X),d) = f^\sharp_x\,\textsf{get}\,(\sigma,\unicode{x00D8}) \\& & \textbf{in}\; (d,X)\end{array}\end{equation*}

In fact, the explicit right-hand side functions $F^\sharp_{{\langle u,a\rangle}}$ of Example 5 are obtained in this way from the functions $f^\sharp_{{\langle u,a\rangle}}$ of Example 6.

In order to relate the abstract with a corresponding concrete system of equations, we assume that there is a Galois connection (Cousot and Cousot Reference Cousot, Cousot, Graham, Harrison and Sethi1977) between the complete lattices $\mathcal{P} (\mathcal{Q})$ and $\mathbb{D}$ , that is, monotonic mappings $\alpha : \mathcal{P} (\mathcal{Q})\to\mathbb{D}$ (the abstraction) and $\gamma : \mathbb{D}\to \mathcal{P} (\mathcal{Q})$ (the concretization) so that

\begin{equation*}\alpha\,Q\sqsubseteq a\qquad\text{iff}\qquad Q\subseteq\gamma\,a\end{equation*}

holds for all $Q\in\mathcal{P} (\mathcal{Q})$ and $a\in\mathbb{D}$ .

In general, the sets of unknowns of the concrete system to be analyzed and the corresponding abstract system need not coincide. For interprocedural context-sensitive analysis, for example, the set of concrete unknowns is given by the set of all pairs ${\langle{u,q}\rangle}$ where u is a program point and $q\in\mathcal{Q}$ is a program state. The set of abstract unknowns are of the same form. The second components of pairs, however, now represent abstract calling contexts. Therefore, we assume that we are given a description relation $\mathrel{\mathcal{R}} \subseteq \mathcal{X} \times \mathcal{X}^\sharp$ between the concrete and abstract unknowns. In case of interprocedural analysis, for example, we define $\mathrel{\mathcal{R}}$ by

\begin{equation*}{\langle{u,q}\rangle} \mathrel{\mathcal{R}} {\langle u,a\rangle}\quad\text{iff}\quad q\in\gamma(a)\end{equation*}

Using the concretization $\gamma$ , the description relation $\mathrel{\mathcal{R}}$ on unknowns is extended as follows.

  • For sets of unknowns $Y\subseteq\mathcal{X}$ and $Y^\sharp\subseteq\mathcal{X}^\sharp$ , $Y\mathrel{\mathcal{R}} Y^\sharp$ iff for each $y\in Y$ , $y\mathrel{\mathcal{R}} y^\sharp$ for some $y^\sharp\in Y^\sharp$ .

  • For sets of states $Q\in\mathcal{P} (\mathcal{Q})$ and $d\in\mathbb{D}$ , $Q\mathrel{\mathcal{R}} d$ iff $Q\subseteq\gamma\,d$ ;

  • For partial assignments $(\sigma,\textit{dom})$ and $(\sigma^\sharp,\textit{dom}^\sharp)$ with $\sigma : \mathcal{X}\to\mathbb{D},\sigma^\sharp : \mathcal{X}^\sharp\to\mathbb{D}^\sharp$ and $\textit{dom}\subseteq\mathcal{X},\textit{dom}^\sharp\subseteq\mathcal{X}^\sharp$ , $(\sigma,\textit{dom})\mathrel{\mathcal{R}}(\sigma^\sharp,\textit{dom}^\sharp)$ holds if $\textit{dom}\mathrel{\mathcal{R}}\textit{dom}^\sharp$ , and for all $y\in\textit{dom}$ , $y\in\textit{dom}^\sharp$ with $y\mathrel{\mathcal{R}} y^\sharp$ , $\sigma\,y\subseteq\gamma(\sigma^\sharp\,y^\sharp)$ .

  • For (elaborated) right-hand sides $F : (\mathcal{X}\to \mathcal{P} ({\mathcal{Q}}))\to(\mathcal{P} (\mathcal{Q})\times \mathcal{P} (\mathcal{X}))$ and $F^\sharp : (\mathcal{X}^\sharp\to \mathbb{D})\to(\mathbb{D}\times \mathcal{P} ({\mathcal{X}^\sharp}))$ , $F\mathrel{\mathcal{R}} F^\sharp$ iff $(F\,\sigma)_1\subseteq\gamma\,(F^\sharp\,\sigma^\sharp)_1$ , and $(F\,\sigma)_2\mathrel{\mathcal{R}}(F^\sharp\,\sigma^\sharp)_2$ whenever $(\sigma,\textit{dom})\mathrel{\mathcal{R}}(\sigma^\sharp,\textit{dom}^\sharp)$ holds for domains $\textit{dom},\textit{dom}^\sharp$ which are $(\sigma,\mathbb{E})$ -closed and $(\sigma^\sharp,\mathbb{E}^\sharp)$ -closed, respectively.

  • For equation systems $\mathbb{E} : \mathcal{X} \to ((\mathcal{X}\to \mathcal{P} ({\mathcal{Q}}))\to(\mathcal{P} (\mathcal{Q})\times \mathcal{P} (\mathcal{X})))$ and $\mathbb{E}^\sharp : \mathcal{X}^\sharp \to ((\mathcal{X}^\sharp\to \mathcal{M}_S(\mathbb{D}))\to\mathcal{M}_S(\mathbb{D}))$ , $\mathbb{E} \mathrel{\mathcal{R}} \mathbb{E}^\sharp$ iff $(\mathbb{E}\,x) \mathrel{\mathcal{R}} F^\sharp{}_{x^\sharp}$ for each $x \in \mathcal{X}$ , $x^\sharp \in \mathcal{X}^\sharp$ , where $F^\sharp{}_{x^\sharp}$ is the elaboration of $\mathbb{E}^\sharp\,x^\sharp$ .

Let $(\sigma,\textit{dom})$ be the least solution of the concrete system $\mathbb{E}$ for some set X of interesting unknowns. Let $\mathbb{E}^\sharp$ denote an abstract system corresponding to $\mathbb{E}$ and $X^\sharp$ a set of abstract unknowns and $\mathrel{\mathcal{R}}$ a description relation between the unknowns of $\mathbb{E}$ and $\mathbb{E}^\sharp$ such that $\mathbb{E}\;\mathrel{\mathcal{R}}\;\mathbb{E}^\sharp$ and $X\;\mathrel{\mathcal{R}}\;X^\sharp$ holds. A local solver then determines for $\mathbb{E}^\sharp$ and $X^\sharp$ a pair $(\sigma^\sharp,\textit{dom}^\sharp)$ so that $X\subseteq\textit{dom}^\sharp$ , $\textit{dom}^\sharp$ is $\sigma^\sharp$ -closed and $(\sigma,\textit{dom})\;\mathrel{\mathcal{R}}\;(\sigma^\sharp,\textit{dom}^\sharp)$ holds, that is, the result produced by the solver is a sound description of the least partial solution of the concrete system.

In absence of narrowing, the correctness of a solver can be proven intrinsically, that is, just by verifying that it terminates with a post-solution of the system of equations.

We first convince ourselves that the following holds:

Proposition 3. Assume that $\mathbb{E}\mathrel{\mathcal{R}}\mathbb{E}^\sharp$ holds and $X\mathrel{\mathcal{R}} X^\sharp$ for subsets X and $X^\sharp$ concrete and abstract unknowns, respectively. Assume that $(\sigma,\textit{dom})$ is the least partial post-solution of $\mathbb{E}$ with $X\subseteq\textit{dom}$ , and $(\sigma^\sharp,\textit{dom}^\sharp)$ some partial post-solution of $\mathbb{E}^\sharp$ with $X^\sharp\subseteq\textit{dom}^\sharp$ . Then $(\sigma,\textit{dom})\mathrel{\mathcal{R}}(\sigma^\sharp,\textit{dom}^\sharp)$ holds.

Proposition 3 is a special case of Proposition 4 where additionally side effects and dynamic description relations are taken into account. Therefore, the proof of Proposition 3 is omitted. Proposition 3 can be used to prove soundness for local solver algorithms which perform accumulating fixpoint iteration and thus return partial post-solutions. These kinds of solvers require abstract domains where strictly ascending chains are always finite. This assumption, however, is no longer met for more complicated domains such as the interval domain (Cousot and Cousot Reference Cousot, Cousot, Graham, Harrison and Sethi1977) or octagons (Mine 2001). As already observed in Cousot and Cousot (Reference Cousot, Cousot, Graham, Harrison and Sethi1977), their applicability to these domains can still be extended by introducing widening operators. According to Cousot and Cousot (Reference Cousot and Cousot1992), Cousot (Reference Cousot, D’Souza, Lal and Larsen2015), a widening operator $\nabla$ is a mapping $\nabla : \mathbb{D}\times\mathbb{D}\to\mathbb{D}$ with the following two properties:

  1. (1). $a\sqcup b\sqsubseteq a\nabla b$ for all $a,b\in\mathbb{D}$ ;

  2. (2). Every sequence $a_0,a_1,\ldots $ defined by $a_{i+1} = a_i\nabla b_i$ , $i\geq 0$ for any $b_i\in\mathbb{D}$ is ultimately stable.

Acceleration with widening then means that the occurrences of $\sqcup$ in the solver are replaced with $\nabla$ .

Example 8. For intervals over $\mathbb{Z}{}_{-\infty}^{+\infty}$ we could use primitive widening:

\begin{align*} [a_1, b_1] \nabla [a_2, b_2] = [&\textbf{if }a_2 < a_1 \textbf{ then } -\infty \textbf{ else } a_1, \\ &\textbf{if }b_2 > b_1 \textbf{ then } +\infty \textbf{ else } b_1]\end{align*}

Alternatively, we could use threshold widening where several intermediate bounds are introduced that can be jumped to. Note that widening in general is not monotonic in the first argument: $[0,1] \sqsubseteq [0,2]$ but $[0,1] \nabla [0,2] = [0, +\infty] \not\sqsubseteq [0,2] = [0,2] \nabla [0,2]$ . We remark that Cousot and Cousot (Reference Cousot and Cousot1992), Cousot (Reference Cousot, D’Souza, Lal and Larsen2015) provide a more general notion of widening which refers not to the ordering of $\mathbb{D}$ but (via $\gamma$ ) to the ordering in the concrete lattice $\mathcal{P} (\mathcal{Q})$ alone. W.r.t. that definition, $a\sqcup b$ is no longer necessarily less or equal $a\nabla b$ . In many applications, however, accelerated loss of precision due to widening may result in unacceptable analysis results. Therefore, Cousot and Cousot (Reference Cousot, Cousot, Graham, Harrison and Sethi1977) proposed to complement a terminating widening iteration with a narrowing iteration which subsequently tries to recover some of the precision loss. Following Cousot and Cousot (Reference Cousot and Cousot1992), Cousot (Reference Cousot, D’Souza, Lal and Larsen2015), a narrowing operator $\Delta$ is a mapping $\Delta : \mathbb{D}\times\mathbb{D}\to\mathbb{D}$ with the following two properties:

  1. (1). $a\sqcap b\sqsubseteq(a\Delta b)\sqsubseteq a$ for all $a,b\in\mathbb{D}$ ;

  2. (2). Every sequence $a_0,a_1,\ldots $ defined by $a_{i+1} = a_i\Delta b_i$ , $i\geq 0$ for any $b_i\in\mathbb{D}$ is ultimately stable.

Example 9. For intervals over $\mathbb{Z}{}_{-\infty}^{+\infty}$ we could use primitive narrowing:

\begin{align*} [a_1, b_1] \Delta [a_2, b_2] = [&\textbf{if }a_1 = -\infty \textbf{ then } a_2 \textbf{ else } a_1, \\ &\textbf{if }b_1 = +\infty \textbf{ then } b_2 \textbf{ else } b_1]\end{align*}

which improves infinite bounds only. More sophisticated narrowing operators may allow a bounded number of improvements of finite bounds as well. Again we remark that, according to the more general definition in Cousot and Cousot (Reference Cousot and Cousot1992), Cousot (Reference Cousot, D’Souza, Lal and Larsen2015), the first property need not necessarily be satisfied. For monotonic systems of equations, the narrowing iteration when starting with a (partial) post-solution still will return a (partial) post-solution. The correctness of the solver started with an initial query $x^\sharp$ and returning a partial assignment $\sigma^\sharp$ with domain $\textit{dom}^\sharp$ thus can readily be checked by verifying that

  1. 1. $x^\sharp\in\textit{dom}^\sharp$ ;

  2. 2. $(F^\sharp{}_x\,\sigma^\sharp)_2\subseteq\textit{dom}^\sharp$ for all $x\in\textit{dom}^\sharp$ , that is, $\textit{dom}^\sharp$ is $(\sigma^\sharp,\mathbb{E}^\sharp)$ -closed; and

  3. 3. $\sigma^\sharp\,x\sqsupseteq(F^\sharp{}_x\,\sigma^\sharp)_1$ for all $x\in\textit{dom}^\sharp$ .

When the system of equations is non-monotonic, though, the computed assignment still is a sound description. It is, however, no longer guaranteed to be a post-solution of $\mathbb{E}^\sharp$ . In Section 6, we come back to this point.

4. The Terminating Solver TDterm

In this section, we present our modification to the TD solver with widening and narrowing which improves on the variant in Apinis et al. (Reference Apinis, Seidl, Vojdani, Probst, Hankin and Hansen2016) in that termination guarantees can be proven even for non-monotonic abstract systems of equations. The vanilla TD solver from Muthukumar and Hermenegildo (Reference Muthukumar and Hermenegildo1990), Charlier and Van Hentenryck (Reference Charlier and Van Hentenryck1992) (see Appendix A for a pseudo code formulation of this solver along the lines presented in Fecht and Seidl Reference Fecht and Seidl1999) starts by querying the value of a given unknown. In order to answer the query, the solver evaluates the corresponding right-hand side. Whenever in the course of that evaluation, the value of another unknown is required, the best possible value for that unknown is computed first, before evaluation of the current right-hand side continues. Interestingly, the strategy employed by TD for choosing the next unknown to iterate upon, thereby resembles the iteration orders considered in Bourdoncle (Reference Bourdoncle, Bjørner, Broy and Pottosin1993) (see Fecht and Seidl Reference Fecht and Seidl1999 for a detailed comparison) for systems of equations derived from control-flow graphs of programs. The most remarkable difference, however, is that TD determines its order on-the-fly, while the ordering in Bourdoncle (Reference Bourdoncle, Bjørner, Broy and Pottosin1993) is determined via preprocessing.

In Apinis et al. (Reference Apinis, Seidl, Vojdani, Probst, Hankin and Hansen2016), the vanilla TD solver from Muthukumar and Hermenegildo (Reference Muthukumar and Hermenegildo1990), Charlier and Van Hentenryck (Reference Charlier and Van Hentenryck1992), Fecht and Seidl (Reference Fecht and Seidl1999) is enhanced with widening and narrowing. For that, the solver is equipped with a novel technique for identifying not only accesses to unknowns, but also widening and narrowing points on-the-fly. Moreover, that solver does not delegate the narrowing iteration to a separate second phase (as was done in the original papers on widening and narrowing Cousot and Cousot Reference Cousot and Cousot1992; Cousot Reference Cousot, D’Souza, Lal and Larsen2015), once a proceeding widening iteration has completed. Instead, widening and narrowing iterations may occur intertwined (Amato et al. Reference Amato, Scozzari, Seidl, Apinis and Vojdani2016). This is achieved by combining the widening operator $\nabla$ with the narrowing operator $\Delta$ into a single warrowing operator $\unicode{x29C4}$ :

\begin{equation*}a\unicode{x29C4} b = \begin{array}[t]{l} \textbf{if}\; b\sqsubseteq a\;\textbf{then}\;a\Delta b\\ \textbf{else}\;a\nabla b \end{array}\end{equation*}

This operator applies $\Delta$ whenever values decrease and otherwise applies $\nabla$ .

In Apinis et al. (Reference Apinis, Seidl, Vojdani, Probst, Hankin and Hansen2016), it is proven that solver TD (in the formulation of Fecht and Seidl Reference Fecht and Seidl1999) and equipped with warrowing at dynamically detected widening/narrowing points terminates for monotonic systems – whenever only finitely many unknowns are encountered. Example 10, though, shows a non-monotonic system for which this solver does not terminate – while the new solver $\mathbf{TD}_{\mathbf{term}}$ does.

Example 10. Consider the single equation:

\begin{equation*}x = \textsf{if}\, x = 0 \,\textsf{then}\, 1 \,\textsf{else}\, 0\end{equation*}

over the lattice of naturals (with infinity) with $a \nabla b = \infty$ whenever $a < b$ and $a \Delta b = b$ whenever $a = \infty$ . The right-hand side of this equation is not monotonic. An iteration with warrowing leads to the sequence of values for x

\begin{equation*}0 \to \infty \to 0 \to \infty \to \ldots\end{equation*}

and thus will not terminate.

In order to deal with non-monotonic systems, we do no longer rely on warrowing. Instead, we equip the solver with extra logic to switch for each unknown from widening to narrowing (and never back). Our new solver is presented as OCaml pseudocode operating on abstract systems of equations. W.l.o.g., we also assume that solving starts with a single unknown of interest. In case that simultaneously values for unknowns from an arbitrary finite set X are of interest, we may introduce an artificial fresh unknown $x_0$ whose right-hand side successively queries the values of $x\in X$ .

For better readability, the solver state is not threaded through the evaluation of right-hand sides by means of a monad, but realized by mutable data structures:

Here, the functional argument f to the function Map.create is meant to return an initial value $f\;()$ for a key which has not yet been assigned in the given map data structure.

Under that proviso, the OCaml type of a right-hand side function $f^\sharp_x$ thus is just $(\mathcal{X}\to\mathbb{D})\to\mathbb{D}$ . When reasoning about the values computed by right-hand sides as well as the sets of unknowns accessed during the evaluation, we will refer to the elaborated right-hand sides $F^\sharp_x$ corresponding to the $f^\sharp_x$ . The solver itself consists of three functions: the function solve which performs the iteration for a given unknown x; the function eval which wraps lookups of values of unknowns in the current state; and finally, the function destabilize which marks encountered unknowns possibly affected by a change to the value of some unknown, for re-evaluation.

We briefly sketch the intuition behind this algorithm. The set $\textit{called}$ is the set of all unknowns where iteration currently is in progress, that is, which are contained in the call stack of the solver. The set $\textit{stable}$ on the other hand consists of all unknowns where the iteration (relative to the current values of unknowns on the call stack) has terminated. For unknowns in any of these two sets, solving should immediately terminate.

A key issue is to propagate the information that the value of some unknown y has changed, to all unknowns whose right-hand sides have been evaluated under wrong assumptions on y. A key ingredient both of the original TD solver and the variant in Apinis et al. (Reference Apinis, Seidl, Vojdani, Probst, Hankin and Hansen2016) is that dependences between unknowns are dynamically detected and recorded in the map $\textit{infl}:\mathcal{X}^\sharp\to \mathcal{P} ({\mathcal{X}^\sharp})$ . The set $\textit{infl}\,y$ is meant to record the set of unknowns x where the evaluation of $f^\sharp{}_x$ has accessed the current value of y, that is, where y influences x.

As soon as the value of an unknown y is changed, therefore, the function $\textsf{destabilize}$ is called. $\textsf{destabilize}\,y$ removes all unknowns directly or indirectly influenced by y from the set $\textit{stable}$ . That recursive removal only stops at unknowns which are contained in the set $\textit{called}$ . We remark that by the call $\textsf{destabilize}\,y$ , also the set $\textit{infl}\,y$ is reset to $\unicode{x00D8}$ .

The current values for unknowns are maintained in the map $\sigma$ . In order to track dependences between unknowns, we wrap the access to entries in $\sigma$ into the call $\textsf{eval}\,x$ where x is the unknown in whose right-hand side the values in $\sigma$ are queried. Thus, a call $\textsf{eval}\,x\,y$ ultimately returns the latest value in $\sigma$ for y. Before that, however, it checks whether $y\in\textit{called}$ holds. If this is the case, y is turned into an unknown where widening and narrowing is applied. All these unknowns are collected into the set $\textit{point}$ . If this is not the case, the call $\textsf{solve}\,\nabla\,y$ is evaluated to determine the best possible value for y before-hand. Then, x is added to the set $\textit{infl}\,y$ of unknowns influenced by y, and finally, the value for y in $\sigma$ is returned.

The main ingredient of the algorithm, though, is the function $\textsf{solve}$ . The function $\textsf{solve}\,\nabla$ is applied to an unknown x to determine the best possible value for x. In case that $x\in\textit{point}$ , the computation starts with a local widening iteration on x which is followed by a call $\textsf{solve}\,\Delta\,x$ to subsequently perform a local narrowing iteration on x. Any call $\textsf{solve}\,p\,x$ immediately terminates if x is already found to be in $\textit{called}\cup\textit{stable}$ . If this is not the case, x is added to $\textit{stable}$ and $\textit{called}$ , and the right-hand side $f^\sharp{}_x$ of x is evaluated for the argument $\textsf{eval}\,x$ , and the first component of the result stored in the temporary $\textit{tmp}$ . After the evaluation, x is removed from $\textit{called}$ . If x has been found to be in $\textit{point}$ , we use p to combine the old value for x as stored in $\sigma$ with the new value in $\textit{tmp}$ . Otherwise, we use $\textit{tmp}$ directly.

Assume that the new value is the same as the old value for x as provided by $\sigma$ . If $p=\nabla$ , then the widening phase is completed. Therefore, x is removed from $\textit{stable}$ and $solve\,\Delta\,x$ is called. Otherwise, we are done.

Now assume that the new value is different from the old value for x. Then, we update the value of $\sigma$ for x to the new value, call $\textsf{destabilize}\,x$ in order to propagate this information, and recursively call $\textsf{solve}\,p\,x$ .

5. Termination of TDterm

Each state s attained by the solver during its evaluation, consists of the tuple of mutable data structures $s=(\sigma,\textit{infl},\textit{stable},\textit{called},\textit{point})$ . We call s consistent if

  1. 1. for all $x\in(\textit{stable}\setminus\textit{called})$ , and all $y \in (F^\sharp{}_x\sigma)_2$ , $y\in(\textit{stable}\,\cup\,\textit{called})$ and $x\in\textit{infl}\,y$ holds; and

  2. 2. for each $x\not\in(\textit{stable}\,\cup\,\textit{called})$ , $\textit{infl}\,x=\unicode{x00D8}$ .

Each call $\textsf{solve}\,p\,x$ encountered during the evaluation of the initial call, starts in a consistent solver state s. Upon its termination, a solver state $s'=(\sigma',\textit{infl}',\textit{stable}',\textit{called}',\textit{point}')$ is attained such that $s=s'$ whenever $x\in\textit{stable}\cup\textit{called}$ . Otherwise, it holds that

  1. 1. s’ is again consistent;

  2. 2. $\textit{called}'=\textit{called}$ and $\textit{stable}\subseteq\textit{stable}'$ where for all $y\in\textit{stable}\cup\textit{called}$ , $\sigma'\,y=\sigma\,y$ and $\textit{infl}\,y\subseteq\textit{infl}'\,y$ ;

  3. 3. $\textit{point}\subseteq\textit{point}'$ ;

  4. 4. $x\in\textit{stable}'$ .

Also, each call $\textsf{eval}\,x\,y$ encountered during the evaluation starts in a consistent state s where $x\in(\textit{stable}\,\cap\,\textit{called})$ . Upon its termination, a solver state s’ is attained such that

  1. 1. s’ is again consistent;

  2. 2. $\textit{called}'=\textit{called}$ ;

  3. 3. $\textit{stable}\subseteq\textit{stable}'$ where for all $y'\in\textit{stable}\cup\textit{called}$ , $\sigma'\,y'=\sigma\,y'$ and $\textit{infl}\,y'\subseteq\textit{infl}'\,y'$ ;

  4. 4. $\textit{point}\subseteq\textit{point}'$ ;

  5. 5. If $y\in\textit{stable}\cup\textit{called}$ , then

  6. $\sigma' = \sigma$ , $\textit{stable}'=\textit{stable}$ and $\textit{point}' = \textit{point}$ if $y\not\in\textit{called}$ whereas $\textit{point}' = \textit{point}\cup\{y\}$ if $y\not\in\textit{called}$ ; ;

  7. $\textit{infl}'\,y'=\textit{infl}\,y'$ for all $y'\neq y$ , and

  8. $\textit{infl}'\,y= \textit{infl}\,y\cup\{x\}$ ;

  9. 6. $y\in\textit{stable}'$ , $x\in\textit{infl}'\,y$ , and the value $\sigma'\,y$ is returned.

In particular, x is still contained in $\textit{stable}'\cap\textit{called}'$ . Finally, consider the call to $\textsf{destabilize}\,x$ in the body of $\textsf{solve}$ , and let $s=(\sigma,\textit{infl},\textit{stable},\textit{called},\textit{point})$ before this call. Then, s is consistent with $x\in\textit{stable}\backslash\textit{called}$ , where upon termination of the call, a solver state s’ is attained so that

  1. 1. s’ is again consistent;

  2. 2. $\sigma'=\sigma,\textit{called}=\textit{called}',\textit{point}=\textit{point}'$ ;

  3. 3. $\textit{stable}'\subseteq\textit{stable}$ , and

  4. 4. for all y, $\textit{infl}'\,y$ either equals $\unicode{x00D8}$ or $\textit{infl}\,y$ where $\textit{infl}'$ and $\textit{stable}'$ are maximal so that s’ is consistent while $\textit{infl}'\,x=\unicode{x00D8}$ and $\textit{infl}\,x\cap\textit{stable}'=\unicode{x00D8}$ .

This invariant allows us to prove that the solver $\mathbf{TD}_{\mathbf{term}}$ indeed terminates for arbitrary systems of abstract equations.

Theorem 1 Let $\mathbb{E}^\sharp$ denote an arbitrary system of abstract equations, and let $x_0$ be the initial unknown of interest. Assume that initially the sets $\textit{called}$ and $\textit{stable}$ are empty, and likewise, $\textit{infl}$ maps each unknown to the empty set. Then, the call $\textsf{solve}\,\nabla\,x_0$ will always terminate, as long as only finitely many unknowns are encountered.

Proof. First we note that every call $\textsf{destabilize}\,y$ terminates. This is immediate in case when $\textit{infl}\,y$ is empty. Moreover, $\textit{infl}\,y'$ is non-empty for only finitely many unknowns y’. Since the set $\textit{infl}\,y$ is set to $\unicode{x00D8}$ , before any recursive call to unknowns, termination follows.

Assume now that during evaluation of the initial call $\textsf{solve}\,\nabla\,x_0$ , only finitely many unknowns x are encountered for which $\textsf{solve}\;p\;x$ is called for some p.

In order to prove termination of all calls $\textsf{solve}\,p\,x$ encountered during the evaluation of $\textsf{solve}\,\nabla\,x_0$ , we perform an induction on the cardinality of the set of unknowns which are not in $\textit{called}$ . Let Y denote the set of all unknowns $y\in\mathcal{X}\setminus(\{x\}\cup\textit{called}\cup\textit{stable})$ for which $\textsf{solve}\,\nabla\,y$ is called during the call $\textsf{solve}\,p\,x$ and proceeds to the evaluation of the right-hand side of y.

Assume for a contradiction that the call $\textsf{solve}\,p\,x$ would not terminate. Let us first assume that $x\not\in\textit{point}$ throughout the iteration, that is, all tail-recursive calls to $\textsf{solve}\,p\,x$ . In particular, this means that for none of the unknowns $y\in Y$ , evaluation of the right-hand side $f^\sharp{}_y$ accessed the unknown x – as in this case, x would have been added to $\textit{point}$ . Therefore, when $p = \nabla$ , $\textit{infl}\,x$ does not contain any of the unknowns in Y, that is, is still empty after the first update of $\sigma\,x$ . Accordingly, destabilization of x will immediately terminate and leave x in the set $\textit{stable}$ . If $p = \Delta$ , $\textit{infl}\,x = \unicode{x00D8}$ at least for all calls after the first call to $\textsf{solve}\,\Delta\,x$ . As a consequence, the call $\textsf{solve}\,p\,x$ terminates for each phase p – in contradiction to our assumption.

Therefore, necessarily, $x\in\textit{point}$ at least after the first evaluation of $f^\sharp{}_{x}$ . We distinguish two cases.

Case 1. $p=\Delta$ .

By inductive hypothesis, all occurring calls $\textsf{solve}\,p\,y$ with $y\in Y$ terminate. Let $a_i,i\geq 0$ denote the sequence of values of $\sigma\,x$ before the ith tail-recursive call to $\textsf{solve}\,\Delta\,x$ . Then necessarily $a_i\neq a_{i+1}$ for all $i\geq 0$ . Let $b_1,\ldots,b_i,\ldots$ denote the sequence of values returned by the ith evaluation of the right-hand side $f^\sharp{}_{x}$ of x. Then, $a_{i+1} = a_i\Delta b_{i+1}$ for $i\geq 0$ . Due to the properties of narrowing, however, the latter sequence is ultimately stable, that is, $a_i= a_{i+1}$ for some i – in which case the recursion terminates: contradiction. Therefore, each call $\textsf{solve}\,\Delta\,x$ eventually terminates.

Case 2. $p=\nabla$ .

By inductive hypothesis, again all calls $\textsf{solve}\,\nabla\,y$ with $y\in Y$ terminate. Let $a_i,i\geq 0$ denote the sequence of values of $\sigma\,x$ before the ith tail-recursive call to $\textsf{solve}\,\nabla\,x$ . Assume that $a_i\neq a_{i+1}$ for all $i\geq 0$ . Let $b_1,\ldots$ denote the sequence of values returned by the ith evaluation of the right-hand side $f^\sharp{}_x$ of x. Then, $a_{i+1} = a_i\nabla b_{i+1}$ for $i\geq 0$ . Due to the properties of widening, however, the latter sequence is ultimately stable, that is, $a_i= a_{i+1}$ for some i. Therefore eventually, $\textsf{solve}\,\Delta\,x$ is tail-recursively called. But then, termination follows due to termination of the call $\textsf{solve}\,\Delta\,x$ : contradiction!

Accordingly, we conclude that all encountered calls $\textsf{solve}\,p\,x$ terminate, and thus also the call $\textsf{solve}\,\nabla\,x_0$ .

6. Correctness of TDterm

Our goal is to prove that the result of our algorithm is a sound description of the least partial solution of the concrete system. In case of monotonic abstract systems and total solutions, soundness is known to hold for any post-solution of the abstract system, whenever the concrete system is described by the abstract system. Recall that while the right-hand sides of our concrete system are monotonic, this need not necessarily be the case for the abstract system. Consider, for example, an interprocedural analysis as in Examples 5 and 6 and there the elaborated right-hand side

\begin{equation*}F^\sharp_{{\langle2,a\rangle}} =\textbf{fun}\,\sigma\to \begin{array}[t]{l} (\textsf{combine}^\sharp\,(\sigma{\langle1,a\rangle})\,(\sigma{\langle7,\sigma{\langle1,a\rangle}\rangle}),\\ \quad{{\langle1,a\rangle},{\langle7,\sigma{\langle1,a\rangle}\rangle}}) \end{array}\end{equation*}

for the unknown ${\langle2,a\rangle}$ . Since for different values in $\sigma$ for ${\langle1,a\rangle}$ , different unknowns are queried, this function cannot be monotonic. In order to deal with such non-monotonicities, Frielinghaus et al. (Reference Frielinghaus, Seidl and Vogler2016) have introduced the concept of a lower monotonization of the abstract system $\mathbb{E}^\sharp$ .

For the system $\mathbb{E}^\sharp$ with set of unknowns $\mathcal{X}^\sharp$ , the lower monotonization $\underline{\mathbb{E}^\sharp}$ is a system of equations with the same set of unknowns where the elaborated right-hand side $\underline{F}_{x}^\sharp$ for $x\in\mathcal{X}^\sharp$ is given by $\underline{F}_{x}^\sharp\,\sigma = (d,X)$ with

\begin{equation*}\begin{array}{lll} d &=& \sqcap{(F^\sharp{}_{x}\,\sigma')_1\mid \sigma'\sqsupseteq\sigma} \\ X &=& \mathcal{X}^\sharp \\\end{array}\end{equation*}

Thus, we over-approximate the sets of unknowns influencing the result of a right-hand side by the full set $\mathcal{X}^\sharp$ while d is the greatest lower bound to all first components of results of $F^\sharp{}_x$ for assignments exceeding $\sigma$ .

Since all right-hand sides of $\underline{\mathbb{E}^\sharp}$ are monotonic, the system has a least total solution $\underline\sigma$ . Let $\sigma$ denote the least total solution of some concrete system $\mathbb{E}$ which is described by $\mathbb{E}^\sharp$ . In Frielinghaus et al. (Reference Frielinghaus, Seidl and Vogler2016), it has been proven that then also $\sigma\mathrel{\mathcal{R}}\underline\sigma$ holds. Moreover, let $\textit{dom}$ denote the least $(\sigma,\mathbb{E})$ -closed subset containing some initial unknown x. Let $x\mathrel{\mathcal{R}} y$ for some unknown $y\in\mathcal{X}^\sharp$ . Let $\sigma':\mathcal{X}^\sharp\to\mathbb{D}$ be some abstract assignment, and $\textit{dom}^\sharp$ be $(\sigma',\mathbb{E}^\sharp)$ -closed with $y\in\textit{dom}^\sharp$ . Assume further that $\underline\sigma\,y'\sqsubseteq\sigma'\,y'$ for all $y'\in\textit{dom}^\sharp$ . Then by the results of Frielinghaus et al. (Reference Frielinghaus, Seidl and Vogler2016), also $\textit{dom}\mathrel{\mathcal{R}}\textit{dom}^\sharp$ holds. Accordingly, the pair $(\sigma',\textit{dom}^\sharp)$ can then be understood as a sound description of the least partial post-solution of the concrete system $\mathbb{E}$ for x.

Therefore, now assume that we are given a set X of unknowns of the concrete system of equations $\mathbb{E}$ , together with a an abstract unknown $x_0$ so that for all $x\in X$ , $x\mathrel{\mathcal{R}} x_0$ holds. Assume further that the solver $\mathbf{TD}_{\mathbf{term}}$ , when started with the call $\textsf{solve}\,\nabla\,x_0$ , returns the abstract assignment $\sigma^\sharp$ where by default, $\sigma^\sharp\,y=\bot$ for all unknowns y which have not been encountered during solving. It therefore suffices to prove:

  1. 1. There is a subset $\textit{dom}^\sharp\subseteq\mathcal{X}^\sharp$ containing $x_0$ which is $(\sigma^\sharp,\mathbb{E}^\sharp)$ -closed.

  2. 2. $\underline\sigma^\sharp\,y\sqsubseteq\sigma^\sharp\,y$ holds for all $y\in\textit{dom}^\sharp$ .

After evaluation of each call $\textsf{solve}\,p\,x$ , evaluation of the right-hand side of an unknown in stable will access only unknowns which are either again in stable, or in called. As a consequence, the set of all stable unknowns after termination of all calls $\textsf{solve}\,\nabla\,x_0$ is $(\sigma^\sharp,\mathbb{E}^\sharp)$ -closed. Therefore, it remains to verify the second property. Let $\textit{dom}^\sharp$ denote the subset $\textit{stable}\subseteq\mathcal{X}^\sharp$ upon termination of the call $\textsf{solve}\,\nabla\,x_0$ . Let $\underline\sigma : \mathcal{X}^\sharp\to\mathbb{D}$ denote the least solution of the lower monotonization $\underline{\mathbb{E}^\sharp}$ of $\mathbb{E}^\sharp$ . Our goal is to show that $\underline\sigma\,x\sqsubseteq\sigma^\sharp\,x$ for all $x\in\textit{dom}^\sharp$ .

Assume that we are given a subset Y of unknowns together with an assignment $\tau : Y\to\mathbb{D}$ . Here, we assume Y and $\tau$ to represent the set of unknowns which are currently stable (or called) together with their current values. In the following, we use the binary operator $\oplus$ to denote an update of the left argument with the bindings provided by the right argument. Let $\mathbb{E}^\sharp{}_{\tau}$ denote the system of equations with unknowns from $\mathcal{X}^\sharp\setminus Y$ where the right-hand side $f^\sharp_{\tau,y}$ of y behaves like the right-hand side $f^\sharp{}_y$ of $\mathbb{E}^\sharp$ , but looks up the values for encountered unknowns from Y in $\tau$ . Technically, this means that

\begin{equation*}\begin{array}{lll}f^\sharp{}_{\tau,y}\,\sigma &=& f^\sharp{}_y\,(\sigma\oplus(\textsf{return}\circ\tau))\end{array}\end{equation*}

Accordingly, the elaborated right-hand side $F^\sharp{}_{\tau,y}$ for the unknown y is given by

\begin{equation*}F^\sharp{}_{\tau,y}\,\sigma \quad=\begin{array}[t]{l} \textbf{let}\; (d,X) = F^\sharp{}_{y}\,(\sigma\oplus\tau)\\ \textbf{in}\; (d,X\setminus Y) \end{array}\end{equation*}

Let $s=(\sigma,\textit{infl},\textit{stable},\textit{called},\textit{point})$ denote a consistent solver state. Let $\tau : \textit{called}\to\mathbb{D}$ denote the restriction of $\sigma$ to the set $\textit{called}$ . We call s saturated if for all $x\in \textit{stable}\setminus\textit{called}$ , $\underline\sigma\, x \sqsubseteq \sigma\,x$ where $(\underline\sigma,X^\sharp\setminus\textit{called})$ is the least total solution of the lower monotonization of $\mathbb{E}^\sharp{}_\tau$ . We claim:

Theorem 2. Each call $\textsf{solve}\,\nabla\,x$ starting in a saturated solver state, results upon termination, in a solver state $s'=(\sigma',\textit{infl}',\textit{stable}',\textit{called}',\textit{point}')$ which is again saturated where additionally, $x\in\textit{stable}'$ . Since before the initial call $\textsf{solve}\,\nabla\,x_0$ the set $\textit{called}$ is empty, this theorem implies that upon termination, $\textit{dom}^\sharp$ is $(\sigma',\mathbb{E}^\sharp)$ -closed with $x_0\in\textit{dom}^\sharp$ , and $\underline\sigma\,x\sqsubseteq\sigma'\,x$ for all $x\in\textit{dom}^\sharp$ .

Proof. We proceed by induction on the set $\mathcal{X}'$ of unknowns not contained in the set $\textit{stable}$ of stable unknowns before the call. For $\mathcal{X}'=\unicode{x00D8}$ , the assertion obviously holds. For the inductive step, first assume that after the evaluation of the right-hand side $f^\sharp{}_{x}$ , x is not included in $\textit{point}$ . This means that no variable in $\mathcal{X}'$ may depend on the unknown x. Assume that the values of x before and after $\textsf{solve}$ are d and d’, respectively. Consider the least total solutions $\underline\sigma$ and $\underline\sigma'$ of the lower monotonizations of the systems $\mathbb{E}^\sharp{}_{\tau}$ and $\mathbb{E}^\sharp{}_{\tau\oplus\{x\mapsto d'\}}$ , respectively, where $\tau : \textit{stable}\to\mathbb{D}$ records the values of all unknowns from $\textit{stable}$ . Let $Y = (F^\sharp{}_{\tau\oplus\{x\mapsto d\},x})_2\setminus\textit{stable}$ be the set of unknowns accessed during the evaluation of the right-hand side for x (in particular, $x\not\in Y$ ). Then $\mathcal{X}'\setminus\{x\}$ is the disjoint union of subsets $\mathcal{X}'_y,y\in Y$ , where $\mathcal{X}'_y$ is the subset of unknowns freshly solved when y is encountered. By applying the inductive hypothesis to the unknowns in y in sequence, we obtain that for all $y\in Y$ , $\underline\sigma'\,y'\sqsubseteq\sigma\,y'$ for all $y'\in\mathcal{X}{}_y$ . Accordingly, we have for all $y\in\mathcal{X}'\setminus\{x\}$ , that

\begin{equation*}(\underline F'_y\underline\sigma')_1\sqsubseteq\sigma'\,y\end{equation*}

Here, $\underline F'_{y}$ denotes the elaborated right-hand side of the lower monotonization of $\mathbb{E}{}_{\tau\oplus\{x\mapsto d'\}}$ for y. This allows us to deduce for x that

\begin{equation*}\begin{array}{lll}(\underline F^\sharp{}_{\tau,x}(\underline\sigma'\oplus\{x\mapsto d'\}))_1&\sqsubseteq &(\underline F^\sharp{}_{\tau,x}(\sigma\oplus\{x\mapsto d'\}))_1 \\&\sqsubseteq& (F^\sharp{}_{\tau,x}(\sigma\oplus\{x\mapsto d'\}))_1 \\&=& (F^\sharp{}_{\tau,x}(\sigma\oplus\{x\mapsto d\}))_1 \\&=& d'\end{array}\end{equation*}

Thereby, we have used that the value of the unknown x is not contained in $(F^\sharp{}_{x}(\sigma\oplus\{x\mapsto d'\}))_2$ – implying that it is also not contained in $(F^\sharp{}_{x}(\sigma\oplus\{x\mapsto d\}))_2$ . Moreover, for $y\in\mathcal{X}'$ different from x,

\begin{equation*}(\underline F^\sharp{}_{\tau,y}\,(\underline\sigma'\oplus\{x\mapsto d'\}))_1 \sqsubseteq(\underline F'_{y}\,\underline\sigma')_1\sqsubseteq\underline\sigma'\,y\end{equation*}

Accordingly, $\underline\sigma'\oplus\{x\mapsto d'\}$ is a post-solution of the lower monotonization of $\mathbb{E}{}_\tau$ , implying that $\underline\sigma\sqsubseteq\sigma'$ .

Now assume that after the evaluation of the right-hand side $f^\sharp{}_{x}$ , $x\in\textit{point}$ holds. We concentrate on the last tail-recursive call $\textsf{solve}\,\nabla\,x$ before the call to $\textsf{solve}\,\Delta\,x$ . Let $s_0 = (\sigma{}_0,\textit{infl}{}_0,\textit{stable}{}_0,\textit{called}{}_0,\textit{point}{}_0)$ . Let d denote the value of x before the evaluation of the right-hand side, and d’ the value returned by evaluating the right-hand side. Let $\mathcal{X}'$ denote the set of unknowns accessed during the call which are not stable before. Since subsequently $\textsf{solve}\,\Delta\,x$ is called, after that the evaluation of $f^\sharp_{x}$ , x is still stable. Therefore, one of the following two situations is encountered after the evaluation of the right-hand side:

  1. 1. $d'\sqsubseteq d$ , that is, the value d’ is subsumed by the current value of x; or

  2. 2. subsequent destabilization will not destabilize x.

In the second case, the unknowns from $\mathcal{X}'$ that directly or indirectly influence x cannot depend on x (w.r.t. the current map $\textit{infl}$ ). Thus, a similar argument as for unknowns not in $\textit{point}$ applies.

Accordingly, it remains to consider the first case. Consider the lower monotonizations of the abstract systems $\mathbb{E}^\sharp{}_\tau$ and $\mathbb{E}^\sharp{}_{\tau\oplus\{x\mapsto d\}}$ with least solutions $\underline\sigma$ and $\underline\sigma'$ , respectively. By inductive hypothesis applied to the unknowns in $\mathcal{X}'\setminus\{x\}$ and $\tau\oplus\{x\mapsto d\}$ , we find that $\underline\sigma'\,y\sqsubseteq\sigma{}_0\,y$ for all unknowns $y\in\mathcal{X}'\setminus\{x\}$ . This allows us to prove that $\underline\sigma'\oplus\{x\mapsto d\}$ is a post-solution of the lower monotonization of $\mathbb{E}^\sharp{}_\tau$ . Therefore, $\underline\sigma\,y \sqsubseteq(\underline\sigma'\oplus\{x\mapsto d\})\,y \sqsubseteq(\sigma{}_0\oplus\{x\mapsto d\})\,y$ for all $y\in\mathcal{X}'$ holds, and the claim follows.

Now consider the narrowing iteration performed for x in the subsequent call $\textsf{solve}\,\Delta\,x$ . Let $d_0$ denote the value after the last call to $\textsf{solve}\,\nabla\,x$ , and $d_1,\ldots, d_k$ denote the values returned by evaluating the right-hand side of x during this iteration and define $d'_0 = d_0$ and for $i>0$ , $d'_i = d'_{i-1}\Delta d_i$ . Let $\sigma{}_i$ denote the assignment attained after the ith narrowing step restricted to the unknowns $\mathcal{X}^\sharp\setminus(\mathcal{X}{}_0\cup\{x\})$ . For $i>0$ , let $\underline\sigma{}_i$ denote the least solution of the lower monotonization of $\mathbb{E}^\sharp{}_{\tau\oplus\{x\mapsto d_{i-1}\}}$ . By inductive hypothesis, $\underline\sigma{}_i\,y\sqsubseteq\sigma{}_i$ for all $y\neq x$ which are stable after the ith iteration. By induction on i, we prove that $\underline\sigma\,x\sqsubseteq d_i$ and thus also $\underline\sigma\,x\sqsubseteq d'_i$ , and $\underline\sigma\,y\sqsubseteq\sigma{}_i\,y$ for every $y\in\mathcal{X}^\sharp\setminus(\mathcal{X}{}_0\cup\{x\})$ which is stable after the ith narrowing iteration. This assertion holds for $i=0$ . For $i>0$ , the assertion on the $y\neq x$ follows by inductive hypothesis for a larger set of called unknowns. And for x, we have

\begin{equation*}\begin{array}{lcl}d_i &=& (F^\sharp{}_{x} (\tau\oplus\sigma{}_i\oplus\{x\mapsto d'_{i-1}\}))_1 \\ &\sqsupseteq& (\underline F^{(i)}_{x}(\sigma{}_i))_1 \\ &\sqsupseteq& (\underline F^{(i)}_{x}(\underline\sigma{}_i))_1 \\ &\sqsupseteq& (\underline F^\sharp{}_{x}(\underline\sigma{}_i\oplus\{x\mapsto d_{i-1}\}))_1 \\ &\sqsupseteq& (\underline F^\sharp{}_{x}\underline\sigma)_1\end{array}\end{equation*}

Here, $\underline F^\sharp{}_{x}$ and $\underline F^{(i)}_{x}$ are the right-hand sides of the lower monotonizations of $\mathbb{E}^\sharp{}_\tau$ and $\mathbb{E}^\sharp{}_{\tau\oplus\{x\mapsto d_{i-1}\}}$ for x, respectively. This completes the proof of the theorem.

7. The Space-efficient Solver TDspace

So far, our solver maintains an abstract value for each queried unknown. Given that the program to be analyzed is not small (e.g., more than 10,000 LOC) and program points must be analyzed for multiple contexts, the number of unknowns to be considered by the solver can be quite large. For more complicated properties to be analyzed, these abstract values to be recorded for these unknowns in themselves are space-consuming. The applicability of solvers for interprocedural analysis based on such solvers therefore is significantly increased if space consumption can be reduced. This is the objective of our second modification to the local generic solver TD.

In contrast to algorithm $\mathbf{TD}_{\mathbf{term}}$ , the new solver maintains abstract values only for widening and narrowing points as collected in the set $\textit{point}$ . The intuition is that the current values in $\sigma$ for all other unknowns can be reconstructed by evaluating their right-hand sides. Thus, we only call $\textsf{solve}$ for unknowns in $\textit{point}$ , which is why we now always perform widening or narrowing in $\textsf{solve}$ .

We remark that in absence of procedure calls, the set $\textit{point}$ may be statically chosen as the set of loop heads – given that each loop is dominated by a single program point. In presence of procedure calls, however, this is no longer easily possible.

Example 11 Consider again the example program from Figure 1, and the corresponding elaborated right-hand sides from Example 5. For the assignment $\sigma$ with $\sigma{\langle3,a\rangle} = \sigma{\langle4,a\rangle} = \sigma{\langle6,a\rangle} = a$ and $\sigma{\langle5,a\rangle} = h^\sharp{}_1\,a$ , the right-hand side of unknown ${\langle7,a\rangle}$ accesses, for example, the unknown ${\langle7,\sigma{\langle5,a\rangle}\rangle} = {\langle7,h^\sharp{}_1\,a\rangle}$ which may put ${\langle7,a\rangle}$ into $\textit{point}$ only if $(h^\sharp{}_1)^r a = a$ for some $r>0$ . The call $\textsf{eval}\,x\,y$ behaves the same as before for unknowns $y\in\textit{point}$ . For unknowns $y\not\in\textit{point}$ , the value now must be recovered. For that, y first is marked as $\textit{called}$ . Then, the right-hand side of y is evaluated (still passing x as first argument to eval); finally, y is again removed from $\textit{called}$ . If y is still not in $\textit{point}$ , the result for y is plainly returned. If, however, the evaluation of $f^\sharp{}_y$ has inserted y into the set $\textit{point}$ , the function $\textsf{eval}$ proceeds as if y had been contained in $\textit{point}$ right from the beginning. This means that $\textsf{solve}\,\nabla\,y$ is called, x is inserted into the set $\textit{infl}\,y$ , and subsequently, the value of $\sigma$ for y is returned.

We remark that on some inputs, the solver $\mathbf{TD}_{\mathbf{space}}$ may be rather inefficient, since the same unknown $y\not\in\textit{point}$ must be re-evaluated whenever the value of y is queried. At the expense of slightly more space, this deficiency can be remedied by maintaining the values for these unknowns encountered during the re-evaluation of the right-hand side of some unknown $x\in\textit{point}$ in a separate map $\tau$ (see the algorithm in Section B of the appendix).

8. Termination and Correctness of TDspace

In the following, we convince ourselves that the solver $\mathbf{TD}_{\mathbf{space}}$ has the same termination behavior as the solver $\mathbf{TD}_{\mathbf{term}}$ .

For a finite subset of unknowns $Y\subseteq\mathcal{X}^\sharp$ , we construct from $\mathbb{E}^\sharp$ the residual system $\mathbb{E}^\sharp{}_Y$ where the right-hand sides $\bar f^\sharp_{Y,y}$ are obtained from the right-hand sides $f^\sharp_y$ successively exploring the right-hand sides of unknowns not contained in Y. Technically, this means that for $y\in Y$ and $\sigma:Y\to\mathcal{M}(\mathbb{D})$ ,

\begin{equation*}\begin{array}{lll}f^\sharp_{Y,y}\,\sigma &=& f^\sharp_y\,(\textsf{bar}_Y\,\sigma)\qquad\text{where} \\\textsf{bar}_Y\,\sigma\,y &=& \textbf{if}\;y\in Y\;\textbf{then}\;\sigma\,y\; \textbf{else}\;f^\sharp_y\,(\textsf{bar}_Y\,\sigma)\end{array}\end{equation*}

Example 12 Consider the monadic right-hand side $f^\sharp_{{\langle7,a\rangle}}$ from Example 3 for the program from Figure 1, and $Y=\{{\langle7,a\rangle}\mid a\in\mathbb{D}\}$ , we have

\begin{equation*}\begin{array}{lll}f^\sharp_{Y,{\langle7,a\rangle}}\,\sigma &=& f^\sharp_{{\langle7,a\rangle}}\,(\textsf{bar}_Y\,\sigma) \\[0.5ex] &=& \textsf{bind}\,(\textsf{bar}_Y\,\sigma\,{\langle5,a\rangle})\,(\textbf{fun}\,b_1\to \\ & & \textsf{bind}\,(\textsf{bar}\,\sigma\,{\langle7,b_1\rangle})\,(\textbf{fun}\,b_2\to \\ & & \textsf{bind}\,(\textsf{bar}_Y\,\sigma\,{\langle6,a\rangle})\,(\textbf{fun}\,b_3\to \\ & & \textsf{return}\,(\textsf{combine}^\sharp\,b_1\,b2\;\sqcup\;h^\sharp_2\,b_3)))) \\[0.5ex] &=& \textsf{bind}\,(\textsf{return}\,(h^\sharp_1\,a))\,(\textbf{fun}\,b_1\to \\ & & \textsf{bind}\,(\sigma\,{\langle7,b_1\rangle})\,(\textbf{fun}\,b_2\to \\ & & \textsf{bind}\,(\textsf{return}\,a)\,(\textbf{fun}\,b_3\to \\ & & \textsf{return}\,(\textsf{combine}^\sharp\,b_1\,b2\;\sqcup\;h^\sharp_2\,b_3)))) \\[0.5ex] &=& \textsf{bind}\,(\sigma\,{\langle7,h^\sharp_1\,a\rangle})\,(\textbf{fun}\,b_2\to \\ & & \textsf{return}\,(\textsf{combine}^\sharp\,(h^\sharp_1\,a)\,b_2\;\sqcup\;h^\sharp_2\,a))\end{array}\end{equation*}

In general, let $F^\sharp{}_{Y,y}$ be the elaboration of $f^\sharp_{Y,y}$ . For some $y\in Y$ and some $\sigma$ , the evaluation of the right-hand side $f^\sharp_{Y,y}$ may not terminate – in which case, $F^\sharp{}_{Y,y}\,\sigma$ is undefined. For $\subseteq Y$ , let us call $\sigma$ $(Y,\textit{dom})$ -consistent if $F^\sharp{}_{Y,y}\,\sigma$ is defined for all $y\in \textit{dom}$ . In that case, there is a set $\bar Y=Y\cup\{y_1,\ldots,y_h\}\subseteq\mathcal{X}$ together with a map $\bar\sigma : \bar Y\to\mathbb{D}$ such that $\bar\sigma|_Y=\sigma$ , and

  1. 1. $(F^\sharp{}_{y_j}\,\bar\sigma)_1=\bar\sigma\,y_j$ and $(F^\sharp{}_{y_j}\,\bar\sigma)_2\subseteq\bar Y\cup\{y_1,\ldots,y_{j-1}\}$ for all $j=1,\ldots,h$ ; and

  2. 2. $(F^\sharp{}_{y}\,\bar\sigma)_2\subseteq\bar Y$ for all $y\in \textit{dom}$ .

The values of the unknowns in $\bar Y$ thus are sufficient to evaluate all right-hand sides of unknowns in $\textit{dom}\cup(\bar Y\backslash Y)$ , while the values of $\bar Y\setminus Y$ can be recovered from the values of the unknowns in Y.

Example 13. Continuing with Example 12, we have that the elaborated right-hand side $F^\sharp{}_{Y,{\langle7,a\rangle}}$ is given by:

\begin{equation*}\begin{array}{lll}F^\sharp{}_{Y,{\langle7,a\rangle}}\,\sigma &=& \begin{array}[t]{@{}l} (\textsf{combine}^\sharp\,(h^\sharp_1\,a)\,(\sigma\,{\langle7,h^\sharp_1\,a\rangle})\,\sqcup\,h^\sharp{}_2\,a, \{{\langle7,h^\sharp_1\,a\rangle}\}) \end{array}\end{array}\end{equation*}

where the required auxiliary unknowns y are given by

\begin{equation*}\begin{array}{l}{\langle3,a\rangle},{\langle4,a\rangle},{\langle5,a\rangle},{\langle6,a\rangle}\end{array}\end{equation*}

Subsequently, we adapt the notion of consistency from Section 5 to the case where values from $\mathbb{D}$ are only recorded for unknowns in $\textit{point}$ . Moreover, we maintain that $\textit{stable}$ as well as all sets $\textit{infl}\,y$ are all subsets of $\textit{point}$ . We now call a solver state $s=(\sigma,\textit{infl},\textit{stable},\textit{called},\textit{point})$ consistent if for $Y=(\textit{stable}\cup\textit{called})\cap\textit{point}$ and $\textit{dom}=\textit{stable}\backslash\textit{called}$ ,

  1. 1. $\sigma$ is $(Y,\textit{dom})$ -consistent;

  2. 2. for all $x\in \textit{dom}$ and all $y \in (F^\sharp{}_{Y,x}\,\sigma)_2$ , $y\in Y$ and $x\in\textit{infl}\,y$ holds;

  3. 3. for each $x\not\in Y$ , $\textit{infl}\,x=\unicode{x00D8}$ .

With this new notion, the invariants for the calls $\textsf{solve}\,p\,x$ , and $\textsf{destabilize}\,x$ from Section 5, now stay literally the same – with the extra assumption that x should necessarily be contained in $\textit{point}$ . For $\textsf{eval}\,x\,y$ , the new invariant must distinguish whether y is contained in $\textit{point}$ or not. Assuming that the solver state $s=(\sigma,\textit{infl},\textit{stable},\textit{called},\textit{point})$ before the call is consistent where $x\in\textit{stable}\cap\textit{called}\cap\textit{point}$ , the solver state $s'=(\sigma',\textit{infl}',\textit{stable}',\textit{called}',\textit{point}')$ after the call should now satisfy:

  1. 1. s’ is again consistent;

  2. 2. $\textit{called}=\textit{called}'$ , and

  3. 3. $\textit{stable}\subseteq\textit{stable}'$ where for all $y'\in(\textit{stable}\cup\textit{called})\cap\textit{point}$ , $\sigma'\,y'=\sigma\,y'$ and $\textit{infl}\,y'\subseteq\textit{infl}'\,y'$ ;

  4. 4. $\textit{point}\subseteq\textit{point}'$ where $y\in\textit{point}'$ whenever $y\in\textit{called}$ ;

  5. 5. If $y\in(\textit{stable}\cup\textit{called})\cap\textit{point}$ , then

  6. $\sigma = \sigma'$ , $\textit{stable}=\textit{stable}'$ and $\textit{point} = \textit{point}'$ ;

  7. $\textit{infl}'\,y'=\textit{infl}'\,y'$ for all $y'\neq y$ , and

  8. $\textit{infl}'\,y= \textit{infl}\,y\cup\{x\}$ .

  9. 6. In all cases when $y\in\textit{point}'$ , then $y\in\textit{stable}'$ and $x\in\textit{infl}'\,y$ and the value $\sigma'\,y$ is returned;

  10. 7. If $y\not\in\textit{point}'$ , then $F^\sharp{}_{Y',y}\,\sigma'$ is defined and produces the return value where ${Y'}=(\textit{stable}'\cup\textit{called}')\cap\textit{point}'$ .

In particular, x is still contained in $\textit{stable}'\cap\textit{called}'$ . What we additionally need is an extra argument why evaluation of the right-hand sides of unknowns $y\not\in\textit{point}$ will necessarily terminate. For that, we observe that, according to our assumption, evaluation of each right-hand side