mirror of
https://github.com/Z3Prover/z3
synced 2026-03-23 12:59:12 +00:00
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>
78 lines
2.7 KiB
Markdown
78 lines
2.7 KiB
Markdown
# [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`
|