Hostname: page-component-54dcc4c588-2ptsp Total loading time: 0 Render date: 2025-09-12T10:23:18.598Z Has data issue: false hasContentIssue false

Hiord$^{{\kern2pt}\sharp}$: An Approach to the Specification and Verification of Higher-Order (C)LP Programs

Published online by Cambridge University Press:  09 September 2025

MARCO CICCALÈ
Affiliation:
Universidad Politécnica de Madrid (UPM), IMDEA Software Institute, Madrid, Spain (e-mails: m.ciccale@alumnos.upm.es, marco.ciccale@imdea.org, daniel.jurjo@alumnos.upm.es, daniel.jurjo@imdea.org, josefrancisco.morales@upm.es, josef.morales@imdea.org)
DANIEL JURJO-RIVAS
Affiliation:
Universidad Politécnica de Madrid (UPM), IMDEA Software Institute, Madrid, Spain (e-mails: m.ciccale@alumnos.upm.es, marco.ciccale@imdea.org, daniel.jurjo@alumnos.upm.es, daniel.jurjo@imdea.org, josefrancisco.morales@upm.es, josef.morales@imdea.org)
JOSE F. MORALES
Affiliation:
Universidad Politécnica de Madrid (UPM), IMDEA Software Institute, Madrid, Spain (e-mails: m.ciccale@alumnos.upm.es, marco.ciccale@imdea.org, daniel.jurjo@alumnos.upm.es, daniel.jurjo@imdea.org, josefrancisco.morales@upm.es, josef.morales@imdea.org)
PEDRO LÓPEZ-GARCÍA
Affiliation:
Spanish Council for Scientific Research, IMDEA Software Institute, Madrid, Spain (e-mails: pedro.lopez@csic.es, pedro.lopez@imdea.org)
MANUEL V. HERMENEGILDO
Affiliation:
Universidad Politécnica de Madrid (UPM), IMDEA Software Institute, Madrid, Spain (e-mail: manuel.hermenegildo@upm.es, manuel.hermenegildo@imdea.org)
Rights & Permissions [Opens in a new window]

Abstract

Higher-order constructs enable more expressive and concise code by allowing procedures to be parameterized by other procedures. Assertions allow expressing partial program specifications, which can be verified either at compile time (statically) or run time (dynamically). In higher-order programs, assertions can also describe higher-order arguments. While in the context of (constraint) logic programming ((C)LP), run-time verification of higher-order assertions has received some attention, compile-time verification remains relatively unexplored. We propose a novel approach for statically verifying higher-order (C)LP programs with higher-order assertions. Although we use the Ciao assertion language for illustration, our approach is quite general, and we believe is applicable to similar contexts. Higher-order arguments are described using predicate properties – a special kind of property which exploits the (Ciao) assertion language. We refine the syntax and semantics of these properties and introduce an abstract criterion to determine conformance to a predicate property at compile time, based on a semantic order relation comparing the predicate property with the predicate assertions. We then show how to handle these properties using an abstract interpretation-based static analyzer for programs with first-order assertions by reducing predicate properties to first-order properties. Finally, we report on a prototype implementation and evaluate it through various examples within the Ciao system.

Information

Type
Original Article
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 (https://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), 2025. Published by Cambridge University Press

1 Introduction

Abstraction is a fundamental principle in computer science often used for managing complexity. Higher-order constructs are a form of abstraction that enables writing code that is more concise and expressive by allowing procedures to be parameterized by other procedures, resulting in more modular and maintainable code. (Constraint) logic programming languages like Prolog (Körner et al. Reference Körner, Leuschel, Barbosa, Santos-Costa, Dahl, Hermenegildo, Morales, Wielemaker, Diaz, Abreu and Ciatto2022) and functional programming languages like Haskell (Marlow Reference Marlow2010) have included different forms of higher-order since their early days, and languages from other programming paradigms like Java or C++ have adopted them later on. In particular, Prolog systems allow defining higher-order predicates and making higher-order calls. For example, the query: , passes the term even as an argument to the higher-order predicate filter/3, which applies the even/1 predicate to each element of the input list, selecting those that succeed, yielding . Assertions are linguistic constructs for writing partial program specifications, which can then be verified or used to detect deviations in program behavior w.r.t. such specifications. The assertion-based approach to program verification (Hermenegildo et al. Reference Hermenegildo, Puebla and Bueno1999; Puebla et al. Reference Puebla, Bueno and Hermenegildo2000b; Sanchez-Ordaz et al. Reference Sanchez-Ordaz, Garcia-Contreras, Perez-Carrasco, Morales, Lopez-Garcia and Hermenegildo2021) differs from other approaches such as strong type systems (Cardelli Reference Cardelli1989) in that assertions are optional and can include properties that are undecidable at compile time, and thus some checking may need to be relegated to run time. Hence, the assertion-based approach is closer to gradual typing in functional languages (Siek and Taha Reference Siek and Taha2006). The combination of higher-order predicates and assertions in the (C)LP context was already explored by Stulova et al. (Reference Stulova, Morales and Hermenegildo2014). This work introduced the notion of predicate properties, a special kind of properties that allow using the full power of the (Ciao) assertion language for describing the higher-order arguments of procedures. This work also proposed an operational semantics for dynamically checking higher-order (C)LP programs annotated with such higher-order assertions. However, the static verification of programs with higher-order assertions was not addressed in that work, and remains relatively unexplored since other related work in (C)LP that supports higher order (e.g. Miller (Reference Miller1991); Hill and Lloyd (Reference Hill and Lloyd1994); Somogyi et al. (Reference Somogyi, Henderson and Conway1996)) generally adheres to the strong typing model. In this work we propose a novel approach for the compile-time verification of higher-order (C)LP programs with assertions describing higher-order arguments. We present a refinement of both the syntax and the semantics of predicate properties (§3). Next, we define an abstract criterion to determine whether a predicate conforms to a predicate property at compile time, based on a semantic order relation between the definition of a predicate property and the partial specification of a predicate (§4.1). Then, we introduce an approach for “casting” predicate usage in a program analysis-friendly manner that enhances and complements the proposed abstract criterion (§4.2). We also propose a technique for dealing with these properties using an abstract interpretation-based static analyzer for programs with first-order assertions, by representing predicate properties as first-order properties that are natively understood by such an analyzer (§4.3). Finally, we present a prototype implementation of these techniques and study its application to a number of examples (§5). For concreteness, we use in our presentation the Ciao (Hermenegildo et al. Reference Hermenegildo, Bueno, Carro, López-García, Mera, Morales and Puebla2012) assertion language, and make use of its CiaoPP preprocessor (Hermenegildo et al. Reference Hermenegildo, Puebla, Bueno and Garcia2005), that combines both static and dynamic analysis. However, we believe the approach is quite general and flexible, and can be applied, at least conceptually, to similar gradual approaches.

2 Preliminaries and notation

Variables start with a capital letter. The set of terms is inductively defined as follows: (1) variables are terms (2) if $f$ is an $n$ -ary function symbol and $t_{1},\ldots , t_{n}$ are terms, then $f(t_{1},\ldots , t_{n})$ is a term. We use the overbar notation $(\bar {\cdot })$ to denote a finite sequence of elements (e.g. $\bar {t} \equiv t_{1},\ldots , t_{n}$ ), and write $|(\bar {\cdot })|$ for representing its length. An atom has the form $p(\bar {t})$ where $p$ is an $n$ -ary predicate symbol, and $\bar {t}$ are terms. The function $\mathsf{ar}{(p)}$ denotes the arity of a predicate $p$ . A higher-order atom has the form $X(\bar {t})$ where $X$ is a variable and $\bar {t}$ are terms. (Note that variables are not allowed in the function symbol position of terms, only in literals). A constraint is a conjunction of expressions built from predefined predicates whose arguments are constructed using predefined functions and variables, for example, $X-Y \gt \mathit{abs}(Z)$ . A literal is either an atom, a higher-order atom, or a constraint. Negation is encoded as finite failure, supported through a program expansion. A goal is a finite sequence of literals. A rule has the form $H\,\texttt {:-}\,B$ where $H$ , the head, is an atom and $B$ , the body, is a possibly empty finite sequence of literals. A higher-order constraint logic program, or higher-order program $P$ is a finite set of rules. We use $\sigma$ to represent a variable renaming, and $\sigma (L)$ or $L\sigma$ to represent the result of applying $\sigma$ to a syntactic object $L$ . The definition of an atom $L$ in a program, $\mathsf{defn}(L)$ , is the set of renamed program rules s.t. each renamed rule has $L$ as its head. We assume that all rule heads are normalized, that is, $H$ is an atom of the form $p(\bar {v})$ where $\bar {v}$ are distinct variables. Let $\bar {\exists }_L \theta$ denote the projection of the constraint $\theta$ onto the variables of $L$ . We denote constraint entailment by $\theta _1\models \theta _2$ .

2.1 Operational semantics of higher-order programs

The operational semantics of a higher-order program $P$ is given in terms of its derivations, which are sequences of reductions between states. A state $\langle G\, |\, \theta \rangle$ consists of a goal $G$ , and a constraint store (or store) $\theta$ . We denote sequence concatenation by (::). We assume for simplicity that the underlying constraint solver is complete and projection exists. We use $S\leadsto S'$ to indicate that a reduction step can be applied to state $S$ to obtain state $S'$ . Naturally, $S\leadsto ^* S'$ indicates that there is a sequence of reduction steps from $S$ to $S'$ . A state $S = \langle L::G\, |\, \theta \rangle$ where $L$ is a literal, is reduced to a state $S'$ as follows:

  1. 1. If $L$ is a constraint and $\theta \land L$ is satisfiable, then $S' = \langle G\, |\, \theta \land L \rangle$ .

  2. 2. If $L$ is an atom and $\exists ({L\,\texttt {:-}\,B})\in {\mathsf{defn}(L)}$ , then $S' = \langle B::G\, |\, \theta \rangle$ .

  3. 3. If $L$ is a higher-order atom of the form $X(\bar {t})$ , then $S' = \langle p(\bar {t}) :: G\, |\, \theta \rangle$ given that $\exists p \in {P} {\ldotp \;} \theta \models (X = p) \wedge {\mathsf{ar}{(p)}}={|\bar {t}|}$ .

Let $L$ be an atom, $S=\langle L::G\, |\, \theta \rangle$ , $S'=\langle G\, |\, \theta ' \rangle$ , and suppose $S\leadsto ^* S'$ . We refer to $S$ as a call state for $L$ , and $S'$ as a success state for $L$ . A query $Q$ is a pair $(L,\theta )$ , where $L$ is a literal and $\theta$ a store for which the (C)LP system starts a computation from state $\langle L\, |\, \theta \rangle$ . The set of all derivations of $P$ from a query $Q$ is denoted $\mathsf{derivs}({P},{Q})$ , and this notation is naturally extended to a set of queries ${\mathcal{Q}}$ . Let $D_{[-1]}$ denote the last state of any derivation $D$ . A finite derivation from a query $Q$ is finished if the last state in the derivation cannot be reduced. A finished derivation from a query $Q$ is successful if the last state is of the form $\langle \square \, |\, \theta ' \rangle$ , where $\square$ denotes the empty goal sequence. In that case, the constraint $\bar {\exists }_{L}\theta '$ is an answer to $Q$ . We denote by $\mathsf{answers}({P},{Q})$ the set of answers of $P$ to a query $Q$ . A finished derivation is failed if the last state is not of the form $\langle \square \, |\, \theta \rangle$ . A query $Q$ finitely fails if all derivations in $\mathsf{derivs}({P},{Q})$ are finished and have failed.

2.2 Property formulas

Conditions on the constraint store are stated as property formulas. A property formula is a DNF formula of property literals. A property literal is a literal corresponding to a special kind of predicates called properties. Properties are typically defined in the source language, in the same way as ordinary predicates but marked accordingly, and are required to meet certain conditions (Hermenegildo et al. Reference Hermenegildo, Puebla and Bueno1999; Puebla et al. Reference Puebla, Bueno and Hermenegildo2000b). In particular, they are normally required to be checkable at run time but not necessarily decidable at compile time, where they are safely approximated.Footnote 1

Example 2.1 (Properties). The following program defines the properties (“being a list”) and (“being a prefix of a list”):

The property formula states that Xs and Ys should be lists, and that Xs should be a prefix of Ys. This formula contains three property literals corresponding to the and properties.

We now recall an instrumental definition about properties from Puebla et al. (Reference Puebla, Bueno and Hermenegildo2000b):

Definition 2.1 (Succeeds trivially). A property literal $L$ succeeds trivially for $\theta$ in a program $P$ , denoted $\theta \Rightarrow _{P}L$ , iff $\exists \theta '\in {\mathsf{answers}(P,(L,\theta ))} {\ldotp \;} \theta \models \theta '$ . A property formula succeeds trivially for $\theta$ if all of the property literals of at least one conjunct of the formula succeeds trivially.

Intuitively, a property literal (or formula) succeeds trivially if it succeeds for $\theta$ in $P$ without adding new “relevant” constraints to $\theta$ . For example, checks “X being a list.”

2.3 Traditional assertions

Assertions are syntactic objects for expressing properties of programs that must be satisfied at program execution. We recall the herein relevant parts of the assertion schema of Puebla et al. (Reference Puebla, Bueno and Hermenegildo2000a). Traditional (or first-order) predicate (or pred) assertions have the following syntax: “,” where $\mathit{Pred}$ is a normalized atom representing a predicate, and $\mathit{Pre}$ and $\mathit{Post}$ are property formulas. They express that all calls to $\mathit{Pred}$ must satisfy the precondition $\mathit{Pre}$ , and, if such calls succeed, the postcondition $\mathit{Post}$ must be satisfied. If there are several pred assertions, the $\mathit{Pre}$ field of at least one of them must be satisfied.

Example 2.2 (Assertions). The following assertions for the take/3 predicate relating a list and its prefix:

restrict the meaning of take/3 as follows:

  • take(N,Xs,Ys) must be called with Xs bound to a list, and either N bound to an integer or Ys bound to a prefix of Xs.

  • If take(N,Xs,Ys) succeeds when called with N bound to an integer and Xs bound to a list, then Ys must be bound to a prefix of Xs.

  • If take(N,Xs,Ys) succeeds when called with Xs bound to a list and Ys bound to a prefix of Xs, then N must be bound to an integer.

We represent checks on the store by a set of assertions with a set of assertion conditions.

Definition 2.2 (Assertion conditions). Given a predicate represented by a normalized atom $\mathit{Pred}$ , and its corresponding set of assertions $\{A_{1},\ldots , A_{n}\}$ with $A_i=$ ,” the set of assertion conditions for $\mathit{Pred}$ is $\{C_0,C_{1},\ldots , C_{n}\}$ with

\begin{equation*} C_i = \begin{cases} {\mathsf{calls}(\mathit{Pred},\bigvee _{j=1}^n\, \mathit{Pre_j})} & i = 0\\ {\mathsf{success}(\mathit{Pred},\mathit{Pre_i},\mathit{Post_i})} & i \in 1..n \end{cases} \end{equation*}

Condition $C_0$ encodes the checks that ensure that all calls to the predicate represented by $\mathit{Pred}$ are within those admissible by the set of assertions; we refer to it as the calls assertion condition. Conditions $C_{1},\ldots , C_{n}$ encode the checks for compliance of the successes for particular sets of calls, and we call them the success assertion conditions.

From this point on, we denote by $\mathcal{A}$ both the set of assertions of the program and, interchangeably, its associated set of assertion conditions. Also, for a normalized atom $\mathit{Pred}$ , ${\mathcal{A}}(\mathit{Pred})$ denotes only the assertions of $\mathcal{A}$ associated to the predicate $\mathit{Pred}$ .

Example 2.3 (Assertion conditions). The set of assertion conditions for the set of pred assertions in Example 2.2 is:

2.4 Operational semantics of higher-order programs with traditional assertions

This operational semantics checks whether assertion conditions hold or not while computing the derivations from a query, halting the derivation as soon as an assertion condition is violated. For identifying a possible assertion condition violation, every assertion condition $C$ is related to a unique label $\ell$ via a mapping $\mathsf{label}(C) = \ell$ . States of derivations are now of the form $\langle G\, |\, \theta \, |\, {\mathcal{E}} \rangle$ , where $\mathcal{E}$ denotes the set of labels for falsified assertion conditions (with ${|{\mathcal{E}}|} \leqslant 1$ ); while such set is unnecessary if execution halts upon an assertion condition violation, we include it to keep the semantics presented in this paper close to that of previous work. A finished derivation from a query ${Q}=(L,\theta )$ is now successful if the last state is of the form $\langle \square \, |\, \theta '\, |\, \varnothing \rangle$ , failed if the last state is of the form $\langle L'\, |\, \theta '\, |\, \varnothing \rangle$ , and erroneous if the last state is of the form $\langle L'\, |\, \theta '\, |\, \{{\ell }\} \rangle$ . We also extend the set of literals with syntactic objects of the form $\mathsf{check}(L,\ell )$ where $L$ is a literal and $\ell$ is a label for an assertion condition, which we call check literals. Thus, a literal is now a constraint, an atom, a higher-order atom, or a $\mathsf{check}$ literal. We now recall the notion of Semantics with Assertions from Stulova et al. (Reference Stulova, Morales and Hermenegildo2018), which we adapt to support higher-order atoms. A state $S = \langle L::G\, |\, \theta \, |\, \varnothing \rangle$ , can be reduced to a state $S'$ , denoted $S\leadsto _{{\mathcal{A}}} S'$ , as follows:

  1. 1. If $L$ is a constraint or a higher-order atom, then $S' = \langle G'\, |\, \theta '\, |\, \varnothing \rangle$ , with $G'$ and $\theta '$ obtained as in the operational semantics without assertions: $\langle L::G\, |\, \theta \rangle \leadsto \langle G'\, |\, \theta ' \rangle$ .

  2. 2. If $L$ is an atom and $\exists ({L\,\texttt {:-}\,B})\in {\mathsf{defn}(L)}$ , then

    \begin{equation*} S' = \begin{cases} \langle G\, |\, \theta \, |\, \{\ell \} \rangle &\text{if } \exists C = {\mathsf{calls}(L,{\mathit{Pre}})} \in {\mathcal{A}} {\ldotp \;} \mathsf{label}(C) = \ell \wedge {{\theta } \not \Rightarrow _{P}}{{\mathit{Pre}}}\\[1mm] \langle B :: \mathit{PostC} :: G\, |\, \theta \, |\, \varnothing \rangle &\text{otherwise} \end{cases} \end{equation*}
    where $\mathit{PostC} = {\mathsf{check}(L,\ell _1)} :: \ldots :: {\mathsf{check}(L,\ell _n)}$ includes all the checks $\mathsf{check}(L,\ell _i)$ such that $\ell _i = \mathsf{label}(C_i)$ , with $C_i = {\mathsf{success}(L,{\mathit{Pre}}_i,{\mathit{Post}}_i)} \in {\mathcal{A}} \wedge {\theta \Rightarrow _{P}{\mathit{Pre}_i}}$ .
  3. 3. If $L$ is a check literal $\mathsf{check}(L',\ell )$ , then

    \begin{equation*} S' = \begin{cases} \langle G\, |\, \theta \, |\, \{\ell \} \rangle &\text{if } \exists C = {\mathsf{success}(L,\_,{\mathit{Post}})} \in {\mathcal{A}} {\ldotp \;} \mathsf{label}(C) = \ell \wedge {{\theta } \not \Rightarrow _{P}}{{\mathit{Post}}}\\ \langle G\, |\, \theta \, |\, \varnothing \rangle &\text{otherwise} \end{cases} \end{equation*}

The set of derivations for a program $P$ with assertions $\mathcal{A}$ from a set of queries ${\mathcal{Q}}$ using the semantics with assertions is denoted ${\mathsf{derivs}_{\mathcal{A}}}({{P}},{{{\mathcal{Q}}}})$ . Given a predicate represented by a normalized atom $L$ , a store $\theta$ , and a set of queries ${\mathcal{Q}}$ , we define the success context $\mathcal{S}_{{\mathcal{A}}}(L,\theta ,{P},{{\mathcal{Q}}})$ of $L$ and $\theta$ for $P$ and ${\mathcal{Q}}$ as $\{\bar {\exists }_{L}\theta '\,|\,\exists D \in {\mathsf{derivs}_{\mathcal{A}}}({{P}},{{{\mathcal{Q}}}}) {\ldotp \;} \exists G {\ldotp \;} \langle L::G\, |\, \theta \rangle \in D {\ldotp \;} D_{[-1]} = \langle G\, |\, \theta ' \rangle \}$ . Intuitively, the success context of a predicate $p$ with its assertions is the set of stores of the success states of $p$ obtained using the semantics above.

2.5 Static program analysis by abstract interpretation

Abstract interpretation (Cousot and Cousot Reference Cousot and Cousot1977) is a mathematical framework for constructing sound, static program analysis tools. These tools extract properties about a program by interpreting it over a special abstract domain ( $D^\sharp$ ), whose elements are finite representations of (possibly infinite) sets of actual constraints in the concrete domain ( $D$ ). Elements of the concrete and abstract domains are related by two functions: abstraction ( $\alpha : {D} \rightarrow {D^\sharp }$ ) and concretization ( $\gamma : {D^\sharp } \rightarrow {D}$ ). Provided certain conditions on $D^\sharp$ , $\alpha$ , and $\gamma$ , abstract interpretation guarantees soundness and termination of the analysis.

2.6 Goal-dependent abstract interpretation

We use, for concreteness, goal-dependent abstract interpretation, in particular the PLAI algorithm (Muthukumar and Hermenegildo Reference Muthukumar and Hermenegildo1992). This technique takes a program $P$ , an abstract domain $D^\sharp$ , and a set of initial abstract queries ${\mathcal{Q}}^\sharp$ , describing all the possible initial concrete queries to $P$ . An abstract query $Q^\sharp$ is a pair $(L,\lambda )$ , where $L$ is an atom and $\lambda \in {D^\sharp }$ an abstraction of a set of concrete initial program states (e.g. constraint stores). A set of abstract queries ${\mathcal{Q}}^\sharp$ represents a set of concrete queries defined as $\gamma ({{\mathcal{Q}}^\sharp })=\{(L,\theta )\,|\,(L,\lambda )\in {{\mathcal{Q}}^\sharp }\,\wedge \, \theta \in \gamma (\lambda )\}$ . The algorithm computes a set of triples $\{{\langle {L_{1},\lambda ^c_{1},\lambda ^s_{1}} \rangle },\ldots ,{\langle {L_{n},\lambda ^c_{n},\lambda ^s_{n}} \rangle }\}$ where $L_i$ is an atom, and $\lambda _{i}^c$ and $\lambda _{i}^s$ are abstractions approximating the set of all call and success states for $L_i$ , respectively, for all occurrences of literal $L_i$ in all possible derivations of $P$ from $\gamma ({{\mathcal{Q}}^\sharp })$ . Higher-order atoms are supported by reducing them to first-order calls when the called predicate can be determined by the analysis, or making conservative assumptions otherwise. For the rest of the paper, we assume that the abstract interpretation of a program $P$ for the set of initial abstract queries ${\mathcal{Q}}^\sharp$ , denoted by $[\![ {P}]\!]_{{\mathcal{Q}^\sharp }}^\sharp$ , works with an implicit abstract domain $D^\sharp$ , which safely approximates the concrete values and operations. Although not strictly required, $D^\sharp$ has a lattice structure with a bottom-most element $\bot$ , meet $(\sqcap )$ , join $(\sqcup )$ , and less than $(\sqsubseteq )$ operators. As usual, $\bot$ denotes the abstraction s.t. $\gamma (\bot )=\varnothing$ .

2.7 Compile-time verification of (first-order) assertions

In addition to generating the results mentioned above, the analyzer also checks any (first-order) assertions in the program by safely approximating the property formulas of such assertions, and comparing them against the analysis results ( $[\![ {P}]\!]_{{\mathcal{Q}^\sharp }}^\sharp$ ) using the abstract operators.Footnote 2 The verification result is reported as changes in the status and transformations of the assertions: if the properties are satisfied; if some property is proved not to hold; or if neither of the first two can be determined, in which case run-time checks will be inserted into the program to ensure run-time safety. The verification process yields an assignment of a value , , or to each assertion in $\mathcal{A}$ , denoted $\mathsf{acheck}({\mathcal{A}},{[\![ {P}]\!]_{{\mathcal{Q}^\sharp }}^\sharp })$ .

3 Specifying higher-order programs: predicate properties

In higher-order (C)LP, variables can be bound to predicate symbols that are later invoked. This naturally gives rise to the need for expressing conditions on these predicates that must hold during program execution. To this end, predicate properties were introduced in Stulova et al. (Reference Stulova, Morales and Hermenegildo2014), which we revise and refine here.Footnote 3 A predicate property is defined as a set of anonymous assertions. Anonymous assertions generalize traditional assertions by allowing the predicate symbol in the $\mathit{Pred}$ field to act as a placeholder.

Definition 3.1 (Anonymous assertion). An anonymous assertion ${}^{\circ {\kern-3.5pt}}A$ is an assertion whose $\mathit{Pred}$ field is of the form $\_(\bar {v})$ , where $\bar {v}$ are free, distinct variables, and $\_$ is a placeholder for a predicate symbol. Footnote 4 Instantiating $\_$ with a specific predicate symbol $p$ produces a traditional assertion for $p$ derived from the anonymous assertion ${}^{\circ {\kern-3.5pt}}A$ , denoted ${{}^{\circ {\kern-3.5pt}}A}|_{p}$ .

Example 3.1 (Anonymous assertion). Let ${}^{\circ {\kern-3.5pt}}A$ be the anonymous assertion: “.” Then, ${{}^{\circ {\kern-3.5pt}}A}|_{p}$ is the traditional assertion: “” obtained by instantiating the anonymous assertion ${}^{\circ {\kern-3.5pt}}A$ with the predicate symbol p.

Definition 3.2 (Predicate property). A predicate property $\Pi$ is a set of anonymous assertions $\{{}^{\circ {\kern-3.5pt}}{A}_{1},\ldots , {}^{\circ {\kern-3.5pt}}{A}_{n}\}$ . Its syntax is: “ $\Pi$ $\_(\bar {v})$ $\mathit{Pre}_1$ $\mathit{Post}_1$ $\_(\bar {v})$ $\mathit{Pre}_n$ $\mathit{Post}_n$ ”. The function $\mathsf{ar}{(\Pi )}$ denotes the arity of the predicates for which all of the anonymous assertions in $\Pi$ express a property. Instantiating $\Pi$ with a specific predicate symbol $p$ produces a set of traditional assertions for $p$ , denoted ${\Pi |_{p}} = \{{{{}^{\circ {\kern-3.5pt}}A}_1|_{p}},\ldots ,{{{}^{\circ {\kern-3.5pt}}A}_n|_{p}}\}$ .

We use $\Pi$ to refer to both a set of anonymous assertions and, interchangeably, the corresponding set of anonymous assertion conditions; extending instantiation accordingly.

Example 3.2 (Predicate property). The following program defines the predicate property (“being a predicate that behaves as an integer nondeterministic binary operator”), and a higher-order assertion:

The predicate property literal (Op) states that Op should be a $3$ -ary predicate s.t., if called with its first two arguments bound to integers, then its third argument should be bound to an integer upon success. The higher-order assertion for the eval/4 predicate states that it must be called with its first two arguments bound to integers and its third argument bound to a predicate that conforms to property , and that if any such call succeeds, then its fourth argument should be bound to an integer.

4 Verifying higher-order programs

Once established how to specify higher-order programs using predicate properties, we now concentrate on how to verify such programs. We first recall some instrumental definitions from Puebla et al. (Reference Puebla, Bueno and Hermenegildo2000b) for reasoning about abstractions of property formulas. For the rest of the discussion, let $P$ be a program and $F$ a property formula defined in $P$ .

Definition 4.1 (Trivial success set). We define the trivial success set of $F$ as ${F^{\natural }} = \{ \bar {\exists }_F\theta \, |\, {\theta \Rightarrow _{P}{F}} \}$ .

Example 4.1 (Trivial success set). Let $F=$ (L), both $\theta _1=$ {L } and $\theta _2=$ {L } are in $F^{\natural }$ , but $\theta _3=$ {L } is not, since a call to (L , (L)) would further instantiate the second argument of . The trivial success set $F^{\natural }$ of $F$ captures the notion that the (L) property formula requires L to be instantiated to (the structure of) a list.

Definition 4.2 (Abstract trivial success subset). An abstraction is an abstract trivial success subset of $F$ , denoted $F^{\sharp -}$ , iff $\gamma ({F^{\sharp -}}) \subseteq {F^{\natural }}.\,$

Definition 4.3 (Abstract trivial success superset). An abstraction is an abstract trivial success superset of $F$ , denoted $F^{\sharp +}$ , iff $\gamma ({F^{\sharp +}}) \supseteq {F^{\natural }}.$

Intuitively, $F^{\sharp -}$ and $F^{\sharp +}$ are, respectively, a safe under- and over-approximation of the trivial success set $F^{\natural }$ of the property formula $F$ , and they can always be computed at compile-time by choosing the closest element in the abstract domain.

4.1 Conformance to a predicate property

When we provide a partial specification for a higher-order argument $X$ of a higher-order predicate using a predicate property $\Pi$ , we are describing requirements that the predicates that $X$ may be bound to must meet. We refer to a predicate $p$ behaving correctly w.r.t. $\Pi$ as: $p$ conforming to $\Pi$ . We will now formalize this notion, with the goal of being able to safely approximate the set of predicates that $X$ can be bound to without violating $\Pi$ .

Definition 4.4 (Covered predicate). Given $C = {\mathsf{calls}({\_(\bar {v})},{{}^{\circ {\kern-2pt}}\mathit{Pre}})} \in \Pi$ and ${{}^{\circ {\kern-1.5pt}}C} = {\mathsf{calls}({p(\bar {v})},\mathit{Pre})} \in {\mathcal{A}}$ , we say that $p$ can be covered with $\Pi$ iff ${{{}^{\circ {\kern-2pt}}\mathit{Pre}}^{\natural }} \subseteq {{\mathit{Pre}}^{\natural }}$ .

Intuitively, $p$ can be covered with $\Pi$ if the set of admissible calls to $p$ is a superset of the set of admissible calls described by $\Pi$ .

Definition 4.5 (Redundance). Under the same conditions as in Definition 4.4, given that $p$ can be covered with $\Pi$ , we define the set of assertion conditions ${\mathcal{A}}'$ as follows:

\begin{equation*} {\mathcal{A}}' = \{{\mathsf{calls}({p(\bar {v})},\mathit{Pre} \wedge {{}^{\circ {\kern-2pt}}\mathit{Pre}})}\} \cup ({\mathcal{A}} \setminus \{C\}) \cup (\Pi \setminus \{{{}^{\circ {\kern-1.5pt}}C}\})|_{p} \end{equation*}

Given a sequence of literals $G$ , let $\mathcal{U}(G)$ denote the result of removing all check literals from $G$ . We extend $\mathcal{U}$ to derivations so that $\mathcal{U}(D)$ denotes the derivation resulting from transforming any (extended) state $\langle G\, |\, \theta \, |\, {\mathcal{E}} \rangle$ in $D$ into the state $\langle G'\, |\, \theta \rangle$ , where $G'= \mathcal{U}(G)$ . Let ${Q}_p$ be a query to $p$ . We say that $\Pi$ is redundant for $p$ under ${Q}_p$ iff

\begin{equation*} \forall D' \in {\mathsf{derivs}_{\mathcal{A}'}}({{P}},{{Q}_p}) {\ldotp \;} D'_{[-1]} = \langle G'\, |\, \theta \, |\, \{\ell '\} \rangle , \end{equation*}

and

\begin{equation*} \forall D \in {\mathsf{derivs}_{\mathcal{A}}}({P},{{Q}_p}) {\ldotp \;} \mathcal{U}(D) = \mathcal{U}(D'), \end{equation*}

it holds that $D_{[-1]}\leadsto ^*_{{\mathcal{A}}} \langle G\, |\, \theta \, |\, \{\ell \} \rangle$ through a derivation that reduces only check literals (if any at all), Footnote 5 where $\ell$ (resp., $\ell '$ ) is the label for a $\mathsf{calls}$ or $\mathsf{success}$ assertion condition in ${\mathcal{A}}({p(\bar {v})})$ (resp., ${\mathcal{A}}'({p(\bar {v})})$ ).

Intuitively, a predicate property $\Pi$ is redundant for a predicate $p$ under a query ${Q}_p$ to $p$ iff augmenting the original set of assertion conditions ( $\mathcal{A}$ ) with that of $\Pi$ ( ${\mathcal{A}}'$ ) does not introduce new run-time check errors in any derivation starting from ${Q}_p$ .Footnote 6

Definition 4.6 (Conformance). Let ${{\mathcal{Q}}}_p$ be the set of all possible queries to $p$ . A predicate $p$ conforms to $\Pi$ , denoted $p {\mathbin {\prec }} \Pi$ , iff $\forall {Q}_p \in {{\mathcal{Q}}}_p {\ldotp \;} \Pi$ is redundant for $p$ under ${Q}_p$ . Conversely, $p$ does not conform to $\Pi$ , denoted $p {\mathbin {\nprec }} \Pi$ , iff $\exists {Q}_p \in {{\mathcal{Q}}}_p {\ldotp \;} \Pi$ is not redundant for $p$ under ${Q}_p$ .

To prove that a predicate conforms to a predicate property, all possible derivations from all possible queries to that predicate have to be considered, which is often not feasible in practice. To this end, we introduce the notion of abstract conformance as a compile-time conformance criterion. Abstract conformance safely approximates the notion of conformance by comparing the assertion conditions of a predicate and those of a predicate property under the order relation of an abstract domain. We denote by $({\mathbin {\prec ^{\sharp -}}})$ the notion of strong abstract conformance, and by $({\mathbin {\prec ^{\sharp +}}})$ that of weak abstract conformance. That is, an under- and over-approximation of abstract conformance, respectively. Intuitively, strong abstract conformance captures only predicates known to conform, while weak abstract conformance also includes those for which conformance is unknown. Thus, the negation of weak abstract conformance captures the predicates that are known not to conform.

Definition 4.7 (Abstract conformance on “calls”). Let $\mathit{Pre}$ be the precondition of the $\mathsf{calls}$ assertion condition for $p$ in $\mathcal{A}$ , and ${}^{\circ {\kern-1.5pt}}C$ be an anonymous $\mathsf{calls}$ assertion condition $\mathsf{calls}({\_(\bar {v})},{{}^{\circ {\kern-2pt}}\mathit{Pre}})$ . Then:

\begin{align*} p {\mathbin {\prec ^{\sharp -}}} {{}^{\circ {\kern-1.5pt}}C} &\Leftrightarrow ({{\mathit{Pre}}^{\sharp +}} \sqsubseteq {{{}^{\circ {\kern-2pt}}\mathit{Pre}}^{\sharp -}}) \wedge ({{\mathit{Pre}}^{\sharp -}} \sqsupseteq {{{}^{\circ {\kern-2pt}}\mathit{Pre}}^{\sharp +}})\\ p {\mathbin {\nprec ^{\sharp +}}} {{}^{\circ {\kern-1.5pt}}C} &\Leftrightarrow {{\mathit{Pre}}^{\sharp +}} \sqcap {{{}^{\circ {\kern-2pt}}\mathit{Pre}}^{\sharp +}} = \bot \end{align*}

Definition 4.8 (Abstract conformance on “success”). Let $\mathcal{A}$ be the set of assertion conditions for $p$ , and ${}^{\circ {\kern-1.5pt}}C$ be an anonymous $\mathsf{success}$ assertion condition $\mathsf{success}({\_(\bar {v})},{{}^{\circ {\kern-2pt}}\mathit{Pre}},{{}^{\circ {\kern-2pt}}\mathit{Post}})$ . Then:

\begin{align*} p {\mathbin {\prec ^{\sharp -}}} {{}^{\circ {\kern-1.5pt}}C} \Leftrightarrow &\;\exists S \subset {\mathcal{A}}, ({{\mathit{Pre}}^{\sharp -}}_{\sqcup } \sqsupseteq {{{}^{\circ {\kern-2pt}}\mathit{Pre}}^{\sharp +}}) \wedge ({{\mathit{Post}}^{\sharp +}}_{\sqcup } \sqsubseteq {{{}^{\circ {\kern-2pt}}\mathit{Post}}^{\sharp -}}),\\ &\;\text{ where } \begin{cases} {{\mathit{Pre}}^{\sharp -}}_{\sqcup } & =\sqcup \{{{\mathit{Pre}}^{\sharp -}}\,\,|\,{\mathsf{success}({p(\bar {v})},{\mathit{Pre}},\_)} \;\;\in S\}\\ {{\mathit{Post}}^{\sharp +}}_{\sqcup } & =\sqcup \{{{\mathit{Post}}^{\sharp +}}\,|\,{\mathsf{success}({p(\bar {v})},\_,{\mathit{Post}})} \, \in S\} \end{cases}\\ p {\mathbin {\nprec ^{\sharp +}}} {{}^{\circ {\kern-1.5pt}}C} \Leftrightarrow &\;\exists \,{\mathsf{success}({p(\bar {v})},{\mathit{Pre}},{\mathit{Post}})}\in {\mathcal{A}}, ({\mathit{Pre}^{\sharp +}} \sqsubseteq {{{}^{\circ {\kern-2pt}}\mathit{Pre}}^{\sharp -}}) \,\wedge \\ &\wedge ({{\mathit{Post}}^{\sharp +}} \sqcap {{{}^{\circ {\kern-2pt}}\mathit{Post}}^{\sharp +}} = \bot ) \wedge \exists \theta \in {{\mathit{Pre}}^{\natural }} {\ldotp \;} \mathcal{S}_{{\mathcal{A}}}({p(\bar {v})},\theta ,{P},\gamma ({{\mathcal{Q}}^\sharp }_{p})) \neq \varnothing \end{align*}

where ${{\mathcal{Q}}^\sharp }_p$ is the set of abstract queries s.t. $\gamma ({{\mathcal{Q}}^\sharp }_p)$ is a superset of the set of all valid queries to $p$ described by the $\mathsf{calls}$ assertion condition of $p$ in $\mathcal{A}$ .

Definition 4.9 (Abstract conformance). We define abstract conformance to a predicate property as follows:

\begin{align*} p {\mathbin {\prec ^{\sharp -}}} \Pi &\Leftrightarrow \forall \,{{}^{\circ {\kern-1.5pt}}C} \in \Pi _C {\ldotp \;} p {\mathbin {\prec ^{\sharp -}}} {{}^{\circ {\kern-1.5pt}}C}\\ p {\mathbin {\nprec ^{\sharp +}}} \Pi &\Leftrightarrow \exists \,{{}^{\circ {\kern-1.5pt}}C} \in \Pi _C {\ldotp \;} p {\mathbin {\nprec ^{\sharp +}}} {{}^{\circ {\kern-1.5pt}}C} \end{align*}

Note that abstract conformance is computed by first computing the abstract trivial success subsets or supersets of the involved property formulas, and then applying the operators of the abstract domain. For abstract domains which may lose precision with their ( $\sqcup$ ) abstract operator, more advanced techniques for leveraging multiple abstractions become necessary, for example, covering (Debray et al. Reference Debray, Lopez-Garcia and Hermenegildo1997). We now relate the notions of conformance and abstract conformance.

Theorem 4.1. Let $p$ be a predicate, $\Pi$ be a predicate property: $p {\mathbin {\prec ^{\sharp -}}} \Pi \Rightarrow p {\mathbin {\prec }} \Pi$ , and $p {\mathbin {\nprec ^{\sharp +}}} \Pi \Rightarrow p {\mathbin {\nprec }} \Pi .$

Proof. The proofs proceed by contradiction and direct proof, respectively, using Defs. 4.2 to 4.8 and some basic set manipulation. Detailed proofs can be found in Appendix A.

Fig. 1. Example case analysis on a predicate property and assertions.

Example 4.2 (Abstract conformance). Consider determining conformance to the predicate property in Figure 1a which, for simplicity, we will interchangeably refer to as $\Pi$ for the rest of the example – given the assertions ${\mathcal{A}}=\{A_{1},\ldots , A_{5} \}$ in Figure 1b . (Notice that the property formulas of both $\Pi$ and $\mathcal{A}$ include elements of the abstract domain represented by the lattice in Figure 1c ). Their corresponding sets of assertion conditions are:

We aim to determine which predicates partially specified by $\mathcal{A}$ abstractly conform to the predicate property $\Pi$ . For each predicate $\mathit{Pred}_i$ and its associated $\mathsf{calls}$ and $\mathsf{success}$ assertion conditions, we: (1) determine abstract conformance to the anonymous $\mathsf{calls}$ condition of $\Pi$ ; (2) determine abstract conformance to the anonymous $\mathsf{success}$ condition of $\Pi$ ; and (3) determine abstract conformance to $\Pi$ :

Tables 1 and 2 summarize the abstract conformance analysis between the $\mathsf{calls}$ and $\mathsf{success}$ assertion conditions of each predicate and those of $\Pi$ , respectively. Specifically, for $\mathsf{calls}$ (in Table 1 ), we compare the preconditions and apply Definition 4.7; for $\mathsf{success}$ (in Table 2 ), we compare both pre- and post-conditions and apply Definition 4.8.

As a summary, the only predicate that definitely conforms to is n2n/2, since both of its assertion conditions conform to $\Pi$ .

Table 1. Abs. conf. on “calls” example with ${{}^{\circ {\kern-2pt}}\mathit{Pre}}=$ (X)

Table 2. Abs. conf. on “success” example with ${{}^{\circ {\kern-2pt}}\mathit{Pre}}=$ (X) and ${{}^{\circ {\kern-2pt}}\mathit{Post}}=$ (Y)

4.2 Wrappers

Consider a predicate $p$ and a predicate property $\Pi$ s.t. $p$ can be covered by $\Pi$ . From Definition4.4 we know that given their respective preconditions $\mathit{Pre}$ and ${}^{\circ {\kern-2pt}}\mathit{Pre}$ , ${{\mathit{Pre}}^{\natural }} \supseteq {{{}^{\circ {\kern-2pt}}\mathit{Pre}}^{\natural }}$ . Thus, according to Definition4.7, $p$ may abstractly conform to $\Pi$ ( $p {\mathbin {\prec ^{\sharp +}}} \Pi$ ), since $\mathit{Pre}$ may describe more admissible call states for $p$ than ${}^{\circ {\kern-2pt}}\mathit{Pre}$ , which can lead to omitting some run-time check errors that would be raised by ${}^{\circ {\kern-2pt}}\mathit{Pre}$ .

Example 4.3 (Weak abstract conformance). Consider a query to the following program.

Take a derivation of such query that starts by reducing to the body of the first clause $(1)$ : no $\mathsf{calls}$ assertion condition violation is expected since all predicates that conform to must accept all natural numbers on calls. Now, take a derivation that reduces to the body of the second clause $(2)$ : a $\mathsf{calls}$ assertion condition violation is expected since all predicates that conform to must raise an error for any input different from a natural number. However, in this particular case, no error is raised, since the predicate even/1 accepts any integer on calls. Moreover, if we had:

then a clause like foo(P) bar(P). would be problematic. For this clause, looking at the assertion of foo(P), the predicate in P is required to conform to , which would be an error when calling bar(P) since is disjoint from . However, if we consider the particular case in which P = even, it may not. So, according to Definition 4.7, we could only conclude that even/1 $\mathbin {\prec ^{\sharp +}}$ , that is, even/1 may abstractly conform.

In the example above, we motivate the need for such a restrictive condition for abstract conformance on calls (see Definition4.7). However, we may want to use predicates whose set of admissible calls is greater than that of a predicate property, but without unexpected behavior. To this end, we propose a technique to restrict the set of admissible calls of a predicate $p$ described by $\mathit{Pre}$ to match that of ${}^{\circ {\kern-2pt}}\mathit{Pre}$ in a program analysis-friendly manner. This restriction is implemented using wrappers. A wrapper for $p$ with $\Pi$ is simply a new predicate $w(\bar {v})\,\texttt {:-}\,p(\bar {v})$ with an assertion “ $w(\bar {v})$ : ${}^{\circ {\kern-2pt}}\mathit{Pre}$ .” (note that fields of pred assertions, in this case the postcondition, can be omitted, equivalently to true). A wrapper for $p$ with $\Pi$ also makes explicit the intention of creating a $\Pi$ -tailored version of $p$ . Additionally, wrappers can also be used to alleviate the process of determining abstract conformance on calls (particularly useful in the implementation), since the wrapper would syntactically (and thus, semantically) match the precondition of the predicate property.

Example 4.4 (Wrapper). As a follow-up of the previous example, consider wrapping even/1 with :

Intuitively, even_nat/1 conforms to , and the analyzer can now infer that the clause foo(P) :- bar(P). should raise an error since even_nat/1 only accepts naturals.

4.2.1 Rationale for explicit wrappers

The design of ${Hiord}^{\sharp}$ follows the philosophy behind the Ciao system (Hermenegildo et al. Reference Hermenegildo, Bueno, Carro, López-García, Mera, Morales and Puebla2012), which extends Prolog with static and dynamic assertion checking (among other modular extensions) without altering its untyped nature. We also considered some alternative solutions to the problem at hand, such as tainting each predicate passed as an argument annotated with a predicate property, and restricting its future use in all internal (recursive) calls. However, this approach would have required modifying the standard Prolog semantics regarding higher-order calls. The use of wrappers allows us to simulate this behavior without altering the underlying semantics.

4.3 First-order representation of predicate properties

As mentioned in §2, the abstract interpretation-based static analyzer can infer properties about higher-order programs, and also verify first-order assertions. However, here we obviously need to deal with predicate properties in assertions. Usually, for a new type of property, a new abstract domain is needed. As an alternative approach, we herein propose representing predicate properties as first-order properties of a kind which can be natively supported by the analyzer, thus allowing us to leverage existing and mature abstract domains. More concretely, we propose representing predicate properties as regular types, a special kind of properties (and thus defined as predicates) that are used to describe the shape of a term. Intuitively, such types will capture sets of predicate names. For example, given the predicate property , we can represent that the predicates p, and q strongly, and r weakly conforms to as the following regular types: $^{-}$ /1 = $\{$ $^{-}$ (p), $^{-}$ (q) $\}$ and $^{+}$ /1 = $\{$ $^{+}$ (p), $^{+}$ (q), $^{+}$ (r) $\}$ .Footnote 7 Formally, given a predicate property $\Pi$ , we define two associated regular types: $\pi ^{-}$ /1 and $\pi ^{+}$ /1, that capture the set of predicates that strongly and weakly abstractly conform to $\Pi$ as follows: $\pi ^{-}\texttt {/1} = \{\pi ^{-}(p)\,|\,p \in {P} \wedge p {\mathbin {\prec ^{\sharp -}}} \Pi \}$ , and $\pi ^{+}\texttt {/1} = \{\pi ^{+}(p)\,|\,p \in {P} \wedge p {\mathbin {\prec ^{\sharp +}}} \Pi \}$ . By definition, $\pi ^{-}$ /1 is a subtype of $\pi ^{+}$ /1. These regular types reduce the compile-time checking of higher-order assertions to that of first-order assertions. Regular types can be abstracted and inferred by several abstract domains; for concreteness we use eterms (Vaucheret and Bueno Reference Vaucheret and Bueno2002).

4.4 Hiord $^{{\kern.5pt}\sharp}$ algorithm

Algorithm 1 [Hiord ]: Verify a higher-order program with higher-order assertions

We now present $\textit{Hiord} ^{{\kern.5pt}\sharp}$ , the core algorithm for the compile-time verification of a higher-order program $P$ with higher-order assertions $\mathcal{A}$ (Algorithm1). First, it initializes a set of rules $R$ , and it computes the regular type representations of each predicate property $\Pi$ in $P$ , that is, the $\pi ^{-}$ and $\pi ^{+}$ predicates respectively (lines 4 to 6). This computation is performed by directly applying Definitions4.7 to 4.9 using the operators of the abstract domain, and (implicitly) extending the program $P$ with $R$ . Since predicate properties can include predicate property literals from other predicate properties – that is, dependencies among predicate properties – lines 4 to 6 are repeated until a fixpoint is reached (lines 2 and 7). Next, it computes the abstract interpretation of $P$ , augmented with the regular type representations of every predicate property, for the set of abstract queries ${\mathcal{Q}}^\sharp$ (line 8). Finally, it performs the compile-time verification of the set of (now first-order) assertions $\mathcal{A}$ w.r.t. the static analysis results, where predicate properties are now treated as standard regular types (line 8). As the result of the algorithm, we obtain the verified status of each assertion of $\mathcal{A}$ , where each assertion can be discharged (), disproved () and an error flagged, or left in status, and subject to run-time checks, as in Stulova et al. (Reference Stulova, Morales and Hermenegildo2014). We argue that, despite the inherent complexity of the verification problem in hand, the proposed concepts make the compile-time checking algorithm clear and concise; and, more importantly, easily implementable using a first-order assertion checker.

5 Implementation and experiments

To demonstrate the potential of our approach, we have implemented a prototype of the ${Hiord}^{\sharp}$ technique as part of the Ciao system. It implements Algorithm1 and uses CiaoPP, the Ciao program preprocessor, with the eterms abstract domain. We ran experiments on a set of small but representative higher-order programs that were not possible to verify until this point. We illustrate below our experiments with a selection of these programs.

5.1 A synthetic benchmark

We started by defining a test case comprising a predicate property using an anonymous pred assertion and 25 predicates, each with a pred assertion, designed to exhaustively cover all possible orderings between the pre- and post-conditions of the predicate property and of each predicate. We then ran ${Hiord}^{\sharp}$ , obtaining the correct results that 2 predicates definitely did conform and 7 predicates definitely did not conform, with 16 predicates left where no definite conclusion could be reached.

5.2 Higher-order list utilities

We defined various partially specified higher-order utility predicates specialized for working with lists of a particular type , for example, (X) (X). For example, consider the predicate property defined below:

which describes comparator predicates of elements of type , that we then use in the higher-order assertion for a comparator-parameterizable quicksort implementation:

and where the partition/4 predicate includes a call P(X,Y). The analysis is able to propagate the property on P to that point, and if in P(X,Y), X is inferred to be bound to, for example, a, an error is statically captured by ${Hiord}^{\sharp}$ . Consider the following comparators:

For a query qsort(Xs,lex,Ys), ${Hiord}^{\sharp}$ reports a warning on calls since lex/2 $\mathbin {\prec ^{\sharp +}}$ (it weakly abstractly conforms). Intuitively, lex/2 is not definitely conformant since it will not raise a run-time check error when called with a term that is not of type , introducing unexpected behavior. For a query qsort(Xs,lex_t,Ys) ${Hiord}^{\sharp}$ proves that it behaves correctly w.r.t. its higher-order assertion, since lex_t/2 $\mathbin {\prec ^{\sharp -}}$ .

Additionally, consider a predicate property which represents a parameterizable sorter of lists of elements of type , defined “in terms of” the predicate property:

For determining that qsort/3 $\mathbin {\prec ^{\sharp -}}$ , ${Hiord}^{\sharp}$ would need to perform an additional iteration of the fixpoint computation after the one above, that is, after computing the predicates that weakly or strongly abstractly conform to the predicate property.

5.3. $\mathsf{HTTP}$ server

Consider the following schematic HTTP server, parameterized by a predicate that must be able to handle four REST operations. We use regular types for representing requests and responses, and a predicate property for representing handlers:

and we add the following higher-order assertion to the server predicate:

${Hiord}^{\sharp}$ detects that the predicate h/2 does not definitely conform to (h/2 $\mathbin {\prec ^{\sharp +}}$ ) due to one its clauses:

5.4. Dutch national flag

This problem involves sorting a list of red, white, or blue elements, such that elements of the same color are grouped together in a specified order (typically red, then white, then blue). However, we want to generalize the solution by allowing the user to provide a comparator that, given two elements, yields their comparison. We first define regular types to represent colored elements and the result of their comparison:

Next, we define a predicate property describing comparators between elements; and provide a higher-order assertion to the dutch_flag/3 higher-order predicate:

Assume that we are given the implementation of dutch_flag/3 and we need to provide a comparator cmp/3 which conforms to . Consider a first implementation attempt:

When determining its conformance to , ${Hiord}^{\sharp}$ finds that cmp/3 $\mathbin {\nprec ^{\sharp +}}$ , since CiaoPP infers the regular type for the elements to compare, and $\sqcap$ $=\bot$ in eterms. We proceed by correcting it, but we accidentally mistype some of the r elements for o elements in lines 7 and 8; and CiaoPP infers the following assertion and regular type:

However, ${Hiord}^{\sharp}$ now reports that cmp/3 $\mathbin {\prec ^{\sharp +}}$ , since it would not raise a run-time check error when called with an o on its first argument. Formally, ( (X), (Y)) $\sqsupseteq$ ( (X), (Y)) in eterms. In an attempt at improving the precision of the ordering, we refine cmp/3 to yield more informative results on the order relation between elements:

In particular, we introduce and to reflect that X is “much lower” than Y, and vice-versa; and define a new regular type and assertion. However, ${Hiord}^{\sharp}$ still reports that cmp/3 $\mathbin {\prec ^{\sharp +}}$ , since cmp/3 may yield comparison results that are not reflected in . Formally (R) $\sqsupseteq$ lge(R) in eterms. Finally, we develop the following comparator:

And ${Hiord}^{\sharp}$ proves that cmp/3 $\mathbin {\prec ^{\sharp -}}$ , since it behaves exactly as expected.

6 Conclusions

We have presented ${Hiord}^{\sharp}$ , a novel approach for the compile-time verification of higher-order (C)LP programs with higher-order assertions. We started by refining both the syntax and semantics of predicate properties. Then, we introduced an abstract criterion to determine whether a predicate conforms with a predicate property at compile time. We also motivated and explained a wrapper-based technique for “casting” predicate usage in a program analysis-friendly manner that enhances and complements the proposed abstract criterion. We then proposed a technique for dealing with these properties using an abstract interpretation-based static analyzer for programs with first-order assertions. Finally, we reported on a prototype implementation and studied the effectiveness of the approach with various examples within the Ciao system. We believe our proposal constitutes a practical approach to closing the existing gap in the verification at compile time of higher-order assertions; and that it is quite general and flexible, and can be applied, at least conceptually, to other similar gradual approaches.

Competing interests

The authors declare none.

Supplementary material

Supplementary material for this paper can be found at: https://doi.org/10.1017/S147106842510015X.

Footnotes

*

Partially funded by MICIU projects CEX2024-001471-M María de Maeztu and TED2021-132464B-I00 PRODIGY, as well as by the Tezos foundation. We would also like to thank the anonymous reviewers for their very useful and constructive feedback.

1 Ciao assertions can also include global properties, which may not always be checkable at run time (e.g. termination), but we focus for brevity on the described types of assertions and properties.

2 We refer the reader to Puebla et al. (Reference Puebla, Bueno and Hermenegildo2000b); Sanchez-Ordaz et al. (Reference Sanchez-Ordaz, Garcia-Contreras, Perez-Carrasco, Morales, Lopez-Garcia and Hermenegildo2021) for the technical details on this subject.

3 We propose a more compact syntax here that avoids having to use a named variable for the anonymous predicate symbol (as in Stulova et al. (Reference Stulova, Morales and Hermenegildo2014)) and takes advantage of functional notation (:=).

4 We also use for compactness “_” as anonymous functor, a syntactic extension from the Ciao hiord package (Cabeza et al. Reference Cabeza, Hermenegildo and Lipton2004), but double quotes ‘’ can also be used to stay within ISO-Prolog syntax.

5 Note that this implies $\mathcal{U}(G) = \mathcal{U}(G')$ .

6 Note that, for the purposes of determining conformance, the assertions for the predicates in the program can be provided by the user, inferred by analysis, or a combination of both.

7 Or, using Ciao’s functional notation: “.”

References

Cabeza, D., Hermenegildo, M. and Lipton, J. 2004. Hiord: A Type-free higher-order logic programming language with predicate abstraction. In ASIAN’04, Springer-Verlag, Vol. 3321 in LNCS, 93108.Google Scholar
Cardelli, L. 1989. Typeful programming. In Formal Description of Programming Concepts, 1989, IFIP State-of-the-Art Reports, E. J. Neuhold and M. Paul, Eds. Springer, 431498.Google Scholar
Cousot, P. and Cousot, R. 1977. Abstract interpretation: A unified lattice model for static analysis of programs by construction or approximation of fixpoints. In Proc. of POPL’77, ACM Press, 238252.Google Scholar
Debray, S., Lopez-Garcia, P. and Hermenegildo, M. 1997. Non-failure analysis for logic programs. In 1997 International Conference on Logic Programming, MIT Press, Cambridge, MA, 4862.Cambridge, MAGoogle Scholar
Hermenegildo, M., Bueno, F., Carro, M., López-García, P., Mera, E., Morales, J. and Puebla, G. 2012. An overview of Ciao and its design philosophy. Theory and Practice of Logic Programming 12, 219252.CrossRefGoogle Scholar
Hermenegildo, M., Puebla, G. and Bueno, F. 1999. Using global analysis, partial specifications, and an extensible assertion language for program validation and debugging. In The Logic Programming Paradigm: A 25–Year Perspective, Springer-Verlag, 161192.CrossRefGoogle Scholar
Hermenegildo, M., Puebla, G., Bueno, F. and Garcia, P. L. 2005. Integrated program debugging, verification, and optimization using abstract interpretation (and The Ciao System Preprocessor). Science of Computer Programming 58, 1-2, 115140.CrossRefGoogle Scholar
Hill, P. and Lloyd, J. 1994. The Goedel Programming Language. MIT Press, Cambridge MA.Google Scholar
Körner, P., Leuschel, M., Barbosa, J., Santos-Costa, V., Dahl, V., Hermenegildo, M. V., Morales, J. F., Wielemaker, J., Diaz, D., Abreu, S. and Ciatto, G. 2022. Fifty years of prolog and beyond. Theory and Practice of Logic Programming 22, 6, 776858, 20th Anniversary Special Issue.CrossRefGoogle Scholar
Marlow, S. 2010. Haskell 2010 language report . Haskell Language Committee.Google Scholar
Miller, D. 1991. A logic programming language with $\lambda$ -abstraction, function variables, and simple unification. Journal of Logic and Computation 1, 4, 497536.CrossRefGoogle Scholar
Muthukumar, K. and Hermenegildo, M. 1992. Compile-time derivation of variable dependency using abstract interpretation. Journal of Logic Programming 13, 2-3, 315347.CrossRefGoogle Scholar
Puebla, G., Bueno, F. and Hermenegildo, M. 2000a. An assertion language for constraint logic programs. In Analysis and Visualization Tools for Constraint Programming, Springer-Verlag, Vol. 1870 in LNCS, 2361.CrossRefGoogle Scholar
Puebla, G., Bueno, F. and Hermenegildo, M. 2000b. Combined static and dynamic assertion-based debugging of constraint logic programs. In Proc. of LOPSTR’99, Springer-Verlag, Vol. 1817 LNCS, 273292.Google Scholar
Sanchez-Ordaz, M., Garcia-Contreras, I., Perez-Carrasco, V., Morales, J. F., Lopez-Garcia, P. and Hermenegildo, M. 2021. VeriFly: On-the-fly assertion checking via incrementality. Theory and Practice of Logic Programming 21, 6, 768784.CrossRefGoogle Scholar
Siek, J. G. and Taha, W. 2006. Gradual typing for functional languages. In Scheme and Functional Programming Workshop, 8192. University of Chicago Department of Computer Science.Google Scholar
Somogyi, Z., Henderson, F. and Conway, T. 1996. The execution algorithm of mercury: An efficient purely declarative logic programming language. Journal of Logic Programming 29, 1-3, 1764.CrossRefGoogle Scholar
Stulova, N., Morales, J. and Hermenegildo, M. 2014. Assertion-based debugging of higher-order (C)LP programs. In PPDP’14, ACM, 225235.Google Scholar
Stulova, N., Morales, J. F. and Hermenegildo, M. 2018. Some trade-offs in reducing the overhead of assertion run-time checks via static analysis. Science of Computer Programming 155, 326.CrossRefGoogle Scholar
Vaucheret, C. and Bueno, F. 2002. More precise yet efficient type inference for logic programs. In LNCS SAS’02, Springer, Vol. 2477 in LNCS, 102116.Google Scholar
Figure 0

Fig. 1. Example case analysis on a predicate property and assertions.

Figure 1

Table 1. Abs. conf. on “calls” example with ${{}^{\circ {\kern-2pt}}\mathit{Pre}}=$(X)

Figure 2

Table 2. Abs. conf. on “success” example with ${{}^{\circ {\kern-2pt}}\mathit{Pre}}=$(X) and ${{}^{\circ {\kern-2pt}}\mathit{Post}}=$(Y)

Figure 3

Algorithm 1 [Hiord]: Verify a higher-order program with higher-order assertions

Supplementary material: File

Ciccalè et al. supplementary material

Ciccalè et al. supplementary material
Download Ciccalè et al. supplementary material(File)
File 196.1 KB