3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-27 01:12:40 +00:00
Commit graph

18258 commits

Author SHA1 Message Date
dependabot[bot]
f8f763bdf1
Bump shell-quote from 1.8.4 to 1.10.0 in /src/api/js (#10193)
Bumps [shell-quote](https://github.com/ljharb/shell-quote) from 1.8.4 to
1.10.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ljharb/shell-quote/blob/main/CHANGELOG.md">shell-quote's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/ljharb/shell-quote/compare/v1.9.0...v1.10.0">v1.10.0</a>
- 2026-07-10</h2>
<h3>Merged</h3>
<ul>
<li>[New] <code>parse</code>: add opt-in <code>splitUnquoted</code>
option for shell field-splitting of unquoted expansions <a
href="https://redirect.github.com/ljharb/shell-quote/pull/1"><code>[#1](https://github.com/ljharb/shell-quote/issues/1)</code></a></li>
</ul>
<h3>Commits</h3>
<ul>
<li>[Fix] <code>parse</code>: match nested <code>${...}</code> braces so
nested parameter expansion is consumed as one substitution <a
href="c0842c8a7a"><code>c0842c8</code></a></li>
<li>[Tests] <code>parse</code>: pin single-quote literalness and
unmatched-quote handling <a
href="a0d03e35c8"><code>a0d03e3</code></a></li>
<li>[readme] remove the space in js code fences so evalmd evaluates them
<a
href="2116fa36ae"><code>2116fa3</code></a></li>
<li>[Tests] <code>quote</code>: pin conservative escaping of
<code>=</code>, <code>@</code>, <code>^</code>, <code>,</code>,
<code>:</code>, <code>!</code> (<a
href="https://redirect.github.com/ljharb/shell-quote/issues/11">#11</a>)
<a
href="1c36f3ff77"><code>1c36f3f</code></a></li>
<li>[readme] document that <code>quote</code> outputs POSIX quoting, not
<code>cmd.exe</code>/PowerShell <a
href="100e96e0ff"><code>100e96e</code></a></li>
<li>[readme] document <code>parse</code>'s supported parameter-expansion
subset <a
href="e1c75cd6e4"><code>e1c75cd</code></a></li>
<li>[Fix] <code>parse</code>: a backslash inside single quotes must not
escape the closing quote <a
href="5d460a332b"><code>5d460a3</code></a></li>
<li>[readme] fix stale example outputs <a
href="2de86f5d44"><code>2de86f5</code></a></li>
<li>[Tests] <code>quote</code>: pin that a backslash with whitespace is
not doubled in single quotes (<a
href="https://redirect.github.com/ljharb/shell-quote/issues/14">#14</a>)
<a
href="190e236bcf"><code>190e236</code></a></li>
<li>[readme] <code>quote</code>: use output verbatim; do not re-quote it
(<a
href="https://redirect.github.com/ljharb/shell-quote/issues/11">#11</a>)
<a
href="1b364683b1"><code>1b36468</code></a></li>
<li>[Refactor] <code>parse</code>: fix swapped
<code>SINGLE_QUOTE</code>/<code>DOUBLE_QUOTE</code> variable names <a
href="801af5c935"><code>801af5c</code></a></li>
<li>[types] fix an error TS v6 ignores but v7 fails on <a
href="59bbf8b81b"><code>59bbf8b</code></a></li>
<li>[Dev Deps] update <code>@arethetypeswrong/cli</code>,
<code>evalmd</code> <a
href="a04d47516e"><code>a04d475</code></a></li>
<li>[Dev Deps] update <code>@arethetypeswrong/ci</code>,
<code>eslint</code> <a
href="d390f9a92b"><code>d390f9a</code></a></li>
<li>[Tests] <code>quote</code>: the tilde test escapes every
<code>~</code>, not just a leading one (<a
href="https://redirect.github.com/ljharb/shell-quote/issues/9">#9</a>)
<a
href="617d119795"><code>617d119</code></a></li>
</ul>
<h2><a
href="https://github.com/ljharb/shell-quote/compare/v1.8.4...v1.9.0">v1.9.0</a>
- 2026-06-24</h2>
<h3>Commits</h3>
<ul>
<li>[New] add types <a
href="dca6e21a02"><code>dca6e21</code></a></li>
<li>[Dev Deps] update <code>eslint</code> <a
href="9aa9e8f609"><code>9aa9e8f</code></a></li>
<li>[Fix] <code>parse</code>: finalize tokens in linear time
(GHSA-395f-4hp3-45gv) <a
href="7ff5488599"><code>7ff5488</code></a></li>
<li>[actions] update workflows <a
href="75e849741f"><code>75e8497</code></a></li>
<li>[actions] Windows + node 4/6/7: pin eslint to 9 before install,
since npm 2/3 cannot stage eslint 10<code>@types/esrecurse</code> <a
href="3fb739de44"><code>3fb739d</code></a></li>
<li>[actions] retry <code>npm install</code> on Windows to survive npm
2/3 staging-rename flake <a
href="abe0163293"><code>abe0163</code></a></li>
<li>[actions] Windows + node 5/7: install deps with a modern node <a
href="b4bafa2e7e"><code>b4bafa2</code></a></li>
<li>[Fix] <code>quote</code>: escape leading <code>~</code> to prevent
shell tilde-expansion <a
href="7a76c1a12d"><code>7a76c1a</code></a></li>
<li>[Dev Deps] update <code>auto-changelog</code>, <code>tape</code> <a
href="7184b4458b"><code>7184b44</code></a></li>
<li>[Dev Deps] apparently <code>jackspeak</code> is no longer in the
graph <a
href="9ba368a405"><code>9ba368a</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="64988d9a0e"><code>64988d9</code></a>
v1.10.0</li>
<li><a
href="617d119795"><code>617d119</code></a>
[Tests] <code>quote</code>: the tilde test escapes every <code>~</code>,
not just a leading one (<a
href="https://redirect.github.com/ljharb/shell-quote/issues/9">#9</a>)</li>
<li><a
href="59bbf8b81b"><code>59bbf8b</code></a>
[types] fix an error TS v6 ignores but v7 fails on</li>
<li><a
href="190e236bcf"><code>190e236</code></a>
[Tests] <code>quote</code>: pin that a backslash with whitespace is not
doubled in singl...</li>
<li><a
href="a04d47516e"><code>a04d475</code></a>
[Dev Deps] update <code>@arethetypeswrong/cli</code>,
<code>evalmd</code></li>
<li><a
href="b9545b39f4"><code>b9545b3</code></a>
[New] <code>parse</code>: add opt-in <code>splitUnquoted</code> option
for shell field-splitting of...</li>
<li><a
href="1b364683b1"><code>1b36468</code></a>
[readme] <code>quote</code>: use output verbatim; do not re-quote it (<a
href="https://redirect.github.com/ljharb/shell-quote/issues/11">#11</a>)</li>
<li><a
href="1c36f3ff77"><code>1c36f3f</code></a>
[Tests] <code>quote</code>: pin conservative escaping of <code>=</code>,
<code>@</code>, <code>^</code>, <code>,</code>, <code>:</code>,
<code>!</code> (<a
href="https://redirect.github.com/ljharb/shell-quote/issues/11">#11</a>)</li>
<li><a
href="e1c75cd6e4"><code>e1c75cd</code></a>
[readme] document <code>parse</code>'s supported parameter-expansion
subset</li>
<li><a
href="c0842c8a7a"><code>c0842c8</code></a>
[Fix] <code>parse</code>: match nested <code>${...}</code> braces so
nested parameter expansion is ...</li>
<li>Additional commits viewable in <a
href="https://github.com/ljharb/shell-quote/compare/v1.8.4...v1.10.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=shell-quote&package-manager=npm_and_yarn&previous-version=1.8.4&new-version=1.10.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/Z3Prover/z3/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-22 18:00:43 -07:00
dependabot[bot]
5055183037
Bump linkify-it from 5.0.1 to 5.0.2 in /src/api/js (#10194)
Bumps [linkify-it](https://github.com/markdown-it/linkify-it) from 5.0.1
to 5.0.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/markdown-it/linkify-it/blob/master/CHANGELOG.md">linkify-it's
changelog</a>.</em></p>
<blockquote>
<h2>5.0.2 / 2026-07-02</h2>
<ul>
<li>Fixed DoS in <code>mailto:</code> links (restrict user name to 64
chars).</li>
<li>Restricted user/pass part length in links.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="50a0c914f8"><code>50a0c91</code></a>
5.0.2 released</li>
<li><a
href="de3b88554b"><code>de3b885</code></a>
Update package hooks</li>
<li><a
href="13effaaa46"><code>13effaa</code></a>
Add package lock</li>
<li><a
href="39d748dbfc"><code>39d748d</code></a>
Bump c8</li>
<li><a
href="00ce8771ac"><code>00ce877</code></a>
Drop tlds deps</li>
<li><a
href="ecde82341a"><code>ecde823</code></a>
Update benchmark to mitata</li>
<li><a
href="23c62cdd14"><code>23c62cd</code></a>
Refactor demo / doc build and publish</li>
<li><a
href="fd63f3b4ab"><code>fd63f3b</code></a>
CI config update</li>
<li><a
href="f4ea5afaa6"><code>f4ea5af</code></a>
demo: update bootstrap &amp; layout</li>
<li><a
href="1454fb645f"><code>1454fb6</code></a>
lint: dim warnings</li>
<li>Additional commits viewable in <a
href="https://github.com/markdown-it/linkify-it/compare/5.0.1...5.0.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=linkify-it&package-manager=npm_and_yarn&previous-version=5.0.1&new-version=5.0.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/Z3Prover/z3/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-22 18:00:27 -07:00
Daniel Tang
d91bb5b5fa
Fix some lost Solver.solutions(t) (#10195)
z3-rs correctly used Or. I accidentally forgot that enclosing function
call, and had what resulted in And.

Test case:

```python
s = Solver()
x, y, z = Ints("x y z")
s.add(x >= 0, x <= 2, y >= 0, y <= 2, z >= 0, z <= 2, x + y + z == 2)
# I didn't test multivariable constraints last time fearing nondeterminism
# Sorting avoids that
print(sorted(map(lambda x: list(map(lambda x: x.as_long(), x)), s.solutions([x, y, z]))))
```

**Before**: `[[0, 1, 1], [2, 0, 0]]`
**After**: `[[0, 0, 2], [0, 1, 1], [0, 2, 0], [1, 0, 1], [1, 1, 0], [2,
0, 0]]`

Fixes: #8633

---------

Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-22 18:00:06 -07:00
Copilot
847ee63b2d
Fix invalid model from int_to_bv missing modular reduction in bv2int_translator (#10185)
With `smt.bv.solver=2`, `int_to_bv(x)` was translated to the raw integer
`x` without normalizing it to `[0, 2^N)`. Bitwise operations like
`bvxor(A, B)` are translated as `A + B - 2·band(A, B)`, which is only
valid when both operands are in `[0, 2^N)`. When `x` was negative or ≥
2^N, the LP solver could assign a band value inconsistent with bit
semantics, producing a model that fails validation.

## Changes

- **`OP_INT2BV` translation** (`translate_bv`): Apply `umod(e, 0)`
instead of passing the raw integer argument through. This normalizes the
value to `[0, 2^N)` before it participates in any bitwise arithmetic.

  ```cpp
  // Before
  case OP_INT2BV:
      r = arg(0);  // raw integer, may be negative or ≥ 2^N

  // After
  case OP_INT2BV:
      r = umod(e, 0);  // normalized to [0, 2^N)
  ```

- **`amod` shortcut**: Add a fast-path for `mod(t, N)` when the divisor
already equals `N` — the result is already in `[0, N)`, so wrapping it
again with another `mod(_, N)` is unnecessary and avoids expression
bloat.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-22 10:34:23 -07:00
Copilot
9ac438b199
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>
2026-07-22 10:30:45 -07:00
davedets
e2df18faaf
Test PR for disabling semicolon warnings (#10169)
This is another PR towards the goal of getting Z3 to compile cleanly
when included via FetchContents into clang-tidy, which uses a pretty
strict set of warnings.

https://github.com/Z3Prover/z3/pull/10020 deleted a number of
unnecessary (and, by a strict interpretation, illegal) semicolons. These
were detected by adding -Wextra-semi to CLANG_ONLY_WARNINGS during
testing. The PR did not eliminate all such unnecessary/illegal
semi-colons; Nikolaj Bjorner argued that some IDEs would be confused by
doing so for macro invocations, which otherwise look like function
calls. So it left those, and did not include adding -Wextra-semi to
CLANG_ONLY_WARNINGS in the PR.

I'm worried that not having -Wextra-semi will allow instances of the
semis we'd like to eliminate to creep back in. This PR introduces a
mechanism to disable the warnings for regions with such macro
invocations, and uses that mechanism for one file. If accepted, a
subsequent PR would use this mechanism in all the remaining places, so
that there is a clean clang build with -Wextra-semi.

I verified that:
* It compiles cleanly for if the enable/disable macros have null
definitions, as they would for non-clang compilations.
* It compiles cleanly for a clang build without -Wextra-semis.
* It compiles successfully, albeit with *many* warnings, for a clang
build with-Wextra-semis -- but none of those errors are for the regions
in ast.h where the warning is disabled.
2026-07-22 10:28:13 -07:00
Copilot
ae190b8b88
fix: parallel mode exits unknown immediately for QF_BV due to reason-string mismatch (#10183)
Under `parallel.enable=true`, QF_BV workers that exhaust their per-cube
conflict budget (1000) are misclassified as unrecoverably incomplete,
causing the portfolio to return `unknown` in ~1 second instead of
escalating the budget and continuing.

## Root cause

`parallel_tactical.cpp` only recognizes `"max-conflicts-reached"` (the
`smt::context` spelling) as a signal to escalate the conflict budget.
SAT-core-backed solvers — which QF_BV workers use — report the same
condition as `"sat.max.conflicts"` (from
`sat_solver::reached_max_conflicts()`). The mismatch causes every QF_BV
cube attempt to fall through to `b.set_unknown()` instead of
`update_max_thread_conflicts()`.

## Fix

```diff
- if (reason != "max-conflicts-reached") {
+ if (reason != "max-conflicts-reached" && reason != "sat.max.conflicts") {
```

Both spellings now correctly route to the budget-escalation path. The
parallel `smt_parallel.cpp` path is unaffected — it owns `smt::context`
workers, which can only produce `"max-conflicts-reached"`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-21 19:48:55 -07:00
Copilot
35f6b0869a
parallel solver: suppress bare reason_unknown on stderr at default verbosity (#10182)
`IF_VERBOSE(0, ...)` in the parallel tactic's `l_undef` handler caused
the raw reason string (e.g. `sat.max.conflicts`) to be written
unconditionally to stderr, polluting output for any application
embedding libz3 that hits the conflict-budget give-up path.

## Change

- **`src/solver/parallel_tactical.cpp:2186`** — raise verbosity
threshold from `0` to `1`:

```cpp
// Before: fires at default verbosity, writes bare string to stderr
IF_VERBOSE(0, verbose_stream() << reason << "\n");

// After: only fires under -v:1
IF_VERBOSE(1, verbose_stream() << reason << "\n");
```

The reason string remains fully accessible via `(get-info
:reason-unknown)` and `set_reason_unknown` regardless of verbosity.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-21 19:48:07 -07:00
1sgtpepper
479ff3476e
Fix polymorphic application arity validation (#10179)
## Summary

Validate a polymorphic declaration's arity before matching argument
sorts. This prevents
`Z3_mk_app` from reading past the declaration domain and makes both
too-few and too-many
arguments return `Z3_INVALID_ARG`.

Adds C API regression coverage for valid, too-few, and too-many
applications.

Fixes #10177.

## Testing

- `./build-release/test-z3 api`
- `./build-release/test-z3 /a` (92 passed)
- Standalone #10177 reproducer against the patched shared library
2026-07-21 19:11:38 -07:00
Michael Tautschnig
0105b220fd
Make string_hash independent of char signedness (#10163)
`string_hash`'s tail-byte handling reads bytes through plain `char`,
whose signedness is implementation-defined. On signed-char platforms
(x86_64 Linux, macOS) bytes ≥ `0x80` are sign-extended before entering
the hash state (e.g. `c+=((unsigned)data[10]<<24);` becomes `c +=
0xFF80xxxx` instead of `c += 0x0080xxxx`); on unsigned-char platforms
(Linux aarch64) they are not. Hashes of byte strings containing such
bytes therefore differ across platforms.

This is not just cosmetic: `mpz_manager::hash` feeds the digit arrays of
large numerals through `string_hash`, so AST hashes of bit-vector
constants like `(_ bv36028797018963968 64)` (= 2^55, whose digit bytes
include `0x80`) differ between architectures. AST hashes determine hash
table layouts throughout the solver, so preprocessing and search take
platform-dependent paths for byte-identical input.

Observed impact (Z3 4.15.3 release binaries as well as local gcc-13
builds, identical `.smt2` input generated by CBMC from the
[mldsa-native](https://github.com/pq-code-package/mldsa-native)
verification suite — quantifiers + arrays + bit-vectors, ~120k lines):

| instance | x86_64 | aarch64 |
|---|---|---|
| instance A | 643 s | 11.7 s |
| instance B | 22.8 s | 1578 s |

Tracing AST construction on both hosts showed the first divergence at
the registration of the numeral `2^55`, whose node hash was `2587296535`
on x86_64 vs `808470355` on aarch64, with identical mpz digit arrays;
from that point on, hash table iteration orders (and consequently
`ctx-simplify` steps, quantifier instantiation order, and case-split
order) diverge. With this patch, instance A solves in ~21 s on x86_64
(down from 643 s), matching the aarch64 behaviour class.

The fix casts the tail bytes to `unsigned char`, matching the
4-byte-chunk path (which is already signedness-independent via `memcpy`
into `unsigned`). Note that hash values on signed-char platforms change
for inputs containing bytes ≥ `0x80` (pure-ASCII symbol names are
unaffected).

(Found while investigating cross-platform proof-time instability
reported in diffblue/cbmc#8991.)

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
2026-07-21 19:03:48 -07:00
Michael Tautschnig
09aaadf963
Sequence AST-creating arguments in rewriters for cross-compiler determinism (#10165)
### Problem

The order of evaluation of function arguments is unspecified in C++
(arguments are indeterminately sequenced since C++17). Compilers use
this freedom differently:

```c++
static int f(int i) { printf("%d ", i); return i; }
static void g(int, int, int) { printf("\n"); }
int main() { g(f(1), f(2), f(3)); }
```
| compiler/target | output |
|---|---|
| gcc 13, x86_64 | `3 2 1` |
| gcc 13, aarch64 | `1 2 3` |
| clang 18, x86_64 | `1 2 3` |

Z3 has many call sites where **two or more arguments each create AST
nodes**, e.g. (before this PR, `bv_rewriter.cpp:876`):

```c++
result = m.mk_ite(c, m_mk_extract(high, low, t), m_mk_extract(high, low, e));
```

The two extract nodes are hash-consed and receive their AST ids in
evaluation order, so the id assignment differs between
compilers/targets. AST ids feed heuristic tie-breaking throughout the
solver (`bool_rewriter`'s `m_order_eq` equality-operand ordering,
id-based sorts in `array_rewriter`, case-split ordering, ...), so
**byte-identical input takes different solver paths depending on the
compiler and architecture z3 was built with**.

### Evidence

Investigated while chasing cross-platform proof-time instability in
CBMC/mldsa-native CI (diffblue/cbmc#8991), on byte-identical ~12 MB SMT2
instances (bit-vectors + arrays + quantifiers), with the `string_hash`
fix from #10163 applied to isolate this effect. Z3 4.15.3, gcc 13 on
x86_64 Linux and aarch64 Linux (Graviton):

* one instance: **17 s on x86_64 vs 1633 s on aarch64** (both `unsat`; a
sibling instance shows the reverse direction). Run-to-run within one
host: ±1 %.
* Instrumenting `ast_manager::register_node_core` with an order
fingerprint (running hash over `(node hash, node id)`) shows both
architectures construct **identical AST sequences up to registration
#41,789**, where x86_64 creates `(extract[0:0] #xFFFFFFFF)` before
`(extract[0:0] #xFFFFFFFE)` and aarch64 the other way around — from
identical call stacks at the `mk_ite`-over-two-`mk_extract` site quoted
above. All divergence between the two hosts flows from such events
(pointer/ASLR effects experimentally excluded: fingerprints are
invariant under `setarch -R` and across repeated runs).
* Sequencing that one site by hand moved the first divergence to
#248,118 — the analogous `mk_ite(c, mk_select(...), mk_select(...))`
site in `array_rewriter.cpp`. Sequencing that one, too, moved it to
#248,411, inside `nnf:👿:process_iff_xor` — i.e. the next layer of
the same onion.
* With the whole `ast/rewriter` layer swept (this PR), the instrumented
builds produce **identical AST construction traces on both architectures
throughout the entire rewriter phase** of this 546k-line industrial
instance; the first divergence left is the NNF one.

### Fix

Following the precedent of 37904b9e8, e113d39aa, 360193098, 93ff8c76d,
9b88aaf13 ("parameter evaluation order", `bool_rewriter`/`seq_rewriter`)
and the existing comments in `seq_rewriter.cpp` ("introduce temporaries
to ensure deterministic evaluation order..."), this PR hoists
AST-creating arguments into named temporaries with a defined evaluation
order, across `src/ast/rewriter/` — 126 call sites in 17 files. The
transformation is purely sequencing: it selects one of the two valid C++
evaluation orders and makes it the same everywhere. (Temporaries are raw
pointers in rewriter-local scope, matching the precedent commits;
nothing can trigger GC between creation and consumption.)

The sites were found with a small AST-argument scanner (statement-level
call sites whose argument list contains ≥ 2 top-level arguments that
each contain an AST-creating call); I am happy to share/contribute the
script. Known remaining work, deliberately out of scope here to keep the
diff reviewable:

* 41 sites in `src/ast/rewriter/` that need manual treatment (inside
`if` conditions, ternaries, or multi-statement expressions) — list
available on request;
* `src/ast/normal_forms/nnf.cpp` (`process_iff_xor`, proven divergent by
the trace above), ~7 sites in `src/ast/simplifiers/`, ~3 in
`src/ast/converters/`, ~9 in `src/ast/`;
* other theory/solver layers (`src/smt/`, `src/sat/`, ...) — divergences
there only matter after search starts, where paths have usually already
split, but a full sweep would be needed for bit-reproducibility across
compilers.

Together with #10163, this is a step towards z3 builds whose behaviour
does not depend on the compiler or target architecture — which matters
for verification CI that runs identical proofs on heterogeneous
platforms and expects comparable runtimes.

---------

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
2026-07-21 19:03:02 -07:00
z3prover-ci-bot[bot]
7f7f8ae59b
[snapshot-regression-fix] fpa: fp.to_bv of large-exponent values should be unspecified, not unknown (#10174)
## Summary

Fixes a completeness regression where a **satisfiable** floating-point
problem is answered `unknown`.

- **Originating discussion:**
https://github.com/Z3Prover/bench/discussions/3232
- **Benchmark:** `iss-3199/bug-1.smt2` (from
https://github.com/Z3Prover/z3/issues/3199)

### Divergence

```diff
--- bug-1.expected.out (expected)
+++ produced (current z3)
@@ -1 +1 @@
-sat
+unknown
```

The benchmark:

```smt2
(declare-fun x () (_ FloatingPoint 40 60))
(declare-fun y () (_ BitVec 8))
(assert (= ((_ fp.to_ubv 8) RTP x) y #x00))
(check-sat)
```

`(get-info :reason-unknown)` reports `"exponents over 31 bits are not
supported"`.

### Root cause

The problem is clearly `sat` (e.g. `x = 0.0`, `y = #x00`; and for any
out-of-range `x`, `fp.to_ubv` is unspecified so `= #x00` is
satisfiable). z3 bit-blasts and finds a model that assigns `x` a value
with a very large binary exponent (the `FloatingPoint 40 60` sort has a
40-bit exponent field). During model reconstruction,
`fpa_util::is_considered_uninterpreted` performs a range check by
calling `mpf_manager::to_sbv_mpq`, which **throws** `"exponents over 31
bits are not supported"` when the unpacked exponent does not fit into an
`int` (guard added in `eacde16b` for #3199). The tactic framework
catches that exception and turns the whole result into `unknown`,
instead of treating the conversion as out-of-range/unspecified.

### Fix

A finite FP value whose (unbiased) binary exponent is at least the
target bitwidth `bv_sz` has magnitude `>= 2^bv_sz` and therefore cannot
fit into a `bv_sz`-bit signed or unsigned integer — the conversion is
unspecified. Short-circuit this case using the exponent, before reaching
`to_sbv_mpq`, in the two range-check paths:

- `src/ast/fpa_decl_plugin.cpp`
(`fpa_util::is_considered_uninterpreted`) — return `true` (considered
uninterpreted / out of range).
- `src/ast/rewriter/fpa_rewriter.cpp` (`fpa_rewriter::mk_to_bv`) —
return the unspecified result.

This is consistent with the existing precise logic for every exponent it
covers (those values are already classified out-of-range), and it avoids
materializing an astronomically large integer. It does not affect
in-range or boundary conversions.

### Validation

Built z3 from this checkout (Python/`make` build) and re-ran the
benchmark:

- `z3 -T:20 inputs/issues/iss-3199/bug-1.smt2` now prints `sat`,
matching the recorded `bug-1.expected.out` oracle.

Additional manual sanity checks (all correct, no regression):

- `fp.to_ubv 8` of `5.0` -> `#x05`
- `fp.to_sbv 8` of `-5.0` -> `#xfb` (-5)
- `fp.to_sbv 8` of `128.0` -> unspecified (`sat`, out of range)
- `fp.to_sbv 8` of `-128.0` -> `#x80` (-128, in-range boundary
preserved)

Opened as a **draft** for human review.




> [!WARNING]
> <details>
> <summary>Firewall blocked 1 domain</summary>
>
> The following domain was blocked by the firewall during workflow
execution:
>
> - `pypi.org`
>> To allow these domains, add them to the `network.allowed` list in
your workflow frontmatter:
>
> ```yaml
> network:
>   allowed:
>     - defaults
>     - "pypi.org"
> ```
>
> See [Network
Configuration](https://github.github.com/gh-aw/reference/network/) for
more information.
>
> </details>


> Generated by [Fix a Z3 snapshot-regression
divergence](https://github.com/Z3Prover/bench/actions/runs/29804584327)
· 314.9 AIC · ⌖ 20.3 AIC · ⊞ 10.7K ·
[◷](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.65, model: claude-opus-4.8, id:
29804584327, workflow_id: snapshot-regression-fixer, run:
https://github.com/Z3Prover/bench/actions/runs/29804584327 -->

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

Co-authored-by: z3prover-ci-bot[bot] <305651407+z3prover-ci-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-21 18:55:46 -07:00
Nikolaj Bjorner
c9a4a5907d tptp: set weight 1 on parsed quantifiers
Route all forall/exists creation in the TPTP frontend through a
with_weight1() helper so quantifiers use weight 1 instead of the
default 0. Improves smt.ho_matching on higher-order TPTP problems
(+26 net solved, fewer timeouts over the ^ benchmark set).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 96a14756-2ffe-4cc3-87e7-49fda1b6113a
2026-07-18 11:28:41 -07:00
Copilot
1f306e1e29
Fix Pyodide wheel packaging for emscripten executable name variants (#10158)
`build-pyodide` failed because Python packaging assumed the wasm shell
artifact is always emitted as `build/z3.wasm`. In the failing run, the
expected file was absent, causing wheel build to abort during binary
copy.

- **Root cause handling in `setup.py`**
- In the emscripten path, add executable fallbacks (`z3.js.wasm`, `z3`)
alongside the canonical `z3.wasm`.
- During packaging, probe known output names in order and copy the first
match.

- **Stable wheel layout**
- Keep packaged artifact name stable as `bin/z3.wasm` regardless of
which build output variant exists.

- **Failure diagnostics**
- If no candidate exists, raise a `FileNotFoundError` listing all
attempted paths to make CI failures immediately actionable.

```python
executable_names = (EXECUTABLE_FILE,) + tuple(EXECUTABLE_FILE_FALLBACKS)
for executable_name in executable_names:
    candidate = os.path.join(BUILD_DIR, executable_name)
    if os.path.exists(candidate):
        shutil.copy(candidate, os.path.join(BINS_DIR, EXECUTABLE_FILE))
        break
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-17 19:55:23 -07:00
Copilot
63730fefaf
Fix swapped assert_expr arguments in smt_solver::translate for named assertions (#10135)
With `parallel.enable=true`, Z3 could return a SAT model for a QF_BV
instance that violates its own assertions. The bug traces to solver
translation: named assertions were re-registered with swapped
formula/indicator arguments, corrupting the translated solver's
assertion state.

## Bug

`m_name2assertion` stores `indicator → formula`. In
`smt_solver::translate()`, the structured binding `[k, v]` gives `k =
indicator`, `v = formula`, but `assert_expr(t, a)` expects `(formula,
indicator)`:

```cpp
// Before — args reversed
for (auto& [k, v] : m_name2assertion) {
    expr* val = translator(k);  // indicator
    expr* key = translator(v);  // formula
    result->assert_expr(val, key);  // assert_expr(indicator, formula) ← wrong
}
```

## Fix

```cpp
// After — correct order
for (auto& [k, v] : m_name2assertion) {
    expr* fml = translator(v);  // formula
    expr* ind = translator(k);  // indicator
    result->assert_expr(fml, ind);  // assert_expr(formula, indicator) ✓
}
```

This affects any code path that calls `smt_solver::translate()` and uses
named assertions (`assert_and_track` / `Z3_solver_assert_and_track`),
including all parallel solving modes.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-16 13:46:19 -07:00
Nikolaj Bjorner
2a3b64a7f9 fix #10137
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-16 13:44:38 -07:00
Nikolaj Bjorner
88448d4afd
Fix unsound model from parallel QF_BV solving (#10133) (#10142)
## Problem

Fixes #10133. For `QF_BV` with `parallel.enable=true`, the solver could
return `sat` with a model that violates its own assertions.

`mk_qfbv_tactic` routes SAT solving to `mk_psat_tactic`, which built the
solver via `mk_inc_sat_solver(m, p, false)` (non-incremental). The
parallel cube-and-conquer engine **reuses this single solver** across
many `check_sat(cube)` calls, but SAT variable/blocked-clause
elimination ran because the simplifier's incremental gate was disabled.
Elimination model reconstruction is only sound for a single one-shot
solve; under repeated cube assumptions it produces models where
eliminated Tseitin variables get values contradicting the original
clauses.

## Root cause (two coupled defects)

1. **Stale simplifier cache.** `inc_sat_solver`'s constructor called
`m_solver.set_incremental()` *after* `updt_params()`. The SAT simplifier
caches `m_incremental_mode` from the SAT config *during* `updt_params`,
so setting the incremental flag afterwards left a stale non-incremental
mode and elimination stayed enabled. (This is why simply passing
`incremental_mode=true` had no effect on its own.)
2. **Non-incremental parallel solver.** `mk_psat_tactic` created the
reused solver as non-incremental.

## Fix

- Move `set_incremental()` **before** `updt_params()` in the
`inc_sat_solver` constructor so the simplifier caches the correct mode.
- Create the parallel solver with `incremental_mode=true` in
`mk_psat_tactic`.

## Validation

- Variable elimination on the QF_BV parallel path drops to zero
(`sat-elim-bool-vars-res` 21664 -> 0); remaining `elim-clauses/literals`
are sound cleaner/subsumption steps.
- Consistent with ground truth: on 4.16.0, `sat.elim_vars=false` removed
all 7 `failed to verify` errors; this change disables exactly that
unsound elimination, scoped to the reused/parallel solver.
- A solvable QF_BV instance returns a **valid** model under
`parallel.enable=true model_validate=true`.
- All 92 unit tests pass (`test-z3 /a`).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-16 11:47:07 -07:00
z3prover-ci-bot[bot]
396147851e
[fixer-selftest] Fix typo in euf_ac_plugin.cpp comment: "betwen" → "between" (#10141)
## Automated workflow self-test

This PR is an **automated workflow self-test** of the `fixer-selftest` /
`snapshot-regression-fixer` pipeline running on the self-hosted
`rise-runner-1` pool. Its purpose is to verify that Copilot inference
runs and that the `create-pull-request` safe output can open a real
**draft** pull request on `Z3Prover/z3`.

### The fix

- **File:** `src/ast/euf/euf_ac_plugin.cpp` (line 995)
- **Before:** `// add difference betwen dst.l and src.l to both src.l,
src.r`
- **After:** `// add difference between dst.l and src.l to both src.l,
src.r`

A single spelling mistake (`betwen` → `between`) in a `//` code comment.

### Why no rebuild is needed

The change is **comment-only** — no code, string literals, identifiers,
or build files were touched — so it cannot affect z3's behaviour and
requires no compilation or testing.

### For maintainers

This is a genuine, correct fix, so feel free to **merge** it. Equally,
you may simply **close** it — the success of the self-test does not
depend on this PR being merged.




> Generated by [Self-test the agentic PR pipeline with a tiny z3 comment
fix](https://github.com/Z3Prover/bench/actions/runs/29523479037) · 54.5
AIC · ⌖ 18.9 AIC · ⊞ 8K ·
[◷](https://github.com/search?q=repo%3AZ3Prover%2Fz3+%22gh-aw-workflow-id%3A+fixer-selftest%22&type=pullrequests)

<!-- gh-aw-agentic-workflow: Self-test the agentic PR pipeline with a
tiny z3 comment fix, engine: copilot, version: 1.0.65, model:
claude-opus-4.8, id: 29523479037, workflow_id: fixer-selftest, run:
https://github.com/Z3Prover/bench/actions/runs/29523479037 -->

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

Co-authored-by: z3prover-ci-bot[bot] <305651407+z3prover-ci-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-16 11:33:30 -07:00
Nikolaj Bjorner
436237bfbb fixup unit tests
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-16 09:35:18 -07:00
Nikolaj Bjorner
cb51821c25 disabling newly failing tests
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-16 09:00:50 -07:00
Nikolaj Bjorner
694a72c785 push quantifiers
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-16 08:38:28 -07:00
Lev Nachmanson
6d4f9b5cd5
Recognize rational roots in closest-root isolation (#10132) 2026-07-16 06:56:38 -07:00
Nikolaj Bjorner
ca2ed44951 disable instantiation for inconsistent states
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-15 20:55:11 -07:00
Nikolaj Bjorner
09ffec52e8 disable instantiation for inconsistent states
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-15 20:54:23 -07:00
Nikolaj Bjorner
9945e3dc9a add Margus' unfold-fold operation and consolidate range-predicate recognizer/constructor.
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-15 20:44:46 -07:00
Nikolaj Bjorner
2db625606d fold functionality into seq_range_collapse
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-15 20:04:48 -07:00
Nikolaj Bjorner
9fb2b491d6 remove relvancy marking code
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-15 15:17:14 -07:00
Nikolaj Bjorner
661bb13039 change relevancy marking to top-level on inconsistent states
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-15 11:29:20 -07:00
Nikolaj Bjorner
ad063580dc theory_lra: eagerly propagate offset equalities x=y (fixes #10065)
When a term column x - y is fixed to 0 (e.g. from t <= ca and t >= ca),
theory_lra previously discovered the implied equality x = y only lazily via
assume_eqs() during final_check. On the FP fuel-recursive axiom in issue #10065
this discovery is starved by E-matching, which unfolds the recursion and
bit-blasts an exploding FP subproblem before the branch closes.

Add propagate_offset_eq() to detect a fixed 2-variable offset term with opposite
unit-scaled coefficients and propagate the operand equality x = y directly to the
core, so congruence closure merges dependent terms immediately. This mirrors the
offset-row propagation performed by theory_arith (propagate_cheap_eq) and matches
its behavior on this benchmark (timeout -> unsat 0.06s, 2 quant-instantiations).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 726c4e71-03ff-45f6-8322-5253254e1d7e
2026-07-14 22:46:06 -07:00
Nikolaj Bjorner
7c8c6a4df0 fixup pattern inference 2026-07-14 21:01:15 -07:00
Lev Nachmanson
1b39b0e50f
Fix SIGSEGV in nlsat from incorrect algebraic number comparison (#10129)
## Problem

A QF_NIA benchmark (`From_T2__ex16.t2__p22243_terminationG_0.smt2`, run
with `-T:200 model_validate=true`) crashes with SIGSEGV inside nlsat.

## Root cause

In `algebraic_numbers::manager:👿:compare_core`, the
interval-separation workaround computed the isolating intervals of `a`
and `b` with:

```cpp
if (get_interval(a, la, ua, precision) &&
    get_interval(b, lb, ub, precision)) { ... }
```

`&&` short-circuits: when `a` is **rational**, `get_interval(a, ...)`
finds the exact root and returns `false`, so `get_interval(b, ...)`
never runs and `b`'s bounds `lb`/`ub` stay **0**. Those bounds are used
*unconditionally* below the `if` (in the `compare(cell_a, u_b)` /
`compare(cell_b, l_a)` checks), so `a` was effectively compared against
`0`, producing an incorrect and self-inconsistent sign (`compare`
returned `+1` while `<`, `=`, `>` were all false).

Concretely, comparing `c = 39017/131072` (rational) with `d ≈
0.297676176` (root of a quadratic) returned `c > d`, though `c < d`.
Downstream, this made nlsat's `interval_set::is_full` miss full coverage
of ℝ, so `pick_in_complement` was invoked on an empty complement and
read `s->m_intervals[UINT_MAX]` — a crash guarded only by a
release-stripped `SASSERT` (`nlsat_interval_set.cpp`).

## Fix

Compute both intervals unconditionally so `b`'s bounds are always valid
before they are used.

## Validation

- The crashing benchmark now returns `unsat` (verified on both macOS and
a Linux `RelWithDebInfo` build where the SIGSEGV was originally
reproduced under gdb).
- Unit tests pass: `algebraic`, `upolynomial`, `polynomial`, `nlsat`.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 17:08:08 -07:00
Nikolaj Bjorner
0790dfd876 Fix use-after-free in spacer hypothesis_reducer::reduce_core (#10123)
reduce_core looped with while (true) and read p = todo.back() with no
empty check, exiting only when it reached a hypothesis-free sub-proof of
false. When hypothesis reduction cannot close all hypotheses on the root
proof, todo drains and todo.back() reads past the end of the vector,
producing a heap-use-after-free (SIGSEGV in Fixedpoint.query with
spacer.keep_proxy=false). Whether the root closes depends on search order,
making the crash nondeterministic / seed-dependent.

Bound the loop by todo emptiness, track the reduced root across cache-hit
pops, and return it if the loop drains without hitting the false-subproof
early return.

Verified on the issue #10123 benchmark: the UAF is eliminated across
spacer.random_seed 0/3/7/13/42/99, all returning unsat.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 726c4e71-03ff-45f6-8322-5253254e1d7e
2026-07-14 15:18:02 -07:00
Nikolaj Bjorner
febe471ea4 put tag back in 2026-07-14 14:10:36 -07:00
Nikolaj Bjorner
24bde17501 debug array models 2026-07-14 13:52:14 -07:00
Nikolaj Bjorner
c57c6e564f fix doc build 2026-07-14 13:52:14 -07:00
Nikolaj Bjorner
1a8e18bc48 hardwire ARM tag to 0 on macosx 2026-07-14 13:52:14 -07:00
Lev Nachmanson
becb995757
lp: avoid heap allocation when relocating coefficients in static_matrix::remove_element (#10115)
## Summary

Optimizes `lp::static_matrix<..>::remove_element`, reported as a hotspot
in
[Z3Prover/bench#3143](https://github.com/Z3Prover/bench/discussions/3143)
(the #1 exclusive-time function, ~19.6%, on
`inputs/issues/iss-5131/bug-1.smt2`).

`remove_element` uses swap-remove but **deep-copied** the relocated tail
coefficient:

```cpp
auto & rc = row_vals[row_offset] = row_vals.back(); // copy from the tail
```

In namespace `lp`, `mpq` is a typedef for the copyable `rational`, so
this copy-assign allocates a fresh bignum whenever the **source (the
tail)** is big — matching the `malloc`/`_int_malloc` entries in the
reported profile. The tail element is `pop_back`'d immediately
afterwards, so the allocation is wasteful.

## Change

A copy-assign allocates only when the **source** is big
(`mpz_manager::set` → `big_set`). So relocate the tail coefficient by
**swapping** exactly in that case — stealing its already-allocated
storage, zero `malloc`. When the tail is small, a plain copy never
allocates and is cheaper than swapping the `mpz` internals; the
destination's size is irrelevant. The column-cell relocation is
unchanged (a `column_cell` carries no coefficient).

Single-file change; no new parameters.

## Benchmarks

A/B produced by toggling the new code path against the original
deep-copy (via a temporary parameter, not included here).

- **rise-runner-2** (initial `is_big()||is_big()` variant): QF_LIA_small
neutral; certora identical outcomes, −1.5% paired solve-time.
- **128-core Linux box**, `run_on_dir.py`, `-max_workers 32` (final
tail-only variant):

| Set | Files | `-T` | Solved (new = orig) | Avg-time ratio new/orig |
Correctness |
|---|---|---|---|---|---|
| QF_LIA (SMT-LIB) | 6947 | 20s | 5817 ≈ 5815 | 1.00000 | identical (±2
timeout-edge) |
| certora | 308 | 120s | 186 = 186 | 0.9977 | identical, 0 unique
timeouts |
| QF_LRA (SMT-LIB 2025) | 1753 | 120s | 1552 = 1552 | 0.9985–0.9991 |
identical, 0 real regressions |

Consistently **correctness-neutral and marginally faster** (~0.1–0.5%)
on large-coefficient LP sets, flat on small-coefficient inputs. The
per-`remove_element` allocation saved is small relative to total solve
time, so the whole-solver delta is a fraction of a percent — a clean
micro-optimization with no downside.

## Validation
- `make`/`ninja` build clean; `test-z3 /a` — 92/92 pass.
- Baseline vs patched output byte-identical on the reported benchmark;
identical solve sets across all three benchmark suites above.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 13:04:12 -07:00
Nikolaj Bjorner
2f48e355d8
Add symbolic-modulus congruence rule to nla_divisions (#10119)
Implement check_mod_congruence in nla_divisions: for two mod-atoms
sharing a (possibly symbolic) divisor y, emit the model-guided tautology
div(x,y) - div(s,y) = delta => mod(x,y) - mod(s,y) = (x - s) - delta*y.
This discharges linear congruences over a symbolic modulus that the
nonlinear core did not otherwise isolate. Thread the div(x,y) variable
through add_divisibility (nla_core/nla_solver/nla_divisions) and
register it in theory_lra for symbolic-divisor mod terms.

Solves FStar.BitVector-1 (0.7s) and FStar.Matrix-1 (1.6s), previously
300s timeouts; all 92 unit tests pass.


Copilot-Session: 726c4e71-03ff-45f6-8322-5253254e1d7e

---------

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-14 12:31:17 -07:00
Copilot
82a0d42970
Improve hash mixing to eliminate bitvector-expression hash-table clustering (#10120)
Large Python bitvector workloads were hitting a sharp performance cliff
during `Solver.add(...)`, consistent with severe hash-table clustering
in expression-heavy assertion paths. The issue was sensitive to input
size/alignment, indicating weak low-bit dispersion in hash combination.

- **Hash mixing update (`src/util/hash.h`)**
- Replaced the old `combine_hash(h1, h2)` arithmetic/xor sequence with
stronger mixing:
    - boost-style combine step
    - `hash_u(...)` finalization
- Goal: improve low-bit entropy used by chained hash-table bucket
selection under aligned/high-volume AST patterns.

- **Regression guard and A/B comparison (`src/test/chashtable.cpp`)**
- Added `tst_combine_hash_low_bits()` and invoked it from
`tst_chashtable()`.
- The test stresses aligned first components (`i << 12`) combined with a
fixed seed.
- Added an in-test comparison between the **old** and **new** pairwise
hash combiners and validates:
- reduced collision counts for low-bit projections (8-bit and 16-bit
suffixes),
    - improved low-bit uniformity for 8-bit and 16-bit suffixes,
- reported prefix/suffix uniformity metrics (high/low 8 and 16 bits) for
visibility in test output.

```cpp
static inline unsigned combine_hash(unsigned h1, unsigned h2) {
    h1 ^= h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2);
    return hash_u(h1);
}
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-14 11:51:49 -07:00
Nikolaj Bjorner
c4cb5bbc15 update release.yml and tptp_frontend
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-14 11:16:56 -07:00
Nikolaj Bjorner
e2b9e3a6dc distribute quantifiers over Booleans 2026-07-14 09:57:04 -07:00
Nikolaj Bjorner
8f2713bdb1 Add array eta-reduction rewrite: (lambda (x*) (select a x*)) -> a
Sound by array extensionality when a is independent of the bound
variables. Implemented as array_rewriter::mk_lambda_core and wired into
th_rewriter::reduce_quantifier alongside the ground-lambda case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 09:24:16 -07:00
Copilot
26ad30bb76
Fix invalid sequence models for seq.foldl results observed through seq.nth (#10111)
`seq.foldl` could produce a concrete sequence model while related
`seq.nth` constraints were still validated against stale or
underconstrained length information, leading to invalid models. In the
reported case, `all` was modeled as `(seq.++ (seq.unit 7) (seq.unit 0))`
while `final = (seq.nth all 0)` remained inconsistent with `final = 6`.

- **Root cause**
- Sequence solutions were propagated as equalities, but parent `seq.len`
terms were not updated when a sequence term was solved.
- As a result, `seq.nth` guard reasoning could miss that a solved
sequence had known in-bounds length.

- **Solver change**
- Extend `theory_seq::add_solution` to collect parent `seq.len`
expressions of a solved term when the solved result is sequence-typed.
- After propagating the solved sequence equality, also propagate the
rewritten length equality for those parent length terms.
- Keep this propagation guarded to sequence results so scalar
`seq.foldl`/`seq.foldli` solutions do not regress from `sat` to
`unknown` under model validation.

- **Regression coverage**
  - Add a focused test for the reported SMT-LIB pattern:
    - `all = seq.foldl(...)`
    - `final = seq.nth all 0`
    - `initial = 0`
    - `final = 6`
- Add focused scalar `seq.foldl`/`seq.foldli` model-validation coverage
for the existing benchmark shapes that must continue returning `sat`.
- The regressions check both that model validation no longer reports an
invalid model for the `seq.nth` case and that scalar fold/foldi cases do
not regress to `unknown`.

- **Effect**
- Solved sequence terms now push enough derived length information for
dependent `seq.nth` constraints to validate against the actual modeled
sequence.
  - Existing scalar fold/foldi solving behavior is preserved.

```smt2
(define-fun all_sums ((prev_sums (Seq Int)) (elem Int)) (Seq Int)
  (seq.++ (seq.unit (+ (seq.nth prev_sums 0) elem)) prev_sums)
)

(assert (= all (seq.foldl all_sums (seq.unit initial) elements)))
(assert (= final (seq.nth all 0)))
(assert (= initial 0))
(assert (= final 6))
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 17:33:39 -07:00
Copilot
710b4082d1
Add constant-bound contradiction shortcut for string < constraints in theory_seq (#10112)
The reported case showed expensive reasoning for lexicographic string
comparisons under the default sequence solver, and incorrect handling
expectations with `z3str3` (which does not interpret these comparisons).
This change targets the default solver path by short-circuiting
contradictory constant-bound `<` constraints earlier.

- **Theory shortcut for constant lexical bounds**
- In `theory_seq::assign_eh`, detect asserted `str.<` constraints of the
form `c < x` or `x < c` where `c` is a string constant.
- When a complementary bound on the same equivalence class is already
true, check bound consistency immediately.
- If bounds are contradictory (`!(lower < upper)`), emit a direct theory
conflict from the two active literals instead of waiting for deeper
axiom propagation.

- **Preserve existing comparison reasoning**
  - Existing `check_lts` transitivity/axiom flow is retained.
- The new logic is a narrow fast path for contradictory constant bounds
and does not alter general string-order semantics.

- **Regression coverage**
- Added a solver-level regression in `src/test/seq_rewriter.cpp` for
contradictory date-like lexical bounds to ensure this class of
constraints is rejected as `unsat`.

```cpp
ctx.assert_expr(su.str.mk_lex_lt(su.str.mk_string("2024-01-01"), x));
ctx.assert_expr(su.str.mk_lex_lt(x, su.str.mk_string("2024-12-31")));
ctx.assert_expr(su.str.mk_lex_lt(x, su.str.mk_string("2023-01-01")));
ENSURE(ctx.check() == l_false);
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 12:56:57 -07:00
Copilot
038b367d68
Fix non-termination in mod rewriter for symbolic modulus (#10105)
Combining `mod0`/`div0` quantifier axioms with a mod-idempotency
quantifier caused Z3 to loop forever. The core issue was that
`mk_mod_core` in `arith_rewriter.cpp` only handled rewrite rules for
*numeral* moduli, leaving two gaps for symbolic `y`:

1. `mod(a + k*y, y)` was not reduced to `mod(a, y)`, so `(not (= (mod (+
a b) b) (mod a b)))` stayed unreduced and caused the nlsat solver to
spin.
2. The E-matching pattern `(mod (mod x y) y)` fired on every new term it
produced, creating an unbounded chain of nested `mod` expressions.

```lisp
; Previously non-terminating, now returns unsat immediately
(assert (forall ((x Int)) (! (= (mod0 x 0) 0) :pattern ((mod0 x 0)))))
(assert (forall ((x Int)) (! (= (div0 x 0) 0) :pattern ((div0 x 0)))))
(assert (forall ((x Int) (y Int))
  (! (= (mod (mod x y) y) (mod x y)) :pattern ((mod (mod x y) y)))))
(assert (not (= (mod (+ a b) b) (mod a b))))
(check-sat)
```

## Changes

- **`src/ast/rewriter/arith_rewriter.cpp` — symbolic summand
elimination**: In `mk_mod_core`, when the modulus is a non-numeral
integer and the dividend is an `add`, strip any summand equal to the
modulus or an integer multiple of it. Soundness: `k*0 = 0` for all `k`,
so the rule holds even at `y = 0`. This immediately collapses the
reported formula to `false`.

- **`src/ast/rewriter/arith_rewriter.cpp` — symbolic idempotency via
ite**: Extend the existing `mod(mod(x,y), y) → mod(x,y)` rule
(previously numeral-only) to symbolic `y` by rewriting to `ite(y=0,
mod(mod(x,0),0), mod(x,y))`. The `y=0` branch uses a numeral divisor,
which is excluded by the `!v2.is_zero()` guard, halting the E-matching
chain.

- **`src/test/arith_rewriter.cpp`**: Regression tests for `mod(a+y, y) =
mod(a,y)`, `mod(a+2y, y) = mod(a,y)`, and `mod(mod(a,3),3) = mod(a,3)`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 09:20:03 -07:00
Copilot
98e1f5ca2d
Fix assertion violation in bv2int_translator with smt.bv.solver=2 (#10109)
`ASSERTION VIOLATION` at `bv2int_translator.h:72` when using
`smt.bv.solver=2` with formulas containing `abs` (or other arith
expressions that rewrite to ITE with arith predicates).

**Root cause**

`ensure_translated` skips adding sub-expressions of boolean non-BV nodes
to the `todo` list — correct, since the base theory owns them in plugin
mode. However, `translate_expr`'s early-return only covered
`basic_family_id` booleans, not non-basic ones (e.g.,
`arith_family_id`).

When `(abs f)` is rewritten by the arith rewriter to `(ite (>= f 0) f (-
f))`, the predicate `(>= f 0)` ends up in `todo` (as a child of the
ITE), but its own children (e.g., the integer literal `0`) are not
added. `translate_expr` then calls `translated(0)` on an unmapped
expression, firing `SASSERT(r)`.

Reproducer:
```smt2
(declare-const f Int)
(assert (= 0 (mod 0 (bv2nat ((_ int_to_bv 1) (abs f))))))
(check-sat)
; z3 test.smt2 smt.bv.solver=2  →  ASSERTION VIOLATION (before fix)
```

**Fix**

Extend the early-return in `translate_expr` to match
`ensure_translated`'s skip condition — all boolean non-BV expressions in
plugin mode map to themselves:

```cpp
// before
if (m_is_plugin && ap->get_family_id() == basic_family_id && m.is_bool(ap)) {

// after
if (m_is_plugin && m.is_bool(ap) && ap->get_family_id() != bv.get_family_id()) {
```

BV boolean predicates (`bvule`, etc.) are unaffected — they still route
through `translate_bv`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 21:56:12 -07:00
Copilot
965942be79
Java API: return EnumSort instead of DatatypeSort from Sort.create() (#10104)
`Expr.getSort()` on a constant with an `EnumSort` sort returns a
`DatatypeSort`, causing `ClassCastException` when enum-specific methods
are called.

```java
Context ctx = new Context();
EnumSort<Foo> enumSort = ctx.mkEnumSort("my-enum", "e1", "e2");
Expr<EnumSort<Foo>> c = ctx.mkConst("my-const", enumSort);
c.getSort().getName();  // ClassCastException — getSort() returns DatatypeSort, not EnumSort
```

### Changes

- **`Sort.java`**: In `Sort.create()`, detect enum sorts at the
`Z3_DATATYPE_SORT` case by checking whether all constructors have arity
0 — matching Z3's own `util::is_enum_sort` logic in
`datatype_decl_plugin.cpp`. Return `EnumSort<>` when true,
`DatatypeSort<>` otherwise.
- **`EnumSort.java`**: Add package-private `EnumSort(Context ctx, long
obj)` constructor so an `EnumSort` can be instantiated from an existing
native sort handle (analogous to `DatatypeSort(Context ctx, long obj)`).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 21:46:46 -07:00
Nikolaj Bjorner
7b26fe135a
Add linear divisibility closure lemma for lp/nla solver (#7464) (#10107)
## Summary

Fixes the divergence in issue #7464: formulas involving `mod`/`div` by a
**variable** divisor could send `smt.arith.solver=6` into a
non-terminating nonlinear search.

Minimal reproducer (UNSAT, previously timed out; now solved in <0.5s):

```smt2
(declare-fun V () Int)
(declare-fun n () Int)
(declare-fun l () Int)
(assert (and (> V 0) (= 0 (mod n 2)) (= (div n 2) (div n l)) (= 0 (mod (div n l) V))))
(assert (distinct 0 (mod n V)))
(check-sat)
```

## Root cause

A variable-divisor `mod n V` is axiomatized by the Euclidean identity
`n = V*(n div V) + (n mod V)`. The `V*(n div V)` term is nonlinear, so
arith.solver=6
hands the problem to the nlsat/Gröbner branch, which branches on values
of `V` with no
termination bound and diverges.

## Fix

Add a **linear divisibility closure** lemma in `nla_divisions`:

> `mod(a, y) = 0 & x = c*a` (c an integer constant) ⟹ `mod(x, y) = 0`.

The emitted clause

```
(x - c*a != 0)  \/  (mod(a, y) != 0)  \/  (mod(x, y) = 0)
```

is a **tautology for every integer `c`**, so mining a candidate `c =
val(x)/val(a)` from
the current model can never be unsound. It is only emitted when all
three literals are
false in the current model, so the clause is a genuine
conflict/propagation and always
makes progress. This lets the theory refute the instance directly
instead of entering the
divergent nonlinear branch.

Variable-divisor `mod` terms were previously **not registered** in nla
at all; they are now
registered into a new `m_divisibility` list in `theory_lra`, so the
reasoner can pair a
violated `mod(x, y)` with a satisfied `mod(a, y)` of the same divisor.

## Changes

- `src/math/lp/nla_divisions.{h,cpp}` — new `m_divisibility` list
`{r=mod, x=dividend, y=divisor}`, `add_divisibility(...)`, and
`check_linear_divisibility()`; invoked from `divisions::check()`.
- `src/math/lp/nla_core.h`, `src/math/lp/nla_solver.{h,cpp}` —
forwarding of `add_divisibility`.
- `src/smt/theory_lra.cpp` — register variable-divisor `mod` into the
divisibility list.

## Validation

- `min.smt2` → `unsat` in 0.46s, minimized core → 0.15s (were timeouts).
- Soundness: 350 differential fuzz formulas (arith.solver=6 vs
arith.solver=2), **0 mismatches**.
- Spot checks correct (divisor-3 variant → unsat; non-divisible variants
→ sat).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-12 21:20:50 -07:00
Copilot
634b2886ba
fix: declare type variables before use in solver display output (#10103)
`decl_collector::visit_sort` did not collect sorts with `poly_family_id`
(type variables created via `mk_type_var` / `declare-type-var`), so
`solver::display` and `ast_pp_util::display_decls` never emitted type
variable declarations before referencing them — producing invalid
SMT-LIB2 output.

## Changes

- **`src/ast/decl_collector.h`**: added a dedicated `lim_svector<sort*>
m_type_vars` field (separate from `m_sorts`) with a `get_type_vars()`
getter; `reset()` clears it; `push()`/`pop()` maintain its scope.

- **`src/ast/decl_collector.cpp` — `visit_sort`**: sorts with
`poly_family_id` are now pushed to `m_type_vars` instead of `m_sorts`,
keeping type variables distinct from uninterpreted sorts:

```cpp
if (m.is_uninterp(n))
    m_sorts.push_back(n);
else if (fid == poly_family_id)
    m_type_vars.push_back(n);
```

- **`src/ast/ast_pp_util.h`**: added a `stacked_value<unsigned>
m_type_vars` cursor to track which type variables have already been
printed.

- **`src/ast/ast_pp_util.cpp` — `display_decls`**: emits
`(declare-type-var <name>)` for each collected type variable before
other sort declarations; `reset()`/`push()`/`pop()` maintain the new
cursor.

**Example** — given `(declare-type-var A)(declare-fun f (A) A)`, the
dump now correctly produces:

```smt2
(declare-type-var A)
(declare-fun f (A) A)
(assert ...)
```

The output round-trips cleanly through the Z3 parser.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 20:56:00 -07:00
Copilot
087eeaf33c
Fix api_datalog test: reuse of Z3_context across set-logic calls (#10101)
The `api_datalog` unit test was failing in CI with `"the logic has
already been set"`. Two consecutive regression tests shared a single
`Z3_context`, but both called `Z3_eval_smtlib2_string` with `(set-logic
HORN)` — the second call always fails because context logic state is
permanent.

## Changes

- **`src/test/api_datalog.cpp`**: Give each
`Z3_eval_smtlib2_string`-based regression test its own
`Z3_config`/`Z3_context`, destroyed immediately after the test block.
The outer context is retained only for the two tests that don't invoke
the SMT-LIB evaluator.

```cpp
// Before: both blocks shared `ctx`, second (set-logic HORN) always errored
Z3_string response = Z3_eval_smtlib2_string(ctx, chc1);  // sets HORN logic
Z3_string response = Z3_eval_smtlib2_string(ctx, chc2);  // ERROR: logic already set

// After: each block owns its context
Z3_config cfg2 = Z3_mk_config();
Z3_context ctx2 = Z3_mk_context(cfg2);
Z3_del_config(cfg2);
Z3_string response = Z3_eval_smtlib2_string(ctx2, chc2);
Z3_del_context(ctx2);
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 20:32:02 -07:00