Skip to content

Commit b8e78e2

Browse files
committed
Add loop contracts and harness for slice::binary_search_by
1 parent c4a1f45 commit b8e78e2

File tree

2 files changed

+39
-1
lines changed

2 files changed

+39
-1
lines changed

library/core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@
263263
#![feature(tbm_target_feature)]
264264
#![feature(wasm_target_feature)]
265265
#![feature(x86_amx_intrinsics)]
266+
#![cfg_attr(kani, feature(proc_macro_hygiene))]
266267
// tidy-alphabetical-end
267268

268269
// allow using `core::` in intra-doc links

library/core/src/slice/mod.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ use crate::simd::{self, Simd};
1515
use crate::ub_checks::assert_unsafe_precondition;
1616
use crate::{fmt, hint, ptr, slice};
1717

18+
#[cfg(kani)]
19+
use crate::kani;
20+
1821
#[unstable(
1922
feature = "slice_internals",
2023
issue = "none",
@@ -2777,18 +2780,20 @@ impl<T> [T] {
27772780
}
27782781
let mut base = 0usize;
27792782

2783+
let mut cmp:Ordering = Equal;
27802784
// This loop intentionally doesn't have an early exit if the comparison
27812785
// returns Equal. We want the number of loop iterations to depend *only*
27822786
// on the size of the input slice so that the CPU can reliably predict
27832787
// the loop count.
2788+
#[cfg_attr(kani, kani::loop_invariant(size <= self.len() && size >= 1 && base <= self.len() && size+base <= self.len()))]
27842789
while size > 1 {
27852790
let half = size / 2;
27862791
let mid = base + half;
27872792

27882793
// SAFETY: the call is made safe by the following inconstants:
27892794
// - `mid >= 0`: by definition
27902795
// - `mid < size`: `mid = size / 2 + size / 4 + size / 8 ...`
2791-
let cmp = f(unsafe { self.get_unchecked(mid) });
2796+
cmp = f(unsafe { self.get_unchecked(mid) });
27922797

27932798
// Binary search interacts poorly with branch prediction, so force
27942799
// the compiler to use conditional moves if supported by the target
@@ -4911,4 +4916,36 @@ impl<const N: usize> fmt::Display for GetManyMutError<N> {
49114916
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49124917
fmt::Display::fmt("an index is out of bounds or appeared multiple times in the array", f)
49134918
}
4919+
}
4920+
4921+
#[cfg(kani)]
4922+
#[unstable(feature = "kani", issue = "none")]
4923+
pub mod verify {
4924+
use super::*;
4925+
4926+
// Copied from https://github.com/model-checking/kani/blob/main/library/kani/src/slice.rs
4927+
// should be removed when these functions are moved to `kani_core`
4928+
pub fn any_slice_of_array<T, const LENGTH: usize>(arr: &[T; LENGTH]) -> &[T] {
4929+
let (from, to) = any_range::<LENGTH>();
4930+
&arr[from..to]
4931+
}
4932+
4933+
fn any_range<const LENGTH: usize>() -> (usize, usize) {
4934+
let from: usize = kani::any();
4935+
let to: usize = kani::any();
4936+
kani::assume(to <= LENGTH);
4937+
kani::assume(from <= to);
4938+
(from, to)
4939+
}
4940+
4941+
#[kani::proof]
4942+
pub fn check_binary_search_by() {
4943+
const ARR_SIZE: usize = 1000;
4944+
let x: [u8; ARR_SIZE] = kani::any();
4945+
let xs = any_slice_of_array(&x);
4946+
let key: u8 = kani::any();
4947+
unsafe {
4948+
xs.binary_search_by(|p| p.cmp(&key));
4949+
}
4950+
}
49144951
}

0 commit comments

Comments
 (0)