Skip to content

Add automated overflow/underflow checks for bitvector and floating-point arithmetic#592

Open
tautschnig wants to merge 11 commits intomainfrom
tautschnig/overflow-checks
Open

Add automated overflow/underflow checks for bitvector and floating-point arithmetic#592
tautschnig wants to merge 11 commits intomainfrom
tautschnig/overflow-checks

Conversation

@tautschnig
Copy link
Contributor

Motivation

Arithmetic overflow is a major source of bugs in systems code. Signed integer overflow is undefined behavior in C/C++, unsigned overflow silently wraps, and floating-point overflow silently produces infinity. Unlike division-by-zero (added in #510), these errors are easy to miss in code review and hard to trigger in testing.

This PR extends Strata's automated verification to catch arithmetic overflow at every use site — the same way #510 catches every division by zero — so that front-end languages like Laurel (and future C/Java front-ends) get overflow checking for free, without any source-level instrumentation.

Approach

Following the pattern established by #510 for division-by-zero:

  1. Safe operator variants (e.g., Bv32.SafeAdd) carry built-in preconditions asserting that overflow does not occur.
  2. The existing PrecondElim transform automatically generates verification conditions at each use site.
  3. Front-end translators emit safe variants instead of unchecked ones — no language-level instrumentation required.

This keeps the architecture uniform: the same mechanism handles division-by-zero, signed overflow, unsigned overflow, signed division overflow (INT_MIN / -1), and floating-point overflow to infinity.

What's covered

Signed bitvector overflow (all sizes: bv1, bv2, bv8, bv16, bv32, bv64):

  • SafeAdd/SafeSub/SafeMul/SafeNeg with preconditions using SMT-LIB's bvsaddo/bvssubo/bvsmulo/bvnego

Signed division overflow (INT_MIN / -1):

  • SafeSDiv/SafeSMod with dual preconditions: divisor ≠ 0 AND ¬(dividend = INT_MIN ∧ divisor = −1)

Unsigned bitvector overflow (configurable via unsignedOverflowCheck option):

  • SafeUAdd/SafeUSub/SafeUMul/SafeUNeg with unsigned overflow predicates

IEEE 754 Float64:

  • New float64 type in the SMT layer, encoded as (_ FloatingPoint 11 53)
  • Float64.SafeAdd/SafeSub/SafeMul/SafeDiv with overflow predicates
  • Overflow = finite inputs producing ±infinity (following CBMC's float_overflow_check)
  • Laurel's TFloat64 now translates to real Float64 (was "Float64IsNotSupportedYet")

GOTO backend:

  • Overflow predicates translate to CBMC's native overflow-+, overflow--, overflow-*, overflow-unary- expressions
  • Safe operators translate to the same GOTO operations as their unchecked counterparts

Key design decisions

  • Safe variants are opt-in at the Core levelBv32.Add (wrapping) and Bv32.SafeAdd (checked) coexist. Front-end translators choose which to emit.
  • Unsigned overflow is off by default — wrapping is the intended semantics in most languages. Enable with unsignedOverflowCheck := true.
  • Signed overflow is always checked when using safe variants — this matches C/C++ undefined behavior semantics.
  • The GOTO backend uses CBMC's native overflow expressions rather than the SMT encoding, so CBMC's solver infrastructure handles them natively.

Description of changes:

New arithmeticOverflow property type alongside the existing divisionByZero, with SARIF classification, display labels, and check mode handling. Overflow predicates and safe operators for all BV sizes generated via Lean4 macros. SMT encoding for signed overflow uses SMT-LIB builtins; unsigned and Float64 use custom encodings. Comprehensive unit tests verify factory presence, WF obligation generation, and GOTO translation.

Co-authored-by: Kiro kiro-agent@users.noreply.github.com

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

tautschnig and others added 8 commits March 17, 2026 10:10
Add PropertyType.arithmeticOverflow alongside the existing divisionByZero
property type. This is the foundation for bitvector and floating-point
overflow/underflow checks.

Changes:
- EvalContext.lean: new PropertyType.arithmeticOverflow variant
- MetaData.lean: new MetaData.arithmeticOverflow constant
- StatementEval.lean: recognize "arithmeticOverflow" in metadata
- Verifier.lean: display labels/emoji, check mode handling
- SarifOutput.lean: SARIF classification "arithmetic-overflow"
- PrecondElim.lean: classify BvN.Safe* operators as overflow checks

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Add safe BV operator variants (SafeAdd, SafeSub, SafeMul, SafeNeg) with
built-in overflow preconditions for all BV sizes (1, 2, 8, 16, 32, 64).
Each safe operator carries a precondition asserting that the corresponding
signed overflow predicate is false.

New overflow predicate functions (SAddOverflow, SSubOverflow, SMulOverflow,
SNegOverflow) are added as factory functions that map to SMT-LIB's bvsaddo,
bvssubo, bvsmulo, and bvnego operations.

The PrecondElim transform automatically generates verification conditions
at each use site of a safe operator, classified as 'arithmeticOverflow'
property type.

Changes:
- IntBoolFactory.lean: add optional preconditions parameter to unaryOp
- Factory.lean: new BVEvalKind variants (overflowBinary/overflowUnary),
  BVSafeOpSpecs, ExpandBVSafeOpFuncDefs/Names/Exprs macros
- SMTEncoder.lean: map safe ops to normal SMT ops, overflow predicates
  to bvsaddo/bvssubo/bvsmulo/bvnego
- ASTtoCST.lean: handle safe ops and overflow predicates in CST printing
- New OverflowCheckTest.lean: unit tests verifying factory presence and
  WF obligation generation
- Updated ProgramTypeTests.lean, ProcedureEvalTests.lean: new factory
  function listings

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Add SafeSDiv and SafeSMod operators that check both division-by-zero
(y ≠ 0) and signed division overflow (x ≠ INT_MIN ∨ y ≠ -1) for all
BV sizes. These use the new SDivOverflow predicate which leverages
Lean4's BitVec.sdivOverflow from the standard library.

Each SafeSDiv/SafeSMod carries two preconditions:
1. ¬(y == 0) — division by zero (classified as divisionByZero)
2. ¬SDivOverflow(x, y) — INT_MIN / -1 overflow (classified as arithmeticOverflow)

Changes:
- Factory.lean: SDivOverflow predicate in BVOpSpecs, SafeSDiv/SafeSMod
  via ExpandBVSafeDivOpFuncDefs macro with dual preconditions
- SMTEncoder.lean: SDivOverflow encoded as (x == INT_MIN) ∧ (y == -1),
  SafeSDiv/SafeSMod map to bvsdiv/bvsrem
- ASTtoCST.lean: SafeSDiv/SafeSMod CST printing
- Updated test expected outputs

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Add unsigned overflow predicates (UAddOverflow, USubOverflow, UMulOverflow,
UNegOverflow) and safe unsigned operators (SafeUAdd, SafeUSub, SafeUMul,
SafeUNeg) for all BV sizes.

Unsigned overflow is controlled by the new unsignedOverflowCheck option
in VerifyOptions (default: false). Front-end translators use the safe
unsigned variants when this option is enabled.

SMT encoding for unsigned overflow predicates:
- UAddOverflow: bvult(bvadd(x, y), x) — wrapping add < operand
- USubOverflow: bvult(x, y) — unsigned subtraction underflow
- UMulOverflow: bvugt(zero_extend(N, x) * zero_extend(N, y), zero_extend(N, MAX))
- UNegOverflow: x != 0

Note: ProgramTypeTests and ProcedureEvalTests expected output needs
updating for the new factory functions (the OverflowCheckTest validates
the core functionality).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Teach the GOTO backend to translate overflow predicate expressions to
CBMC's native overflow expression types:
- SAddOverflow/UAddOverflow → overflow-+ (PlusOverflow)
- SSubOverflow/USubOverflow → overflow-- (MinusOverflow)
- SMulOverflow/UMulOverflow → overflow-* (MultOverflow)
- SNegOverflow/UNegOverflow → overflow-unary- (UnaryMinusOverflow)

Safe operators (SafeAdd, SafeSub, etc.) translate to the same GOTO
operations as their unsafe counterparts (Plus, Minus, etc.), since
the overflow checks are handled by PrecondElim-generated assertions.

Changes:
- Expr.lean: add MinusOverflow, MultOverflow (Binary), UnaryMinusOverflow
  (Unary) with format strings and helper constructors
- LambdaToCProverGOTO.lean: map overflow predicates and safe ops in
  fnToGotoID; add signed ops to isSignedBvOp
- Tests: verify overflow predicate → GOTO expression mapping

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Add IEEE 754 double-precision floating-point support to the SMT layer
and Core language, with safe arithmetic operators for overflow detection.

SMT layer:
- TermPrimType.float64: maps to (_ FloatingPoint 11 53) in SMT-LIB
- TermPrim.rne: RoundNearestTiesToEven rounding mode constant
- Op.FP: fp.add, fp.sub, fp.mul, fp.div, fp.neg, fp.isInfinite, fp.isNaN,
  fp.isZero operations
- DDM Translate: float64 sort and rne constant encoding

Core Factory:
- Float64.Add/Sub/Mul/Div/Neg: basic arithmetic (unevaluated)
- Float64.IsInfinite/IsNaN: predicates
- Float64.SafeAdd/SafeSub/SafeMul/SafeDiv: overflow-checked variants
- SMT encoding: FP ops use RNE rounding mode

Laurel integration:
- TFloat64 now maps to LMonoTy.float64 (was "Float64IsNotSupportedYet")
- Arithmetic on Float64 uses safe variants (SafeAdd/Sub/Mul/Div)
- PrecondElim classifies Float64.Safe* as arithmeticOverflow

Note: Float64 safe ops are currently unevaluated (no preconditions via
binaryOp). Full overflow checking (result becomes ±infinity from finite
inputs) requires a dedicated transform that inserts assertions using
Float64.IsInfinite, similar to CBMC's float_overflow_check approach.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…encoding

Address all remaining work items:

1. Test file updates:
   - ProgramTypeTests.lean: replaced docstring with actual output including
     all new factory functions (Float64, unsigned overflow, safe ops)
   - ProcedureEvalTests.lean: same
   - TypeDecl.lean: added float64 to known types list

2. Float64 overflow predicates:
   - Float64.AddOverflow/SubOverflow/MulOverflow/DivOverflow: predicates
     that return true when finite inputs produce infinite result
   - SMT encoding: ¬isInfinite(x) ∧ ¬isInfinite(y) ∧ isInfinite(fp.op(RNE, x, y))
   - Following CBMC's float_overflow_check approach

3. Bug fixes:
   - UAddOverflow SMT encoding: use operand type (not return type) for bvadd
   - SMT Denote: add float64 case (returns none)
   - SMT Translate: add float64 case (throws unsupported)
   - Remove Float64IsNotSupportedYet datatype from HeapParameterizationConstants
   - Add float64 to KnownLTys so Core type checker recognizes it

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Replace #check with example := for silent compilation checks.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
@tautschnig tautschnig requested a review from a team March 17, 2026 14:02
Copilot AI review requested due to automatic review settings March 17, 2026 14:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends Strata’s “safe operator + PrecondElim” verification pipeline (previously used for division-by-zero) to also generate verification conditions for arithmetic overflow/underflow across bitvector operations and (newly supported) Float64, with a new arithmeticOverflow property type surfaced through reporting/SARIF and supported in the CBMC GOTO backend.

Changes:

  • Introduces new Core bitvector overflow predicate functions and corresponding Safe* operators intended to carry overflow preconditions (plus special handling for signed div overflow).
  • Adds an SMT-layer float64 sort and Float64 operators/predicates, and updates Laurel’s TFloat64 translation to use real Float64 operators.
  • Adds reporting/plumbing for the new arithmeticOverflow property type (labels/emojis/SARIF classification) and adds/updates tests for factory presence and backend translation.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
StrataTest/Languages/Core/Tests/ProgramTypeTests.lean Updates expected environment dump to include new Float64/BV overflow/safe functions.
StrataTest/Languages/Core/Tests/ProcedureEvalTests.lean Updates expected prelude with new Float64/BV overflow/safe functions.
StrataTest/Languages/Core/Tests/OverflowCheckTest.lean Adds unit tests for presence of safe ops/predicates and WF obligation generation.
StrataTest/Languages/Core/Examples/TypeDecl.lean Updates expected reserved-type list to include float64.
StrataTest/Backends/CBMC/GOTO/LambdaToCProverGOTO.lean Adds tests for mapping overflow predicates and safe ops to CBMC identifiers.
Strata/Transform/PrecondElim.lean Classifies safe BV/Float64 preconditions as arithmeticOverflow for SARIF/reporting.
Strata/Languages/Laurel/LaurelToCoreTranslator.lean Translates Laurel Float64 types/ops to Core Float64 (and uses Float64 safe ops for arithmetic).
Strata/Languages/Laurel/HeapParameterizationConstants.lean Removes the old placeholder Float64IsNotSupportedYet datatype.
Strata/Languages/Core/Verifier.lean Extends minimal/full label+emoji logic to include arithmeticOverflow.
Strata/Languages/Core/StatementEval.lean Maps MetaData.propertyType strings to the new .arithmeticOverflow PropertyType.
Strata/Languages/Core/SarifOutput.lean Adds SARIF classification string for arithmeticOverflow and updates unreachable handling comment.
Strata/Languages/Core/SMTEncoder.lean Adds SMT encoding for float64 sort/ops, float overflow predicates, signed overflow builtins, and unsigned overflow encodings.
Strata/Languages/Core/Options.lean Adds unsignedOverflowCheck option field (default false).
Strata/Languages/Core/Factory.lean Adds float64 to known types; generates BV overflow predicates and BV safe ops; adds Float64 functions to the factory.
Strata/Languages/Core/DDMTransform/ASTtoCST.lean Extends CST translation mappings for safe BV ops (and attempts overflow predicate handling).
Strata/DL/Util/BitVec.lean Adds BitVec.unegOverflow.
Strata/DL/SMT/Translate.lean Rejects float64 sort in Lean-denotation translation.
Strata/DL/SMT/TermType.lean Adds float64 to SMT term primitive types.
Strata/DL/SMT/Term.lean Adds rounding-mode primitive rne and its typing.
Strata/DL/SMT/Op.lean Adds SMT-LIB floating-point ops (fp.add/sub/mul/div, etc.) and abbrev generation.
Strata/DL/SMT/Denote.lean Marks float64 as having no Lean denotation.
Strata/DL/SMT/DDMTransform/Translate.lean Emits SMT-LIB (_ FloatingPoint 11 53) for float64 and emits RNE constant.
Strata/DL/Lambda/LTy.lean Adds LMonoTy.float64.
Strata/DL/Lambda/IntBoolFactory.lean Extends unaryOp helper to accept preconditions (for safe operator construction).
Strata/DL/Imperative/MetaData.lean Adds MetaData.arithmeticOverflow classification string constant.
Strata/DL/Imperative/EvalContext.lean Adds PropertyType.arithmeticOverflow and integrates it into formatting/unreachable semantics.
Strata/Backends/CBMC/GOTO/LambdaToCProverGOTO.lean Maps safe ops and overflow predicates to CBMC GOTO overflow IDs and marks them as signed BV ops where appropriate.
Strata/Backends/CBMC/GOTO/Expr.lean Adds CBMC expression identifiers for minus/mult/unary-minus overflow and helper constructors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…T handling

Address all 5 Copilot review comments:

1. Float64 safe ops now carry overflow preconditions (Comment 5):
   Float64.SafeAdd/Sub/Mul/Div each require ¬Float64.*Overflow(x, y).
   PrecondElim now generates overflow VCs at each Float64 use site.

2. SafeSDiv/SafeSMod div-by-zero classification (Comment 3):
   classifyPrecondition now takes a per-function precondition index.
   For SafeSDiv/SafeSMod, precondition 0 (y≠0) is classified as
   divisionByZero, precondition 1 (INT_MIN/-1) as arithmeticOverflow.

3. ASTtoCST overflow predicate handling (Comments 1 & 2):
   - SNegOverflow/UNegOverflow: changed from neg_expr to Bool.Not
   - Binary overflow predicates (SAddOverflow etc.): added to
     bvBinaryOpMap with boolean approximation for CST printing
   - These only appear in generated assertions, not user code

4. unsignedOverflowCheck documentation (Comment 4):
   Added doc comment explaining the option is forward-looking
   infrastructure, to be wired when front-end languages gain BV types.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
| .Sub => return binOp (if isReal then realSubOp else intSubOp)
| .Mul => return binOp (if isReal then realMulOp else intMulOp)
| .Div => return binOp (if isReal then realDivOp else intSafeDivOp)
| .Add => return binOp (if isFloat64 then float64SafeAddOp else if isReal then realAddOp else intAddOp)
Copy link
Contributor

@keyboardDrummer keyboardDrummer Mar 18, 2026

Choose a reason for hiding this comment

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

I think floats in the Laurel source languages, Java and Python, implement IEEE 754 so they do allow overflows for floating point operations, so I think Laurel's translation of floating ops should not use the 'safe' floating point operators in Core.

I think we could define a subset type for float64 that does not have positive or negative infinity as a value, and then something like var x: finiteFloat64 := a * b would simplify to

var x: float64 := 
  var t := a * b; 
  assert t != Infinity && t != -Infinity; 
  t

But it would be up to the user to indicate when they want to use a finiteFloat64 instead of a float64.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed — IEEE 754 defines overflow to ±∞ as valid behavior, so unconditionally inserting overflow assertions for Laurel float ops would produce false positives for standard Java/Python programs.

Fixed in 5cae06b: the Laurel translator now uses the non-safe float operators by default. I also took the opportunity to unify the approach across all overflow check categories. There's now an OverflowChecks structure in Core.Options:

structure OverflowChecks where
  signedBV   : Bool := true   -- C: signed overflow is UB
  unsignedBV : Bool := false  -- C: unsigned wraps (defined)
  float64    : Bool := false  -- IEEE 754: overflow to ±∞ (defined)

This replaces the previous (unused) unsignedOverflowCheck flag. The config is threaded through LaurelTranslateOptionsTranslateState and exposed via --overflow-checks signed,unsigned,float64 on the CLI. Users who want finite-float checking can opt in with --overflow-checks float64.

The finiteFloat64 subset type idea is interesting for a future iteration — it would let users express the constraint at the type level rather than as a global flag.

@tautschnig tautschnig self-assigned this Mar 19, 2026
tautschnig and others added 2 commits March 19, 2026 10:36
Replace the unused unsignedOverflowCheck bool with a structured
OverflowChecks configuration covering signed BV, unsigned BV, and
float64 overflow checks. Defaults match C/IEEE 754 semantics:
signed BV checked (UB in C), unsigned BV unchecked (wraps), float64
unchecked (overflows to ±∞ per IEEE 754).

- Add OverflowChecks structure to Core.Options
- Wire --overflow-checks CLI flag to StrataVerify
- Thread config through LaurelTranslateOptions and TranslateState
- Laurel translator now consults overflowChecks.float64 to choose
  between safe and non-safe float ops (non-safe by default)

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…hecks

# Conflicts:
#	Strata/Languages/Core/Options.lean
#	Strata/Languages/Laurel/HeapParameterizationConstants.lean
#	Strata/Languages/Laurel/LaurelToCoreTranslator.lean
#	StrataVerify.lean
@tautschnig tautschnig removed their assignment Mar 19, 2026
@tautschnig tautschnig enabled auto-merge March 19, 2026 11:34
@shigoel
Copy link
Contributor

shigoel commented Mar 21, 2026

I haven't looked at this PR deeply, but I'd recommend removing FP support in Strata's SMT dialect from this PR and keeping it focused only on integers for now. Let's go back to the design board before we use SMTLib's FP theory.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants