Add division-by-zero verification for Laurel via Core safe operators#510
Add division-by-zero verification for Laurel via Core safe operators#510keyboardDrummer merged 12 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a Laurel-to-Laurel transformation pass intended to prevent division-by-zero by inserting assert divisor != 0 before division/modulo operations, with a new CLI command to run the pass + verification and a new test suite for the transformation.
Changes:
- Introduce
insertDivisionByZeroChecksLaurel transformation to add non-zero divisor assertions. - Add unit + end-to-end tests for the transformation and verification behavior.
- Add
laurelDivCheckCLI command and register it under the “Laurel” command group.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| Strata/Languages/Laurel/DivisionByZeroCheck.lean | Implements the div-by-zero assertion insertion pass. |
| StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean | Adds unit tests for AST output and an e2e verification test for unsafe division. |
| StrataMain.lean | Adds and wires a laurelDivCheck command to run the pass and verification. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Implement a transformation pass that inserts 'assert divisor != 0' before every division and modulo operation (Div, Mod, DivT, ModT) in Laurel programs. New files: - Strata/Languages/Laurel/DivisionByZeroCheck.lean: the transformation - StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean: tests including unit tests verifying the transformed AST structure, and end-to-end tests that verify safe division passes and unsafe division is flagged Modified files: - StrataMain.lean: add 'laurelDivCheck' command to parse a Laurel file, apply the division-by-zero check transformation, and run verification Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
- Remove unused emptyMd and bare definitions (review comment 1) - In collectDivChecks, only collect checks from the condition of IfThenElse and While, not from their branches/body. Branch checks are already inserted inside each branch by insertDivChecksStmt, avoiding false positives on non-taken branches (review comment 2) - Add unit test for division inside an if-then-else branch to verify the check is inserted inside the branch, not hoisted out (comment 5) Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Instead of hoisting division-by-zero assertions out of quantifiers (which would reference bound variables out of scope), rewrite: - forall(x) => body → forall(x) => (divisor != 0) ==> body - exists(x) => body → exists(x) => (divisor != 0) && body Add insertDivChecksExpr for expression-level rewriting and refactor collectDivChecks to use a shared collectDivConditions helper. Add unit test for forall with division (review comment 3). Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
For isFunctional procedures, inserting assert statements would break purity and prevent translation to Core functions. Instead, collect all division conditions from the body and add them as 'requires' preconditions. Callers of the function must then prove the divisor is non-zero. Also rewrite quantifier bodies inside functional procedure bodies using insertDivChecksExpr. Add unit test verifying the precondition is generated, and an e2e test verifying that calling a pure division function with an unconstrained argument is flagged as an error (review comment 4). Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
9d5e5d2 to
e87560c
Compare
|
I haven't looked in detail yet, but is there a reason this does not just translate to the Core safe division/mod operators which automatically do this check? |
Remove the Laurel-to-Laurel DivisionByZeroCheck transformation and instead translate Laurel's division/modulo operations (Div, Mod, DivT, ModT) to Core's safe operators (Int.SafeDiv, Int.SafeMod, Int.SafeDivT, Int.SafeModT). These safe operators have built-in preconditions (divisor ≠ 0) that the PrecondElim transform automatically converts into verification conditions, eliminating the need for any Laurel-level instrumentation. Changes: - Add Int.SafeDivT and Int.SafeModT operators (with preconditions) to the Lambda IntBoolFactory and Core Factory, mirroring the existing SafeDiv/SafeMod - Update SMT encoder and ASTtoCST to handle the new operators - Change LaurelToCoreTranslator to emit safe operators for all division/modulo - Remove DivisionByZeroCheck.lean and the laurelDivCheck CLI command - Adapt DivisionByZeroCheckTest to use the standard Laurel verification pipeline - Update expected test outputs for factory function listings Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Thank you, I simply wasn't aware of their existence! Implementation completely reworked in the latest commit, pull-request description updated to reflect what actually happens. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add safedivt_expr and safemodt_expr grammar nodes (printing as /t and %t) so that Int.DivT/Int.SafeDivT and Int.ModT/Int.SafeModT round-trip correctly through the Core DDM concrete syntax, preserving the truncating-vs-euclidean distinction. - Grammar.lean: add safedivt_expr (/t) and safemodt_expr (%t) - ASTtoCST.lean: map DivT/SafeDivT and ModT/SafeModT to the new nodes - Translate.lean: add CST→AST mappings and pattern match entries Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
The Laurel pipeline now emits Int.SafeDiv for floor division, which produces an additional passing verification condition (init_calls_Int.SafeDiv_0) in the test_arithmetic expected output. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
|
Some thoughts on how this connects to source languages. In Java, dividing an integer by zero throws an Example (uses some made-up-syntax for exception handling): |
Yes, I think it would just mean the preconditions of SafeDiv are guaranteed to be upheld. BTW: the tooling that compiles Java to Laurel may have to enable the passes that create those |
StrataTest/Languages/Python/expected_laurel/test_arithmetic.expected
Outdated
Show resolved
Hide resolved
…ator Attach the Python statement's source range metadata to LocalVariable nodes and their initializer expressions in the AnnAssign handler. This ensures that PrecondElim-generated assertions (e.g., division-by-zero checks from SafeDiv) carry source location info through the pipeline. Previously, LocalVariable nodes were created with mkStmtExprMd (empty metadata), so the downstream LiftImperativeExpressions pass—which uses the initializer expression's metadata for the output LocalVariable—would produce nodes with no file range. The verifier output then lacked line/column info for these assertions. Addresses review comment by andrewmwells-amazon on PR #510. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…translator Extend the metadata propagation from the previous commit to cover all statement types in translateStmt: Assign, self.field assignments, IfThenElse, While, Return, Assert wrapper blocks, Expr statements, Try blocks, and For loop blocks. This ensures that any future diagnostics generated from these statement types will carry the Python source location through the pipeline. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
3942b50
Previously Int.DivT and Int.SafeDivT both mapped to safedivt_expr in the AST-to-CST printer, losing the safe/unsafe distinction on round-trip. Same for Int.ModT vs Int.SafeModT. Add divt_expr (prints as 'divt') and modt_expr (prints as 'modt') CST nodes, mirroring the existing div_expr/safediv_expr pattern. Update the CST-to-AST translator and isArithOp match accordingly. Addresses review comment by joehendrix on PR #510. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
The previous commit used 'divt' and 'modt' as infix keywords, which conflicts with user-defined identifiers in Boogie programs (e.g., the DivMod.bpl test defines functions named divt and modt). Switch to function-call syntax Int.DivT(a, b) and Int.ModT(a, b) instead, which avoids introducing new keywords into the grammar. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
cf22418
joehendrix
left a comment
There was a problem hiding this comment.
Reapproved after reviewing the latest change — function-call syntax for unchecked divt/modt CST nodes looks good.
…trata-org#510) Laurel division and modulo operations (/, %, /t, %t) now fail verification when the divisor may be zero. This is achieved by translating Laurel's division/modulo to Core's safe operator variants (Int.SafeDiv, Int.SafeMod, Int.SafeDivT, Int.SafeModT), which carry a built-in y ≠ 0 precondition. The existing PrecondElim transform then automatically generates the necessary verification conditions at each use site — no Laurel-level instrumentation is required. *Description of changes:* New Core operators — Int.SafeDivT and Int.SafeModT added to the Lambda IntBoolFactory and Core Factory, mirroring the existing Int.SafeDiv/Int.SafeMod. SMT encoding and AST-to-CST printing updated accordingly. Laurel-to-Core translator — Div/Mod/DivT/ModT now emit the safe operator variants instead of the unchecked ones. End-to-end tests — New DivisionByZeroCheckTest.lean verifies: - Safe division with a known non-zero divisor passes ✅ - Division by an unconstrained parameter fails ❌ - A pure function with an explicit requires y != 0 precondition passes, but callers that don't satisfy it fail ❌ 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. --------- Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
PR #510 added safe truncating division/modulo operators with division-by-zero preconditions. Map them to the same GOTO operators as their non-safe counterparts (Int.DivT/Int.ModT), since the precondition check is handled separately by the Laurel-to-Core translation.
Laurel division and modulo operations (/, %, /t, %t) now fail verification when the divisor may be zero.
This is achieved by translating Laurel's division/modulo to Core's safe operator variants (Int.SafeDiv, Int.SafeMod,
Int.SafeDivT, Int.SafeModT), which carry a built-in y ≠ 0 precondition. The existing PrecondElim transform then
automatically generates the necessary verification conditions at each use site — no Laurel-level instrumentation is
required.
Description of changes:
New Core operators — Int.SafeDivT and Int.SafeModT added to the Lambda IntBoolFactory and Core Factory, mirroring the
existing Int.SafeDiv/Int.SafeMod. SMT encoding and AST-to-CST printing updated accordingly.
Laurel-to-Core translator — Div/Mod/DivT/ModT now emit the safe operator variants instead of the unchecked ones.
End-to-end tests — New DivisionByZeroCheckTest.lean verifies:
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.