Skip to content

Commit b3ebaec

Browse files
committed
feat(quantifiers): Support pointer arithmetic in quantifier predicates
Lower known pointer arithmetic intrinsics (wrapping_add, wrapping_byte_offset, arith_offset, offset) directly to CBMC pointer Plus expressions in the pure expression inliner. These intrinsics have no GOTO body to inline, so they need special handling in inline_call_as_pure_expr. This enables quantifier predicates like: forall!(|i in (0, len)| unsafe { *ptr.wrapping_byte_offset(i as isize) == 0 }) Changes: - goto_ctx.rs: Recognize pointer arithmetic intrinsics by name and emit ptr.plus(offset) directly - rfc/0010-quantifiers.md: Document pointer arithmetic intrinsic lowering in the Detailed Design section - tests/kani/Quantifiers/pointer_arithmetic.rs: Dedicated test with 3 harnesses (wrapping_byte_offset forall/exists, wrapping_add forall)
1 parent d2c6dca commit b3ebaec

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,23 @@ impl GotocCtx<'_, '_> {
512512
return original_expr.clone();
513513
}
514514

515+
// Lower known pointer arithmetic intrinsics directly to CBMC expressions.
516+
// These intrinsics have no GOTO body to inline, so we handle them specially.
517+
// We check that the function name contains a pointer-type path segment to
518+
// avoid false-positives on user-defined functions with similar names.
519+
// TODO: Replace name-based heuristic with a proper intrinsic registry
520+
// (e.g., matching on DefId or a dedicated KaniIntrinsic enum) for
521+
// robustness against mangling variations.
522+
let fn_name = fn_id.to_string();
523+
let is_ptr_fn = fn_name.contains("::ptr::") || fn_name.contains("const_ptr");
524+
let is_ptr_arith = is_ptr_fn
525+
&& (fn_name.contains("wrapping_add") || fn_name.contains("wrapping_byte_offset"));
526+
if is_ptr_arith && arguments.len() >= 2 && arguments[0].typ().is_pointer() {
527+
let ptr = self.inline_as_pure_expr(&arguments[0], visited);
528+
let offset = self.inline_as_pure_expr(&arguments[1], visited);
529+
return ptr.plus(offset);
530+
}
531+
515532
let function_body = self.symbol_table.lookup(*fn_id).and_then(|sym| match &sym.value {
516533
SymbolValues::Stmt(stmt) => Some(stmt.clone()),
517534
_ => None,

rfc/src/rfcs/0010-quantifiers.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,15 @@ To solve this, Kani generates **pure expression trees** for quantifier bodies:
234234
are inlined by looking up the function body in the symbol table, resolving its
235235
return expression, and substituting parameters — producing a pure expression.
236236

237+
5. **Pointer arithmetic intrinsic lowering**: Wrapping pointer arithmetic functions
238+
(`wrapping_byte_offset`, `wrapping_add`) are compiler intrinsics with no GOTO body
239+
to inline. The inliner recognizes these by name and lowers them directly to CBMC
240+
`Plus` expressions on pointers. Non-wrapping variants (`offset`, `add`) are not
241+
supported because they trigger CBMC bounds checks inside quantifier bodies. Example:
242+
```rust
243+
kani::forall!(|i in (0, len)| unsafe { *ptr.wrapping_byte_offset(i as isize) == 0 })
244+
```
245+
237246
### Typed quantifier variables
238247

239248
The `forall!` and `exists!` macros support an optional type annotation:
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Z quantifiers
4+
5+
//! Tests for pointer arithmetic inside quantifier predicates.
6+
//! These exercise the intrinsic lowering in `inline_call_as_pure_expr`
7+
//! which converts wrapping_byte_offset/wrapping_add to CBMC Plus.
8+
//!
9+
//! Quantifier ranges are half-open: `|i in (lo, hi)|` means `lo <= i < hi`.
10+
//! All ranges below match the array lengths to stay in-bounds.
11+
12+
#[kani::proof]
13+
fn check_wrapping_byte_offset_forall() {
14+
let arr: [u8; 8] = [0; 8];
15+
let ptr = arr.as_ptr();
16+
unsafe {
17+
assert!(kani::forall!(|i in (0, 8)| *ptr.wrapping_byte_offset(i as isize) == 0));
18+
}
19+
}
20+
21+
#[kani::proof]
22+
fn check_wrapping_byte_offset_exists() {
23+
let arr: [u8; 8] = [0, 0, 0, 42, 0, 0, 0, 0];
24+
let ptr = arr.as_ptr();
25+
unsafe {
26+
assert!(kani::exists!(|i in (0, 8)| *ptr.wrapping_byte_offset(i as isize) == 42));
27+
}
28+
}
29+
30+
#[kani::proof]
31+
fn check_wrapping_add_forall() {
32+
let arr: [u32; 4] = [10, 20, 30, 40];
33+
let ptr = arr.as_ptr();
34+
unsafe {
35+
assert!(kani::forall!(|i in (0, 4)| *ptr.wrapping_add(i) >= 10));
36+
}
37+
}
38+
39+
/// Tests `ptr.wrapping_add(i)` with a different element type (u8 vs u32).
40+
#[kani::proof]
41+
fn check_wrapping_add_u8_forall() {
42+
let arr: [u8; 4] = [1, 2, 3, 4];
43+
let ptr = arr.as_ptr();
44+
unsafe {
45+
assert!(kani::forall!(|i in (0, 4)| *ptr.wrapping_add(i) > 0));
46+
}
47+
}

0 commit comments

Comments
 (0)