Add automated overflow/underflow checks for bitvector and floating-point arithmetic#592
Add automated overflow/underflow checks for bitvector and floating-point arithmetic#592tautschnig wants to merge 11 commits intomainfrom
Conversation
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>
There was a problem hiding this comment.
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
float64sort and Float64 operators/predicates, and updates Laurel’sTFloat64translation to use real Float64 operators. - Adds reporting/plumbing for the new
arithmeticOverflowproperty 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 LaurelTranslateOptions → TranslateState 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.
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
|
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. |
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:
Bv32.SafeAdd) carry built-in preconditions asserting that overflow does not occur.PrecondElimtransform automatically generates verification conditions at each use site.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/SafeNegwith preconditions using SMT-LIB'sbvsaddo/bvssubo/bvsmulo/bvnegoSigned division overflow (
INT_MIN / -1):SafeSDiv/SafeSModwith dual preconditions: divisor ≠ 0 AND ¬(dividend = INT_MIN ∧ divisor = −1)Unsigned bitvector overflow (configurable via
unsignedOverflowCheckoption):SafeUAdd/SafeUSub/SafeUMul/SafeUNegwith unsigned overflow predicatesIEEE 754 Float64:
float64type in the SMT layer, encoded as(_ FloatingPoint 11 53)Float64.SafeAdd/SafeSub/SafeMul/SafeDivwith overflow predicatesfloat_overflow_check)TFloat64now translates to real Float64 (was"Float64IsNotSupportedYet")GOTO backend:
overflow-+,overflow--,overflow-*,overflow-unary-expressionsKey design decisions
Bv32.Add(wrapping) andBv32.SafeAdd(checked) coexist. Front-end translators choose which to emit.unsignedOverflowCheck := true.Description of changes:
New
arithmeticOverflowproperty type alongside the existingdivisionByZero, 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.