Skip to content
Function Contracts and Modular Verification

Function Contracts and Modular Verification

Contracts are the basis of modular (assume-guarantee) verification in ESBMC. This page explains the underlying principle and how it fits the other verification algorithms; for the full annotation syntax, command-line flags, worked examples, and limitations, see the Function Contracts how-to guide.

The assume-guarantee principle

A contract states what a function assumes of its callers (a precondition) and what it guarantees in return (a postcondition), together with a frame condition. Given such a contract, a function is verified once against its specification, and every call to it is then replaced by that specification rather than re-analysed.

A frame condition names the locations an operation is allowed to modify and — crucially — asserts that every other location is left unchanged. In a contract it is the assigns (or modifies) clause: __ESBMC_assigns(x) says “this call may change x and nothing else.” The “nothing else” half is what makes contracts useful — it lets a caller keep everything it already knew about the rest of the state across the call, and it is exactly what replace mode relies on when it havocs only the listed locations. The need to state this explicitly (rather than leaving “what does not change” implicit) is the frame problem in procedure specifications [1].

This decomposes a single whole-program proof into independent per-function obligations:

  • Enforce — assume the precondition, run the body, assert the postcondition and frame. This discharges “the implementation meets its contract.”
  • Replace — at a call site, assert the precondition, havoc the frame, and assume the postcondition. This lets a caller be verified against the specification of its callees, without unrolling their bodies.

Enforcing each function once and replacing it everywhere it is called yields a proof whose cost does not blow up with call depth — the key scalability win over inlining everything into one bounded model checking query.

Relationship to the other algorithms

Function contracts are an over-approximation: replacing a call with its contract admits any behaviour the postcondition permits, which may be more than the concrete body produces. This is sound for proofs but, like any abstraction, can yield spurious counterexamples if a contract is too weak — tightening the ensures/assigns clauses recovers precision. (The how-to guide’s Replace mode is an over-approximation note covers this in practice.)

The same assume-guarantee idea applies to loops: a loop invariant plus a loop frame condition lets ESBMC summarise a loop instead of unrolling it, turning a bounded check into an inductive one — closely related to k-induction and detailed in the Loop Invariants guide. The “everything else is unchanged” guarantee a frame condition provides is the contract-level analogue of the frame rule in separation logic [2].

See also

References

[1] Alexander Borgida, John Mylopoulos, Raymond Reiter: On the Frame Problem in Procedure Specifications. IEEE Trans. Software Eng. 21(10):785–798, 1995. doi:10.1109/32.469460

[2] John C. Reynolds: Separation Logic: A Logic for Shared Mutable Data Structures. LICS 2002: 55–74. doi:10.1109/LICS.2002.1029817