3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-04-23 20:33:30 +00:00

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>
This commit is contained in:
Copilot 2026-03-20 19:07:51 -07:00 committed by GitHub
parent 8ef491edea
commit f518faac9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 932 additions and 0 deletions

View file

@ -0,0 +1,78 @@
# [nseq] Soundness bug: str.replace with empty-string input returns sat instead of unsat
**Labels**: bug, c3, nseq, soundness
## Summary
The nseq solver returns `sat` on benchmarks where the correct answer is `unsat` when
`str.replace` is applied to an empty input string and the result is constrained to an
impossible value. The seq solver correctly returns `unsat` for all of these cases.
## Affected benchmarks (Ostrich suite, c3 branch)
| File | seq verdict | nseq verdict |
|------|-------------|--------------|
| `replace-special.smt2` | unsat | **sat** (WRONG) |
| `replace-special-4.smt2` | unsat | **sat** (WRONG) |
| `replace-special-5.smt2` | unsat | **sat** (WRONG) |
| `simple-replace-4b.smt2` | unsat | **sat** (WRONG) |
Data from: https://github.com/Z3Prover/z3/discussions/9071
## Reproducing examples
```smt2
; replace-special.smt2 — EXPECTED: unsat, nseq returns: sat
(set-logic QF_SLIA)
(declare-fun x () String)
(assert (not (= (= "B" (str.replace "" x "A")) false)))
(check-sat)
```
```smt2
; replace-special-4.smt2 — EXPECTED: unsat, nseq returns: sat
(set-logic QF_SLIA)
(declare-fun x () String)
(declare-fun y () String)
(assert (not (= (str.replace "" (str.replace "" x "A") "B")
(str.substr "B" 0 (str.len x)))))
(check-sat)
```
```smt2
; simple-replace-4b.smt2 — EXPECTED: unsat, nseq returns: sat
(set-logic QF_S)
(declare-fun x_7 () String)
(declare-fun x_11 () String)
(declare-fun x_12 () String)
(assert (= x_7 (str.replace (str.++ x_11 x_12) "e" "a")))
(assert (= x_7 "hello"))
(check-sat)
```
## Analysis
`replace_axiom` in `src/ast/rewriter/seq_axioms.cpp` generates clauses for
`r = str.replace u s t`:
- `~(u="") (s="") (r=u)` — empty input without empty pattern gives `r = u`
- `~(s="") (r = t ++ u)` — empty pattern gives `r = t ++ u`
- `contains(u,s) r = u` — no occurrence gives `r = u`
For `replace-special.smt2` with `u=""`:
- If `s=""`: `r = "A" ++ "" = "A"`, but `r = "B"` → contradiction → unsat
- If `s≠""`: `r = u = ""`, but `r = "B"` → contradiction → unsat
The clauses are individually correct, but the nseq Nielsen graph apparently fails to
derive the contradiction when the result `r` is constrained via equality to an impossible
constant. The likely root cause is incomplete propagation of the result equality through
the egraph into the replace axiom conclusions.
For `simple-replace-4b.smt2`, the issue is that `str.replace (x_11 ++ x_12) "e" "a" = "hello"`
is unsatisfiable because "hello" contains 'l', which cannot be produced by replacing 'e'→'a' in
any string, yet nseq claims sat.
## Files to investigate
- `src/ast/rewriter/seq_axioms.cpp``replace_axiom`
- `src/smt/theory_nseq.cpp``dequeue_axiom`, `populate_nielsen_graph`