Skip to content

Add regression tests for ArithmeticOnIntegerBot triggers - #2174

Open
karoliineh wants to merge 11 commits into
masterfrom
arithbot-tests
Open

karoliineh wants to merge 11 commits into
masterfrom
arithbot-tests

Conversation

@karoliineh

@karoliineh karoliineh commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

This PR adds regression tests that trigger various paths that eventually raise ArithmeticOnIntegerBot. These tests are creduced from several sv-benchmarks (documented in the test cases).

Short descriptions for each:

Regression and trigger Raise location and cause
20-evalint-mul-bottom.c, line 11: b * d DefExc.mul receives Error int and Unknown int([-31,31]). Its fallback (defExcDomain.ml:434) delegates to lift2_inj. Because exactly one operand is bottom, lift2_inj raises ArithmeticOnIntegerBot at defExcDomain.ml:391. This occurs during a forward EvalInt query requested by Apron.
21-if-overflow-bottom.c, line 5: 24007455 * 128 / 8 The multiplication 24007455 * 128 exceeds the signed int range, so DefExc.norm returns Bot under assume_none (defExcDomain.ml:203-205). During backward refinement of the enclosing division, BaseInvariant calculates ID.rem a b (baseInvariant.ml:331). Tuple rem first invokes division (intDomTuple.ml:448), so DefExc.div receives Error int and 8, delegates to lift2, and lift2 raises at defExcDomain.ml:372.
22-recursive-assert-overflow-bottom.c, line 6: 6 - *d * *d * *d As recursion increases *d, the cubic multiplication eventually overflows, so DefExc.norm returns Bot under assume_none (defExcDomain.ml:203-205). The enclosing DefExc.sub receives 6 and Error int (defExcDomain.ml:426), delegates to lift2_inj, and lift2_inj raises at defExcDomain.ml:391. This occurs during forward EvalInt evaluation of the assertion expression.
23-inverse-mul-bottom.c, line 20: a * b While inverting the multiplication, BaseInvariant.refine_by calculates ID.div c y to refine one operand (baseInvariant.ml:318). The Interval component receives [0,4294967295] and Bot. Interval.div detects that exactly one operand is bottom and raises ArithmeticOnIntegerBot at intervalDomain.ml:357. Unlike tests 20 and 22, the failing operation is division used internally for inverse multiplication refinement.

@karoliineh karoliineh added bug sv-comp SV-COMP (analyses, results), witnesses labels Sep 29, 2026
karoliineh and others added 4 commits September 29, 2026 16:43
Apron relational domains compute rational bounds, while `EvalInt` must return integer values. A rationally feasible state can therefore have an empty integer projection. For example, 3 * b - 13 = 0 gives b = 13/3, whose rounded integer bounds are [5, 4].

Extend bound_texpr results to distinguish an empty integer result from unknown or unbounded bounds. Return integer bottom for empty results while retaining top when bounds are unavailable. Handle bottom states and exact non-integral constants consistently across the Apron and equality domains.

Co-authored-by: OpenAI Codex <codex@openai.com>
Comment on lines +115 to +120
let bounds = A.bound_texpr Man.mgr d texpr1 in
let min = SharedFunctions.int_of_scalar ~round:`Ceil bounds.inf in
let max = SharedFunctions.int_of_scalar ~round:`Floor bounds.sup in
match min, max with
| Some min, Some max when Z.gt min max -> None
| min, max -> Some (min, max)

@karoliineh karoliineh Oct 2, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apron can represent a non-bottom rational polyhedron that contains no integer points. For example, the constraint 3 * b - 13 = 0 has the rational solution b = 13/3, so Apron considers the state feasible even though b is an integer variable.

So, the conversion gives min = ceil(13/3) = 5, max = floor(13/3) = 4 -> [5,4]

Previously, these reversed bounds were passed to the integer domains, where different domain
components handled them inconsistently and eventually caused an ArithmeticOnIntegerBot exception (exposed by test 23-inverse-mul-bottom.c) .

Now, the added outer option distinguishes:

  • None: the expression has no possible integer value.
  • Some (None, None): values may exist, but this domain cannot provide bounds.
  • Some (Some min, Some max): known bounds

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the tests still fail at b7964d4 though, does it only make a difference when combined with all the following changes?

Comment on lines +357 to +366
if t.d = None then None
else
match simplify_to_ref_and_offset t (Texpr1.to_expr texpr) with
| Some (None, offset, divisor) when Z.equal (Z.rem offset divisor) Z.zero -> let res = Z.div offset divisor in
(if M.tracing then M.tracel "bounds" "min: %a max: %a" GobZ.pretty res GobZ.pretty res;
Some res, Some res)
| _ -> None, None
| Some (None, offset, divisor) when Z.equal (Z.rem offset divisor) Z.zero ->
let res = Z.div offset divisor in (
if M.tracing then M.tracel "bounds" "min: %a max: %a" GobZ.pretty res GobZ.pretty res;
Some (Some res, Some res)
)
| Some (None, _, _) -> None
| _ -> Some (None, None)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the affine equality domains, the added if t.d = None then None preserves a bottom relational state instead of reporting merely unknown bounds. Separating Some _ from None similarly distinguishes an exact non-integral rational constant from failure to derive a constant.

For the linear two-variable equality domain, the old t.d = None -> (None, None) also incorrectly converted bottom into unknown bounds. An exact constant offset / divisor is returned as a singleton only when it is integral; otherwise it has no integer value and returns None i.e., bottom.

Treat bottom operands, ArithmeticOnIntegerBot exceptions, and bottom refinement results as contradictions around the entire inverse integer operation. This covers multiplication, division, and modulo consistently with addition and subtraction while preserving unassume behavior.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check consistently for bot values around the callsites of inv_bin_int and inv_bin_float :

  • inv_bin_int: Handle integer arithmetic on bottom at the callsite instead of only in selected meet-operators;
  • inv_bin_float: remove the try-with from around the match and move the exception-handling to the call-site;
  • At the callsite, handle cases where:
    • a or b are bot before refinement
    • refinement causes ArithmeticOnIntegerBot
    • a' or b' (the refined values) become bot

Comment thread src/analyses/mCP.ml
if M.tracing then M.trace "queryanswers" "combined query %a answers %a and %a -> dead code" Queries.Any.pretty anyq Result.pretty a Result.pretty res;
raise Deadcode
| _ -> meet
end

@karoliineh karoliineh Oct 2, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Treat a meet of EvalInt answers that result in bottom as dead code instead of propagating it into subsequent integer arithmetic, where it may trigger ArithmeticOnIntegerBot. This handles several regressions:

  • In 20, def_exc and Apron produce conflicting singleton values 0 and 1: the meet of these becomes bottom;
  • In 22, base returns integer bottom after a definite signed overflow under assume_none;
  • In 23, Apron essentially returns integer bottom (see the other fix about rationals in apron). Meeting the bottom answer with other analysis answers remains bottom.

@karoliineh

karoliineh commented Oct 3, 2026 •

Copy link
Copy Markdown
Member Author

Testing the changes on sv-benchmarks (on svcomp26 tag) with 20s timeout revealed two incorrect true results:

  • ldv-linux-3.0/usb_urb-drivers-media-dvb-ttusb-dec-ttusb_dec.ko.cil.out.i: portfolio configuration level00 returned true.
  • hardware-verification-bv/btor2c-lazyMod.mul6.c: configurations level00 and level01 returned unknown, after which level02 returned true.

So some unsoundness going on here: investigating.

Use the expression's target integer kind when CIL cannot infer a kind capable of representing an intermediate value. Thread the kind through DefExc and Enums range construction so oversized Apron bounds do not produce narrower, contradictory tuple components.

Update the pinned goblint-cil revision for the optional intKindForValue result and corrected enum handling.
@karoliineh

Copy link
Copy Markdown
Member Author

Fix for hardware-verification-bv/btor2c-lazyMod.mul6.c

The benchmark initializes an unsigned __int128 value using a mask whose exact upper bound is 2^128 - 1. With the Apron octagon domain that uses floating-point values, this bound is rounded outwards to 2^128 due to the floating-point imprecision.

Previously, Cil.intKindForValue always returned an integer kind, even when the value was not representable by any supported kind. So it obtained the narrower range [0, 64] due to the default Long Long case in cil. Meeting with another correctly represented unsigned __int128 range in MCP (due to the new change) thus meeted to bottom and resulted in dead code.

The CIL-side fix is in goblint/cil@90de8c4: intKindForValue now returns None when no integer kind can represent the value.

On the Goblint side, the already-known expression kind is now passed through DefExc and Enums range construction. If CIL cannot infer a representing kind, the target kind is used as the fallback.

After these changes, analysis on btor2c-lazyMod.mul6.c returns the sound result unknown.

Comment thread src/cdomain/value/cdomains/intDomain0.ml
Inc (BISet.filter value_in_ikind xs)
else
top_of ikind
| Exc (xs, r) ->

@karoliineh karoliineh Oct 3, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A possible followup by codex:

Enums.norm currently assumes that an Exc range is already contained in the target ikind and therefore returns it unchanged.

That invariant is not guaranteed when intKindForValue successfully finds a wider kind than the target kind. For example, a bound outside a 32-bit target may still be representable by a 64-bit kind, causing min_range_sign_agnostic to construct a range wider than the target type.

This does not affect the btor2c-lazyMod.mul6.c case: 2^128 is not representable by any supported kind, so the new fallback uses the target IUInt128 range (0,128) directly. Nevertheless, Enums.norm should eventually handle the more general case. A conservative fix would be:

| Exc (_, r) when not (R.leq r (size ikind)) ->
  top_of ikind
| Exc (xs, r) ->
  (* existing handling *)

@DrMichaelPetter DrMichaelPetter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The apron / linear equality part looks ok to me - I found minor explanation in comments helpful, though.

Comment on lines +135 to +136
| Some _ -> None
| None -> Some (None, None)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This confused me when reading, let's explain

Suggested change
| Some _ -> None
| None -> Some (None, None)
| Some _ -> None (* Term is a constant but has a denominator different from one means an actual fracture -> no integer value *)
| None -> Some (None, None) (* Term is not constant *)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug sv-comp SV-COMP (analyses, results), witnesses

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants