fix: Unsound interval rules for *, div, mod, / and ** - #1159
Draft
fabiomadge wants to merge 2 commits into
Draft
fabiomadge wants to merge 2 commits into
fabiomadge wants to merge 2 commits into
Conversation
This was referenced Aug 31, 2026
fabiomadge
force-pushed
the
fix/interval-arithmetic-rules
branch
from
August 31, 2026 16:30
8f4cb09 to
64c9b66
Compare
Four transfer functions in `PEVisitor` claimed a bound that their premises do not give, so `-infer:j` proved false assertions. Each is reported correctly without inference. `Mul` had a branch for two negative lower bounds that set `Hi = lo0 * lo1`, deriving an upper bound from two lower bounds. It holds only if both operands are also bounded above by zero, which nothing established. From `-3 <= x` and `-3 <= y` it inferred `x * y < 10`, refuted by x = y = 1000. The branch goes; recovering it soundly needs both upper bounds known and non-positive, and all four corner products. The real form of this is reachable from Dafny, which emits `*` on reals natively and enables this analysis unconditionally, and there it proves `false` from a program with no floating point and no flags. `Div`, `Mod` and `RealDiv` bounded their result whenever both operands had a lower bound of at least zero, which admits a zero divisor. Division and mod by zero are underspecified in SMT-LIB, so the result is an arbitrary value and no bound holds of it. Each now requires a divisor of at least one. `RealDiv` already required that for its upper bound and not for its lower; an integer bound cannot say "positive but below one", so a real divisor in that range now yields nothing. `Pow` gave no correct bound at all and is removed. It claimed `Hi = hi1`, bounding the power by the *exponent's* upper bound -- 2.0 ** 3.0 is 8.0, which no bound on 3.0 constrains -- and read the exponent again for the lower bound, where a base of at least one was meant: 0.5 ** 2.0 is 0.25. `real_pow` is also underspecified at 0 ** 0. Boogie's `**` typechecks only for reals and the solver rejects what Boogie emits for it, so this was unreachable rather than harmful. Found with `Test/infer-fuzz.py`, added here, which generates programs over int and real arithmetic and runs each under `/checkInfer` -- turning every inferred invariant into an obligation the prover must discharge. On its default 400 programs: 75 inferred something it could not, every one implicating `*`, `div`, `mod` or `/`, and none implicating anything else. After this, none do. It is a tool rather than a test, since it is slow and a run says nothing about the programs it did not generate, so it is not wired into CI. The existing corpus flagged only one file, because no test happens to multiply two variables that both have a negative lower bound -- which is also why `Test/aitest1/Linear5.bpl` recorded `x < 2` after `assume -1 <= x; x := x*x;`. That expectation was pinning the defect; x = 10 refutes it.
fabiomadge
force-pushed
the
fix/interval-arithmetic-rules
branch
from
September 20, 2026 15:50
64c9b66 to
33727e8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five of the domain's arithmetic rules inferred bounds that need not hold.
*with two negative lowerbounds claimed the product is at most their product.
div,modand/accepted a divisor whose lowerbound is zero, where division is underspecified.
**claimed a lower bound of 1 for a nonzero exponentand took its upper bound from the exponent.
-infer:j→ 1 verified, 0 errors, thoughx * yis unbounded above. The loop is there only becauseinferred facts reach the prover at loop heads.
Fix.
*keeps the nonnegative case alone, since a negative operand needs all four corner products andso an upper bound on both. The three divisions require
1 <= lo1, which also makesdivand/identicaland lets them share a case — the merged rule is correct under both the half-open reading ints get and the
closed one reals get.
**loses its rule.Testing. Three files covering all five rules.
**cannot be caught by a false assertion, because it isemitted as
real_pow, which the background predicates declare only under a non-monomorphic encoding, soany verification condition mentioning it dies in the solver (#1166 fixes that).
PowerBounds.bplchecksthe inferred invariant directly under
-noVerify, where master infers the false1e0 <= p && p <= 2e0for
x ** ywithx >= 0and1e0 <= y <= 2e0.One of six.