3
0
Fork 0
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:
Copilot 2026-07-22 10:30:45 -07:00 committed by GitHub
parent e2df18faaf
commit 9ac438b199
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 40 additions and 2 deletions

View file

@ -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) {

View file

@ -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.