Skip to main content

Counterexamples (--counterexample)

Concrete failing values that show why verification failed.

When verification fails, the verifier can sometimes produce a counterexample: a specific set of input values that violates the contract. This transforms an abstract "might not hold" message into a concrete debugging scenario with actual values.

When to Use​

Use --counterexample (or the alias --models) when:

  • A postcondition failure message is unclear
  • You need to understand which inputs violate a contract
  • You want to reproduce a verification failure with concrete values
  • You are debugging complex arithmetic or state-based contracts

Basic Usage​

Add --counterexample to any verification command:

sector9 --package core ./core/src --verify --counterexample file.sr9

The alias --models works identically:

sector9 --package core ./core/src --verify --models file.sr9

Counterexamples work with all output formats:

sector9 --package core ./core/src --verify --counterexample file.sr9           # Human-readable
sector9 --package core ./core/src --verify --json --counterexample file.sr9 # JSON output
sector9 --package core ./core/src --verify --toon --counterexample file.sr9 # TOON output

Counterexample extraction uses the Viper server path. If viperserver.jar is not discoverable, the command exits before verification and asks you to set VIPER_SERVER=/absolute/path/to/viperserver.jar or rebuild the toolchain.

What Counterexamples Show​

A counterexample provides concrete values for variables at the point of failure. Consider this buggy function:

public func add(a : Int, b : Int) : async Int
ensures result == a + b + 1; // Bug: off by one
{
a + b
};

Without --counterexample:

✗ verification failed (1 issue)

------------------------------
[1] Postcondition might not hold
Assertion result == a + b + 1 might not hold.
method: add

With --counterexample:

✗ verification failed (1 issue)

------------------------------
[1] Postcondition might not hold
Assertion result == a + b + 1 might not hold.
method: add

Counterexample summary
return: a=0, b=0, result=0

The counterexample reveals that when a=0 and b=0, the function returns 0, but the postcondition expects 1.

Counterexample Structure​

Counterexamples contain one or more models, each representing program state at a specific point:

Model Labels​

LabelMeaning
entryState when function is called (precondition context)
returnState when function returns (postcondition context)
Named labelsState at specific assertion points

Binding Types​

Each variable binding has a kind:

KindDescriptionExamples
localFunction parameters and local variablesx, amount, result
fieldActor or object fieldsself.balance, self.count
internalCompiler-generated variables$Res, $Self

By default, internal bindings are hidden unless they appear in the error message. Use --show-internal to include all internal bindings.

State and Heap Counterexamples​

Counterexamples work for both pure arithmetic and stateful code.

Arithmetic Example​

public func abs(x : Int) : async Int
ensures result >= 0;
ensures x >= 0 ==> result == x; // Bug: missing negative case
{
x // Returns x unchanged
};

Counterexample:

Counterexample summary
return: x=-1, result=-1

The verifier found that x=-1 produces result=-1, violating result >= 0.

State Update Example​

persistent actor Bank {
var balance : Nat = 0;

public func deposit(amount : Nat) : async ()
modifies balance
ensures balance == old(balance) + amount + 1; // Bug: off by one
{
balance := balance + amount;
};
}

Counterexample:

Counterexample summary
return: amount=0, self=...

When amount=0, the balance increases by 0, not by 1 as the postcondition requires.

JSON Counterexample Format​

With --json, counterexamples appear in a structured format:

{
"counterexample": {
"models": [
{
"label": "return",
"bindings": [
{
"name": "a",
"value": "0",
"kind": "local",
"scope": "local"
},
{
"name": "b",
"value": "0",
"kind": "local",
"scope": "local"
},
{
"name": "$Res",
"value": "0",
"kind": "internal",
"scope": "internal"
}
]
}
]
}
}

Extract counterexample values with jq:

# Get all binding names and values
sector9 --package core ./core/src --verify --json --counterexample file.sr9 | \
jq '.failures[0].counterexample.models[].bindings[] | "\(.name)=\(.value)"'

# Get only local bindings
sector9 --package core ./core/src --verify --json --counterexample file.sr9 | \
jq '.failures[0].counterexample.models[].bindings[] | select(.kind == "local")'

Interpreting Counterexample Values​

Numeric Values​

Integer values appear as decimal strings:

a=0, b=-5, result=100

Reference Values​

Object references appear as solver-internal representations:

self=Null($Ref!val!0)       # Null reference
self=Ref ($Ref!val!0) {...} # Non-null reference

These internal representations indicate the solver's model for heap objects. The specific syntax is not meant for direct interpretation; what matters is whether values are distinct or equal.

Field Values​

Actor fields are shown with the self. prefix:

self.balance=100, self.count=5

Limitations​

Counterexamples have some limitations to be aware of:

Not Always Available​

The SMT solver may not produce a model for every failure. Complex constraints or timeouts can result in missing counterexamples.

Symbolic Values​

Some values may appear as symbolic expressions rather than concrete numbers when the solver cannot determine a specific value.

Heap Abstraction​

Heap state (mutable records, arrays) may appear as abstract references rather than fully expanded values.

Backend Requirement​

Counterexample extraction requires the Viper server/Silicon backend with model support. If the backend is missing or lacks counterexample support, an error appears before models are printed:

sector9 verify: could not locate viperserver.jar
FlagEffect
--counterexampleInclude counterexample models in output
--modelsAlias for --counterexample
--show-internalInclude internal bindings (e.g., $Res, $Self)

For machine-readable model data, combine this with JSON Output or TOON Format.

Debugging Workflow​

A typical workflow when a verification failure occurs:

  1. Run with --counterexample to get concrete values
  2. Identify which inputs cause the failure
  3. Check if those inputs are expected (valid inputs that expose a bug) or unexpected (missing precondition)
  4. Fix the implementation or strengthen the contract accordingly

Example:

# Step 1: Get counterexample
$ sector9 --package core ./core/src --verify --counterexample transfer.sr9
...
Counterexample summary
return: amount=100, self.balance=50

# Step 2: Interpret
# The failure shows amount=100 when balance=50
# This means transferring more than available triggers the failure

# Step 3: Fix - add a precondition
# requires amount <= balance;

Summary​

  • Use --counterexample to request concrete failing values
  • --models is an alias for --counterexample
  • Counterexamples show variable values at entry and return points
  • Bindings are classified as local, field, or internal
  • Use with --json for programmatic processing
  • Not all failures produce counterexamples because models depend on the backend and the failing obligation shape
  • Use --show-internal to see compiler-generated variables