Skip to content

Write contracts + pre/post conditions for all unsafe methods in duration #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Dec 11, 2024
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ced3509
write contracts + pre/post conditions for all unsafe methods in time
sgpthomas Oct 24, 2024
0294fa6
Merge branch 'main' into verify_duration_challenge9
sgpthomas Oct 24, 2024
6639910
remove redundant checks + reorder harnesses to match fn def order
sgpthomas Oct 25, 2024
bc9a6d6
add panic harness for Duration::new
sgpthomas Oct 25, 2024
e0abb38
re-add debug_assert
sgpthomas Oct 25, 2024
ba86e4f
Merge branch 'verify_duration_challenge9' of github.com:sgpthomas/ver…
sgpthomas Oct 25, 2024
e63b29d
Merge branch 'main' into verify_duration_challenge9
sgpthomas Nov 7, 2024
fc338af
update TempInvariant to Invariant trait
cvick32 Nov 7, 2024
5fe6c11
revert Cargo.lock
cvick32 Nov 25, 2024
d4bd7f7
remove useless cfg[kani] and delete comment
cvick32 Nov 25, 2024
75a457d
Update library/core/src/time.rs
sgpthomas Nov 25, 2024
f42c801
Update library/core/src/time.rs
sgpthomas Nov 25, 2024
8b7113d
add should_panic harness for Duration::new
cvick32 Nov 26, 2024
58f7f53
change comments to preconditions, add cfg(not(kani)) to checked_div
cvick32 Dec 3, 2024
6bbb5a9
change to true in checked_div post-condition
cvick32 Dec 3, 2024
f19b024
remove comment
cvick32 Dec 3, 2024
8435e17
add should_panic on contract proofs, remove requires, and add cfg_att…
cvick32 Dec 4, 2024
a9131b7
Merge branch 'main' into verify_duration_challenge9
tautschnig Dec 6, 2024
f4fd8e0
add non-panicking harnesses and safe_duration
cvick32 Dec 6, 2024
dff2db7
Merge branch 'verify_duration_challenge9' of https://github.com/sgpth…
cvick32 Dec 6, 2024
8ccd95a
Merge branch 'main' into verify_duration_challenge9
cvick32 Dec 9, 2024
bdf9eb9
Merge branch 'main' into verify_duration_challenge9
tautschnig Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 240 additions & 0 deletions library/core/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@
//! assert_eq!(total, Duration::new(10, 7));
//! ```

use safety::{ensures, Invariant};
use crate::fmt;
use crate::iter::Sum;
use crate::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};

#[cfg(kani)]
use crate::kani;

use crate::ub_checks::Invariant;

const NANOS_PER_SEC: u32 = 1_000_000_000;
const NANOS_PER_MILLI: u32 = 1_000_000;
const NANOS_PER_MICRO: u32 = 1_000;
Expand All @@ -43,6 +49,12 @@ const DAYS_PER_WEEK: u64 = 7;
#[rustc_layout_scalar_valid_range_end(999_999_999)]
struct Nanoseconds(u32);

impl Invariant for Nanoseconds {
fn is_safe(&self) -> bool {
self.0 < NANOS_PER_SEC
}
}

impl Nanoseconds {
// SAFETY: 0 is within the valid range
const ZERO: Self = unsafe { Nanoseconds(0) };
Expand Down Expand Up @@ -95,6 +107,7 @@ impl Default for Nanoseconds {
#[stable(feature = "duration", since = "1.3.0")]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(not(test), rustc_diagnostic_item = "Duration")]
#[derive(Invariant)]
pub struct Duration {
secs: u64,
nanos: Nanoseconds, // Always 0 <= nanos < NANOS_PER_SEC
Expand Down Expand Up @@ -208,6 +221,7 @@ impl Duration {
#[inline]
#[must_use]
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
#[ensures(|duration| duration.is_safe())]
pub const fn new(secs: u64, nanos: u32) -> Duration {
if nanos < NANOS_PER_SEC {
// SAFETY: nanos < NANOS_PER_SEC, therefore nanos is within the valid range
Expand Down Expand Up @@ -238,6 +252,8 @@ impl Duration {
#[must_use]
#[inline]
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
#[ensures(|duration| duration.is_safe())]
#[ensures(|duration| duration.secs == secs)]
pub const fn from_secs(secs: u64) -> Duration {
Duration { secs, nanos: Nanoseconds::ZERO }
}
Expand All @@ -258,6 +274,7 @@ impl Duration {
#[must_use]
#[inline]
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
#[ensures(|duration| duration.is_safe())]
pub const fn from_millis(millis: u64) -> Duration {
let secs = millis / MILLIS_PER_SEC;
let subsec_millis = (millis % MILLIS_PER_SEC) as u32;
Expand All @@ -284,6 +301,7 @@ impl Duration {
#[must_use]
#[inline]
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
#[ensures(|duration| duration.is_safe())]
pub const fn from_micros(micros: u64) -> Duration {
let secs = micros / MICROS_PER_SEC;
let subsec_micros = (micros % MICROS_PER_SEC) as u32;
Expand Down Expand Up @@ -315,6 +333,7 @@ impl Duration {
#[must_use]
#[inline]
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
#[ensures(|duration| duration.is_safe())]
pub const fn from_nanos(nanos: u64) -> Duration {
const NANOS_PER_SEC: u64 = self::NANOS_PER_SEC as u64;
let secs = nanos / NANOS_PER_SEC;
Expand Down Expand Up @@ -485,6 +504,7 @@ impl Duration {
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
#[must_use]
#[inline]
#[ensures(|secs| *secs == self.secs)]
pub const fn as_secs(&self) -> u64 {
self.secs
}
Expand All @@ -508,6 +528,7 @@ impl Duration {
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
#[must_use]
#[inline]
#[ensures(|ms| *ms == self.nanos.0 / NANOS_PER_MILLI)]
pub const fn subsec_millis(&self) -> u32 {
self.nanos.0 / NANOS_PER_MILLI
}
Expand All @@ -531,6 +552,7 @@ impl Duration {
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
#[must_use]
#[inline]
#[ensures(|ms| *ms == self.nanos.0 / NANOS_PER_MICRO)]
pub const fn subsec_micros(&self) -> u32 {
self.nanos.0 / NANOS_PER_MICRO
}
Expand All @@ -554,6 +576,7 @@ impl Duration {
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
#[must_use]
#[inline]
#[ensures(|nanos| *nanos == self.nanos.0)]
pub const fn subsec_nanos(&self) -> u32 {
self.nanos.0
}
Expand All @@ -572,6 +595,7 @@ impl Duration {
#[rustc_const_stable(feature = "duration_as_u128", since = "1.33.0")]
#[must_use]
#[inline]
#[ensures(|ms| *ms == self.secs as u128 * MILLIS_PER_SEC as u128 + (self.nanos.0 / NANOS_PER_MILLI) as u128)]
pub const fn as_millis(&self) -> u128 {
self.secs as u128 * MILLIS_PER_SEC as u128 + (self.nanos.0 / NANOS_PER_MILLI) as u128
}
Expand All @@ -590,6 +614,7 @@ impl Duration {
#[rustc_const_stable(feature = "duration_as_u128", since = "1.33.0")]
#[must_use]
#[inline]
#[ensures(|ms| *ms == self.secs as u128 * MICROS_PER_SEC as u128 + (self.nanos.0 / NANOS_PER_MICRO) as u128)]
pub const fn as_micros(&self) -> u128 {
self.secs as u128 * MICROS_PER_SEC as u128 + (self.nanos.0 / NANOS_PER_MICRO) as u128
}
Expand Down Expand Up @@ -647,6 +672,7 @@ impl Duration {
without modifying the original"]
#[inline]
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
#[ensures(|duration| duration.is_none() || duration.unwrap().is_safe())]
pub const fn checked_add(self, rhs: Duration) -> Option<Duration> {
if let Some(mut secs) = self.secs.checked_add(rhs.secs) {
let mut nanos = self.nanos.0 + rhs.nanos.0;
Expand Down Expand Up @@ -705,6 +731,7 @@ impl Duration {
without modifying the original"]
#[inline]
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
#[ensures(|duration| duration.is_none() || duration.unwrap().is_safe())]
pub const fn checked_sub(self, rhs: Duration) -> Option<Duration> {
if let Some(mut secs) = self.secs.checked_sub(rhs.secs) {
let nanos = if self.nanos.0 >= rhs.nanos.0 {
Expand Down Expand Up @@ -761,6 +788,7 @@ impl Duration {
without modifying the original"]
#[inline]
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
#[ensures(|duration| duration.is_none() || duration.unwrap().is_safe())]
pub const fn checked_mul(self, rhs: u32) -> Option<Duration> {
// Multiply nanoseconds as u64, because it cannot overflow that way.
let total_nanos = self.nanos.0 as u64 * rhs as u64;
Expand Down Expand Up @@ -816,13 +844,15 @@ impl Duration {
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
#[ensures(|duration| rhs == 0 || duration.unwrap().is_safe())]
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
pub const fn checked_div(self, rhs: u32) -> Option<Duration> {
if rhs != 0 {
let (secs, extra_secs) = (self.secs / (rhs as u64), self.secs % (rhs as u64));
let (mut nanos, extra_nanos) = (self.nanos.0 / rhs, self.nanos.0 % rhs);
nanos +=
((extra_secs * (NANOS_PER_SEC as u64) + extra_nanos as u64) / (rhs as u64)) as u32;
#[cfg(not(kani))]
debug_assert!(nanos < NANOS_PER_SEC);
Some(Duration::new(secs, nanos))
} else {
Expand Down Expand Up @@ -1698,3 +1728,213 @@ impl Duration {
)
}
}

#[cfg(kani)]
#[unstable(feature = "kani", issue = "none")]
pub mod duration_verify {
use crate::kani;
use super::*;

impl kani::Arbitrary for Duration {
fn any() -> Duration {
let secs = kani::any::<u64>();
let nanos = kani::any::<u32>();
Duration::new(secs, nanos)
}
}

fn safe_duration() -> Duration {
let secs = kani::any::<u64>();
let nanos = kani::any::<u32>();
kani::assume(nanos < NANOS_PER_SEC || secs.checked_add((nanos / NANOS_PER_SEC) as u64).is_some());
Duration::new(secs, nanos)
}

#[kani::proof_for_contract(Duration::new)]
fn duration_new() {
let _ = safe_duration();
}

#[kani::proof_for_contract(Duration::new)]
#[kani::should_panic]
fn duration_new_panics() {
let secs = kani::any::<u64>();
let nanos = kani::any::<u32>();
let _ = Duration::new(secs, nanos);
}

#[kani::proof_for_contract(Duration::from_secs)]
fn duration_from_secs() {
let secs = kani::any::<u64>();
let _ = Duration::from_secs(secs);
}

#[kani::proof_for_contract(Duration::from_millis)]
fn duration_from_millis() {
let ms = kani::any::<u64>();
let _ = Duration::from_millis(ms);
}

#[kani::proof_for_contract(Duration::from_micros)]
fn duration_from_micros() {
let micros = kani::any::<u64>();
let _ = Duration::from_micros(micros);
}

#[kani::proof_for_contract(Duration::from_nanos)]
fn duration_from_nanos() {
let nanos = kani::any::<u64>();
let _ = Duration::from_nanos(nanos);
}

#[kani::proof_for_contract(Duration::as_secs)]
fn duration_as_secs() {
let dur = safe_duration();
let _ = dur.as_secs();
}

#[kani::proof_for_contract(Duration::as_secs)]
#[kani::should_panic]
fn duration_as_secs_panics() {
let dur = kani::any::<Duration>();
let _ = dur.as_secs();
}

#[kani::proof_for_contract(Duration::subsec_millis)]
fn duration_subsec_millis() {
let dur = safe_duration();
let _ = dur.subsec_millis();
}

#[kani::proof_for_contract(Duration::subsec_millis)]
#[kani::should_panic]
fn duration_subsec_millis_panics() {
let dur = kani::any::<Duration>();
let _ = dur.subsec_millis();
}

#[kani::proof_for_contract(Duration::subsec_micros)]
fn duration_subsec_micros() {
let dur = safe_duration();
let _ = dur.subsec_micros();
}

#[kani::proof_for_contract(Duration::subsec_micros)]
#[kani::should_panic]
fn duration_subsec_micros_panics() {
let dur = kani::any::<Duration>();
let _ = dur.subsec_micros();
}

#[kani::proof_for_contract(Duration::subsec_nanos)]
fn duration_subsec_nanos() {
let dur = safe_duration();
let _ = dur.subsec_nanos();
}

#[kani::proof_for_contract(Duration::subsec_nanos)]
#[kani::should_panic]
fn duration_subsec_nanos_panics() {
let dur = kani::any::<Duration>();
let _ = dur.subsec_nanos();
}

#[kani::proof_for_contract(Duration::as_millis)]
fn duration_as_millis() {
let dur = safe_duration();
let _ = dur.as_millis();
}

#[kani::proof_for_contract(Duration::as_millis)]
#[kani::should_panic]
fn duration_as_millis_panics() {
let dur = kani::any::<Duration>();
let _ = dur.as_millis();
}

#[kani::proof_for_contract(Duration::as_micros)]
fn duration_as_micros() {
let dur = safe_duration();
let _ = dur.as_micros();
}

#[kani::proof_for_contract(Duration::as_micros)]
#[kani::should_panic]
fn duration_as_micros_panics() {
let dur = kani::any::<Duration>();
let _ = dur.as_micros();
}

#[kani::proof]
fn duration_as_nanos() {
let dur = safe_duration();
let _ = dur.as_nanos();
}

#[kani::proof]
#[kani::should_panic]
fn duration_as_nanos_panics() {
let dur = kani::any::<Duration>();
let _ = dur.as_nanos();
}

#[kani::proof_for_contract(Duration::checked_add)]
fn duration_checked_add() {
let d0 = safe_duration();
let d1 = safe_duration();
let _ = d0.checked_add(d1);
}

#[kani::proof_for_contract(Duration::checked_add)]
#[kani::should_panic]
fn duration_checked_add_panics() {
let d0 = kani::any::<Duration>();
let d1 = kani::any::<Duration>();
let _ = d0.checked_add(d1);
}

#[kani::proof_for_contract(Duration::checked_sub)]
fn duration_checked_sub() {
let d0 = safe_duration();
let d1 = safe_duration();
let _ = d0.checked_sub(d1);
}

#[kani::proof_for_contract(Duration::checked_sub)]
#[kani::should_panic]
fn duration_checked_sub_panics() {
let d0 = kani::any::<Duration>();
let d1 = kani::any::<Duration>();
let _ = d0.checked_sub(d1);
}

#[kani::proof_for_contract(Duration::checked_mul)]
fn duration_checked_mul() {
let d0 = safe_duration();
let amt = kani::any::<u32>();
let _ = d0.checked_mul(amt);
}

#[kani::proof_for_contract(Duration::checked_mul)]
#[kani::should_panic]
fn duration_checked_mul_panics() {
let d0 = kani::any::<Duration>();
let amt = kani::any::<u32>();
let _ = d0.checked_mul(amt);
}

#[kani::proof_for_contract(Duration::checked_div)]
fn duration_checked_div() {
let d0 = safe_duration();
let amt = kani::any::<u32>();
let _ = d0.checked_div(amt);
}

#[kani::proof_for_contract(Duration::checked_div)]
#[kani::should_panic]
fn duration_checked_div_panics() {
let d0 = kani::any::<Duration>();
let amt = kani::any::<u32>();
let _ = d0.checked_div(amt);
}
}
Loading