Skip to content

Commit 7c0d13a

Browse files
zhassan-awstedinski
authored andcommitted
Use dynamic allocation for non-deterministic slices (rust-lang#1011)
* Use dynamic allocation for non-deterministic slices * Rename NonDetSlice to AnySlice
1 parent 7aad91a commit 7c0d13a

File tree

11 files changed

+195
-18
lines changed

11 files changed

+195
-18
lines changed

library/kani/src/slice.rs

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
3-
use crate::{any, any_raw, assume};
4-
use core::ops::{Deref, DerefMut};
3+
use crate::{any, any_raw, assume, Arbitrary};
4+
use std::alloc::{alloc, dealloc, Layout};
5+
use std::ops::{Deref, DerefMut};
56

67
/// Given an array `arr` of length `LENGTH`, this function returns a **valid**
78
/// slice of `arr` with non-deterministic start and end points. This is useful
@@ -38,50 +39,116 @@ fn any_range<const LENGTH: usize>() -> (usize, usize) {
3839
/// between `0..=MAX_SLICE_LENGTH` and with non-deterministic content. This is
3940
/// useful in situations where one wants to verify that all slices with any
4041
/// content and with a length up to `MAX_SLICE_LENGTH` satisfy a certain
41-
/// property. Use `any_slice` to create an instance of this struct.
42+
/// property. Use `any_slice` or `any_raw_slice` to create an instance of this
43+
/// struct.
4244
///
4345
/// # Example:
4446
///
4547
/// ```rust
46-
/// let slice: kani::slice::NonDetSlice<u8, 5> = kani::slice::any_slice();
48+
/// let slice: kani::slice::AnySlice<u8, 5> = kani::slice::any_slice();
4749
/// foo(&slice); // where foo is a function that takes a slice and verifies a property about it
4850
/// ```
49-
pub struct NonDetSlice<T, const MAX_SLICE_LENGTH: usize> {
50-
arr: [T; MAX_SLICE_LENGTH],
51+
pub struct AnySlice<T, const MAX_SLICE_LENGTH: usize> {
52+
layout: Layout,
53+
ptr: *mut T,
5154
slice_len: usize,
5255
}
5356

54-
impl<T, const MAX_SLICE_LENGTH: usize> NonDetSlice<T, MAX_SLICE_LENGTH> {
55-
fn new() -> Self {
56-
let arr: [T; MAX_SLICE_LENGTH] = unsafe { any_raw() };
57-
let slice_len: usize = any();
57+
impl<T, const MAX_SLICE_LENGTH: usize> AnySlice<T, MAX_SLICE_LENGTH> {
58+
fn new() -> Self
59+
where
60+
T: Arbitrary,
61+
{
62+
let any_slice = AnySlice::<T, MAX_SLICE_LENGTH>::alloc_slice();
63+
unsafe {
64+
let mut i = 0;
65+
// Note: even though the guard `i < MAX_SLICE_LENGTH` is redundant
66+
// since the assumption above guarantees that `slice_len` <=
67+
// `MAX_SLICE_LENGTH`, without it, CBMC fails to infer the required
68+
// unwind value, and requires specifying one, which is inconvenient.
69+
// CBMC also fails to infer the unwinding if the loop is simply
70+
// written as:
71+
// for i in 0..slice_len {
72+
// *(ptr as *mut T).add(i) = any();
73+
// }
74+
while i < any_slice.slice_len && i < MAX_SLICE_LENGTH {
75+
*any_slice.ptr.add(i) = any();
76+
i += 1;
77+
}
78+
}
79+
any_slice
80+
}
81+
82+
fn new_raw() -> Self {
83+
let any_slice = AnySlice::<T, MAX_SLICE_LENGTH>::alloc_slice();
84+
unsafe {
85+
let mut i = 0;
86+
// See note on `MAX_SLICE_LENGTH` in `new` method above
87+
while i < any_slice.slice_len && i < MAX_SLICE_LENGTH {
88+
*any_slice.ptr.add(i) = any_raw();
89+
i += 1;
90+
}
91+
}
92+
any_slice
93+
}
94+
95+
fn alloc_slice() -> Self {
96+
let slice_len = any();
5897
assume(slice_len <= MAX_SLICE_LENGTH);
59-
Self { arr, slice_len }
98+
let layout = Layout::array::<T>(slice_len).unwrap();
99+
let ptr = if slice_len == 0 { std::ptr::null() } else { unsafe { alloc(layout) } };
100+
Self { layout, ptr: ptr as *mut T, slice_len }
60101
}
61102

62103
pub fn get_slice(&self) -> &[T] {
63-
&self.arr[..self.slice_len]
104+
if self.slice_len == 0 {
105+
&[]
106+
} else {
107+
unsafe { std::slice::from_raw_parts(self.ptr, self.slice_len) }
108+
}
64109
}
65110

66111
pub fn get_slice_mut(&mut self) -> &mut [T] {
67-
&mut self.arr[..self.slice_len]
112+
if self.slice_len == 0 {
113+
&mut []
114+
} else {
115+
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.slice_len) }
116+
}
68117
}
69118
}
70119

71-
impl<T, const MAX_SLICE_LENGTH: usize> Deref for NonDetSlice<T, MAX_SLICE_LENGTH> {
120+
impl<T, const MAX_SLICE_LENGTH: usize> Drop for AnySlice<T, MAX_SLICE_LENGTH> {
121+
fn drop(&mut self) {
122+
if self.slice_len > 0 {
123+
assert!(!self.ptr.is_null());
124+
unsafe {
125+
dealloc(self.ptr as *mut u8, self.layout);
126+
}
127+
}
128+
}
129+
}
130+
131+
impl<T, const MAX_SLICE_LENGTH: usize> Deref for AnySlice<T, MAX_SLICE_LENGTH> {
72132
type Target = [T];
73133

74134
fn deref(&self) -> &Self::Target {
75135
self.get_slice()
76136
}
77137
}
78138

79-
impl<T, const MAX_SLICE_LENGTH: usize> DerefMut for NonDetSlice<T, MAX_SLICE_LENGTH> {
139+
impl<T, const MAX_SLICE_LENGTH: usize> DerefMut for AnySlice<T, MAX_SLICE_LENGTH> {
80140
fn deref_mut(&mut self) -> &mut Self::Target {
81141
self.get_slice_mut()
82142
}
83143
}
84144

85-
pub fn any_slice<T, const MAX_SLICE_LENGTH: usize>() -> NonDetSlice<T, MAX_SLICE_LENGTH> {
86-
NonDetSlice::<T, MAX_SLICE_LENGTH>::new()
145+
pub fn any_slice<T, const MAX_SLICE_LENGTH: usize>() -> AnySlice<T, MAX_SLICE_LENGTH>
146+
where
147+
T: Arbitrary,
148+
{
149+
AnySlice::<T, MAX_SLICE_LENGTH>::new()
150+
}
151+
152+
pub unsafe fn any_raw_slice<T, const MAX_SLICE_LENGTH: usize>() -> AnySlice<T, MAX_SLICE_LENGTH> {
153+
AnySlice::<T, MAX_SLICE_LENGTH>::new_raw()
87154
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Status: FAILURE\
2+
dereference failure: pointer outside object bounds\
3+
in function check_out_of_bounds
4+
5+
Status: FAILURE\
6+
pointer arithmetic: pointer outside object bounds
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that Kani reports out-of-bound accesses on a non-det slice
5+
// created using kani::slice::any_slice
6+
7+
#[kani::proof]
8+
fn check_out_of_bounds() {
9+
let bytes = kani::slice::any_slice::<i32, 8>();
10+
let val = unsafe { *bytes.get_slice().as_ptr().offset(1) };
11+
assert_eq!(val - val, 0);
12+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Failed Checks: assertion failed: s.len() != 0
2+
Failed Checks: assertion failed: s.len() != 1
3+
Failed Checks: assertion failed: s.len() != 2
4+
Failed Checks: assertion failed: s.len() != 3
5+
Failed Checks: assertion failed: s.len() != 4
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that non-det slices created using kani::slice::any_slice can
5+
// assume any length up the specified maximum
6+
7+
#[kani::proof]
8+
fn check_possible_slice_lengths() {
9+
let s = kani::slice::any_slice::<i32, 4>();
10+
assert!(s.len() != 0);
11+
assert!(s.len() != 1);
12+
assert!(s.len() != 2);
13+
assert!(s.len() != 3);
14+
assert!(s.len() != 4);
15+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Status: FAILURE\
2+
dereference failure: pointer outside object bounds\
3+
in function check_out_of_bounds
4+
5+
Status: FAILURE\
6+
pointer arithmetic: pointer outside object bounds
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that Kani reports out-of-bound accesses on a non-det slice
5+
// created using kani::slice::any_slice
6+
7+
#[kani::proof]
8+
fn check_out_of_bounds() {
9+
let mut bytes = kani::slice::any_slice::<u8, 5>();
10+
let val = unsafe { *bytes.get_slice().as_ptr().add(4) };
11+
kani::assume(val != 0);
12+
assert_ne!(val, 0);
13+
}

tests/kani/NondetSlices/test2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ fn check(s: &[u8]) {
1010
#[kani::proof]
1111
fn main() {
1212
// returns a slice of length between 0 and 5 with non-det content
13-
let slice: kani::slice::NonDetSlice<u8, 5> = kani::slice::any_slice();
13+
let slice: kani::slice::AnySlice<u8, 5> = kani::slice::any_slice();
1414
check(&slice);
1515
}

tests/kani/NondetSlices/test3.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test uses kani::slice::any_slice of i32
5+
6+
// kani-flags: --unwind 6
7+
8+
#[kani::proof]
9+
fn check_any_slice_i32() {
10+
let s = kani::slice::any_slice::<i32, 5>();
11+
s.iter().for_each(|x| kani::assume(*x < 10 && *x > -20));
12+
let sum = s.iter().fold(0, |acc, x| acc + x);
13+
assert!(sum <= 45); // 9 * 5
14+
assert!(sum >= -95); // 19 * 5
15+
}

tests/kani/NondetSlices/test4.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that values returned by `kani::slice::any_slice` satisfy the
5+
// type invariant
6+
7+
// kani-flags: --unwind 4
8+
9+
extern crate kani;
10+
use kani::slice::{any_slice, AnySlice};
11+
use kani::Invariant;
12+
13+
#[kani::proof]
14+
fn check_any_slice_valid() {
15+
let s: AnySlice<char, 3> = any_slice();
16+
for i in s.get_slice() {
17+
assert!(i.is_valid());
18+
}
19+
}

tests/kani/NondetSlices/test5.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This test checks that values returned by `kani::slice::any_raw_slice` do
5+
// *not* necessarily satisfy the type invariant
6+
7+
// kani-flags: --unwind 4
8+
9+
extern crate kani;
10+
use kani::slice::{any_raw_slice, AnySlice};
11+
use kani::Invariant;
12+
13+
#[kani::proof]
14+
fn check_any_raw_slice_invalid() {
15+
let s: AnySlice<char, 3> = unsafe { any_raw_slice() };
16+
for i in s.get_slice() {
17+
kani::expect_fail(i.is_valid(), "any_raw_slice values may not be valid");
18+
}
19+
}

0 commit comments

Comments
 (0)