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>
2.7 KiB
[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
; 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)
; 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)
; 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 givesr = u~(s="") ∨ (r = t ++ u)— empty pattern givesr = t ++ ucontains(u,s) ∨ r = u— no occurrence givesr = u
For replace-special.smt2 with u="":
- If
s="":r = "A" ++ "" = "A", butr = "B"→ contradiction → unsat - If
s≠"":r = u = "", butr = "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_axiomsrc/smt/theory_nseq.cpp—dequeue_axiom,populate_nielsen_graph