Fix mod_inv precondition: any x is invertible modulo 1 - #622
Open
tautschnig wants to merge 1 commit into
Open
Conversation
The `x % 2 != 0` precondition added to `mod_inv` in commit 6be9ca4 ("Remove spurious comments about the need for quantifiers (model-checking#457)") is violated by `mod_inv`'s only caller: `align_offset::<T>(p, 1)` with `size_of::<T>() > 1` takes the GENERAL_CASE path (since `1 % stride != 0`) and computes `s2 = (stride & 0) >> 0 = 0`, calling `mod_inv(0, 1)`. This happens for example via `<[u16]>::align_to::<u8>()`, so any harness reaching `align_to` with a target alignment of 1 fails the asserted precondition when Kani runs without --no-assert-contracts (the violation is currently masked in CI, which passes that flag). The call is mathematically sound: modulo `m == 1` every value is trivially an inverse of every other (the unique residue is 0), and `align_offset` masks the returned value with `a2 - 1 == 0`. It is the precondition that is too strict, not the caller that is wrong: an inverse of `x` modulo a power of two `m` exists iff `gcd(x, m) == 1`, which for `m > 1` means odd `x`, but for `m == 1` holds for all `x`. Weaken the precondition to `m == 1 || x % 2 != 0` accordingly. Also fix the (kani-disabled) postcondition for the same degenerate case: modulo 1, `wrapping_mul(*result, x) % m` is 0, not 1, so compare against `1 % m` instead of `1`. Verified (Kani 152c6a8c + CBMC 6.10.0) that the four harnesses that fail without --no-assert-contracts on this precondition (slice::verify::align_to_from_u16::align_to_u8, slice::verify::align_to_mut_from_char::align_to_mut_u8, slice::verify::align_to_mut_from_u32::align_to_mut_u8, ptr::verify::check_align_offset_u16) now pass with contracts asserted, and that the eight ptr::verify::check_align_offset* proof harnesses still pass in both configurations. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adjusts the verification contract on ptr::mod_inv inside core::ptr::align_offset to correctly handle the degenerate modulus case m == 1, which arises from a real call path (mod_inv(0, 1)) when aligning with a == 1. The change prevents latent contract violations from surfacing once dependency contract assertions are enabled in Kani.
Changes:
- Weakens the
mod_invprecondition from “xmust be odd” to “m == 1 || xis odd”, matching modular arithmetic form == 1. - Updates the (non-Kani) postcondition to compare against
1 % m, so the ensured property remains correct whenm == 1.
feliperodri
approved these changes
Aug 3, 2026
This was referenced Aug 3, 2026
Open
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.
The
x % 2 != 0precondition onptr::mod_inv, added in 6be9ca4 (#457), is violated bymod_inv's only caller:align_offset::<T>(p, 1)withsize_of::<T>() > 1takes the GENERAL_CASE path (since1 % stride != 0) and computess2 = (stride & 0) >> 0 = 0, callingmod_inv(0, 1). This is reachable e.g. via<[u16]>::align_to::<u8>().The violation is currently invisible in CI because
run-kani.shpasses--no-assert-contracts; with dependency contracts asserted (the Kani default since model-checking/kani#3802), the harnessesslice::verify::align_to_from_u16::align_to_u8,slice::verify::align_to_mut_from_char::align_to_mut_u8,slice::verify::align_to_mut_from_u32::align_to_mut_u8, andptr::verify::check_align_offset_u16all fail on the assertedx % 2 != 0clause.The call is mathematically sound — modulo
m == 1every value is trivially an inverse (the unique residue is 0), andalign_offsetmasks the returned value witha2 - 1 == 0— so it is the precondition that is too strict, not the caller that is wrong: an inverse ofxmodulo a power of twomexists iffgcd(x, m) == 1, which form > 1means oddxbut form == 1holds for allx. This PR weakens the precondition tom == 1 || x % 2 != 0and fixes the (kani-disabled) postcondition for the same degenerate case (% m == 1 % minstead of% m == 1, since modulo 1 the result is 0).Verified with Kani 152c6a8c + CBMC 6.10.0: the four harnesses above now pass with contracts asserted, and the eight
ptr::verify::check_align_offset*proof harnesses pass both with and without--no-assert-contracts.Found while investigating what still blocks removing
--no-assert-contractsfromrun-kani.sh: this is one of two genuine latent contract violations that asserting dependency contracts surfaces (the other: #623).By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.