3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-05 14:56:11 +00:00
z3/src/test
Lev Nachmanson 2a8f66f22b
[snapshot-regression-fix] Keep symbolic re.range non-empty; fix soundness regression on range membership (#10017)
## Summary

Fixes a **soundness regression** in the sequence/regex rewriter: a
symbolic character range such as `(re.range x x)` was unsoundly
collapsed to `re.empty`, causing a satisfiable membership constraint to
be reported `unsat`.

This was surfaced by the `snapshot-regression` corpus in
`Z3Prover/bench`.

- **Originating discussion:**
https://github.com/Z3Prover/bench/discussions/2761
- **Benchmark:** `iss-5873/bug-2.smt2` (in `Z3Prover/bench`, under
`inputs/issues/iss-5873/`)
- **z3 under test at capture:** `z3-4.17.0-x64-glibc-2.39` (Nightly)

## Divergence

The recorded oracle expects `sat`; current z3 returns `unsat`:

```diff
--- bug-2.expected.out (expected)
+++ produced (current z3)
@@ -1,3 +1,4 @@
-sat
-((tmp_str0 "\u{0}"))
+unsat
+(error "line 12 column 10: check annotation that says sat")
+(error "line 14 column 22: model is not available")
 (:reason-unknown "")
```

The benchmark asserts (simplified):

```smt2
(assert (= (str.in_re (str.replace tmp_str0 tmp_str0 tmp_str0)
                      (re.range tmp_str0 tmp_str0))
           (str.contains tmp_str0 tmp_str0)))
```

`str.contains x x` is always true and `str.replace x x x = x`, so this
requires `str.in_re x (re.range x x)` to hold, which is satisfiable
exactly when `x` is a single character (`len(x) = 1`).

## Root cause

`seq_rewriter::mk_re_range` treated any bound that is not a concrete
single-character literal as making the whole range **empty**:

```cpp
if (str().is_string(lo, slo) && slo.length() == 1) clo = slo[0];
else if (str().is_unit(lo, lo1) && m_util.is_const_char(lo1, clo)) ;
else is_empty = true;   // unsound for a symbolic bound
```

For a symbolic bound this is unsound: `(re.range x x)` denotes `{x}`
whenever `x` is a single character, not `∅`. Collapsing it to `re.empty`
makes `str.in_re x (re.range x x)` false, contradicting the (true)
`str.contains x x`, so the solver derives an unsound `unsat`.

`git blame` attributes this unsound collapse to z3 commit `15f33f458d`
("Derive with ranges (#9965)"), which post-dates the oracle capture.

## Fix

Two surgical changes in `src/ast/rewriter/seq_rewriter.cpp`:

1. **`mk_re_range`** no longer assumes emptiness for symbolic bounds. It
concludes `re.empty` only when it can *prove* emptiness — a bound whose
length can never be 1, or two concrete bounds with `lo > hi`. When a
bound is symbolic it returns `BR_FAILED` and keeps the range. Concrete
single-character ranges keep their existing handling (`lo == hi →
str.to_re`, inverted → `re.empty`).

2. **`mk_str_in_regexp`** reduces membership in a range that has a
symbolic bound to the equivalent length/order constraints, which are
sound and complete under SMT-LIB `re.range` semantics:

`str.in_re e (re.range lo hi)` ⟶ `len(lo)=1 ∧ len(hi)=1 ∧ len(e)=1 ∧ lo
≤ e ∧ e ≤ hi`

(using `str.<=`). The derivative engine only unfolds ranges whose bounds
are concrete characters, so without this reduction a symbolic-bound
range would otherwise be left unsolved.

## Validation

Rebuilt z3 from this branch on the workflow runner (`./configure && make
-C build -j$(nproc)`) and re-ran the failing benchmark with the same
option the snapshot capture uses (`-T:20`):

```
$ z3 -T:20 inputs/issues/iss-5873/bug-2.smt2
sat
((tmp_str0 "A"))
(:reason-unknown "")
```

The verdict is now **`sat`** (was `unsat`) — the soundness regression is
resolved. A correctness battery over concrete and symbolic ranges all
returns the expected results, e.g.:

- `(str.in_re "b" (re.range "a" "c"))` → `sat`, `(str.in_re "d"
(re.range "a" "c"))` → `unsat`
- `(str.in_re x (re.range x x))` → `sat`; with `(= (str.len x) 2)` →
`unsat`
- `(str.in_re "b" (re.range x y))` → `sat`; with `(str.< y x)` → `unsat`
- `(str.in_re "" (re.range x y))` → `unsat`; `(str.in_re "ab" (re.range
"a" "c"))` → `unsat`

The pre-existing concrete-range derivative fast path is unchanged.

### Note on the model value (benign, unrelated to this fix)

The model value differs from the recorded oracle: current z3 prints
`((tmp_str0 "A"))` whereas the oracle recorded `((tmp_str0 "\u{0}"))`.
Both are valid single-character models (the formula has many). This
difference is **pre-existing and unrelated to this fix**: even a bare
`(assert (= (str.len x) 1))` yields `"A"` on current z3. It stems from
the seq/char theory's default character assignment for
otherwise-unconstrained characters (`theory_char.cpp` assigns fresh
characters starting from `'A'`), not from range handling. I deliberately
did **not** force the character to `\u{0}` — adding `x = "\u{0}"` would
be unsound over-constraining, and changing the global default character
is out of scope for this soundness fix and would perturb unrelated
models. The output is therefore semantically equivalent to the oracle
(same `sat` verdict and reason-unknown) but not byte-identical.

---
*Draft for human review. Diagnosed and fixed by the
`snapshot-regression-fixer` maintenance workflow.*




> Generated by [Fix a Z3 snapshot-regression
divergence](https://github.com/Z3Prover/bench/actions/runs/28502614658)
· 890.7 AIC · ⌖ 46.8 AIC · ⊞ 9K ·
[◷](https://github.com/search?q=repo%3AZ3Prover%2Fz3+%22gh-aw-workflow-id%3A+snapshot-regression-fixer%22&type=pullrequests)

<!-- gh-aw-agentic-workflow: Fix a Z3 snapshot-regression divergence,
engine: copilot, version: 1.0.63, model: claude-opus-4.8, id:
28502614658, workflow_id: snapshot-regression-fixer, run:
https://github.com/Z3Prover/bench/actions/runs/28502614658 -->

<!-- gh-aw-workflow-id: snapshot-regression-fixer -->
<!-- gh-aw-workflow-call-id: Z3Prover/bench/snapshot-regression-fixer
-->

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-02 14:00:51 -07:00
..
fuzzing fix test build 2023-12-22 16:19:28 +00:00
lp Lcube (#9858) 2026-06-14 16:25:21 -07:00
ackermannize.cpp fix build warnings 2026-06-22 18:20:23 -07:00
algebraic.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
algebraic_numbers.cpp fix edge case in algebraic number comparison (#9498) 2026-05-27 05:01:47 -07:00
api.cpp Fix GMP bit-vector modulo semantics causing signed BV unsoundness and invalid SMT2 numerals (#9899) 2026-06-19 10:08:51 -06:00
api_algebraic.cpp Add tests for ackermannization module and Z3_algebraic_eval 2026-02-19 18:18:19 +00:00
api_ast_map.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
api_bug.cpp make include paths uniformly use path relative to src. #534 2017-07-31 13:24:11 -07:00
api_datalog.cpp Prevent Spacer segfault on ADT CHCs by hardening datatype model-value construction (#9571) 2026-05-20 16:04:41 -07:00
api_pb.cpp Daily Test Coverage Improver: Add comprehensive API pseudo-boolean constraint tests (#7898) 2025-09-17 20:45:43 -07:00
api_polynomial.cpp preserve the initial state of the solver with push/pop for multiple objectives (#8264) 2026-02-18 20:57:53 -08:00
api_special_relations.cpp Daily Test Coverage Improver: Add comprehensive API special relations tests (#7925) 2025-09-18 09:20:14 -07:00
arith_rewriter.cpp Fix #7507: simplify (>= product_of_consecutive_ints 0) to true 2026-02-27 06:37:07 -10:00
arith_simplifier_plugin.cpp move smt params to params directory, update release.yml 2025-06-09 10:47:22 -07:00
ast.cpp Centralize and document TRACE tags using X-macros (#7657) 2025-05-28 14:31:25 +01:00
bdd.cpp Additional BDD operations; BDD vectors and finite domain abstraction 2022-08-01 18:37:11 +03:00
bit_blaster.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
bit_vector.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
bits.cpp [WIP] Update code base to use std::span (#8269) 2026-02-18 20:57:51 -08:00
buffer.cpp make include paths uniformly use path relative to src. #534 2017-07-31 13:24:11 -07:00
chashtable.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
check_assumptions.cpp move smt params to params directory, update release.yml 2025-06-09 10:47:22 -07:00
CMakeLists.txt Porting seq_split to master (#9840) 2026-06-30 10:18:28 -07:00
cnf_backbones.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
cube_clause.cpp fix build of tests 2022-06-17 17:11:18 +01:00
datalog_parser.cpp move smt params to params directory, update release.yml 2025-06-09 10:47:22 -07:00
ddnf.cpp disable tracing in test code 2025-06-08 08:08:10 -07:00
deep_api_bugs.cpp fix build warnings 2026-06-22 18:20:23 -07:00
diff_logic.cpp Centralize and document TRACE tags using X-macros (#7657) 2025-05-28 14:31:25 +01:00
distribution.cpp add tests for distribution utility and fix loose ends 2023-04-13 11:19:06 -07:00
dl_context.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
dl_product_relation.cpp fix #6213 2022-07-31 18:40:59 +03:00
dl_query.cpp Adopt std::optional for try_get_value and try_get_size functions (#8268) 2026-02-18 20:57:50 -08:00
dl_relation.cpp fix: fix memory leaks detected by ASan in test code 2026-03-09 16:48:17 +00:00
dl_table.cpp ensure engine is datalog for dl_table and dl_util tests 2026-05-31 15:32:23 -07:00
dl_util.cpp add include directive 2026-06-01 11:39:18 -07:00
dlist.cpp fix build warnings 2026-06-22 18:20:23 -07:00
doc.cpp fix build warnings 2026-06-22 18:20:23 -07:00
egraph.cpp disable tracing in test code 2025-06-08 08:08:10 -07:00
escaped.cpp fix build of tests 2022-06-17 17:11:18 +01:00
euf_arith_plugin.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
euf_bv_plugin.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
ex.cpp add noexcept for signature compatibility 2024-11-04 11:13:49 -08:00
expr_rand.cpp fix a couple hundred deref-after-free bugs due to .c_str() on a temporary string 2020-07-11 20:24:45 +01:00
expr_substitution.cpp Add std::initializer_list overloads for BV and arith operations (#8467) 2026-02-18 20:58:02 -08:00
ext_numeral.cpp fixes 2017-08-27 11:01:45 -07:00
f2n.cpp fix build of tests 2022-06-17 17:11:18 +01:00
factor_rewriter.cpp fix build of tests 2022-06-17 17:11:18 +01:00
finder.cpp consolidate literals 2021-05-20 12:58:27 -07:00
finite_set.cpp Fix finite_set sort cardinality computation for finite base sorts (#7997) 2025-10-23 17:30:17 +02:00
finite_set_rewriter.cpp fix test 2025-10-15 20:55:27 +02:00
fixed_bit_vector.cpp make include paths uniformly use path relative to src. #534 2017-07-31 13:24:11 -07:00
for_each_file.cpp remove dependency on ARRAYSIZE for issue #1616 2018-08-15 22:26:14 -07:00
for_each_file.h booyah 2020-07-04 15:56:30 -07:00
fpa.cpp Fix soundness bug in fpa2bv mk_to_real: wrong exponent power for negative exponents (#9513) 2026-05-13 06:11:36 -04:00
get_consequences.cpp fix build of tests 2022-06-17 17:11:18 +01:00
get_implied_equalities.cpp tune for unit test, delay initialize re-solver 2018-05-13 11:49:33 -07:00
hashtable.cpp fix build warnings 2026-06-22 18:20:23 -07:00
heap.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
heap_trie.cpp fix build of tests 2022-06-17 17:11:18 +01:00
hilbert_basis.cpp fix build warnings 2026-06-22 18:20:23 -07:00
ho_matcher.cpp Refactor mk_and and mk_app to use std::span API (#8285) 2026-02-18 20:57:52 -08:00
horn_subsume_model_converter.cpp fixing build 2022-11-03 22:08:21 -07:00
horner.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
hwf.cpp fix #7143: type punning in test 2024-03-04 14:34:02 +00:00
im_float_config.h booyah 2020-07-04 15:56:30 -07:00
inf_rational.cpp Centralize and document TRACE tags using X-macros (#7657) 2025-05-28 14:31:25 +01:00
interval.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
karr.cpp fix build of tests 2022-06-17 17:11:18 +01:00
lcube.cpp Lcube (#9858) 2026-06-14 16:25:21 -07:00
list.cpp Centralize and document TRACE tags using X-macros (#7657) 2025-05-28 14:31:25 +01:00
main.cpp Porting seq_split to master (#9840) 2026-06-30 10:18:28 -07:00
map.cpp make include paths uniformly use path relative to src. #534 2017-07-31 13:24:11 -07:00
matcher.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
mbp_qel.cpp Cleanup thanks to Copilot (#9709) 2026-06-04 10:46:33 -07:00
memory.cpp fix build warnings 2026-06-22 18:20:23 -07:00
mod_factor.cpp Fix unsound array equality rewrite for const-array store chains (#9572) 2026-05-21 11:15:42 -07:00
model2expr.cpp fix build of tests 2022-06-17 17:11:18 +01:00
model_based_opt.cpp fix unit test 2025-02-17 20:36:38 -08:00
model_evaluator.cpp fix build warnings 2026-06-22 18:20:23 -07:00
model_retrieval.cpp move smt params to params directory, update release.yml 2025-06-09 10:47:22 -07:00
monomial_bounds.cpp Add comprehensive test coverage for math/lp and math/polynomial modules (#7877) 2025-09-14 14:57:21 -07:00
mpbq.cpp fix build of tests 2022-06-17 17:11:18 +01:00
mpf.cpp disable tracing in test code 2025-06-08 08:08:10 -07:00
mpff.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
mpfx.cpp fix build of tests 2022-06-17 17:11:18 +01:00
mpq.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
mpz.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
nla_intervals.cpp Fixes for lar_term== operator (#9284) 2026-04-12 14:31:18 -07:00
nlarith_util.cpp fix build of tests 2022-06-17 17:11:18 +01:00
nlsat.cpp Strengthen historical nlsat regression tests (#9857) 2026-06-16 11:36:13 -06:00
no_overflow.cpp Modernize C++ patterns: range-based for loops and nullptr (#8167) 2026-02-18 20:57:10 -08:00
object_allocator.cpp fix build warnings 2026-06-22 18:20:23 -07:00
old_interval.cpp fix build of tests 2022-06-17 17:11:18 +01:00
optional.cpp Adopt std::optional for try_get_value and try_get_size functions (#8268) 2026-02-18 20:57:50 -08:00
parametric_datatype.cpp fix build warnings 2026-06-22 18:20:23 -07:00
parray.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
pb2bv.cpp move smt params to params directory, update release.yml 2025-06-09 10:47:22 -07:00
pdd.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
pdd_solver.cpp Refactor mk_and/mk_or call sites to use vector overloads (#8286) 2026-02-18 20:57:52 -08:00
permutation.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
polynomial.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
polynomial_factorization.cpp Implement multivariate polynomial factorization via Hensel lifting 2026-03-24 06:25:29 -10:00
polynorm.cpp Centralize and document TRACE tags using X-macros (#7657) 2025-05-28 14:31:25 +01:00
prime_generator.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
proof_checker.cpp Improve test validation for mk_transitivity 2026-02-18 21:02:26 -08:00
psmt.cpp Fix psmt deadlock when formula is theory-incomplete (#9986) 2026-06-28 13:27:58 -06:00
qe_arith.cpp Refactor mk_and and mk_app to use std::span API (#8285) 2026-02-18 20:57:52 -08:00
quant_elim.cpp move smt params to params directory, update release.yml 2025-06-09 10:47:22 -07:00
quant_solve.cpp Refactor mk_and/mk_or call sites to use vector overloads (#8286) 2026-02-18 20:57:52 -08:00
random.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
range_predicate.cpp Derive with ranges (#9965) 2026-06-26 08:44:13 -06:00
rational.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
rcf.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
regex_range_collapse.cpp Derive with ranges (#9965) 2026-06-26 08:44:13 -06:00
region.cpp make include paths uniformly use path relative to src. #534 2017-07-31 13:24:11 -07:00
sat_local_search.cpp Modernize C++ patterns: range-based for loops and nullptr (#8167) 2026-02-18 20:57:10 -08:00
sat_lookahead.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
sat_user_scope.cpp fix build of tests 2022-06-17 17:11:18 +01:00
scoped_timer.cpp fix build of tests 2022-06-17 17:11:18 +01:00
scoped_vector.cpp add scoped_vector invariants and unit tests (#7327) 2024-08-02 19:21:40 -07:00
seq_regex_bisim.cpp Derive with ranges (#9965) 2026-06-26 08:44:13 -06:00
seq_rewriter.cpp [snapshot-regression-fix] Keep symbolic re.range non-empty; fix soundness regression on range membership (#10017) 2026-07-02 14:00:51 -07:00
seq_split.cpp Porting seq_split to master (#9840) 2026-06-30 10:18:28 -07:00
simple_parser.cpp Centralize and document TRACE tags using X-macros (#7657) 2025-05-28 14:31:25 +01:00
simplex.cpp Spacer Global Guidance (#6026) 2022-08-30 15:47:00 -07:00
simplifier.cpp Fix sat.smt=true model reconstruction for QF_UFBV with Bool-valued UF predicates (#9519) 2026-05-14 04:19:37 -04:00
sls_seq_plugin.cpp fix build warnings 2026-06-22 18:20:23 -07:00
sls_test.cpp fix build warnings 2026-06-22 18:20:23 -07:00
small_object_allocator.cpp Centralize and document TRACE tags using X-macros (#7657) 2025-05-28 14:31:25 +01:00
smt2print_parse.cpp re-enable unit tests 2026-06-02 10:39:41 -07:00
smt_context.cpp Fix qe-lite de Bruijn reindexing after bounded quantifier expansion (#9996) 2026-06-29 09:53:02 -07:00
solver_pool.cpp fix build of tests 2022-06-17 17:11:18 +01:00
sorting_network.cpp Refactor mk_and and mk_app to use std::span API (#8285) 2026-02-18 20:57:52 -08:00
stack.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
string_buffer.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
substitution.cpp move smt params to params directory, update release.yml 2025-06-09 10:47:22 -07:00
symbol.cpp make include paths uniformly use path relative to src. #534 2017-07-31 13:24:11 -07:00
symbol_table.cpp make include paths uniformly use path relative to src. #534 2017-07-31 13:24:11 -07:00
tbv.cpp Move tbv to util 2022-08-01 18:37:11 +03:00
term_enumeration.cpp Term enumeration (#9908) 2026-06-20 18:14:44 -06:00
test_util.h make include paths uniformly use path relative to src. #534 2017-07-31 13:24:11 -07:00
theory_dl.cpp fix build of tests 2022-06-17 17:11:18 +01:00
theory_pb.cpp disable tracing in test code 2025-06-08 08:08:10 -07:00
timeout.cpp make include paths uniformly use path relative to src. #534 2017-07-31 13:24:11 -07:00
total_order.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
totalizer.cpp add totalizer version of rc2 2022-06-29 23:10:42 -07:00
tptp.cpp Fix TPTP front-end precedence and Int/Real coercion bugs 2026-06-29 15:00:56 -07:00
trigo.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
udoc_relation.cpp set up udoc relation to use datalog engine 2026-06-01 19:06:25 -07:00
uint_set.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
upolynomial.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
value_generator.cpp fix build of tests 2022-06-17 17:11:18 +01:00
value_sweep.cpp fix build of tests 2022-06-17 17:11:18 +01:00
var_subst.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
vector.cpp Add vector::resize tests including vector<rational> 2026-03-11 23:54:01 +00:00
zstring.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00