Skip to content

Commit 866a195

Browse files
sgpthomascvick32tautschnigcarolynzech
authored
add challenge for safety of primitive conversions (rust-lang#217)
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: Cole Vick <cole.d.vick@gmail.com> Co-authored-by: Michael Tautschnig <mt@debian.org> Co-authored-by: Carolyn Zech <cmzech@amazon.com>
1 parent 928c07e commit 866a195

File tree

2 files changed

+151
-1
lines changed

2 files changed

+151
-1
lines changed

doc/src/SUMMARY.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@
2626
- [11: Safety of Methods for Numeric Primitive Types](./challenges/0011-floats-ints.md)
2727
- [12: Safety of `NonZero`](./challenges/0012-nonzero.md)
2828
- [13: Safety of `CStr`](./challenges/0013-cstr.md)
29-
29+
- [14: Safety of Primitive Conversions](./challenges/0014-convert-num.md)
+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Challenge 14: Safety of Primitive Conversions
2+
3+
- **Status:** Open
4+
- **Tracking Issue:** https://github.com/model-checking/verify-rust-std/issues/220
5+
- **Start date:** 2024/12/15
6+
- **End date:** 2025/2/28
7+
- **Prize:** *TBD*
8+
9+
-------------------
10+
11+
## Goal
12+
13+
Verify the safety of number conversions in `core::convert::num`.
14+
15+
There are two conversions that use unsafe code: conversion from NonZero integer to NonZero integer and conversion from floating point number to integer. All conversions use macros to implement traits for primitive types in bulk. You will need to add contracts into the macros so that contracts are generated for each implementation. As the conversions are all macro generated, it is probably a good idea to write your own macro for generating the harnesses.
16+
17+
### Success Criteria
18+
19+
#### NonZero Conversions
20+
Write a type invariant for `core::num::NonZero`, then write harnesses for all `nonzero` conversions.
21+
22+
Write proof contracts for each NonZero primitive conversion, listed in full below. These conversions are implemented through two macros: `impl_nonzero_int_from_nonzero_int!` and `impl_nonzero_int_try_from_nonzero_int!`.
23+
24+
For each invocation of `impl_nonzero_int_from_nonzero_int!`, prove that the conversion it implements does not cause undefined behavior nor panic. For example, for `impl_nonzero_int_from_nonzero_int!(u8 => u16);`, prove that calling `NonZero<u16>::from(small: NonZero<u8>)` does not cause undefined behavior nor panic for an arbitrary `small` that satisfies the `NonZero` type invariant.
25+
26+
For each invocation of `impl_nonzero_int_try_from_nonzero_int!`, prove that the conversion it implements does not cause undefined behavior. For example, for `impl_nonzero_int_try_from_nonzero_int!(u16 => u8);`, prove that calling `NonZero<u8>::try_from(value: NonZero<u16>)` does not cause undefined behavior for an arbitrary `value` that satisfies the `NonZero` type invariant. Additionally, prove that if the `value` does not fit into the target type (e.g., `value` is too large to fit into a `NonZero<u8>`) that the function panics.
27+
28+
non-zero unsigned integer -> non-zero unsigned integer
29+
- `impl_nonzero_int_from_nonzero_int!(u8 => u16);`
30+
- `impl_nonzero_int_from_nonzero_int!(u8 => u32);`
31+
- `impl_nonzero_int_from_nonzero_int!(u8 => u64);`
32+
- `impl_nonzero_int_from_nonzero_int!(u8 => u128);`
33+
- `impl_nonzero_int_from_nonzero_int!(u8 => usize);`
34+
- `impl_nonzero_int_from_nonzero_int!(u16 => u32);`
35+
- `impl_nonzero_int_from_nonzero_int!(u16 => u64);`
36+
- `impl_nonzero_int_from_nonzero_int!(u16 => u128);`
37+
- `impl_nonzero_int_from_nonzero_int!(u16 => usize);`
38+
- `impl_nonzero_int_from_nonzero_int!(u32 => u64);`
39+
- `impl_nonzero_int_from_nonzero_int!(u32 => u128);`
40+
- `impl_nonzero_int_from_nonzero_int!(u64 => u128);`
41+
42+
non-zero signed integer -> non-zero signed integer
43+
- `impl_nonzero_int_from_nonzero_int!(i8 => i16);`
44+
- `impl_nonzero_int_from_nonzero_int!(i8 => i32);`
45+
- `impl_nonzero_int_from_nonzero_int!(i8 => i64);`
46+
- `impl_nonzero_int_from_nonzero_int!(i8 => i128);`
47+
- `impl_nonzero_int_from_nonzero_int!(i8 => isize);`
48+
- `impl_nonzero_int_from_nonzero_int!(i16 => i32);`
49+
- `impl_nonzero_int_from_nonzero_int!(i16 => i64);`
50+
- `impl_nonzero_int_from_nonzero_int!(i16 => i128);`
51+
- `impl_nonzero_int_from_nonzero_int!(i16 => isize);`
52+
- `impl_nonzero_int_from_nonzero_int!(i32 => i64);`
53+
- `impl_nonzero_int_from_nonzero_int!(i32 => i128);`
54+
- `impl_nonzero_int_from_nonzero_int!(i64 => i128);`
55+
56+
non-zero unsigned -> non-zero signed integer
57+
- `impl_nonzero_int_from_nonzero_int!(u8 => i16);`
58+
- `impl_nonzero_int_from_nonzero_int!(u8 => i32);`
59+
- `impl_nonzero_int_from_nonzero_int!(u8 => i64);`
60+
- `impl_nonzero_int_from_nonzero_int!(u8 => i128);`
61+
- `impl_nonzero_int_from_nonzero_int!(u8 => isize);`
62+
- `impl_nonzero_int_from_nonzero_int!(u16 => i32);`
63+
- `impl_nonzero_int_from_nonzero_int!(u16 => i64);`
64+
- `impl_nonzero_int_from_nonzero_int!(u16 => i128);`
65+
- `impl_nonzero_int_from_nonzero_int!(u32 => i64);`
66+
- `impl_nonzero_int_from_nonzero_int!(u32 => i128);`
67+
- `impl_nonzero_int_from_nonzero_int!(u64 => i128);`
68+
69+
There are also the fallible versions. Remember to cover both the panicking and non-panicking cases.
70+
71+
unsigned non-zero integer -> unsigned non-zero integer
72+
- `impl_nonzero_int_try_from_nonzero_int!(u16 => u8);`
73+
- `impl_nonzero_int_try_from_nonzero_int!(u32 => u8, u16, usize);`
74+
- `impl_nonzero_int_try_from_nonzero_int!(u64 => u8, u16, u32, usize);`
75+
- `impl_nonzero_int_try_from_nonzero_int!(u128 => u8, u16, u32, u64, usize);`
76+
- `impl_nonzero_int_try_from_nonzero_int!(usize => u8, u16, u32, u64, u128);`
77+
78+
signed non-zero integer -> signed non-zero integer
79+
- `impl_nonzero_int_try_from_nonzero_int!(i16 => i8);`
80+
- `impl_nonzero_int_try_from_nonzero_int!(i32 => i8, i16, isize);`
81+
- `impl_nonzero_int_try_from_nonzero_int!(i64 => i8, i16, i32, isize);`
82+
- `impl_nonzero_int_try_from_nonzero_int!(i128 => i8, i16, i32, i64, isize);`
83+
- `impl_nonzero_int_try_from_nonzero_int!(isize => i8, i16, i32, i64, i128);`
84+
85+
unsigned non-zero integer -> signed non-zero integer
86+
- `impl_nonzero_int_try_from_nonzero_int!(u8 => i8);`
87+
- `impl_nonzero_int_try_from_nonzero_int!(u16 => i8, i16, isize);`
88+
- `impl_nonzero_int_try_from_nonzero_int!(u32 => i8, i16, i32, isize);`
89+
- `impl_nonzero_int_try_from_nonzero_int!(u64 => i8, i16, i32, i64, isize);`
90+
- `impl_nonzero_int_try_from_nonzero_int!(u128 => i8, i16, i32, i64, i128, isize);`
91+
- `impl_nonzero_int_try_from_nonzero_int!(usize => i8, i16, i32, i64, i128, isize);`
92+
93+
signed non-zero integer -> unsigned non-zero integer
94+
- `impl_nonzero_int_try_from_nonzero_int!(i8 => u8, u16, u32, u64, u128, usize);`
95+
- `impl_nonzero_int_try_from_nonzero_int!(i16 => u8, u16, u32, u64, u128, usize);`
96+
- `impl_nonzero_int_try_from_nonzero_int!(i32 => u8, u16, u32, u64, u128, usize);`
97+
- `impl_nonzero_int_try_from_nonzero_int!(i64 => u8, u16, u32, u64, u128, usize);`
98+
- `impl_nonzero_int_try_from_nonzero_int!(i128 => u8, u16, u32, u64, u128, usize);`
99+
- `impl_nonzero_int_try_from_nonzero_int!(isize => u8, u16, u32, u64, u128, usize);`
100+
101+
102+
#### Safety for Float to Int
103+
104+
```rust
105+
macro_rules! impl_float_to_int {
106+
($Float:ty => $($Int:ty),+) => {
107+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
108+
impl private::Sealed for $Float {}
109+
$(
110+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
111+
impl FloatToInt<$Int> for $Float {
112+
#[inline]
113+
unsafe fn to_int_unchecked(self) -> $Int {
114+
// SAFETY: the safety contract must be upheld by the caller.
115+
unsafe { crate::intrinsics::float_to_int_unchecked(self) }
116+
}
117+
}
118+
)+
119+
}
120+
}
121+
```
122+
123+
The safety constraints referenced in the comments are that the input value must:
124+
- Not be NaN
125+
- Not be infinite
126+
- Be representable in the return type Int, after truncating off its fractional part
127+
128+
These constraints are given in the [documentation](https://doc.rust-lang.org/std/primitive.f32.html#method.to_int_unchecked).
129+
130+
The intrinsic corresponds to the [fptoui](https://llvm.org/docs/LangRef.html#fptoui-to-instruction)/[fptosi](https://llvm.org/docs/LangRef.html#fptosi-to-instruction) LLVM instructions, which may be useful for reference.
131+
Prove safety for each of the following conversions:
132+
133+
- `impl_float_to_int!(f16 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize)`
134+
- `impl_float_to_int!(f32 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize)`
135+
- `impl_float_to_int!(f64 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize)`
136+
- `impl_float_to_int!(f128 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize)`
137+
138+
### List of UBs
139+
140+
In addition to any properties called out as `SAFETY` comments in the source
141+
code,
142+
all proofs must automatically ensure the absence of the following [undefined behaviors](https://github.com/rust-lang/reference/blob/142b2ed77d33f37a9973772bd95e6144ed9dce43/src/behavior-considered-undefined.md):
143+
144+
* Accessing (loading from or storing to) a place that is dangling or based on a misaligned pointer.
145+
* Reading from uninitialized memory.
146+
* Mutating immutable bytes.
147+
* Producing an invalid value
148+
149+
Note: All solutions to verification challenges need to satisfy the criteria established in the [challenge book](../general-rules.md)
150+
in addition to the ones listed above.

0 commit comments

Comments
 (0)