Abstract 1 Introduction 2 The Programming Language Rec 3 A Denotational Finite-Trace Semantics for Rec 4 A Logic over Finite Traces 5 A Proof Calculus 6 From Trace Formulas to Programs 7 Related Work 8 Future Work 9 Conclusion References Appendix A Selected Proofs

An Expressive Trace Logic for Recursive Programs

Dilian Gurov ORCID KTH Royal Institute of Technology, Stockholm, Sweden Reiner Hähnle ORCID Technical University of Darmstadt, Germany
Abstract

We present an expressive logic over trace formulas, based on binary state predicates, chop, and least fixed points, for precise specification of programs with recursive procedures. Both programs and trace formulas are equipped with a direct-style, fully compositional, denotational semantics that on programs coincides with the standard SOS of recursive programs. We design a compositional proof calculus for proving finite-trace program properties, and prove soundness as well as (relative) completeness. We show that each program can be mapped to a semantics-preserving trace formula and, vice versa, each trace formula can be mapped to a canonical program over slightly extended programs, resulting in a Galois connection between programs and formulas. Our results shed light on the correspondence between programming constructs and logical connectives.

Keywords and phrases:
Denotational semantics, compositional semantics, program specification, compositional verification, fixed point logic, trace logic
Copyright and License:
[Uncaptioned image] © Dilian Gurov and Reiner Hähnle; licensed under Creative Commons License CC-BY 4.0
2012 ACM Subject Classification:
Theory of computation Denotational semantics
; Theory of computation Operational semantics ; Theory of computation Program specifications ; Theory of computation Program verification ; Theory of computation Logic and verification ; Theory of computation Modal and temporal logics
Related Version:
Full Version: https://arxiv.org/abs/2411.13125 [14]
Editor:
Maribel Fernández

1 Introduction

It is uncommon that specification languages used in program verification are as expressive as the programs they are intended to specify: In model checking [7] one typically abstracts away from data and unwinds unbounded control structures. Specification of unbounded computations is achieved by recursively defined temporal operators. In deductive verification [15], first-order Hoare-style contracts [18] are the basis of widely used specification languages [3, 22]. The latter specify computation by taking symbolic memory snapshots in terms of first-order formulas, typically at the beginning and at the end of unbounded control structures, such as procedure call and return, or upon loop entry and exit. Contracts permit to approximate the effect of unbounded computation as a finite number of first-order formulas against which a program’s behavior can be verified.

Imagine, in contrast, a logic for program specification that is at least as expressive as the programs it is supposed to describe. Formulas ϕ of such a logic characterize a generally infinite set of program computation traces σ. One can then form judgments of program statements S of the form S:ϕ with the natural semantics that any possible trace σ of S is one of the traces described by ϕ.

Two arguments against such a trace logic are easily raised: first, one expects a specification language to be capable of abstraction from implementation details; second, its computational complexity. Regarding the first, we note that any desired degree of abstraction can be achieved by definable constructs, i.e. syntactic sugar, in a sufficiently rich trace logic. Regarding the second, it is far from obvious whether the computational worst-case tends to manifest itself in practical verification [15]. First experiments seem to indicate this is not the case. On the other hand, a rich, trace-based specification logic bears many advantages:

  1. 1.

    On the practical side, trace-based specification permits to express, for example, that an event or a call happened (exactly once, at least once) or that did not happen. Likewise, one can specify without reference to the target code (via assertions) that a condition holds at some point which is not necessarily an endpoint of a procedure call. It is also possible to specify inter-procedural properties, such as call sequences, absence of callbacks, etc. Such properties are important in security analysis and they are difficult or impossible to express with Hoare-style contracts or in temporal logic.

  2. 2.

    The semantics of trace formulas in terms of trace sets can be closely aligned with a suitable program semantics, which greatly simplifies soundness and completeness proofs.

  3. 3.

    An expressive trace logic makes for simple and natural completeness proofs for judgments S:ϕ, because it is unnecessary to encode the program semantics in order to construct sufficiently strong first-order conditions.

  4. 4.

    In a calculus for judgments of the form S:ϕ, one can design inference rules that directly decompose judgments into simpler ones, but also algebraic rules that simplify and transform ϕ, and one may mix both reasoning styles.

  5. 5.

    The semantics of programs, of trace formulas, and the rules of the calculus can be formulated in a fully compositional manner: definitions and inference rules involve no intermediate state or context.

  6. 6.

    In consequence, we are able to establish a close correspondence between programs and formulas, which sheds light on the exact relation between each program construct and its corresponding logical connective. We formulate and prove a Galois connection that formalizes this fact.

Our main contribution is a trace logic for imperative programs with recursive procedures where we formalize and prove the claims above. We restrict attention to the terminating executions of programs, i.e. to their finite trace semantics. This is not a fundamental limitation, but the desire not to obscure the construction with complications that may be added later. Still, adaptating the theory to maximal traces (including infinite runs) is not trivial, and working out the details is left as future work (see Section 8.3).

Our paper is structured as follows: In Section 2 we define the programming language Rec used throughout this paper and we give it a standard SOS semantics [29]. In Section 3 we define a denotational semantics for Rec in fully compositional style and prove it equivalent to the SOS semantics. In Section 4 we introduce our trace logic and map programs to formulas by the concept of a strongest trace formula, which is shown to fully preserve program semantics. In Section 5 we define a proof calculus for judgments and show it to be sound and complete. In Section 6, using the concept of a canonical program, we map trace formulas back to programs in the slightly extended language Rec with non-deterministic guards. We prove that Rec and trace formulas form a Galois connection via strongest trace formulas and canonical programs. In Section 7 we discuss related work, in Section 8 we sketch some extensions, including options to render specifications more abstract and how to prove consequence among trace formulas. In Section 9 we conclude.

All proofs and some examples can be found in the accompanying report [14]. The most important of these are also available in the present Appendix.

2 The Programming Language Rec

We define a simple programming language Rec with recursive procedures and give it a standard SOS semantics. We follow the notation of [26], adapted to Rec syntax.

Definition 2.1 (Rec Syntax).

A Rec program is a pair S,T, where S is a statement with the abstract syntax defined by the grammar:

S::=skipx:=aS1;S2ifbthenS1elseS2m()

and where T is a procedure table given by T::=M with declarations of parameter-less procedures M::=m{S}.

In the grammar, a ranges over arithmetic expressions AExp, and b over Boolean expressions BExp. Both are assumed to be side-effect free and are not specified further. All variables are global and range over the set of integers . We assume programs are well-formed, i.e., only declared procedures are called, and procedure names are unique.

Example 2.2.

Consider the Rec program p0=𝖽𝖾𝖿S,T with statement S=𝖽𝖾𝖿x:=3;𝑒𝑣𝑒𝑛() and procedure table:

T=𝖽𝖾𝖿𝑒𝑣𝑒𝑛{ifx=0theny:=1elsex:=x1;𝑜𝑑𝑑()}𝑜𝑑𝑑{ifx=0theny:=0elsex:=x1;𝑒𝑣𝑒𝑛()}

The intended semantics is that 𝑒𝑣𝑒𝑛 terminates in a state where y=1 if and only if it is started in a state where x is even and non-negative, and it terminates in a state where y=0 if and only if it is started in a state where x is odd and non-negative.

Example 2.3.

Consider the Rec program p1=𝖽𝖾𝖿S,T with S=𝖽𝖾𝖿𝑑𝑜𝑤𝑛() and procedure table:

T=𝖽𝖾𝖿𝑑𝑜𝑤𝑛{ifx>0thenx:=x2;𝑑𝑜𝑤𝑛()elseskip}

The intended semantics is that p1 terminates in a state where x=0 if and only if it is started in state where x is even and non-negative.

 Remark 2.4.

Loops can be defined with the help of (tail-)recursive programs. For example, a loop of the form “whilebdoS” can be simulated with a procedure declared in T as:

m{ifbthenS;m()elseskip}

using a unique name m and replacing the occurrence of the loop with a call to m().

Figure 1: SOS rules for Rec.

A standard, structural operational semantics (SOS) for Rec is defined in Figure 1 (sometimes referred to as small-step semantics). We use it as a baseline when defining the denotational finite-trace semantics in Section 3.

Let 𝑉𝑎𝑟 be the set of program variables, and State the set of program states s:𝑉𝑎𝑟. Let 𝒜[[a]](s) denote the integer value of the arithmetic expression a when evaluated in state s, and [[b]](s)𝕋 denote the truth value of the Boolean expression b when evaluated in state s, both defined as expected.

A configuration is either a pair S,s consisting of a statement and a state, designating an initial or intermediate configuration; or a state s, designating a final configuration. To simplify notation we assume that S is evaluated relative to a Rec program with a procedure table T which is not explicitly specified.

The transitions of the SOS either relate two intermediate configurations, or an intermediate with a final one, and thus have the shape S,sS,s or S,ss, respectively.

Definition 2.5 (Rec SOS).

The structural operational semantics (SOS) of Rec is defined by the rules given in Figure 1.

The structural operational semantics of Rec induces a finite-trace semantics in terms of the sequences of states that are traversed from an initial to a final configuration when executing a given statement in the SOS. Let State+ denote the set of all non-empty, finite sequences of states. Formally, we define a function 𝒮𝑠𝑜𝑠[[S]]:Stm2State+, i.e., a function such that 𝒮𝑠𝑜𝑠[[S]]State+ for any statement S.

Definition 2.6 (Induced Finite-Trace Semantics).

Let S be a statement. Then, 𝒮𝑠𝑜𝑠[[S]] is defined as the set of (finite) sequences s0s1sn of states for which there are statements S0,S1,,Sn1 such that S0=S, Si,siSi+1,si+1 for all 0in2, and Sn1,sn1sn.

Observe that in Definition 2.6 any state s0State can serve as the initial state of a finite trace. Next we design a “direct-style”, denotational finite-trace semantics that conforms with the SOS, in the sense that it is equal to the finite-trace semantics induced by the SOS.

3 A Denotational Finite-Trace Semantics for Rec

We define the semantic function 𝒮𝑡𝑟:Stm2State+ with the intention that 𝒮𝑡𝑟[[S]]=𝒮𝑠𝑜𝑠[[S]]. Unlike 𝒮sos, however, 𝒮𝑡𝑟 is defined directly, without referring to other semantic rules as SOS does (hence the term “direct-style”). We define 𝒮𝑡𝑟[[S]] in the style of denotational semantics, compositionally, by induction on the structure of S, and through defining equations. We let σ range over traces.

Let us define a unary restriction operator on trace sets, for any trace set A and Boolean expression bBExp, as follows: Ab=𝖽𝖾𝖿{sσA|[[b]](s)=tt}. It filters out all traces in A whose first state does not satisfy b. Another unary operator on trace sets is defined as A=𝖽𝖾𝖿{ssσ|sσA} which duplicates the first state in each trace in A. Finally, let us define the binary operator on trace sets: AB=𝖽𝖾𝖿{σAsσB|σAsAsσBB} which concatenates traces from A with traces from B that agree on the last and first state, respectively, but without duplicating that state, see also [16].

𝒮𝑡𝑟0[[skip]]ρ=𝖽𝖾𝖿{ss|sState}𝒮𝑡𝑟0[[x:=a]]ρ=𝖽𝖾𝖿{ss[x𝒜[[a]](s)]|sState}𝒮𝑡𝑟0[[S1;S2]]ρ=𝖽𝖾𝖿𝒮𝑡𝑟0[[S1]]ρ𝒮𝑡𝑟0[[S2]]ρ𝒮𝑡𝑟0[[mi()]]ρ=𝖽𝖾𝖿ρ(mi)𝒮𝑡𝑟0[[ifbthenS1elseS2]]ρ=𝖽𝖾𝖿(𝒮𝑡𝑟0[[S1]]ρ)b(𝒮𝑡𝑟0[[S2]]ρ)¬b

Figure 2: Finite-trace semantic equations for Rec.
Definition 3.1 (Denotational Finite-Trace Semantics of Rec).

Let M={m1,,mn} be the set of procedure names in Rec program S,T, where every mi is declared as mi{Si} in T. We define a helper function 𝒮𝑡𝑟0[[S]]ρ that is relativized on an interpretation ρ:M2State+ of the procedure names, inductively, by the equations given in Figure 2. The duplication of the initial states in the equation for the if statement is needed to remain faithful to the SOS of Rec, which allocates a small step for evaluation of the guard b. We then introduce a semantic function H:(2State+)n(2State+)n defined as:

H(ρ)=𝖽𝖾𝖿(𝒮𝑡𝑟0[[S1]]ρ,,𝒮𝑡𝑟0[[Sn]]ρ)

Function H is monotonic and continuous in the CPO with bottom ((2State+)n,,n), where  denotes point-wise set inclusion. Hence, by the Knaster-Tarski Theorem, it has a least fixed point. Let ρ0 denote this least fixed point. The denotational finite-trace semantics of statements S of Rec is defined relative to this interpretation as: 𝒮𝑡𝑟[[S]]=𝖽𝖾𝖿𝒮𝑡𝑟0[[S]]ρ0.

Example 3.2.

We execute the statement x:=2;𝑑𝑜𝑤𝑛() with the procedure table from Example 2.3. The program execution (or run) we obtain from an arbitrary state s is:

x:=2;𝑑𝑜𝑤𝑛(),s𝑑𝑜𝑤𝑛(),s[x2]ifx>0thenx:=x2;𝑑𝑜𝑤𝑛()elseskip,s[x2]x:=x2;𝑑𝑜𝑤𝑛(),s[x2]𝑑𝑜𝑤𝑛(),s[x0]ifx>0thenx:=x2;𝑑𝑜𝑤𝑛()elseskip,s[x0]skip,s[x0]s[x0]

The finite-trace semantics agrees with the SOS of Rec, in the sense that 𝒮𝑡𝑟[[S]] coincides with the finite-trace semantics 𝒮𝑠𝑜𝑠[[S]] induced by the SOS, as defined in Definition 2.6.

Theorem 3.3 (Correctness of Trace Semantics).

For all statements S of Rec, we have:

𝒮𝑡𝑟[[S]]=𝒮𝑠𝑜𝑠[[S]].

4 A Logic over Finite Traces

Our trace logic can be seen as an Interval Temporal Logic [16] with μ-recursion [28], or alternatively, as a temporal μ-calculus with a binary temporal operator corresponding to the chop operation over sets of traces (see, e.g., [32] for a general introduction to μ-calculus).

4.1 Syntax and Semantics of the Logic

The philosophy behind our logic is to have logical counterparts to the statements of the programming language in terms of their finite-trace semantics. For instance, we use binary relation symbols that correspond to the atomic statements, and a chop operator corresponding to sequential composition. This design choice helps to simplify proofs of the properties of the logic and the calculus.

Definition 4.1 (Logic Syntax).

The syntax of the logic of trace formulas is defined by the following grammar:

ϕ::=pRXϕ1ϕ2ϕ1ϕ2ϕ1ϕ2μX.ϕ

where p ranges over state formulas not further specified here, but assumed to contain at least the Boolean expressions BExp, R ranges over binary relation symbols over states, and X over a set 𝖱𝖵𝖺𝗋 of recursion variables.

Definition 4.2 (Logic Semantics).

The finite-trace semantics of a formula ϕ is defined as its denotation ϕ𝒱State+, relativized on a valuation 𝒱:𝖱𝖵𝖺𝗋2State+ of the recursion variables, inductively by the equations given in Figure 3, where in the last clause 𝒱[Xγ] denotes the updated valuation.

p𝒱=𝖽𝖾𝖿{sσ|sp}R𝒱=𝖽𝖾𝖿{ss|R(s,s)}X𝒱=𝖽𝖾𝖿𝒱(X)ϕ1ϕ2𝒱=𝖽𝖾𝖿ϕ1𝒱ϕ2𝒱ϕ1ϕ2𝒱=𝖽𝖾𝖿ϕ1𝒱ϕ2𝒱||ϕ1ϕ2||𝒱=𝖽𝖾𝖿ϕ1𝒱ϕ2𝒱||μX.ϕ||𝒱=𝖽𝖾𝖿{γState+|||ϕ||𝒱[Xγ]γ}

Figure 3: Finite-trace semantic equations for formulas.

One can show that the transformers λγ.ϕ𝒱[Xγ] are monotonic functions in the complete lattice (2State+,) and hence, by Tarski’s fixed point theorem for complete lattices [33], they have least and greatest fixed points. In particular, the least fixed point is simultaneously also the least pre-fixed point, hence the defining equation for μX.ϕ. And because it is a fixed point, we have the following result for unfolding fixed point formulas.

Proposition 4.3 (Fixed Point Unfolding).

Let μX.ϕ be a formula and 𝒱 a valuation. Then: ||μX.ϕ||𝒱=||ϕ[μX.ϕ/X]||𝒱.

Our calculus is based on closed formulas of the logic. Observe that fixed point unfolding preserves closedness. For closed formulas the valuation 𝒱 is immaterial to the semantics ϕ𝒱. In this case, we often omit the subscript and simply write ϕ.

4.2 Binary Relations

We instantiate the set 𝖱𝖾𝗅 of binary relation symbols with two specific relations:

𝐼𝑑(s,s)𝖽𝖾𝖿s=s𝑆𝑏xa(s,s)𝖽𝖾𝖿s=s[x𝒜[[a]](s)]

These two relations are used to model skip and assignment statements, respectively. The transitive closure R+ of a binary relation R over states is easily defined as a recursive formula in our logic as R+𝖽𝖾𝖿μX.(RRX).

Example 4.4.

For any arithmetic expression a let 𝐷𝑒𝑐a be a binary relation symbol interpreted as follows: 𝐷𝑒𝑐a(s,s)𝖽𝖾𝖿𝒜[[a]](s)𝒜[[a]](s). That is, the value of a does not increase between two consecutive states. With this symbol, the formula 𝐷𝑒𝑐a+ expresses the property that the value of a does not increase throughout the whole execution of a program.

𝗌𝗍𝖿(X¯,skip)=𝖽𝖾𝖿𝐼𝑑𝗌𝗍𝖿(X¯,x:=a)=𝖽𝖾𝖿𝑆𝑏xa𝗌𝗍𝖿(X¯,S1;S2)=𝖽𝖾𝖿𝗌𝗍𝖿(X¯,S1)𝗌𝗍𝖿(X¯,S2)

𝗌𝗍𝖿(X¯,ifbthenS1elseS2) =𝖽𝖾𝖿(b𝐼𝑑𝗌𝗍𝖿(X¯,S1))(¬b𝐼𝑑𝗌𝗍𝖿(X¯,S2))
𝗌𝗍𝖿(X¯,m()) =𝖽𝖾𝖿{𝐼𝑑μXm.𝗌𝗍𝖿(X¯{m},Sm)mX¯,m{Sm}T𝐼𝑑Xmotherwise
Figure 4: Definition of strongest trace formula.

4.3 Strongest Trace Formulas

Since our program logic is able to characterize program traces, and not merely pre- and postconditions or intermediate assertions, it is possible to establish a close correspondence between programs and trace formulas. This correspondence is captured by the following – constructive – definition of the strongest trace formula 𝗌𝗍𝖿(S) for a given program S, which characterizes all terminating traces of S.

For each procedure declaration m{Sm} in T, we create a fixed point formula, whenever m is called the first time. Subsequent calls to m result in a recursion variable. To achieve this, we parameterize the strongest trace formula function with the already created recursion variables X¯. This parameter is initialized to  and is ignored by all case definitions except the one for a recursive call.

Definition 4.5 (Strongest Trace Formula).

Let S,T be a Rec program. The strongest trace formula for S, denoted 𝗌𝗍𝖿(S), is defined as 𝗌𝗍𝖿(S)=𝖽𝖾𝖿𝗌𝗍𝖿(,S), where 𝗌𝗍𝖿(X,S) is defined inductively in Figure 4.

Example 4.6.

For the program 𝑒𝑣𝑒𝑛() with the procedure table of Example 2.2, the strongest trace formula is:

𝐼𝑑μX𝑒𝑣𝑒𝑛.((x=0𝐼𝑑𝑆𝑏y1)(x0𝐼𝑑𝑆𝑏xx1𝐼𝑑μX𝑜𝑑𝑑.((x=0𝐼𝑑𝑆𝑏y0)(x0𝐼𝑑𝑆𝑏xx1𝐼𝑑X𝑒𝑣𝑒𝑛))))

The binder for Xodd can be removed without changing the semantics.

Example 4.7.

For the program in Example 2.3, the strongest trace formula is:

𝗌𝗍𝖿(S)=𝐼𝑑μX𝑑𝑜𝑤𝑛.((x>0𝐼𝑑𝑆𝑏xx2𝐼𝑑X𝑑𝑜𝑤𝑛)(x0𝐼𝑑𝐼𝑑)).
Theorem 4.8 (Characterisation of Strongest Trace Formula).

Let S,T be a program of Rec. Then the following holds: 𝗌𝗍𝖿(,S)=𝒮𝑡𝑟[[S]].

The proof of Theorem 4.8 requires an inner fixed point induction for which it is advantageous to break down 𝗌𝗍𝖿 into a modal equation system 𝗆𝖾𝗌 [23] that preserves the structure of procedure declarations.

Definition 4.9 (Modal Equation System).

Given a closed trace formula ϕ, the modal equation system 𝗆𝖾𝗌(ϕ) is an open trace formula together with a set of modal equations of the form Xi=ϕi, inductively defined over ϕ as follows: 𝗆𝖾𝗌(ϕ) is just the homomorphism over the abstract syntax, except when ϕ=μX.ϕ: In this case, 𝗆𝖾𝗌(ϕ)=𝖽𝖾𝖿X, and a new equation X=𝗆𝖾𝗌(ϕ) is added.

The semantics of modal equation systems is defined as in the literature [23], from where we also take the semantic equivalence between ϕ and 𝗆𝖾𝗌(ϕ).

Example 4.10.

The modal equation system corresponding to the strongest trace formula in Example 4.6 is: 𝗆𝖾𝗌(𝗌𝗍𝖿(𝑒𝑣𝑒𝑛()))=𝐼𝑑X𝑒𝑣𝑒𝑛, where:

X𝑒𝑣𝑒𝑛=((x=0 𝐼𝑑𝑆𝑏y1)(x0𝐼𝑑𝑆𝑏xx1𝐼𝑑X𝑜𝑑𝑑))
X𝑜𝑑𝑑=((x=0 𝐼𝑑𝑆𝑏y0)(x0𝐼𝑑𝑆𝑏xx1𝐼𝑑X𝑒𝑣𝑒𝑛))

Observe the structural similarity between the formula 𝐼𝑑X𝑒𝑣𝑒𝑛 in the context of the defining equations for X𝑒𝑣𝑒𝑛 and X𝑜𝑑𝑑, and the statement 𝑒𝑣𝑒𝑛() in the context of the procedure table T of Example 2.2.

To use the decomposition of a trace formula into a modal equation system, we need to define for each program S an open trace formula corresponding to 𝗆𝖾𝗌(𝗌𝗍𝖿(S)). This transformation, called 𝗌𝗍𝖿(S), is defined exactly as 𝗌𝗍𝖿(X,S) in Definition 4.5 (ignoring the parameter X), except for the case S=m(), which is defined as 𝗌𝗍𝖿(m())=Xm.

Lemma 4.11.

Let S,T be a Rec program and M the procedures declared in T. Let ρ:M2State+ be an arbitrary interpretation of the procedures mM, and let 𝒱ρ:𝖱𝖵𝖺𝗋2State+ be the (induced) valuation of the recursion variables defined by 𝒱ρ(Xm)=𝖽𝖾𝖿ρ(m). We have, for all statements S of Rec: 𝗌𝗍𝖿(S)𝒱ρ=𝒮𝑡𝑟0[[S]]ρ.

5 A Proof Calculus

We present a proof calculus for our logic in the form of a Gentzen-style deductive proof system, which is compositional both in the statement and the formula.

5.1 Definition of the Calculus

To obtain a compositional proof rule for procedure calls, its shape will essentially embody the principle of Fixed Point Induction. For this we need to represent recursion variables in the Rec language, whose syntax is extended with a set 𝖲𝖵𝖺𝗋 of statement variables, ranged over by Y. We add these as a new category of atomic statements to Rec.

To define the semantics of programs in the presence of statement variables, we relativize the finite-trace semantics 𝒮𝑡𝑟[[S]] of statements S on interpretations :𝖲𝖵𝖺𝗋2State+ of the statement variables, lifted from 𝒮𝑡𝑟[[S]] in the canonical manner.

Definition 5.1 (Calculus Syntax).

Judgments are of the form S:ϕ, where S is a Rec statement, possibly containing statement variables, and ϕ is a closed trace formula. The sequents of the calculus are of the shape ΓS:ϕ, where Γ is a possibly empty set of judgments.

Figure 5: The rules of the proof calculus.

5.1.1 Rules

The calculus has exactly one rule for each kind of Rec statement, except for statement variables which do not occur in initial judgments to be proven, but are only created intermittently in proofs by the (Call) rule. All statement rules are compositional in the sense that only the statement S in focus without any context appears in the conclusion.

The statement rules and two selected logical rules of the calculus are shown in Figure 5. The remaining logical rules, in particular the axioms, are the standard Gentzen-style ones and are omitted.

To prove the judgment S:ϕ for a program S,T, we prove the sequent S:ϕ. All rules, except the (Call) rule, leave the antecedent Γ invariant.

We first explain the two logical rules. The (Unfold) rule is based on Proposition 4.3 and is used to unfold fixed point formulas. The consequence rule (Cons) permits to strengthen the trace formula ϕ in the succedent, i.e., the specification of the program under verification. This is typically required to achieve a suitable syntactic form of ϕ, or to strengthen an inductive claim. The rule assumes the existence of an oracle for proving the logical entailment between trace formulas.

The (Skip) and (Assign) rules handle the atomic statements, using the two binary relation symbols defined in Section 4.2. The (Seq) rule is a rule for sequential composition. Observe that it is compositional in the sense that no intermediate state between S1 and S2 needs to be considered.

The (If) rule is a compositional rule for conditional statements. The trace formulas ¬bϕ in the left premise (and bϕ in the right one) might at first appear counter-intuitive. Formula ¬bϕ is read as follows: We need not consider program S1 for any trace, where ¬b holds in the beginning, because these traces relate to S2; otherwise, ϕ must hold. A more intuitive notation would be bϕ, but we refrain from introducing implication in our logic.

Unsurprisingly, the (Call) rule is the most complex. We associate with each declaration of a method m in T a unique statement variable Ym. The antecedent Γ contains judgments of the form Ym:ϕm. One can think of the Ym as a generic continuation of any recursive call to m of which we know that it must conform to its contract ϕm. Once this conformance has been established, the fact is memorized in the antecedent Γ. Therefore, the (Call) rule is triggered only when a call to procedure m() is encountered the first time. This is ensured by the condition Ym:ϕmΓ. To avoid having to apply the call rule again to recursive calls of m(), all such calls in the body Sm are replaced with skip;Ym, where the skip models unfolding and Ym is justified by the assumption in Γ. Likewise, any other Ymi:ϕmiΓ triggers an analogous substitution. Now the procedure body Sm[] in the premise contains at most procedure calls to m that do not occur in Γ.

If a different judgment than 𝐼𝑑ϕm is to be proven, then rule (Cons) must be applied before (Call) to achieve the required shape.

Figure 6: Proof of 𝑒𝑣𝑒𝑛():𝗌𝗍𝖿(𝑒𝑣𝑒𝑛()).
Example 5.2.

We prove the judgment 𝑒𝑣𝑒𝑛():𝗌𝗍𝖿(𝑒𝑣𝑒𝑛()) for the program from Example 2.2 in Figure 6. We abbreviate the fixed point formula in 𝗌𝗍𝖿(𝑒𝑣𝑒𝑛()) from Example 4.6 with ϕ𝑒𝑣𝑒𝑛, so that 𝗌𝗍𝖿(𝑒𝑣𝑒𝑛())=𝐼𝑑ϕ𝑒𝑣𝑒𝑛, the body of 𝑒𝑣𝑒𝑛() with S𝑒𝑣𝑒𝑛, and similarly for 𝑜𝑑𝑑(). Moreover, we abbreviate:

ϕ𝑒𝑣𝑒𝑛=(x=0𝐼𝑑𝑆𝑏y1)(x0𝐼𝑑𝑆𝑏xx1𝐼𝑑μX𝑜𝑑𝑑.((x=0𝐼𝑑𝑆𝑏y0)(x0𝐼𝑑𝑆𝑏xx1𝐼𝑑X𝑒𝑣𝑒𝑛)))

The proof starts with the call rule, followed by an unfold of the fixed point formula ϕ𝑒𝑣𝑒𝑛. We now proceed with the other statement rules simultaneously on S𝑒𝑣𝑒𝑛 and the formula on the right until we encounter the call of 𝑜𝑑𝑑() in S𝑒𝑣𝑒𝑛. Here we would like to apply the call rule to 𝑜𝑑𝑑(), but we do not have ϕ𝑜𝑑𝑑 on the right, because the unfolding of ϕ𝑒𝑣𝑒𝑛 went “too deep”. To avoid a lengthy derivation at this point, we use the fact that trace formula on the right is equivalent to ϕ𝑜𝑑𝑑, and use the consequence rule to obtain it.

Now we descend into 𝑜𝑑𝑑(), similarly as before; however, because the judgment Y𝑒𝑣𝑒𝑛:ϕ𝑒𝑣𝑒𝑛 is present on the left, the call rule replaces 𝑒𝑣𝑒𝑛() in S𝑒𝑣𝑒𝑛 with skip;Y𝑒𝑣𝑒𝑛:ϕ𝑒𝑣𝑒𝑛. Finally, we encounter the statement variable Y𝑒𝑣𝑒𝑛, but again the fixed point formula on the right is “too deep”. After a second transformation we close the proof with an axiom.

 Remark 5.3.

It is easy to derive a rule for loops from the (Call) rule using the encoding m{ifbthenS;m()elseskip} given in Remark 2.4. In a pure loop program no unprocessed recursive call except m() ever occurs in the body, so the (Call) rule is applicable with Γ=. Rule (Call) instantiated to m and a suitable ϕm gives Sm[skip;Ym/m()]=ifbthenS;skip;Ymelseskip in the premise on the right, so its single premise becomes: Ym:ϕmifbthenS;skip;Ymelseskip:ϕm. Subsequent application of rule (If) yields the two premises Ym:ϕmskip;S;skip;Ym:¬bϕm and Ym:ϕmskip;skip:bϕm.. In each premise is a spurious skip resulting from evaluating the method call which is only due to the encoding. In addition, the antecedent is not needed to prove the second premise and can be removed. After reordering and simplification, rule (While) is obtained as:

where Γ contains judgments of the form Y:ϕ originating from while loops encountered previously. These are only needed in a proof in the presence of nested loops.

5.1.2 Semantics

Definition 5.4 (Calculus Semantics).

A judgment S:ϕ is termed valid in , denoted S:ϕ, whenever 𝒮𝑡𝑟[[S]]ϕ. A sequent ΓS:ϕ is termed valid, denoted ΓS:ϕ, if for every interpretation , S:ϕ is valid in , whenever all judgments in Γ are valid in .

It is not possible to prove a judgment for m() (or any other statement) that is stronger than its strongest trace formula. In this sense, 𝗌𝗍𝖿(m()) can be seen as a contract for m, in fact the strongest possible contract. This is captured in the following result:

Corollary 5.5 (Strongest Trace Formula).

Let S be a statement not involving any statement variables. Then the strongest trace formula 𝗌𝗍𝖿(S) of S entails any valid formula for S. That is, if S:ϕ, then 𝗌𝗍𝖿(S)ϕ.

Proof.

The result follows directly from Theorem 4.8 and Definition 5.4.

5.2 Soundness and Relative Completeness of the Calculus

Our proof system is sound, in the sense that it can only derive valid sequents.

Theorem 5.6 (Soundness).

The proof system is sound: every derivable sequent is valid.

Our proof system is complete, in the sense that every valid sequent can be derived, relative to an oracle, used by rule (Cons), that provides logical entailment between trace formulas.

By Theorem 4.8 and Definition 5.4 we know that every judgment of the shape S:𝗌𝗍𝖿(S) is valid. We next show that all such judgments are derivable in our proof system. Together with Corollary 5.5, we obtain completeness, with the help of the rule (Cons). By definition: 𝗌𝗍𝖿(m())=𝐼𝑑μXm.𝗌𝗍𝖿({m},Sm), where Sm is the body of m. We abbreviate ϕm=μXm.𝗌𝗍𝖿({m},Sm) and in the following use 𝗌𝗍𝖿(m())=𝐼𝑑ϕm without mentioning it explicitly.

Theorem 5.7 (Existence of Canonical Proof).

Let S,T be a Rec program with n many method declarations in T, and let Γ={Ym1:ϕm1,,Ymn:ϕmn}. Then, the judgment ΓS[skip;Ym1/m1(),,skip;Ymn/mn()]:𝗌𝗍𝖿(S) is derivable in our calculus.

Corollary 5.8 (Relative Completeness).

The proof system is relatively complete: for every Rec program S without statement variables and every closed formula ϕ, any valid judgment of the form S:ϕ is derivable in the proof system.

Proof.

By Theorem 4.8 we know that 𝗌𝗍𝖿(S)ϕ, so we can use rule (Cons) to obtain S:𝗌𝗍𝖿(S), which is derivable by Theorem 5.7.

Compared to a typical completeness proof of first-order Dynamic Logic, where the invariant is constructed as equations over the Gödelized program in the loop, the argument is much simpler, because the inductive specification logic is sufficiently expressive to characterize recursive programs (and loops as a special case). First-order quantifiers are not even necessary, so our logic is not first-order, even though it is obviously Turing-hard and thus undecidable.

6 From Trace Formulas to Programs

In Section 4.3 we showed that any Rec program S can be translated into a trace formula 𝗌𝗍𝖿(S) that has the same semantics in terms of traces. Now we look at the other direction: Given a trace formula ϕ, can we construct a canonical program 𝖼𝖺𝗇(ϕ) that has the same semantics in terms of traces? In general, this is not possible, as the following example shows:

Example 6.1.

Consider the trace formula: 𝑆𝑏y0μX.(𝐼𝑑𝑆𝑏yy+1X). Its semantics are the traces that count y up from 0 to any finite number. It is not possible to model the non-deterministic choice in the fixed point formula directly in Rec, because the number of calls is unbounded.

There is a Rec program that produces exactly the same traces as the formula above, up to auxiliary variables, for example, y:=0;m(), where m is declared as: m(){if(yx)theny:=y+1;m()elseskip}. However, to transform an arbitrary formula with unbounded non-determinism in the number of calls to an equivalent one with non-deterministic initialization, is difficult and not natural.

6.1 Rec Programs with Non-deterministic Choice

To achieve a natural translation from the trace logic to canonical programs, it is easiest to introduce non-deterministic choice in the form of a statement ifthenS1elseS2. The extension of language Rec with the corresponding grammar rule is called Rec.

The SOS rules for non-deterministic choice are:

The finite-trace semantics of non-deterministic choice is:

𝒮𝑡𝑟[[ifthenS1elseS2]]=𝖽𝖾𝖿𝒮𝑡𝑟[[S1]]𝒮𝑡𝑟[[S2]]

The extension of Theorem 3.3 for non-deterministic choice is completely straightforward. Likewise, the theory of strongest trace formulas is easy to extend:

𝗌𝗍𝖿(ifthenS1elseS2)=𝖽𝖾𝖿𝐼𝑑𝗌𝗍𝖿(S1)𝐼𝑑𝗌𝗍𝖿(S2)

It is easy to adapt the proof of Theorem 4.8. The corresponding calculus rule is:

It is also easy to extend the proofs of Theorem 5.6 and Theorem 5.7. A more problematic aspect of the translation to canonical programs concerns formulas of the form ϕ1ϕ2, because there is no natural programming construct that computes the intersection of traces. But in Definition 4.5 general conjunction is not required, so without affecting the results in previous sections we can restrict the syntax of trace formulas in Definition 4.1 to pϕ.

A final issue are the programs that characterize a trace formula of the form p with semantics p=State+p. A program that produces such traces requires a havoc statement that resets all variables to an arbitrary value. This goes beyond non-deterministic choice quite a bit, but luckily, it is not required: As seen above, trace formulas of the form p occur only as subformulas of pϕ. Further, formulas of the form pϕ occur only as intermediate formulas in derivations, and nowhere else. Altogether, for the purpose of mapping formulas to programs, we can leave out the production for p. The grammar in Definition 4.1 is thus simplified to:

ϕ::=𝐼𝑑𝑆𝑏xaXpϕϕψϕψμX.ϕ

In addition, we assume without loss of generality that all recursion variables in a trace formula have unique names.

6.2 Canonical Programs

Definition 6.2 (Canonical Program).

Let ϕ be trace formula. The canonical program for ϕ, denoted 𝖼𝖺𝗇(ϕ)=Sϕ,Tϕ, is inductively defined as follows:

𝖼𝖺𝗇(𝐼𝑑) =𝖽𝖾𝖿skip,ϵ 𝖼𝖺𝗇(pϕ) =𝖽𝖾𝖿ifpthenSϕelsediverge,Tϕ
𝖼𝖺𝗇(𝑆𝑏xa) =𝖽𝖾𝖿x:=a,ϵ 𝖼𝖺𝗇(ϕψ) =𝖽𝖾𝖿ifthenSϕelseSψ,TϕTψ
𝖼𝖺𝗇(ϕψ) =𝖽𝖾𝖿Sϕ;Sψ,TϕTψ 𝖼𝖺𝗇(μX.ϕ) =𝖽𝖾𝖿mX(),Tϕ{mX{Sϕ}}
𝖼𝖺𝗇(X) =𝖽𝖾𝖿mX(),ϵ

The definition contains the statement diverge. It is definable in the Rec language as diverge=𝖽𝖾𝖿𝑎𝑏𝑜𝑟𝑡(), with the declaration 𝑎𝑏𝑜𝑟𝑡{𝑎𝑏𝑜𝑟𝑡()}. We assume that any table Tϕ contains the declaration of procedure 𝑎𝑏𝑜𝑟𝑡() when needed.

Example 6.3.

We translate the formula in Example 6.1:

𝖼𝖺𝗇(𝑆𝑏y0μX.(𝐼𝑑𝑆𝑏yy+1X))=y:=0;mX(),T

where T=mX{ifthenskipelsey:=y+1;mX()}.

Proposition 6.4.

We have 𝒮𝑡𝑟[[diverge]]=.

Proposition 6.5.

Let ϕ be an open trace formula, let Tϕ be declarations of its unbound recursion variables, and let 𝖼𝖺𝗇(ϕ)=Sϕ,Tϕ. Then Sϕ,TϕTϕ is a well-defined Rec program.

Evaluation of procedure calls and Boolean guards introduce stuttering steps as compared to the corresponding logical operators of least fixed point recursion and disjunction, respectively. Hence, canonical programs obtained from a formula ϕ are statements, whose trace semantics is equal to the one of ϕ, but modulo stuttering: The two trace sets are equal when abstracting away the stuttering steps. Further, we say that statement S1 refines statement S2, written S1S2, when 𝒮𝑡𝑟[[S1]]𝒮𝑡𝑟[[S2]].

Definition 6.6 (Stuttering Equivalence).

Let σ~ be the stutter-free version of a trace σ, i.e., where any subtrace of the form sss has been replaced with s. Define A~={σ~σA}. We say two trace sets A, B are stutter-equivalent, written A=~B, if A~=B~.

It is easy to see that A=B implies A=~B. Let ~ and ~ denote entailment between formulas and refinement between statements, respectively, both modulo stuttering equivalence.

Unsurprisingly, the characterization of canonical programs resembles the one of strongest trace formulas, however, modulo stuttering equivalence.

Theorem 6.7 (Characterisation of Canonical Program).

Let ϕ be a closed trace formula, and let 𝖼𝖺𝗇(ϕ)=Sϕ,Tϕ. Then 𝒮𝑡𝑟[[Sϕ]]=~ϕ.

Finally, we can establish that 𝗌𝗍𝖿() and 𝖼𝖺𝗇() form a Galois connection w.r.t. the partial orders ~ on formulas and ~ on statements.

Corollary 6.8.

Let ϕ be a closed trace formula, and let 𝖼𝖺𝗇(ϕ)=Sϕ,Tϕ. Then, for every statement S, we have: 𝗌𝗍𝖿(S)~ϕiffS~Sϕ.

Proof.

By using Theorem 4.8 and Theorem 6.7.

7 Related Work

We do not discuss higher-order logical frameworks [4, 27], because they serve a different purpose: Their expressiveness can be used to mechanize verification frameworks, including syntax, semantics, and calculus. For example, a mechanization of contract-based deductive verification is in [34]. One could do that also for the approach in the present paper.

Stirling [32, p. 528, footnote 2] suggests that the μ-calculus can be generalized to non-unary predicates, but does not develop this possibility further. In [24], Müller-Olm proposes a modal fixed point logic with chop, which can characterize any context-free process up to bisimulation or simulation. The logic is shown to be strictly more expressive than the modal μ-calculus. Lange & Somla [21] relate propositional dynamic logic over context-free programs with Müller-Olm’s logic and show the former to be equi-expressive with a fragment of the latter. Fredlund et al. [13] present a verification tool for the Erlang language based on first-order μ-calculus with actions [9]. Rosu et al. [6, 31] propose matching logic (ML) as a unifying logic for specifying and reasoning about the static structure and dynamic behaviour of programs. It uses patterns and constraints to uniformly represent mathematical domains, data types, and transition systems, whose properties can be reasoned about using one proof system. Our trace logic bares certain similarities with ML, both allowing recursion, but our logic is syntax-driven and has a fixed semantics in the domain of program traces, while ML is a semantic approach. We are not aware of a similar result as Corollary 6.8 in ML.

In contrast to the above mentioned work, we separate programs from fixed point formulas, and relate them in the form of judgments. Our logic has a single binary operator 𝑆𝑏xa over arithmetic expressions a and program variables x, together with the chop operator . The latter models composition of binary relations in the denotational semantics. Our logic is sufficient to characterize any Rec program. Specifically, μ-formulas can serve as contracts of recursive procedures. More importantly, our approach leads to a compositional calculus, where all rules but the consequence rule are analytic.

Kleene algebra with tests (KAT) [19] are an equational algebraic theory that has been shown to be as expressive as propositional while programs. They were mechanized in an interactive theorem prover [30] and are able to express at least Hoare-style judgments [20]. The research around KATs focuses on propositional while programs: we are not aware of results relating KAT with recursive stateful programs. Specifically, our result that procedure contracts can be expressed purely in terms of trace formulas (Theorem 4.8) has not been obtained by algebraic approaches.

Expressive trace-based specification languages are rare in program verification. The trace logic of Barthe et al. [2] is a many-sorted first-order logic, equipped with an arithmetic theory of explicit trace positions to define program semantics. It is intended to model program verification in first-order logic for processing in automated theorem provers. Like 𝒮𝑡𝑟[[]], their program semantics is compositional; however, it uses explicit time points instead of algebraic operators. An extension of Hoare logic with trace specifications is presented by Ernst et al. [11]. Hoare triples are extended with regular expressions recording events emitted before the execution of the command and the events emitted by its execution. Our trace logic is more expressive. Also the first-order temporal logic of nested words (NWTL) of Alur et al. [1] permits to specify certain execution patterns within symbolic traces. It is orthogonal to our approach, being based on nested event pairs and temporal operators, instead of least fixed points and chop. NWTL is equally expressive over nested words as first-order logic. The intended computational model is not Rec, but non-deterministic Büchi automata over nested words for which it is complete. Temporal stream logic of Finkbeiner et al. [12], like our trace logic, has state updates 𝑆𝑏xt (with a different syntax) and state predicates, but it is based on linear temporal logic and has no fixed points or chop. Again, the intended computational model is an extension of Büchi automata, the verification target are FPGA programs.

Cousot & Cousot [8] define a trace-based semantics for modal logics where (infinite) traces are equipped with past, present, and future. Their main focus is to relate model checking and static analysis to abstract interpretation – the trace-based semantics is the basis for it. In contrast, our paper relates a computation model to a logic. Like our semantics, theirs is compositional and the operators mentioned in their paper could inspire abstractions of our trace logic, cf. Section 8.1 below.

Nakata & Uustalu [25] present a trace-based co-inductive operational semantics with chop for an imperative programming language with loops. Following up on this work, [10] extended the approach to an asynchronous concurrent language, but neither of these uses fixed points, so that the specification language is incomplete. Also the calculus is not compositional.

Closest to the present work is our earlier work on trace-based deductive verification [5], where we used a similar trace logic than here. However, neither the semantics nor the calculus were compositional. Furthermore, we proved soundness there, but left completeness as an open question.

8 Future Work

8.1 Abstract Specifications and Extension of Recursive Programs

It is desirable to formulate specifications in a more abstract manner than the programs whose behavior they are intended to capture. For example, if we reinstate the atomic trace formula p in our logic, we can easily express the set of all finite traces as 𝑡𝑟𝑢𝑒=State+. Then we can define a binary connective as ϕψ=𝖽𝖾𝖿ϕ𝑡𝑟𝑢𝑒ψ as “any finite (possibly, empty) computation may occur between ϕ and ψ”. For example, the trace formula μX.(XX) expresses that a procedure mX calls itself at least twice recursively, at the beginning and at the end of its body, respectively.

A more general approach to introduce non-determinism to the logic is to define for a Boolean expression b a binary relation Rb with semantics Rb(s,s)𝖽𝖾𝖿[[b]](s)=tt. The atomic formula p is then definable as: p=𝖽𝖾𝖿RpR𝑡𝑟𝑢𝑒+. The advantage of basing p on a binary relation is that it is more easily represented as a canonical program. To this end, we introduce an atomic statement havoc with the trace semantics 𝒮𝑡𝑟[[havoc]]={sss,sState}, i.e. in any given state s, executing havoc results in an arbitrary successor state. The proof rule for havoc is the axiom Γhavoc:R𝑡𝑟𝑢𝑒 and obviously 𝗌𝗍𝖿(havoc)=𝖽𝖾𝖿R𝑡𝑟𝑢𝑒. Then p is characterized by 𝖼𝖺𝗇(p)=𝖽𝖾𝖿ifpthenℎ𝑎𝑣𝑜𝑐()elsediverge,T, where T contains the declaration ℎ𝑎𝑣𝑜𝑐{ifthenhavocelseskip;ℎ𝑎𝑣𝑜𝑐()}.

Interestingly, R𝑡𝑟𝑢𝑒 (and, therefore, havoc on the side of programs) permits to define concatenation of trace formulas ϕψ with the obvious semantics:

||ϕψ||=𝖽𝖾𝖿{s0sns0sms0sn||ϕ||,s0,sm||ψ||}=||ϕR𝑡𝑟𝑢𝑒ψ||

8.2 Proving Consequence of Trace Formulas

In general this is a difficult problem that requires fixed point induction, but the derivations needed in practice might be relatively simple, as the following example shows.

Example 8.1.

Consider the two trace formulas:

𝗌𝗍𝖿(𝑑𝑜𝑤𝑛()) =𝐼𝑑μX𝑑𝑜𝑤𝑛.((x>0𝐼𝑑𝑆𝑏xx2𝐼𝑑X𝑑𝑜𝑤𝑛)(x0𝐼𝑑𝐼𝑑))
𝐷𝑒𝑐x+ =μX𝑑𝑒𝑐.(𝐷𝑒𝑐xX𝑑𝑒𝑐𝐷𝑒𝑐x)

from Examples 2.3 and 4.7, respectively. We expect the trace formula implication 𝗌𝗍𝖿(𝑑𝑜𝑤𝑛())𝐼𝑑𝐷𝑒𝑐x+ to be provable, because of Theorem 4.8.

It turns out that the following fixed point induction rule and consequence rule, combined with straightforward first-order consequence and logic rules, are sufficient to prove the claim:

In fact, the proof is considerably shorter than proving the judgment 𝑑𝑜𝑤𝑛():𝐼𝑑𝐷𝑒𝑐x+ in the calculus of Section 5, which is as well possible.

A proof system for trace formula implication that can prove the above as well as many other non-trivial examples is given in [17].

8.3 Non-terminating Programs

Our results so far are limited to terminating programs, i.e. to sets of finite traces. To extend the calculus with a termination measure, such that a proof of S:ϕ not only shows correctness of S relative to ϕ, but also ensures it produces only finite traces, is easy.

However, the trace-based setup permits, in principle, also to prove properties of non-terminating programs. To this end, it is necessary to extend the logic with operators whose semantics contains infinite traces. One obvious candidate are greatest fixed points [32]. The downside to this approach is that nested fixed points of opposite polarity are difficult to understand, as is well known from μ-calculus. Is there a restriction of mixed fixed point formulas that naturally corresponds to a certain class of programs? The theory of strongest trace formulas and canonical programs might guide the search for such fragments.

9 Conclusion

We presented a fixed point logic that characterizes recursive programs with non-deterministic guards. Both, programs and formulas have the same kind of trace semantics, which, like the calculus for proving judgments, is fully compositional in the sense that the definitions and rules embody no context. The faithful embedding of programs into a logic seems to suggest that we merely replace one execution model (programs) with another (trace formulas). So why is it worth having such an expressive specification logic? We can see four reasons:

First, the logic renders itself naturally to extension and abstraction that cannot be easily mimicked by programs or that are much less natural for programs. This is corroborated by the discussion in Section 8.1, but also by the case of conjunction: It is trivial to add conjunction ϕψ to the trace logic and to a calculus for trace formulas, but conjunction has no natural program counter-part. Yet it permits to specify certain hyper-properties, i.e., properties relating sets of traces.

Second, the logic offers reasoning patterns that are easily justified algebraically, such as projection, replacement of equivalents, strengthening, distribution, etc., that are not obvious in the realm of programs.

Third, the concept of strongest trace formula leads to a characterization of valid judgments and, thereby, enables a simple completeness proof.

And finally, the duality between programs and formulas permits to prove judgments by freely mixing two styles of reasoning: with the rules of the calculus in Figure 5, or using a calculus for the consequence of trace formulas. For example, a judgment such as 𝑑𝑜𝑤𝑛():𝐼𝑑𝐷𝑒𝑐x+ can be proved as in Example 8.1 or directly with the rules in Figure 5, but also by mixing both styles.

A perhaps surprising feature of our trace logic is the fact that no explicit notion of procedure contract is required to achieve procedure-modular verification: Instead, strongest trace formulas and statement variables are employed. This results in a novel (Call) rule that works with symbolic continuations realized by statement variables.

References

  • [1] Rajeev Alur, Marcelo Arenas, Pablo Barceló, Kousha Etessami, Neil Immerman, and Leonid Libkin. First-order and temporal logics for nested words. Log. Methods Comput. Sci., 4(11):1–44, 2008. doi:10.2168/LMCS-4(4:11)2008.
  • [2] Gilles Barthe, Renate Eilers, Pamina Georgiou, Bernhard Gleiss, Laura Kovács, and Matteo Maffei. Verifying relational properties using trace logic. In Clark W. Barrett and Jin Yang, editors, Formal Methods in Computer Aided Design, FMCAD, pages 170–178, San Jose, CA, USA, 2019. IEEE. doi:10.23919/FMCAD.2019.8894277.
  • [3] Patrick Baudin, Jean-Christophe Filliâtre, Claude Marché, Benjamin Monate, Yannick Moy, and Virgile Prevosto. ACSL: ANSI/ISO C Specification. Technical Report Version 1.17, CEA and INRIA, 2021. URL: https://frama-c.com/download/frama-c-acsl-implementation.pdf.
  • [4] Yves Bertot and Pierre Castéran. Interactive Theorem Proving and Program Development—Coq’Art: The Calculus of Inductive Constructions. Texts in Theoretical Computer Science. An EATCS Series. Springer, Berlin Heidelberg, 2004. doi:10.1007/978-3-662-07964-5.
  • [5] Richard Bubel, Dilian Gurov, Reiner Hähnle, and Marco Scaletta. Trace-based deductive verification. In Logic for Programming, Artificial Intelligence and Reasoning (LPAR 2023), volume 94 of EPiC Series in Computing, pages 73–95. EasyChair, 2023. doi:10.29007/VDFD.
  • [6] Xiaohong Chen, Dorel Lucanu, and Grigore Rosu. Matching logic explained. Journal of Logical and Algebraic Methods in Programming, 120:100638, 2021. doi:10.1016/J.JLAMP.2021.100638.
  • [7] Edmund M. Clarke, Orna Grumberg, and Doron A. Peled. Model Checking. The MIT Press, Cambridge, Massachusetts, 1999.
  • [8] Patrick Cousot and Radhia Cousot. Temporal abstract interpretation. In Mark N. Wegman and Thomas W. Reps, editors, Proc. 27th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, Boston, Massachusetts, USA, pages 12–25. ACM, 2000. doi:10.1145/325694.325699.
  • [9] Mads Dam and Dilian Gurov. μ-calculus with explicit points and approximations. J. of Logic and Computation, 12(2):255–269, April 2002. doi:10.1093/LOGCOM/12.2.255.
  • [10] Crystal Chang Din, Reiner Hähnle, Einar Broch Johnsen, Violet Ka I Pun, and Silvia Lizeth Tapia Tarifa. Locally abstract, globally concrete semantics of concurrent programming languages. In Cláudia Nalon and Renate Schmidt, editors, Proc. 26th Intl. Conf. on Automated Reasoning with Tableaux and Related Methods, volume 10501 of LNCS, pages 22–43, Cham, September 2017. Springer. doi:10.1007/978-3-319-66902-1_2.
  • [11] Gidon Ernst, Alexander Knapp, and Toby Murray. A Hoare logic with regular behavioral specifications. In Tiziana Margaria and Bernhard Steffen, editors, Leveraging Applications of Formal Methods, Verification and Validation, 11th Intl. Symp., ISoLA, Rhodes, Greece, Proc. Part I, volume 13701 of LNCS, pages 45–64. Springer, 2022. doi:10.1007/978-3-031-19849-6_4.
  • [12] Bernd Finkbeiner, Felix Klein, Ruzica Piskac, and Mark Santolucito. Temporal stream logic: Synthesis beyond the bools. In Isil Dillig and Serdar Tasiran, editors, Computer Aided Verification: 31st Intl. Conf., CAV, Part I, volume 11561 of LNCS, pages 609–629, New York City, NY, USA, 2019. Springer. doi:10.1007/978-3-030-25540-4_35.
  • [13] Lars-Åke Fredlund, Dilian Gurov, Thomas Noll, Mads Dam, Thomas Arts, and Gennady Chugunov. A verification tool for Erlang. Journal of Software Tools for Technology Transfer, 4(4):405–420, August 2003. doi:10.1007/S100090100071.
  • [14] Dilian Gurov and Reiner Hähnle. An expressive trace logic for recursive programs. CoRR, abs/2411.13125, 2024. doi:10.48550/arXiv.2411.13125.
  • [15] Reiner Hähnle and Marieke Huisman. Deductive verification: from pen-and-paper proofs to industrial tools. In Bernhard Steffen and Gerhard Woeginger, editors, Computing and Software Science: State of the Art and Perspectives, volume 10000 of LNCS, pages 345–373. Springer, Cham, Switzerland, 2019.
  • [16] Joseph Y. Halpern and Yoav Shoham. A propositional modal logic of time intervals. Journal of the ACM, 38(4):935–962, 1991. doi:10.1145/115234.115351.
  • [17] Niklas Heidler and Reiner Hähnle. A Sequent Calculus for Trace Formula Implication. arXiv CoRR, May 2025. doi:10.48550/arXiv.2505.03693.
  • [18] C. A. R. Hoare. Procedures and parameters: An axiomatic approach. In Erwin Engeler, editor, Symposium on Semantics of Algorithmic Languages, volume 188 of Lecture Notes in Mathematics, pages 102–116. Springer, Berlin, Heidelberg, 1971. doi:10.1007/BFb0059696.
  • [19] Dexter Kozen. Kleene algebra with tests. ACM Transactions on Programming Languages and Systems, 19(3):427–443, 1997. doi:10.1145/256167.256195.
  • [20] Dexter Kozen. On Hoare logic and Kleene algebra with tests. ACM Transactions on Computational Logic, 1(1):60–76, 2000. doi:10.1145/343369.343378.
  • [21] Martin Lange and Rafał Somla. Propositional dynamic logic of context-free programs and fixpoint logic with chop. Information Processing Letters, 100(2):72–75, 2006. doi:10.1016/J.IPL.2006.04.019.
  • [22] Gary T. Leavens, Erik Poll, Curtis Clifton, Yoonsik Cheon, Clyde Ruby, David Cok, Peter Müller, Joseph Kiniry, Patrice Chalin, Daniel M. Zimmerman, and Werner Dietl. JML Reference Manual, May 2013. Draft revision 2344. URL: http://www.eecs.ucf.edu/˜leavens/JML//OldReleases/jmlrefman.pdf.
  • [23] Angelika Mader. Verification of modal properties using Boolean equation systems. PhD thesis, Technical University Munich, 1997.
  • [24] Markus Müller-Olm. A modal fixpoint logic with chop. In Theoretical Aspects of Computer Science (STACS 1999), volume 1563 of LNCS, pages 510–520, Berlin Heidelberg, 1999. Springer. doi:10.1007/3-540-49116-3_48.
  • [25] Keiko Nakata and Tarmo Uustalu. Trace-based coinductive operational semantics for While. In Theorem Proving in Higher Order Logics (TPHOLs), volume 5674 of LNCS, pages 375–390, Berlin Heidelberg, 2009. Springer. doi:10.1007/978-3-642-03359-9_26.
  • [26] Hanne Riis Nielson and Flemming Nielson. Semantics with Applications: An Appetizer. Undergraduate Topics in Computer Science. Springer, London, 2007. doi:10.1007/978-1-84628-692-6.
  • [27] Tobias Nipkow, Lawrence C. Paulson, and Markus Wenzel. Isabelle/HOL — A Proof Assistant for Higher-Order Logic, volume 2283 of LNCS. Springer, Berlin Heidelberg, 2002. doi:10.1007/3-540-45949-9.
  • [28] David Michael Ritchie Park. Finiteness is mu-ineffable. Theoretical Computer Science, 3(2):173–181, 1976. doi:10.1016/0304-3975(76)90022-0.
  • [29] Gordon D. Plotkin. A structural approach to operational semantics. J. Log. Algebr. Program., 60–61:17–139, 2004.
  • [30] Damien Pous. Kleene algebra with tests and Coq tools for while programs. In Sandrine Blazy, Christine Paulin-Mohring, and David Pichardie, editors, Interactive Theorem Proving, 4th Intl. Conf.  ITP, Rennes, France, volume 7998 of LNCS, pages 180–196. Springer, 2013. doi:10.1007/978-3-642-39634-2_15.
  • [31] Grigore Rosu. Matching logic. Logical Methods in Computer Science, 13(4), 2017. doi:10.23638/LMCS-13(4:28)2017.
  • [32] Colin Stirling. Modal and temporal logics. In Handbook of Logic in Computer Science (Vol. 2): Background: Computational Structures, pages 477–563, USA, 1993. Oxford University Press, Inc.
  • [33] Alfred Tarski. A lattice-theoretical fixedpoint theorem and its applications. Pacific Journal of Mathematics, 5:285–309, 1955.
  • [34] David von Oheimb. Hoare logic for Java in Isabelle/HOL. Concurrency and Computation: Practice and Experience, 13(13):1173–1214, 2001. doi:10.1002/CPE.598.

Appendix A Selected Proofs

A.1 Strongest Trace Formula

Proof of Lemma 4.11.

The proof proceeds by induction on the structure of S.

Case 𝑺=if𝒃then𝑺𝟏else𝑺𝟐.

By the induction hypothesis, 𝗌𝗍𝖿(Si)𝒱ρ=𝒮𝑡𝑟0[[Si]]ρ for i=1,2. We have:

𝗌𝗍𝖿(ifbthenS1elseS2)𝒱ρ=(b𝐼𝑑𝗌𝗍𝖿(S1))(¬b𝐼𝑑𝗌𝗍𝖿(S2))𝒱ρ=(b𝐼𝑑𝗌𝗍𝖿(S1))𝒱ρ(¬b𝐼𝑑𝗌𝗍𝖿(S2))𝒱ρ=(𝗌𝗍𝖿(S1)𝒱ρ)b(𝗌𝗍𝖿(S2)𝒱ρ)¬b=(𝒮𝑡𝑟0[[S1]]ρ)b(𝒮𝑡𝑟0[[S2]]ρ)¬b=𝒮𝑡𝑟0[[ifbthenS1elseS2]]ρ
Case 𝑺=𝒎().

We have:

𝗌𝗍𝖿(m())𝒱ρ=Xm𝒱ρ=𝒱ρ(Xm)=ρ(m)=𝒮𝑡𝑟0[[m()]]ρ

Proof of Theorem 4.8.

Since 𝗌𝗍𝖿(S) and 𝗌𝗍𝖿(X,S) are defined identically, except for the case S=m(), the proof is the same as for Lemma 4.11, except for that case:

Case 𝑺=𝒎().

We only sketch the proof here. We translate the formula 𝗌𝗍𝖿(,m()) into a modal equation system 𝗆𝖾𝗌(𝗌𝗍𝖿(,m())). This results in the formula Xm, defined in the context of a system of modal equations: for each fixed point operator μXi in 𝗌𝗍𝖿(,m()), there is an equation Xi=𝐼𝑑𝗌𝗍𝖿(Si), whenever mi is declared as mi{Si} in T. Next, from the standard semantics of modal equation systems, and by Lemma 4.11, it follows that the least solution 𝒱0 of the modal equation system is equal (on the names of the procedures called recursively by m) to the valuation 𝒱ρ0 induced by the interpretation ρ0 defined by the procedure table T. Finally, by Lemma 4.11, we have:

𝗌𝗍𝖿(,m())=Xm𝒱0=Xm𝒱ρ0=𝒱ρ0(Xm)=ρ0(m)=𝒮𝑡𝑟0[[m()]]ρ0=𝒮𝑡𝑟[[m()]]

A.2 Soundness of the Calculus

For the proof we need to relate traces restricted by a condition b to trace formulas. In the following proposition, the intuition for ¬bϕ is that it ignores any trace, that is, it is trivially true for any trace, where b does not hold in the beginning.

Proposition A.1 (State formulas in judgments).

Let b be a Boolean expression, ϕ a trace formula. Then (𝒮𝑡𝑟[[S]])bϕiff𝒮𝑡𝑟[[S]]¬bϕ.

Proof.

“Only If” direction: Assume (𝒮𝑡𝑟[[S]])bϕ and there is a trace sσ𝒮𝑡𝑟[[S]] that is not in ¬bϕ=¬bϕ, hence, sσ¬b and sσϕ. But sσ¬b implies sσ(𝒮𝑡𝑟[[S]])bϕ: contradiction.

“If” direction: Assume 𝒮𝑡𝑟[[S]]¬bϕ and there is a trace sσ(𝒮𝑡𝑟[[S]])b that is not in ϕ. From the assumption and (𝒮𝑡𝑟[[S]])b𝒮𝑡𝑟[[S]] we obtain sσ¬bϕ. However, since [[b]](s)=tt we must have in fact sσϕ: contradiction.

Proof of Theorem 5.6.

The proof system is sound, since every rule of the system is locally sound, in the sense that its conclusion is valid whenever all its premises are valid. We shall prove local soundness of each rule. Without loss of generality, we ignore Γ in most cases.

Rule IF.

Let  be an arbitrary interpretation. Using Proposition A.1, we have:

skip;S1:¬bϕandskip;S2:bϕ𝒮𝑡𝑟[[skip;S1]]¬bϕand𝒮𝑡𝑟[[skip;S2]]bϕ𝒮𝑡𝑟[[S1]]¬bϕand𝒮𝑡𝑟[[S2]]bϕ(𝒮𝑡𝑟[[S1]])bϕand(𝒮𝑡𝑟[[S2]])¬bϕ((𝒮𝑡𝑟[[S1]])b(𝒮𝑡𝑟[[S2]])¬b)ϕ𝒮𝑡𝑟[[ifbthenS1elseS2]]ϕifbthenS1elseS2:ϕ

where we use that 𝒮𝑡𝑟[[skip;S]]=𝒮𝑡𝑟[[S]], and therefore:

skip;S1:¬bϕandskip;S2:bϕifbthenS1elseS2:ϕ
Rule CALL.

For the proof of the rule we employ the principle of Fixed Point Induction. To simplify the presentation, we assume there is only one procedure m(), declared as m{Sm} in T. The general case follows from Bekič’s Principle. The notation ρ[mγ] specifies the interpretation that is identical to ρ, except for ρ(m)=γ.

Ym:ϕmSm[skip;Ym/m()]:ϕm.(𝒮𝑡𝑟[[Ym]]||ϕm||𝒮𝑡𝑟[[Sm[skip;Ym/m()]]]||ϕm||)γ.(γϕm𝒮𝑡𝑟0[[Sm]]ρ[mγ]ϕm)γ.(γ||𝐼𝑑ϕm||𝒮𝑡𝑟0[[Sm]]ρ[mγ]||𝐼𝑑ϕm||)γ.(γ||𝐼𝑑ϕm||𝒮𝑡𝑟0[[Sm]]ρ[mγ]||𝐼𝑑ϕm||)ρ0(m)||𝐼𝑑ϕm||𝒮𝑡𝑟0[[m()]]ρ0||𝐼𝑑ϕm||𝒮𝑡𝑟[[m()]]||𝐼𝑑ϕm||m():𝐼𝑑ϕm

A.3 Completeness of the Calculus

Proof of Theorem 5.7.

The proof proceeds by induction on the structure of S. However, In the case of a call the statement does not necessarily get smaller, because the body of m is expanded. So we need to argue that the induction is well-founded. Indeed, the lexicographic order on N|Γ|,|S| (obviously, the first component is never negative) always decreases. Since Γ is irrelevant for all cases except S=m(), we simplify the claim accordingly for these.

Case 𝑺=if𝒃then𝑺𝟏else𝑺𝟐.

Assume by the induction hypothesis that S1:𝗌𝗍𝖿(S1) and S2:𝗌𝗍𝖿(S2) can be proven. We also use that ϕ(p(¬pϕ)) is a valid consequence. We have:

ifbthenS1elseS2:𝗌𝗍𝖿(ifbthenS1elseS2)ifbthenS1elseS2:(b𝐼𝑑𝗌𝗍𝖿(S1))(¬b𝐼𝑑𝗌𝗍𝖿(S2)){By rule (If) combined with rule (Or)}skip;S1:¬b(b𝐼𝑑𝗌𝗍𝖿(S1))andskip;S2:b(¬b𝐼𝑑𝗌𝗍𝖿(S2)){By rule (Cons)}skip;S1:𝐼𝑑𝗌𝗍𝖿(S1)andskip;S2:𝐼𝑑𝗌𝗍𝖿(S2){By rule (Seq) combined with rule (Skip) andthe induction hypothesis}𝗍𝗋𝗎𝖾
Case 𝑺=𝒎().

There are two subcases: either m{m1,,mn} or not. In the first case, S[skip;Ym1/m1(),,skip;Ymn/mn()]=skip;Ym.

In the second case, S[skip;Ym1/m1(),,skip;Ymn/mn()]=m(). In both cases, we have 𝗌𝗍𝖿(m())=𝐼𝑑ϕm.

Subcase skip;Ym.

We have to prove the judgment Γskip;Ym:𝐼𝑑ϕm. Using rules (Seq) and (Skip), this reduces to ΓYm:ϕm. Because of the assumption m{m1,,mn} we have Ym:ϕmΓ, so the proof is finished.

Subcase m().

We have to prove the judgment Γm():𝐼𝑑ϕm. Rule (Call) is applicable due to assumption m{m1,,mn}. Let

Sm=𝖽𝖾𝖿Sm[skip;Ym/m(),skip;Ym1/m1(),,skip;Ymn/mn()]

The obtained premise yields the new claim to prove:

Γ{Ym:ϕm}Sm:μXm.𝗌𝗍𝖿({m},Sm)

We apply rule (Unfold) on the right and obtain:

Γ{Ym:ϕm}Sm:𝗌𝗍𝖿({m},Sm)[ϕm/Xm]

By induction and by the definition of 𝗌𝗍𝖿(S) we finish the proof up to subgoals of the form (for some mm):

Γ{Ym:ϕm}m():𝗌𝗍𝖿({m},m)[ϕm/Xm]

Unfortunately, the structure of the formula on the right does not conform to 𝗌𝗍𝖿(m) as required. But, observing that 𝗌𝗍𝖿(S)=𝗌𝗍𝖿(X¯,S), as well as soundness of unfolding, we can use Cons to obtain:

Γ{Ym:ϕm}m():𝗌𝗍𝖿(m)

This follows from the induction hypothesis, because of N|Γ|>N|Γ{Ym:ϕm}|.

A.4 Canonical Programs

Proof of Proposition 6.5.

We need to show that for any call mX() in 𝖼𝖺𝗇(ϕ) there is exactly one declaration of mX in Tϕ. This is a straightforward structural induction.

Lemma A.2.

Let ϕ be an open trace formula not containing fixed point binders (μX.), and let 𝖼𝖺𝗇(ϕ)=Sϕ,Tϕ. Then, we have: 𝒮𝑡𝑟0[[Sϕ]]ρ=~ϕ𝒱ρ for all interpretations ρ:M2State+ of the procedures M declared in Tϕ, and (induced) valuations 𝒱ρ:𝖱𝖵𝖺𝗋2State+ defined by 𝒱ρ(Xm)=𝖽𝖾𝖿ρ(m).

Proof.

We proceed by structural induction on ϕ.

Case ϕ=𝒑𝝍.

By the induction hypothesis, 𝒮𝑡𝑟0[[Sψ]]ρ=~ψ𝒱ρ.

𝒮𝑡𝑟0[[ifpthenSψelsediverge]]ρ
=(𝒮𝑡𝑟0[[Sψ]]ρ)p(𝒮𝑡𝑟0[[diverge]]ρ)¬p
=(𝒮𝑡𝑟0[[Sψ]]ρ)p(Proposition 6.4)
=State+p𝒮𝑡𝑟0[[Sψ]]ρ=~State+p𝒮𝑡𝑟0[[Sψ]]ρ
=~State+pψ𝒱ρ(Induction hypothesis)
=pψ𝒱ρ
Case ϕ=ϕ𝟏ϕ𝟐.

By the induction hypothesis, we have that 𝒮𝑡𝑟0[[Sϕ1]]ρ=~ϕ1𝒱ρ and 𝒮𝑡𝑟0[[Sϕ2]]ρ=~ϕ2𝒱ρ. Therefore,

𝒮𝑡𝑟0[[ifthenSϕ1elseSϕ2]]ρ =𝒮𝑡𝑟0[[Sϕ1]]ρ𝒮𝑡𝑟0[[Sϕ2]]ρ
=~𝒮𝑡𝑟0[[Sϕ1]]ρ𝒮𝑡𝑟0[[Sϕ2]]ρ =~ϕ1𝒱ρϕ2𝒱ρ(Ind. hyp.)
=ϕ1ϕ2𝒱ρ
Case ϕ=𝑿.

We have:

𝒮𝑡𝑟0[[mX()]]ρ=ρ(mX)=𝒱ρ(X)=X𝒱ρ

Proof of Theorem 6.7.

By structural induction on ϕ. We only sketch the proof idea here. An argument can be made similar to the one in the last case of the proof of Theorem 4.8, by referring to the modal equation system corresponding to ϕ, and using Lemma A.2 to generalise the treatment to open formulas ϕ and statements that contain calls to procedures not declared in Tϕ. Proposition 6.5 ensures that the program on the left is well-defined. Compositionality of the proof is justified by Bekič’s Principle.