Skip to main content
Z3 can verify that programs behave correctly by encoding program logic as constraints. This tutorial demonstrates bounded model checking to verify algorithm correctness.

What is Program Verification?

Program verification uses formal methods to prove that code satisfies specifications. Instead of testing with specific inputs, we prove correctness for all possible inputs (or find counterexamples). Key concepts:
  • Pre-conditions: What must be true before the program runs
  • Post-conditions: What must be true after the program runs
  • Invariants: Properties that remain true throughout execution
  • Counterexample: An input that violates the specification

Example: Verifying Bubble Sort

Let’s verify that bubble sort correctly sorts arrays.
1

Model the array

Use Z3’s Array type to represent the array before and after sorting:
Z3 arrays are functions from indices to values.
2

Define the sorting property

What does it mean for an array to be sorted?
Select(arr, i) reads the value at index i.
3

Model one bubble sort pass

One pass compares adjacent elements and swaps if needed:
Store(arr, idx, val) creates a new array with arr[idx] = val.
4

Verify correctness

Try to find a counterexample where the algorithm fails:

Complete Verification Example

Here’s a complete example that verifies bubble sort:
Expected output:
Z3 proves bubble sort is correct by showing no counterexample exists!

Finding Bugs: Broken Sort

Let’s intentionally introduce a bug and see Z3 find it:
Expected output:
Z3 found an input where the broken algorithm fails to sort!

Verifying Mathematical Properties

You can verify mathematical properties hold:
Expected output:

Loop Invariants

Verify loop invariants hold:
Expected output:

Verification Best Practices

Verify simple properties first, then build up to complex invariants. Start with small bounds (e.g., arrays of size 3).
For loops, unroll a fixed number of iterations. This finds bugs in early iterations efficiently.
Write precise pre-conditions and post-conditions. Ambiguous specs lead to meaningless verification.
When Z3 finds sat, examine the model carefully - it shows exactly how the property fails.

Bounded Model Checking Pattern

A general pattern for verification:
Always negate the post-condition when checking. If Z3 returns unsat, no counterexample exists, proving correctness.

Limitations

Bounded verification has limitations:
  • Only checks finite cases (bounded loops, fixed sizes)
  • May not scale to very large programs
  • Requires manual modeling of program semantics
  • Unbounded loops need inductive invariants (more advanced)
For full verification of unbounded programs, you need inductive proofs and loop invariants.

Next Steps

Basic Solving

Review fundamental constraint solving concepts

Optimization

Learn to find optimal solutions with Z3 Optimize