Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,23 @@ impl GotocCtx<'_, '_> {
return original_expr.clone();
}

// Lower known pointer arithmetic intrinsics directly to CBMC expressions.
// These intrinsics have no GOTO body to inline, so we handle them specially.
// We check that the function name contains a pointer-type path segment to
// avoid false-positives on user-defined functions with similar names.
// TODO: Replace name-based heuristic with a proper intrinsic registry
// (e.g., matching on DefId or a dedicated KaniIntrinsic enum) for
// robustness against mangling variations.
let fn_name = fn_id.to_string();
let is_ptr_fn = fn_name.contains("::ptr::") || fn_name.contains("const_ptr");
let is_ptr_arith = is_ptr_fn
&& (fn_name.contains("wrapping_add") || fn_name.contains("wrapping_byte_offset"));
if is_ptr_arith && arguments.len() >= 2 && arguments[0].typ().is_pointer() {
let ptr = self.inline_as_pure_expr(&arguments[0], visited);
let offset = self.inline_as_pure_expr(&arguments[1], visited);
return ptr.plus(offset);
Comment on lines +526 to +529
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IIUC the Plus in CBMC scales by the pointee size, so this is correct for wrapping_add. But is it correct for wrapping_byte_offset? Maybe the pointer and offset have already been lowered to bytes?

}

let function_body = self.symbol_table.lookup(*fn_id).and_then(|sym| match &sym.value {
SymbolValues::Stmt(stmt) => Some(stmt.clone()),
_ => None,
Expand Down
9 changes: 9 additions & 0 deletions rfc/src/rfcs/0010-quantifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,15 @@ To solve this, Kani generates **pure expression trees** for quantifier bodies:
are inlined by looking up the function body in the symbol table, resolving its
return expression, and substituting parameters — producing a pure expression.

5. **Pointer arithmetic intrinsic lowering**: Wrapping pointer arithmetic functions
(`wrapping_byte_offset`, `wrapping_add`) are compiler intrinsics with no GOTO body
Comment on lines +237 to +238
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about wrapping_sub? I guess it fails because it's using the original inliner?

to inline. The inliner recognizes these by name and lowers them directly to CBMC
`Plus` expressions on pointers. Non-wrapping variants (`offset`, `add`) are not
supported because they trigger CBMC bounds checks inside quantifier bodies. Example:
```rust
kani::forall!(|i in (0, len)| unsafe { *ptr.wrapping_byte_offset(i as isize) == 0 })
```

### Typed quantifier variables

The `forall!` and `exists!` macros support an optional type annotation:
Expand Down
56 changes: 56 additions & 0 deletions tests/kani/Quantifiers/pointer_arithmetic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Z quantifiers

//! Tests for pointer arithmetic inside quantifier predicates.
//! These exercise the intrinsic lowering in `inline_call_as_pure_expr`
//! which converts wrapping_byte_offset/wrapping_add to CBMC Plus.
//!
//! Quantifier ranges are half-open: `|i in (lo, hi)|` means `lo <= i < hi`.
//! All ranges below match the array lengths to stay in-bounds.

#[kani::proof]
fn check_wrapping_byte_offset_forall() {
let arr: [u8; 8] = [0; 8];
let ptr = arr.as_ptr();
unsafe {
assert!(kani::forall!(|i in (0, 8)| *ptr.wrapping_byte_offset(i as isize) == 0));
}
}

#[kani::proof]
fn check_wrapping_byte_offset_exists() {
let arr: [u8; 8] = [0, 0, 0, 42, 0, 0, 0, 0];
let ptr = arr.as_ptr();
unsafe {
assert!(kani::exists!(|i in (0, 8)| *ptr.wrapping_byte_offset(i as isize) == 42));
}
}

#[kani::proof]
fn check_wrapping_add_forall() {
let arr: [u32; 4] = [10, 20, 30, 40];
let ptr = arr.as_ptr();
unsafe {
assert!(kani::forall!(|i in (0, 4)| *ptr.wrapping_add(i) >= 10));
}
}

/// Tests `ptr.wrapping_add(i)` with a different element type (u8 vs u32).
#[kani::proof]
fn check_wrapping_add_u8_forall() {
let arr: [u8; 4] = [1, 2, 3, 4];
let ptr = arr.as_ptr();
unsafe {
assert!(kani::forall!(|i in (0, 4)| *ptr.wrapping_add(i) > 0));
}
}

#[kani::proof]
fn check_wrapping_add_exists() {
let arr: [u32; 4] = [10, 20, 30, 40];
let ptr = arr.as_ptr();
unsafe {
assert!(kani::exists!(|i in (0, 4)| *ptr.wrapping_add(i) == 30));
}
}
Loading