← Back to AILP Home

Troubleshooting Verification

Understanding Unknown Results

When the solver returns Unknown, it means it couldn't prove or disprove the property within the timeout. The runtime check is preserved — your program is still safe, just not optimized.

Common causes of Unknown results and how to address them.

Cause 1: Missing Constraints

The solver can only reason about what it knows. If a variable has no Rules and no contracts, the solver assumes it can be any value in its type range.

Problem:

func:maybe_overflow = int32(int32:a, int32:b) {
    int32:sum = a + b;    // Unknown: a and b could be anything
    pass sum;
};

Fix — add Rules:

Rules:Bounded = { $ >= 0, $ <= 1000 };

func:safe_add = int32() {
    limit<Bounded> int32:a = 500;
    limit<Bounded> int32:b = 300;
    int32:sum = a + b;    // Proven: max 2000 < INT32_MAX
    pass sum;
};

Cause 2: Complex Expressions

The solver works best with simple arithmetic and comparisons. Complex expressions (nested function calls, pointer arithmetic, string operations) may exceed its reasoning ability.

Problem:

func:complex = int32(int32:x) {
    int32:a = raw helper1(x);
    int32:b = raw helper2(a);
    int32:c = raw helper3(b);
    prove(c > 0);     // Unknown: too many layers of indirection
};

Fix — add intermediate contracts:

func:helper1 = int32(int32:x) requires x > 0 ensures result > 0 { ... };
func:helper2 = int32(int32:x) requires x > 0 ensures result > 0 { ... };
func:helper3 = int32(int32:x) requires x > 0 ensures result > 0 { ... };

func:chained = int32(int32:x) requires x > 0 {
    int32:a = raw helper1(x);       // ensures a > 0
    int32:b = raw helper2(a);       // ensures b > 0
    int32:c = raw helper3(b);       // ensures c > 0
    prove(c > 0);                    // Proven via contract chain
};

Cause 3: Solver Timeout

For very large functions or very complex proofs, the solver may run out of time.

Check remaining budget:

npkc program.npk --verify --prove-report --smt-timeout=10000   # 10s per query

Fix — split large functions:

// Instead of one giant function with 50 operations,
// split into focused helpers with contracts

Cause 4: Non-Linear Arithmetic

Z3's bitvector theory handles linear arithmetic well but struggles with: - Multiplication of two variables (a * b where neither is constant) - Division with variable divisors - Modular arithmetic chains

Workaround: Constrain operands with narrow Rules to help the solver.

Using --prove-report

The --prove-report flag shows every verification verdict:

npkc program.npk --verify --prove-report

Output:

[limit]    Proven:  'x' satisfies Positive
[contract] Proven:  requires clause of safe_divide() satisfied
[overflow] Proven:  addition at line 12 cannot overflow
[overflow] Unknown: multiplication at line 15 — timeout
[race]     Disproven: 'counter' in unsafe_writer() — unprotected write
Z3: 14 proven, 1 disproven, 1 unknown

Using --verify-level for Debugging

Start with a low level to isolate which phase produces Unknown:

npkc program.npk --verify --verify-level=1   # Rules/limits only
npkc program.npk --verify --verify-level=2   # + contracts, overflow
npkc program.npk --verify --verify-level=3   # Everything

If level 1 works but level 2 produces Unknown, the issue is in contract or overflow verification — focus your annotations there.

Interpreting Counterexamples

When a property is Disproven, the solver provides a concrete counterexample:

[contract] Disproven: requires clause of process() NOT satisfied
           Counterexample: val = -1

This tells you exactly which input violates the contract. Fix the caller to not pass that value, or weaken the contract.

Common Gotchas

Symptom Cause Fix
All proofs Unknown --verify not passed Add --verify or --smt-opt
Overflow Unknown No Rules on operands Add limit<> constraints
Contract Disproven Caller doesn't satisfy requires Add constraints to caller
Race false positive Custom sync not recognized Use nitpick_shim_mutex_*
Slow compilation Too many proofs at level 3 Use --verify-level=2 for dev

Performance Budget

Rule of thumb for compilation time: - No verification: baseline - --verify-level=1: +9% (Rules/limits only) - --verify-level=2: +15% (standard) - --verify-level=3: +19% (thorough) - --smt-timeout=2000: faster, more Unknowns - --smt-timeout=10000: slower, fewer Unknowns

For most projects, --verify-level=2 during development and --verify-level=3 --smt-opt for release builds is the sweet spot.

Getting Help