Skip to content

Commit 614eb77

Browse files
authored
Add simple ensures, requires, safety predicates (#15)
I also added one proof-of-concept harness to ensure Kani can verify it.
1 parent 3a164b0 commit 614eb77

File tree

12 files changed

+173
-1
lines changed

12 files changed

+173
-1
lines changed

.github/workflows/kani.yml

+12-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ jobs:
2626
matrix:
2727
# Kani does not support windows.
2828
os: [ubuntu-latest, macos-latest]
29+
include:
30+
- os: ubuntu-latest
31+
base: ubuntu
32+
- os: macos-latest
33+
base: macos
2934
steps:
3035
- name: Checkout Library
3136
uses: actions/checkout@v4
@@ -41,6 +46,11 @@ jobs:
4146
path: kani
4247
ref: features/verify-rust-std
4348

49+
- name: Setup Dependencies
50+
working-directory: kani
51+
run: |
52+
./scripts/setup/${{ matrix.base }}/install_deps.sh
53+
4454
- name: Build `Kani`
4555
working-directory: kani
4656
run: |
@@ -52,5 +62,6 @@ jobs:
5262
env:
5363
RUST_BACKTRACE: 1
5464
run: |
55-
kani verify-std -Z unstable-options ./library --target-dir "target"
65+
kani verify-std -Z unstable-options ./library --target-dir ${{ runner.temp }} -Z function-contracts \
66+
-Z mem-predicates -Z ptr-to-ref-cast-checks
5667

.github/workflows/rustc.yml

+3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ jobs:
6161
6262
- name: Run tests
6363
working-directory: upstream
64+
env:
65+
# Avoid error due to unexpected `cfg`.
66+
RUSTFLAGS: "--check-cfg cfg(kani) --check-cfg cfg(feature,values(any()))"
6467
run: |
6568
./configure --set=llvm.download-ci-llvm=true
6669
./x test --stage 0 library/std

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Session.vim
2626
*.rlib
2727
*.rmeta
2828
*.mir
29+
Cargo.lock
2930

3031
## Temporary files
3132
*~

library/contracts/safety/Cargo.toml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
[package]
5+
name = "safety"
6+
version = "0.1.0"
7+
edition = "2021"
8+
license = "MIT OR Apache-2.0"
9+
10+
[lib]
11+
proc-macro = true
12+
13+
[dependencies]
14+
proc-macro2 = "1.0"
15+
proc-macro-error = "1.0.4"
16+
quote = "1.0.20"
17+
syn = { version = "2.0.18", features = ["full"] }

library/contracts/safety/build.rs

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fn main() {
2+
// We add the configurations here to be checked.
3+
println!("cargo:rustc-check-cfg=cfg(kani_host)");
4+
}

library/contracts/safety/src/kani.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use proc_macro::{TokenStream};
2+
use quote::{quote, format_ident};
3+
use syn::{ItemFn, parse_macro_input};
4+
5+
pub(crate) fn requires(attr: TokenStream, item: TokenStream) -> TokenStream {
6+
rewrite_attr(attr, item, "requires")
7+
}
8+
9+
pub(crate) fn ensures(attr: TokenStream, item: TokenStream) -> TokenStream {
10+
rewrite_attr(attr, item, "ensures")
11+
}
12+
13+
fn rewrite_attr(attr: TokenStream, item: TokenStream, name: &str) -> TokenStream {
14+
let args = proc_macro2::TokenStream::from(attr);
15+
let fn_item = parse_macro_input!(item as ItemFn);
16+
let attribute = format_ident!("{}", name);
17+
quote!(
18+
#[kani_core::#attribute(#args)]
19+
#fn_item
20+
).into()
21+
}

library/contracts/safety/src/lib.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//! Implement a few placeholders for contract attributes until they get implemented upstream.
2+
//! Each tool should implement their own version in a separate module of this crate.
3+
4+
use proc_macro::TokenStream;
5+
use proc_macro_error::proc_macro_error;
6+
7+
#[cfg(kani_host)]
8+
#[path = "kani.rs"]
9+
mod tool;
10+
11+
#[cfg(not(kani_host))]
12+
#[path = "runtime.rs"]
13+
mod tool;
14+
15+
#[proc_macro_error]
16+
#[proc_macro_attribute]
17+
pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream {
18+
tool::requires(attr, item)
19+
}
20+
21+
#[proc_macro_error]
22+
#[proc_macro_attribute]
23+
pub fn ensures(attr: TokenStream, item: TokenStream) -> TokenStream {
24+
tool::ensures(attr, item)
25+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use proc_macro::TokenStream;
2+
3+
/// For now, runtime requires is a no-op.
4+
///
5+
/// TODO: At runtime the `requires` should become an assert unsafe precondition.
6+
pub(crate) fn requires(_attr: TokenStream, item: TokenStream) -> TokenStream {
7+
item
8+
}
9+
10+
/// For now, runtime requires is a no-op.
11+
///
12+
/// TODO: At runtime the `ensures` should become an assert as well.
13+
pub(crate) fn ensures(_attr: TokenStream, item: TokenStream) -> TokenStream {
14+
item
15+
}

library/core/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ name = "corebenches"
2424
path = "benches/lib.rs"
2525
test = true
2626

27+
[dependencies]
28+
safety = {path = "../contracts/safety" }
29+
2730
[dev-dependencies]
2831
rand = { version = "0.8.5", default-features = false }
2932
rand_xorshift = { version = "0.3.0", default-features = false }

library/core/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,9 @@ mod unit;
431431
#[stable(feature = "core_primitive", since = "1.43.0")]
432432
pub mod primitive;
433433

434+
#[cfg(kani)]
435+
kani_core::kani_lib!(core);
436+
434437
// Pull in the `core_arch` crate directly into core. The contents of
435438
// `core_arch` are in a different repository: rust-lang/stdarch.
436439
//

library/core/src/ptr/mod.rs

+21
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ use crate::marker::FnPtr;
416416
use crate::ub_checks;
417417

418418
use crate::mem::{self, align_of, size_of, MaybeUninit};
419+
#[cfg(kani)]
420+
use crate::kani;
419421

420422
mod alignment;
421423
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
@@ -1687,6 +1689,7 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
16871689
#[stable(feature = "volatile", since = "1.9.0")]
16881690
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
16891691
#[rustc_diagnostic_item = "ptr_read_volatile"]
1692+
#[safety::requires(ub_checks::can_dereference(src))]
16901693
pub unsafe fn read_volatile<T>(src: *const T) -> T {
16911694
// SAFETY: the caller must uphold the safety contract for `volatile_load`.
16921695
unsafe {
@@ -1766,6 +1769,7 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
17661769
#[stable(feature = "volatile", since = "1.9.0")]
17671770
#[rustc_diagnostic_item = "ptr_write_volatile"]
17681771
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
1772+
#[safety::requires(ub_checks::can_write(dst))]
17691773
pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
17701774
// SAFETY: the caller must uphold the safety contract for `volatile_store`.
17711775
unsafe {
@@ -2290,3 +2294,20 @@ pub macro addr_of($place:expr) {
22902294
pub macro addr_of_mut($place:expr) {
22912295
&raw mut $place
22922296
}
2297+
2298+
#[cfg(kani)]
2299+
#[unstable(feature="kani", issue="none")]
2300+
mod verify {
2301+
use crate::fmt::Debug;
2302+
use super::*;
2303+
use crate::kani;
2304+
2305+
#[kani::proof_for_contract(read_volatile)]
2306+
pub fn check_read_u128() {
2307+
let val = kani::any::<u16>();
2308+
let ptr = &val as *const _;
2309+
let copy = unsafe { read_volatile(ptr) };
2310+
assert_eq!(val, copy);
2311+
}
2312+
}
2313+

library/core/src/ub_checks.rs

+48
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,51 @@ pub(crate) const fn is_nonoverlapping(
160160
// This is just for safety checks so we can const_eval_select.
161161
const_eval_select((src, dst, size, count), comptime, runtime)
162162
}
163+
164+
pub use predicates::*;
165+
166+
/// Provide a few predicates to be used in safety contracts.
167+
///
168+
/// At runtime, they are no-op, and always return true.
169+
#[cfg(not(kani))]
170+
mod predicates {
171+
/// Checks if a pointer can be dereferenced, ensuring:
172+
/// * `src` is valid for reads (see [`crate::ptr`] documentation).
173+
/// * `src` is properly aligned (use `read_unaligned` if not).
174+
/// * `src` points to a properly initialized value of type `T`.
175+
///
176+
/// [`crate::ptr`]: https://doc.rust-lang.org/std/ptr/index.html
177+
pub fn can_dereference<T>(src: *const T) -> bool {
178+
let _ = src;
179+
true
180+
}
181+
182+
/// Check if a pointer can be written to:
183+
/// * `dst` must be valid for writes.
184+
/// * `dst` must be properly aligned. Use `write_unaligned` if this is not the
185+
/// case.
186+
pub fn can_write<T>(dst: *mut T) -> bool {
187+
let _ = dst;
188+
true
189+
}
190+
191+
/// Check if a pointer can be the target of unaligned reads.
192+
/// * `src` must be valid for reads.
193+
/// * `src` must point to a properly initialized value of type `T`.
194+
pub fn can_read_unaligned<T>(src: *const T) -> bool {
195+
let _ = src;
196+
true
197+
}
198+
199+
/// Check if a pointer can be the target of unaligned writes.
200+
/// * `dst` must be valid for writes.
201+
pub fn can_write_unaligned<T>(dst: *mut T) -> bool {
202+
let _ = dst;
203+
true
204+
}
205+
}
206+
207+
#[cfg(kani)]
208+
mod predicates {
209+
pub use crate::kani::mem::{can_dereference, can_write, can_read_unaligned, can_write_unaligned};
210+
}

0 commit comments

Comments
 (0)