3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-05-17 23:49:28 +00:00
z3/research/docs/nseq-issues/04-soundness-prefix-suffix.md
Copilot f518faac9b
Add nseq issue drafts from Ostrich benchmark analysis (discussion #9071) (#9073)
Agent-Logs-Url: https://github.com/Z3Prover/z3/sessions/04321ea7-2a53-4ed5-9f43-816dc6f7476b

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
2026-03-20 19:07:51 -07:00

78 lines
2.9 KiB
Markdown

# [nseq] Soundness bug: str.prefixof / str.suffixof interaction not fully axiomatized
**Labels**: bug, c3, nseq, soundness
## Summary
The nseq solver returns `sat` for several benchmarks involving `str.prefixof`,
`str.suffixof`, and `str.at` where the correct answer is `unsat`. The seq solver
handles all these cases correctly.
## Affected benchmarks
| File | seq verdict | nseq verdict |
|------|-------------|--------------|
| `prefix-suffix.smt2` | unsat | **sat** (WRONG) |
| `failedProp.smt2` | unsat | **sat** (WRONG) |
| `failedProp2.smt2` | unsat | **sat** (WRONG) |
Data from: https://github.com/Z3Prover/z3/discussions/9071
## Reproducing examples
```smt2
; prefix-suffix.smt2 — EXPECTED: unsat, nseq returns: sat
(set-logic QF_S)
(declare-fun a () String)
(declare-fun b () String)
(assert (not (=> (and (str.prefixof a b) (str.suffixof b a)) (= a b))))
(check-sat)
```
The above asserts: `(a prefix b) AND (b suffix a) AND a ≠ b`.
This is unsatisfiable: if `a` is a prefix of `b` and `b` is a suffix of `a`,
then `|a| ≤ |b|` and `|b| ≤ |a|`, so `|a| = |b|`. Combined with the prefix and
suffix conditions, this forces `a = b`.
```smt2
; failedProp.smt2 — EXPECTED: unsat, nseq returns: sat
(set-logic QF_SLIA)
(declare-fun x () String)
(declare-fun y () String)
(assert (not (= (str.prefixof x (str.at x 1)) (= x ""))))
(check-sat)
```
The above asserts: `(str.prefixof x (str.at x 1)) ≠ (x = "")`.
`str.at x 1` is the second character of `x` (as a length-1 string).
`str.prefixof x (str.at x 1)` is true only when `x` is a prefix of a single character,
which means `x = ""` or `x` is that single character (but then `x` is a prefix of itself only
if `len(x) ≤ 1`). This equivalence should hold: `str.prefixof x (str.at x 1) ↔ x = ""`.
So the assertion is `unsat`.
```smt2
; failedProp2.smt2 — EXPECTED: unsat, nseq returns: sat (same pattern with suffixof)
(set-logic QF_SLIA)
(declare-fun x () String)
(assert (not (= (str.suffixof x (str.at x 1)) (= x ""))))
(check-sat)
```
## Analysis
For `prefix-suffix.smt2`, the current axioms for `prefix_axiom` and `suffix_axiom`
introduce skolem variables but may not derive the length equality `|a| = |b|` from the
combined prefix+suffix condition, or may not then propagate this to `a = b`.
For `failedProp.smt2` and `failedProp2.smt2`, the issue involves interaction between
`str.at`, `str.prefixof`/`str.suffixof`, and string-integer length constraints. The
nseq solver may not properly resolve the combination of:
1. The `at_axiom` (generating constraints on `str.at x 1`)
2. The `prefix_axiom`/`suffix_axiom` (decomposing the prefix/suffix relationship)
3. The arithmetic constraint linking lengths
## Files to investigate
- `src/ast/rewriter/seq_axioms.cpp``prefix_axiom`, `suffix_axiom`, `prefix_true_axiom`, `suffix_true_axiom`, `at_axiom`
- `src/smt/theory_nseq.cpp``assign_eh` for prefix/suffix, `final_check_eh`
- `src/smt/seq/seq_nielsen.cpp` — length propagation in Nielsen graph