Skip to content

Commit db8e5a7

Browse files
authored
transmute_unchecked contracts and harnesses (#185)
This is a draft pull request towards solving #19. ## Changes - Added wrappers for `transmute_unchecked()` - Annotated these wrappers with contracts - Wrote harnesses that verify these wrappers Note: the reason we write wrappers for `transmute_unchecked()` and we annotate those wrappers is that function contracts do not appear to be currently supported for compiler intrinsics (as discussed in [rust-lang#3345](model-checking/kani#3345)). Also, rather than using a single wrapper for `transmute_unchecked()`, we write several with different constraints on the input (since leaving the function parameters completely generic severely restricts what we can do in the contracts, e.g., testing for equality). This is not intended to be a complete solution for verifying `transmute_unchecked()`, but instead a proof of concept to see how aligned this is with the expected solution. Any feedback would be greatly appreciated -- thank you! By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: AlexLB99 <[email protected]>
1 parent 5699f3c commit db8e5a7

File tree

1 file changed

+100
-0
lines changed
  • library/core/src/intrinsics

1 file changed

+100
-0
lines changed

library/core/src/intrinsics/mod.rs

+100
Original file line numberDiff line numberDiff line change
@@ -4642,6 +4642,106 @@ mod verify {
46424642
let dst = if kani::any() { gen_any_ptr(&mut buffer2) } else { gen_any_ptr(&mut buffer1) };
46434643
unsafe { copy_nonoverlapping(src, dst, kani::any()) }
46444644
}
4645+
4646+
//We need this wrapper because transmute_unchecked is an intrinsic, for which Kani does
4647+
//not currently support contracts (https://github.com/model-checking/kani/issues/3345)
4648+
#[requires(crate::mem::size_of::<T>() == crate::mem::size_of::<U>())] //T and U have same size (transmute_unchecked does not guarantee this)
4649+
#[requires(ub_checks::can_dereference(&input as *const T as *const U))] //output can be deref'd as value of type U
4650+
#[allow(dead_code)]
4651+
unsafe fn transmute_unchecked_wrapper<T,U>(input: T) -> U {
4652+
unsafe { transmute_unchecked(input) }
4653+
}
4654+
4655+
//generates harness that transmutes arbitrary values of input type to output type
4656+
//use when you expect all resulting bit patterns of output type to be valid
4657+
macro_rules! transmute_unchecked_should_succeed {
4658+
($harness:ident, $src:ty, $dst:ty) => {
4659+
#[kani::proof_for_contract(transmute_unchecked_wrapper)]
4660+
fn $harness() {
4661+
let src: $src = kani::any();
4662+
let dst: $dst = unsafe { transmute_unchecked_wrapper(src) };
4663+
}
4664+
};
4665+
}
4666+
4667+
//generates harness that transmutes arbitrary values of input type to output type
4668+
//use when you expect some resulting bit patterns of output type to be invalid
4669+
macro_rules! transmute_unchecked_should_fail {
4670+
($harness:ident, $src:ty, $dst:ty) => {
4671+
#[kani::proof]
4672+
#[kani::stub_verified(transmute_unchecked_wrapper)]
4673+
#[kani::should_panic]
4674+
fn $harness() {
4675+
let src: $src = kani::any();
4676+
let dst: $dst = unsafe { transmute_unchecked_wrapper(src) };
4677+
}
4678+
};
4679+
}
4680+
4681+
//transmute between the 4-byte numerical types
4682+
transmute_unchecked_should_succeed!(transmute_unchecked_i32_to_u32, i32, u32);
4683+
transmute_unchecked_should_succeed!(transmute_unchecked_u32_to_i32, u32, i32);
4684+
transmute_unchecked_should_succeed!(transmute_unchecked_i32_to_f32, i32, f32);
4685+
transmute_unchecked_should_succeed!(transmute_unchecked_f32_to_i32, f32, i32);
4686+
transmute_unchecked_should_succeed!(transmute_unchecked_u32_to_f32, u32, f32);
4687+
transmute_unchecked_should_succeed!(transmute_unchecked_f32_to_u32, f32, u32);
4688+
//transmute between the 8-byte numerical types
4689+
transmute_unchecked_should_succeed!(transmute_unchecked_i64_to_u64, i64, u64);
4690+
transmute_unchecked_should_succeed!(transmute_unchecked_u64_to_i64, u64, i64);
4691+
transmute_unchecked_should_succeed!(transmute_unchecked_i64_to_f64, i64, f64);
4692+
transmute_unchecked_should_succeed!(transmute_unchecked_f64_to_i64, f64, i64);
4693+
transmute_unchecked_should_succeed!(transmute_unchecked_u64_to_f64, u64, f64);
4694+
transmute_unchecked_should_succeed!(transmute_unchecked_f64_to_u64, f64, u64);
4695+
//transmute between arrays of bytes and numerical types
4696+
transmute_unchecked_should_succeed!(transmute_unchecked_arr_to_u32, [u8; 4], u32);
4697+
transmute_unchecked_should_succeed!(transmute_unchecked_u32_to_arr, u32, [u8; 4]);
4698+
transmute_unchecked_should_succeed!(transmute_unchecked_arr_to_u64, [u8; 8], u64);
4699+
transmute_unchecked_should_succeed!(transmute_unchecked_u64_to_arr, u64, [u8; 8]);
4700+
//transmute to type with potentially invalid bit patterns
4701+
transmute_unchecked_should_fail!(transmute_unchecked_u8_to_bool, u8, bool);
4702+
transmute_unchecked_should_fail!(transmute_unchecked_u32_to_char, u32, char);
4703+
4704+
//tests that transmute works correctly when transmuting something with zero size
4705+
#[kani::proof_for_contract(transmute_unchecked_wrapper)]
4706+
fn transmute_zero_size() {
4707+
let empty_arr: [u8;0] = [];
4708+
let unit_val: () = unsafe { transmute_unchecked_wrapper(empty_arr) };
4709+
assert!(unit_val == ());
4710+
}
4711+
4712+
//generates harness that transmutes arbitrary values two ways
4713+
//i.e. (src -> dest) then (dest -> src)
4714+
//We then check that the output is equal to the input
4715+
//This tests that transmute does not mutate the bit patterns
4716+
//Note: this would not catch reversible mutations
4717+
//e.g., deterministically flipping a bit
4718+
macro_rules! transmute_unchecked_two_ways {
4719+
($harness:ident, $src:ty, $dst:ty) => {
4720+
#[kani::proof_for_contract(transmute_unchecked_wrapper)]
4721+
fn $harness() {
4722+
let src: $src = kani::any();
4723+
let dst: $dst = unsafe { transmute_unchecked_wrapper(src) };
4724+
let src2: $src = unsafe { transmute_unchecked_wrapper(dst) };
4725+
assert_eq!(src,src2);
4726+
}
4727+
};
4728+
}
4729+
4730+
//transmute 2-ways between the 4-byte numerical types
4731+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_i32_to_u32, i32, u32);
4732+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_u32_to_i32, u32, i32);
4733+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_i32_to_f32, i32, f32);
4734+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_u32_to_f32, u32, f32);
4735+
//transmute 2-ways between the 8-byte numerical types
4736+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_i64_to_u64, i64, u64);
4737+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_u64_to_i64, u64, i64);
4738+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_i64_to_f64, i64, f64);
4739+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_u64_to_f64, u64, f64);
4740+
//transmute 2-ways between arrays of bytes and numerical types
4741+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_arr_to_u32, [u8; 4], u32);
4742+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_u32_to_arr, u32, [u8; 4]);
4743+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_arr_to_u64, [u8; 8], u64);
4744+
transmute_unchecked_two_ways!(transmute_unchecked_2ways_u64_to_arr, u64, [u8; 8]);
46454745

46464746
// FIXME: Enable this harness once <https://github.com/model-checking/kani/issues/90> is fixed.
46474747
// Harness triggers a spurious failure when writing 0 bytes to an invalid memory location,

0 commit comments

Comments
 (0)