3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-15 03:25:43 +00:00

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>
This commit is contained in:
Lev Nachmanson 2026-07-14 17:08:08 -07:00 committed by GitHub
parent 0790dfd876
commit 1b39b0e50f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -2066,8 +2066,15 @@ namespace algebraic_numbers {
scoped_mpbq la(bqm()), ua(bqm());
scoped_mpbq lb(bqm()), ub(bqm());
unsigned precision = 10;
if (get_interval(a, la, ua, precision) &&
get_interval(b, lb, ub, precision)) {
// Important: both intervals must be computed. Do not short-circuit with &&:
// the refined bounds la, ua, lb, ub are all used below (and beyond this
// if statement, in the interval-separation checks that compare against
// the bounds of a and b), so get_interval(b, ...) has to run even when
// get_interval(a, ...) returns false (which happens when a is rational
// and its exact root is found).
bool a_separated = get_interval(a, la, ua, precision);
bool b_separated = get_interval(b, lb, ub, precision);
if (a_separated && b_separated) {
IF_VERBOSE(9, verbose_stream() << "sturm 0\n");
if (la > ub)
return sign_pos;