mirror of
https://github.com/Z3Prover/z3
synced 2026-07-23 23:42:33 +00:00
Fix nested symbolic re.range under re.++ ignoring solver timeout (#10181)
Symbolic `re.range` nested under `re.++` caused Z3 to loop indefinitely,
ignoring timeouts. Direct symbolic range membership was fixed
previously, but the nested case reaches a different code path — the
Brzozowski derivative engine in `derive_range`.
## Root cause
`derive_range` in `seq_derive.cpp` only handled concrete unit-string
bounds. For symbolic bounds it returned a stuck `re.derivative(ele,
re.range(lo, hi))` term. When nested under concatenation, this stuck
term cycled infinitely:
1. `is_nullable` of the stuck term → `is_nullable_symbolic_regex` →
emits `re.in_re("", re.derivative(...))`
2. That `in_re` triggers `propagate_in_re` → new `accept` predicate
3. New `accept` computes another derivative → another stuck term →
repeat
## Fix
Replace the stuck fallback with a proper symbolic ITE. By SMT-LIB
semantics, `re.range(lo, hi)` accepts character `c` iff both bounds are
single-character strings and `lo[0] ≤ c ≤ hi[0]`. The derivative with
respect to `ele` becomes:
```
ite(len(lo)=1 ∧ len(hi)=1 ∧ lo[0] ≤ ele ∧ ele ≤ hi[0], ε, ∅)
```
Length conditions are omitted for bounds already known to be concrete
single-character strings.
```smt2
(set-logic ALL)
(declare-const s String)
(assert (str.in_re "a" (re.++ re.all (re.range s "c"))))
(check-sat)
; Previously hung indefinitely; now returns sat in ~10ms
```
## Changes
- **`src/ast/rewriter/seq_derive.cpp`** — `derive_range`: replace stuck
`re.derivative` fallback with symbolic ITE using `str.nth_i` and length
guards for non-concrete bounds
- **`src/test/seq_rewriter.cpp`** — add solver-level regression test
(case 21) for nested symbolic `re.range` under `re.++`
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
parent
e2df18faaf
commit
9ac438b199
2 changed files with 40 additions and 2 deletions
|
|
@ -421,8 +421,26 @@ namespace seq {
|
|||
return mk_ite(in_range, eps, empty);
|
||||
}
|
||||
|
||||
// Fallback: stuck derivative
|
||||
return expr_ref(re().mk_derivative(m_ele, re().mk_range(lo, hi)), m);
|
||||
// One or both bounds are symbolic. By SMT-LIB semantics re.range(lo,hi)
|
||||
// accepts a single character c iff lo and hi are both single-character
|
||||
// strings and lo[0] <= c <= hi[0]. Build the symbolic derivative
|
||||
// ite(len(lo)=1 ∧ len(hi)=1 ∧ lo[0] ≤ ele ∧ ele ≤ hi[0], ε, ∅)
|
||||
// omitting length conditions for bounds that are already known to be
|
||||
// concrete single-character strings.
|
||||
expr_ref_vector conds(m);
|
||||
expr_ref zero(m_autil.mk_int(0), m);
|
||||
if (!u().str.is_unit_string(lo, c_lo)) {
|
||||
conds.push_back(m.mk_eq(u().str.mk_length(lo), m_autil.mk_int(1)));
|
||||
c_lo = u().str.mk_nth_i(lo, zero);
|
||||
}
|
||||
if (!u().str.is_unit_string(hi, c_hi)) {
|
||||
conds.push_back(m.mk_eq(u().str.mk_length(hi), m_autil.mk_int(1)));
|
||||
c_hi = u().str.mk_nth_i(hi, zero);
|
||||
}
|
||||
conds.push_back(m_util.mk_le(c_lo, m_ele));
|
||||
conds.push_back(m_util.mk_le(m_ele, c_hi));
|
||||
expr_ref in_range = m_br.mk_and(conds);
|
||||
return mk_ite(in_range, eps, empty);
|
||||
}
|
||||
|
||||
expr_ref derive::derive_of_pred(expr* pred, sort* seq_sort) {
|
||||
|
|
|
|||
|
|
@ -325,6 +325,26 @@ void tst_seq_rewriter() {
|
|||
ENSURE(res == l_false);
|
||||
}
|
||||
|
||||
// 21. sat: (str.in_re "a" (re.++ re.all (re.range s "c")))
|
||||
// Regression for nested symbolic re.range under re.++.
|
||||
// The string "a" satisfies the regex when s = "a":
|
||||
// re.all matches "" and re.range "a" "c" accepts "a".
|
||||
// This must not hang; it should return sat.
|
||||
{
|
||||
smt_params sp;
|
||||
smt::context ctx(m, sp);
|
||||
app_ref s(m.mk_fresh_const("s", str_sort), m);
|
||||
expr_ref a_str(su.str.mk_string(zstring('a')), m);
|
||||
expr_ref c_str(su.str.mk_string(zstring('c')), m);
|
||||
expr_ref re_all(su.re.mk_full_seq(re_sort), m);
|
||||
expr_ref re_range(su.re.mk_range(s, c_str), m);
|
||||
expr_ref regex(su.re.mk_concat(re_all, re_range), m);
|
||||
ctx.assert_expr(su.re.mk_in_re(a_str, regex));
|
||||
lbool res = ctx.check();
|
||||
std::cout << "nested symbolic re.range under re.++ sat: " << res << "\n";
|
||||
ENSURE(res == l_true);
|
||||
}
|
||||
|
||||
// 20. unsat: contradictory constant lexical bounds.
|
||||
// "2024-01-01" < x < "2024-12-31" and x < "2023-01-01".
|
||||
// Since "2023-01-01" < "2024-01-01", no such x exists.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue