3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-15 03:25:43 +00:00
z3/src/ast
Lev Nachmanson e1f99b569d
[snapshot-regression-fix] seq_rewriter: re.range with a provably-empty bound must be the empty language (#10047)
## Summary

Fixes a Z3 output regression detected by the `Z3Prover/bench`
snapshot-regression corpus.

- **Originating discussion:**
https://github.com/Z3Prover/bench/discussions/3050
- **Benchmark:** `iss-5134/small.smt2`
(`inputs/issues/iss-5134/small.smt2` in `Z3Prover/bench`)
- **Kind:** `diff` — recorded oracle vs. current nightly z3
(`z3-4.17.0-x64-glibc-2.39`)

## Divergence

The benchmark constrains a string `a` using a regex that contains
`(re.range "" <ite>)`:

```smt2
(declare-fun a () String)
(assert (str.in_re a (re.* (re.union (str.to_re "b") (str.to_re (ite
 (str.in_re a (re.* (re.range "" (ite (str.in_re a (str.to_re "")) ""
 a)))) "" "a"))))))
(assert (not (str.in_re a (re.* (str.to_re "")))))
(check-sat)
(get-model)
```

Recorded oracle (**expected**) vs. current z3 (**current**):

```diff
-sat
-(
-  (define-fun a () String
-    "a")
-)
+unknown
+(error "line 7 column 10: model is not available")
```

## Root cause

Per SMT-LIB, `re.range` over an argument that is **not a single
character** denotes the empty language, so `(re.range "" X)` is
`re.none` regardless of `X` (the lower bound `""` is the empty string).

Before the *"Derive with ranges"* refactor (#9963 / #9965),
`seq_rewriter::mk_re_range` recognised this through several emptiness
checks, including a concrete non-single-character test and a `max_length
== 0` test:

```cpp
if (str().is_string(lo, slo) && slo.length() != 1) is_empty = true;
if (max_length(lo) == std::make_pair(true, rational(0))) is_empty = true;
if (max_length(hi) == std::make_pair(true, rational(0))) is_empty = true;
```

The refactor rewrote `mk_re_range` and kept only the `min_length(..) >
1` emptiness test (a bound provably **≥ 2** characters). That misses a
bound of length **exactly 0**: an empty-string bound has `min_length ==
0`, so it is no longer detected as empty, and `mk_re_range` returns
`BR_FAILED`, leaving `(re.range "" X)` symbolic. The new range-aware
derivative engine (`seq_derive.cpp`) then produces a *stuck* derivative
for such a range (its `is_unit_string("")` test fails), so the sequence
theory can no longer decide membership and the solver answers `unknown`
/ "model is not available".

## Fix

Restore the sound emptiness check the refactor dropped — a bound whose
`max_length` is provably `0` can never be a single character, so the
range is empty:

```cpp
// A bound that is provably of length 0 (e.g. the empty string "") can
// likewise never be a single character, so the range is empty.  Unlike a
// symbolic bound, max_length == 0 is a provable emptiness fact, so this is
// sound (it is never true for a model-dependent bound such as a variable).
if (max_length(lo) == std::make_pair(true, rational(0)))
    is_empty = true;
if (max_length(hi) == std::make_pair(true, rational(0)))
    is_empty = true;
```

This does **not** reintroduce the unsoundness the refactor guarded
against: `max_length == (true, 0)` is a *provable* emptiness fact and is
never true for a model-dependent (symbolic) bound, so `(re.range x x)`
is still correctly left symbolic (it denotes `{x}` whenever `x` is a
single character).

## Validation

Built the patched `./z3` checkout (`./configure && make -C build`) and
re-ran the benchmark with the option the snapshot capture uses
(`-T:20`):

- **Before the fix:** `z3 -T:20 small.smt2` → `unknown` + `(error "...
model is not available")` — reproduces the divergence.
- **After the fix:** `z3 -T:20 small.smt2` → `sat` + `(define-fun a ()
String "a")` — **exactly matches** the recorded oracle.

Additional checks with the rebuilt binary:
- Sibling benchmarks `iss-5134/bug.smt2` and `iss-5134/small-2.smt2`
still match their oracles.
- Symbolic bound not over-collapsed: `(str.in_re "a" (re.range x x))` →
`sat` (x = "a").
- `(re.range "" "a")` is the empty language: `(str.in_re "a" (re.range
"" "a"))` and `(str.in_re "" (re.range "" "a"))` → `unsat`.
- Ordinary ranges unaffected: `"b" ∈ (re.range "a" "c")` sat, `"d" ∈
(re.range "a" "c")` unsat, `(re.range "a" "a")` singleton.




> Generated by [Fix a Z3 snapshot-regression
divergence](https://github.com/Z3Prover/bench/actions/runs/28731229299)
· 592.3 AIC · ⌖ 39.1 AIC · ⊞ 8.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:
28731229299, workflow_id: snapshot-regression-fixer, run:
https://github.com/Z3Prover/bench/actions/runs/28731229299 -->

<!-- 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>
2026-07-05 13:05:38 -07:00
..
converters Remove redundant explicit default constructors (#8470) 2026-02-18 20:58:01 -08:00
euf Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
fpa Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
macros Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
normal_forms disable test in tptp, move to native lambdas 2026-06-02 10:38:51 -07:00
pattern disable test in tptp, move to native lambdas 2026-06-02 10:38:51 -07:00
proofs Refactor mk_and/mk_or call sites to use vector overloads (#8286) 2026-02-18 20:57:52 -08:00
rewriter [snapshot-regression-fix] seq_rewriter: re.range with a provably-empty bound must be the empty language (#10047) 2026-07-05 13:05:38 -07:00
simplifiers fix build 2026-07-04 12:51:52 -07:00
sls Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
substitution replace some copies with moves 2026-02-18 21:02:17 -08:00
act_cache.cpp Fix static analysis findings: uninitialized vars, bitwise shift UB, garbage values 2026-03-02 00:13:55 +00:00
act_cache.h
arith_decl_plugin.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
arith_decl_plugin.h Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
array_decl_plugin.cpp Add SMT-LIB choice support via array OP_CHOICE and instantiate choice axioms in array solvers (#9649) 2026-05-27 10:05:06 -07:00
array_decl_plugin.h [code-simplifier] Align choice axiom naming in theory_array_full (#9660) 2026-06-01 16:03:42 -07:00
array_peq.cpp code simplification 2025-02-18 19:07:58 -08:00
array_peq.h code simplification 2025-02-18 19:07:58 -08:00
ast.cpp Fix clang warnings about casting away const. (#9933) 2026-06-23 19:57:46 -06:00
ast.h disable test in tptp, move to native lambdas 2026-06-02 10:38:51 -07:00
ast_ll_pp.cpp Fix quoting in low-level pretty printer (#9716) 2026-06-04 15:48:27 -07:00
ast_ll_pp.h remove '#include <iostream>' from headers and from unneeded places 2022-06-17 14:10:19 +01:00
ast_lt.cpp Fix static analysis issues: null dereferences, unsafe casts, branch clones, uninitialized members (#9424) 2026-04-29 13:37:11 -07:00
ast_lt.h
ast_pp.h Reapply PR #8190: Replace std::ostringstream with C++20 std::format (#8204) 2026-02-18 20:57:30 -08:00
ast_pp_dot.cpp Fix clang warnings about casting away const. (#9933) 2026-06-23 19:57:46 -06:00
ast_pp_dot.h remove '#include <iostream>' from headers and from unneeded places 2022-06-17 14:10:19 +01:00
ast_pp_util.cpp fixes to mbqi in the new core based on #6575 2023-02-10 16:56:06 -08:00
ast_pp_util.h wip - proof hints 2022-10-08 20:12:57 +02:00
ast_printer.cpp Remove empty leaf destructors. (#6211) 2022-07-30 10:07:03 +01:00
ast_printer.h Use = default for virtual constructors. 2022-08-05 18:11:46 +03:00
ast_smt2_pp.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
ast_smt2_pp.h patch definitions, add pretty print support 2025-10-13 22:39:32 +02:00
ast_smt_pp.cpp Handle choice_k in SMT pretty-printer switch to remove macOS -Wswitch warning (#9734) 2026-06-06 11:37:56 -07:00
ast_smt_pp.h
ast_trail.h remove default destructors 2024-10-02 22:20:12 +01:00
ast_translation.cpp disable test in tptp, move to native lambdas 2026-06-02 10:38:51 -07:00
ast_translation.h
ast_util.cpp Fix static analysis issues: null dereferences, unsafe casts, branch clones, uninitialized members (#9424) 2026-04-29 13:37:11 -07:00
ast_util.h Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
bv_decl_plugin.cpp recognize ubv_to_int as part of BV logic 2026-05-27 13:08:54 -07:00
bv_decl_plugin.h delete dead code 2026-02-18 21:02:30 -08:00
char_decl_plugin.cpp Typo Fixes (#6803) 2023-07-09 11:56:10 -07:00
char_decl_plugin.h Typo Fixes (#6803) 2023-07-09 11:56:10 -07:00
CMakeLists.txt Implement finite_set_decl_plugin with complete operator support and polymorphism infrastructure (#7961) 2025-10-13 10:58:53 +02:00
cost_evaluator.cpp
cost_evaluator.h
datatype_decl_plugin.cpp address #8376 2026-02-18 20:57:58 -08:00
datatype_decl_plugin.h Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
decl_collector.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
decl_collector.h fixes to mbqi in the new core based on #6575 2023-02-10 16:56:06 -08:00
display_dimacs.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
display_dimacs.h
dl_decl_plugin.cpp Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
dl_decl_plugin.h Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
expr2polynomial.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
expr2polynomial.h remove default destructors 2024-10-02 22:20:12 +01:00
expr2var.cpp Centralize and document TRACE tags using X-macros (#7657) 2025-05-28 14:31:25 +01:00
expr2var.h
expr_abstract.cpp Centralize and document TRACE tags using X-macros (#7657) 2025-05-28 14:31:25 +01:00
expr_abstract.h wip - throttle AC completion, enable congruences over bound bodies 2025-07-11 12:48:27 +02:00
expr_delta_pair.h Remove redundant default constructors when they're the only constructor (#8461) 2026-02-18 20:58:01 -08:00
expr_functors.cpp
expr_functors.h Use = default for virtual constructors. 2022-08-05 18:11:46 +03:00
expr_map.cpp
expr_map.h
expr_stat.cpp Refactor expr_stat to use structured bindings for traversal pairs (#8441) 2026-02-18 20:58:00 -08:00
expr_stat.h Remove redundant default constructors when they're the only constructor (#8461) 2026-02-18 20:58:01 -08:00
expr_substitution.cpp bugfix to elim_uncnstr to ensure nodes are created. Prepare smt_internalizer to replay unit literals 2024-12-04 15:32:15 -08:00
expr_substitution.h wip - testing solve-eqs2, added as tactic 2022-11-05 22:42:59 -07:00
finite_set_decl_plugin.cpp Code simplifications for finite set plugin 2026-02-21 02:08:04 +00:00
finite_set_decl_plugin.h Code simplifications for finite set plugin 2026-02-21 02:08:04 +00:00
for_each_ast.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
for_each_ast.h Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
for_each_expr.cpp Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
for_each_expr.h Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
format.cpp Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
format.h Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
fpa_decl_plugin.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
fpa_decl_plugin.h remove default destructors 2024-10-02 22:20:12 +01:00
func_decl_dependencies.cpp
func_decl_dependencies.h
has_free_vars.cpp fixup std-order / inv-order 2024-10-03 19:35:16 -07:00
has_free_vars.h
is_variable_test.h
justified_expr.h move from justified_expr to dependent_expr by aligning datatypes 2025-01-22 11:46:10 -08:00
macro_substitution.cpp
macro_substitution.h
num_occurs.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
num_occurs.h
occurs.cpp AIX compat (#8113) 2026-02-18 20:57:04 -08:00
occurs.h #6805 2023-07-11 09:41:29 -07:00
pb_decl_plugin.cpp
pb_decl_plugin.h Remove empty leaf destructors. (#6211) 2022-07-30 10:07:03 +01:00
polymorphism_inst.cpp Fix memory leaks in polymorphism instantiation engine 2026-07-03 16:34:57 -07:00
polymorphism_inst.h Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
polymorphism_util.cpp Fix memory leaks in polymorphism instantiation engine 2026-07-03 16:34:57 -07:00
polymorphism_util.h Implement finite_set_decl_plugin with complete operator support and polymorphism infrastructure (#7961) 2025-10-13 10:58:53 +02:00
pp.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
pp.h
pp_params.pyg print lemmas2console faster 2023-03-20 17:07:04 +01:00
quantifier_stat.cpp Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
quantifier_stat.h Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
recfun_decl_plugin.cpp fix #9234 2026-04-23 13:54:09 -07:00
recfun_decl_plugin.h Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
recurse_expr.h
recurse_expr_def.h Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
reg_decl_plugins.cpp Implement finite_set_decl_plugin with complete operator support and polymorphism infrastructure (#7961) 2025-10-13 10:58:53 +02:00
reg_decl_plugins.h
scoped_proof.h overhaul of proof format for new solver 2022-08-28 17:44:33 -07:00
seq_decl_plugin.cpp Derive with ranges (#9965) 2026-06-26 08:44:13 -06:00
seq_decl_plugin.h Derive with ranges (#9965) 2026-06-26 08:44:13 -06:00
shared_occs.cpp
shared_occs.h cleanup 2022-11-24 22:46:35 +07:00
special_relations_decl_plugin.cpp add EUF plugin framework. 2023-11-30 13:58:30 -08:00
special_relations_decl_plugin.h add EUF plugin framework. 2023-11-30 13:58:30 -08:00
static_features.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
static_features.h disable new code until pre-condition gets fixed 2022-11-30 22:29:59 -08:00
used_symbols.h Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
used_vars.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
used_vars.h
value_generator.cpp Mark override methods appropriately. (#6207) 2022-07-29 23:29:15 +02:00
value_generator.h Use = default for virtual constructors. 2022-08-05 18:11:46 +03:00
well_sorted.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-02-18 20:57:29 -08:00
well_sorted.h