Skip to content

Commit 8a248d9

Browse files
sgpthomascvick32celinvaltautschnig
authored
Write contracts + pre/post conditions for all unsafe methods in duration (#136)
Resolves #72 We added invariants for Nanoseconds and Duration to match the safety conditions for those types. We add safety requirements to the following methods: - `new`, `from_secs`, `from_millis`, `from_micros`, `from_nanos`, `as_secs`, `as_millis`, `as_micros`, `as_nanos`, `subsec_millis`, `subsec_micros`, `subsec_nanos`, `checked_add`, `checked_sub`, `checked_mul`, `checked_div` We additionally add correctness conditions to the following methods: - `from_secs`, `as_secs`, `subsec_millis`, `subsec_micros`, `subsec_nanos`, `as_millis`, `as_micros` Support for `kani::Invariant` depends on #87. For the interim we implemented a proxy trait `TempInvariant` that exposes the same `is_safe` method. We will update this once #87 is merged. While the safety check for `Duration::as_nanos()` succeeds, we ran into timeouts for `Duration::as_nanos()` when we tried to use a correctness contract and we're looking for advice on how to speed up that verification time. We were able to prove it for `u16::MAX`, but hit timeouts for larger numbers. We are unsure if the pre-conditions for `Duration::new()` are acceptable because they limit the range of values that you can call `Duration::new()` with. However, we think it's reasonable since we limit the values to values that don't panic. Let us know if this is a thing that we should change. 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 <[email protected]> Co-authored-by: Celina G. Val <[email protected]> Co-authored-by: Michael Tautschnig <[email protected]>
1 parent 002c7c0 commit 8a248d9

File tree

1 file changed

+240
-0
lines changed

1 file changed

+240
-0
lines changed

library/core/src/time.rs

+240
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@
1919
//! assert_eq!(total, Duration::new(10, 7));
2020
//! ```
2121
22+
use safety::{ensures, Invariant};
2223
use crate::fmt;
2324
use crate::iter::Sum;
2425
use crate::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
2526

27+
#[cfg(kani)]
28+
use crate::kani;
29+
30+
use crate::ub_checks::Invariant;
31+
2632
const NANOS_PER_SEC: u32 = 1_000_000_000;
2733
const NANOS_PER_MILLI: u32 = 1_000_000;
2834
const NANOS_PER_MICRO: u32 = 1_000;
@@ -43,6 +49,12 @@ const DAYS_PER_WEEK: u64 = 7;
4349
#[rustc_layout_scalar_valid_range_end(999_999_999)]
4450
struct Nanoseconds(u32);
4551

52+
impl Invariant for Nanoseconds {
53+
fn is_safe(&self) -> bool {
54+
self.0 < NANOS_PER_SEC
55+
}
56+
}
57+
4658
impl Nanoseconds {
4759
// SAFETY: 0 is within the valid range
4860
const ZERO: Self = unsafe { Nanoseconds(0) };
@@ -95,6 +107,7 @@ impl Default for Nanoseconds {
95107
#[stable(feature = "duration", since = "1.3.0")]
96108
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
97109
#[cfg_attr(not(test), rustc_diagnostic_item = "Duration")]
110+
#[derive(Invariant)]
98111
pub struct Duration {
99112
secs: u64,
100113
nanos: Nanoseconds, // Always 0 <= nanos < NANOS_PER_SEC
@@ -208,6 +221,7 @@ impl Duration {
208221
#[inline]
209222
#[must_use]
210223
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
224+
#[ensures(|duration| duration.is_safe())]
211225
pub const fn new(secs: u64, nanos: u32) -> Duration {
212226
if nanos < NANOS_PER_SEC {
213227
// SAFETY: nanos < NANOS_PER_SEC, therefore nanos is within the valid range
@@ -238,6 +252,8 @@ impl Duration {
238252
#[must_use]
239253
#[inline]
240254
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
255+
#[ensures(|duration| duration.is_safe())]
256+
#[ensures(|duration| duration.secs == secs)]
241257
pub const fn from_secs(secs: u64) -> Duration {
242258
Duration { secs, nanos: Nanoseconds::ZERO }
243259
}
@@ -258,6 +274,7 @@ impl Duration {
258274
#[must_use]
259275
#[inline]
260276
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
277+
#[ensures(|duration| duration.is_safe())]
261278
pub const fn from_millis(millis: u64) -> Duration {
262279
let secs = millis / MILLIS_PER_SEC;
263280
let subsec_millis = (millis % MILLIS_PER_SEC) as u32;
@@ -284,6 +301,7 @@ impl Duration {
284301
#[must_use]
285302
#[inline]
286303
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
304+
#[ensures(|duration| duration.is_safe())]
287305
pub const fn from_micros(micros: u64) -> Duration {
288306
let secs = micros / MICROS_PER_SEC;
289307
let subsec_micros = (micros % MICROS_PER_SEC) as u32;
@@ -315,6 +333,7 @@ impl Duration {
315333
#[must_use]
316334
#[inline]
317335
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
336+
#[ensures(|duration| duration.is_safe())]
318337
pub const fn from_nanos(nanos: u64) -> Duration {
319338
const NANOS_PER_SEC: u64 = self::NANOS_PER_SEC as u64;
320339
let secs = nanos / NANOS_PER_SEC;
@@ -485,6 +504,7 @@ impl Duration {
485504
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
486505
#[must_use]
487506
#[inline]
507+
#[ensures(|secs| *secs == self.secs)]
488508
pub const fn as_secs(&self) -> u64 {
489509
self.secs
490510
}
@@ -508,6 +528,7 @@ impl Duration {
508528
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
509529
#[must_use]
510530
#[inline]
531+
#[ensures(|ms| *ms == self.nanos.0 / NANOS_PER_MILLI)]
511532
pub const fn subsec_millis(&self) -> u32 {
512533
self.nanos.0 / NANOS_PER_MILLI
513534
}
@@ -531,6 +552,7 @@ impl Duration {
531552
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
532553
#[must_use]
533554
#[inline]
555+
#[ensures(|ms| *ms == self.nanos.0 / NANOS_PER_MICRO)]
534556
pub const fn subsec_micros(&self) -> u32 {
535557
self.nanos.0 / NANOS_PER_MICRO
536558
}
@@ -554,6 +576,7 @@ impl Duration {
554576
#[rustc_const_stable(feature = "duration_consts", since = "1.32.0")]
555577
#[must_use]
556578
#[inline]
579+
#[ensures(|nanos| *nanos == self.nanos.0)]
557580
pub const fn subsec_nanos(&self) -> u32 {
558581
self.nanos.0
559582
}
@@ -572,6 +595,7 @@ impl Duration {
572595
#[rustc_const_stable(feature = "duration_as_u128", since = "1.33.0")]
573596
#[must_use]
574597
#[inline]
598+
#[ensures(|ms| *ms == self.secs as u128 * MILLIS_PER_SEC as u128 + (self.nanos.0 / NANOS_PER_MILLI) as u128)]
575599
pub const fn as_millis(&self) -> u128 {
576600
self.secs as u128 * MILLIS_PER_SEC as u128 + (self.nanos.0 / NANOS_PER_MILLI) as u128
577601
}
@@ -590,6 +614,7 @@ impl Duration {
590614
#[rustc_const_stable(feature = "duration_as_u128", since = "1.33.0")]
591615
#[must_use]
592616
#[inline]
617+
#[ensures(|ms| *ms == self.secs as u128 * MICROS_PER_SEC as u128 + (self.nanos.0 / NANOS_PER_MICRO) as u128)]
593618
pub const fn as_micros(&self) -> u128 {
594619
self.secs as u128 * MICROS_PER_SEC as u128 + (self.nanos.0 / NANOS_PER_MICRO) as u128
595620
}
@@ -647,6 +672,7 @@ impl Duration {
647672
without modifying the original"]
648673
#[inline]
649674
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
675+
#[ensures(|duration| duration.is_none() || duration.unwrap().is_safe())]
650676
pub const fn checked_add(self, rhs: Duration) -> Option<Duration> {
651677
if let Some(mut secs) = self.secs.checked_add(rhs.secs) {
652678
let mut nanos = self.nanos.0 + rhs.nanos.0;
@@ -705,6 +731,7 @@ impl Duration {
705731
without modifying the original"]
706732
#[inline]
707733
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
734+
#[ensures(|duration| duration.is_none() || duration.unwrap().is_safe())]
708735
pub const fn checked_sub(self, rhs: Duration) -> Option<Duration> {
709736
if let Some(mut secs) = self.secs.checked_sub(rhs.secs) {
710737
let nanos = if self.nanos.0 >= rhs.nanos.0 {
@@ -761,6 +788,7 @@ impl Duration {
761788
without modifying the original"]
762789
#[inline]
763790
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
791+
#[ensures(|duration| duration.is_none() || duration.unwrap().is_safe())]
764792
pub const fn checked_mul(self, rhs: u32) -> Option<Duration> {
765793
// Multiply nanoseconds as u64, because it cannot overflow that way.
766794
let total_nanos = self.nanos.0 as u64 * rhs as u64;
@@ -816,13 +844,15 @@ impl Duration {
816844
#[must_use = "this returns the result of the operation, \
817845
without modifying the original"]
818846
#[inline]
847+
#[ensures(|duration| rhs == 0 || duration.unwrap().is_safe())]
819848
#[rustc_const_stable(feature = "duration_consts_2", since = "1.58.0")]
820849
pub const fn checked_div(self, rhs: u32) -> Option<Duration> {
821850
if rhs != 0 {
822851
let (secs, extra_secs) = (self.secs / (rhs as u64), self.secs % (rhs as u64));
823852
let (mut nanos, extra_nanos) = (self.nanos.0 / rhs, self.nanos.0 % rhs);
824853
nanos +=
825854
((extra_secs * (NANOS_PER_SEC as u64) + extra_nanos as u64) / (rhs as u64)) as u32;
855+
#[cfg(not(kani))]
826856
debug_assert!(nanos < NANOS_PER_SEC);
827857
Some(Duration::new(secs, nanos))
828858
} else {
@@ -1698,3 +1728,213 @@ impl Duration {
16981728
)
16991729
}
17001730
}
1731+
1732+
#[cfg(kani)]
1733+
#[unstable(feature = "kani", issue = "none")]
1734+
pub mod duration_verify {
1735+
use crate::kani;
1736+
use super::*;
1737+
1738+
impl kani::Arbitrary for Duration {
1739+
fn any() -> Duration {
1740+
let secs = kani::any::<u64>();
1741+
let nanos = kani::any::<u32>();
1742+
Duration::new(secs, nanos)
1743+
}
1744+
}
1745+
1746+
fn safe_duration() -> Duration {
1747+
let secs = kani::any::<u64>();
1748+
let nanos = kani::any::<u32>();
1749+
kani::assume(nanos < NANOS_PER_SEC || secs.checked_add((nanos / NANOS_PER_SEC) as u64).is_some());
1750+
Duration::new(secs, nanos)
1751+
}
1752+
1753+
#[kani::proof_for_contract(Duration::new)]
1754+
fn duration_new() {
1755+
let _ = safe_duration();
1756+
}
1757+
1758+
#[kani::proof_for_contract(Duration::new)]
1759+
#[kani::should_panic]
1760+
fn duration_new_panics() {
1761+
let secs = kani::any::<u64>();
1762+
let nanos = kani::any::<u32>();
1763+
let _ = Duration::new(secs, nanos);
1764+
}
1765+
1766+
#[kani::proof_for_contract(Duration::from_secs)]
1767+
fn duration_from_secs() {
1768+
let secs = kani::any::<u64>();
1769+
let _ = Duration::from_secs(secs);
1770+
}
1771+
1772+
#[kani::proof_for_contract(Duration::from_millis)]
1773+
fn duration_from_millis() {
1774+
let ms = kani::any::<u64>();
1775+
let _ = Duration::from_millis(ms);
1776+
}
1777+
1778+
#[kani::proof_for_contract(Duration::from_micros)]
1779+
fn duration_from_micros() {
1780+
let micros = kani::any::<u64>();
1781+
let _ = Duration::from_micros(micros);
1782+
}
1783+
1784+
#[kani::proof_for_contract(Duration::from_nanos)]
1785+
fn duration_from_nanos() {
1786+
let nanos = kani::any::<u64>();
1787+
let _ = Duration::from_nanos(nanos);
1788+
}
1789+
1790+
#[kani::proof_for_contract(Duration::as_secs)]
1791+
fn duration_as_secs() {
1792+
let dur = safe_duration();
1793+
let _ = dur.as_secs();
1794+
}
1795+
1796+
#[kani::proof_for_contract(Duration::as_secs)]
1797+
#[kani::should_panic]
1798+
fn duration_as_secs_panics() {
1799+
let dur = kani::any::<Duration>();
1800+
let _ = dur.as_secs();
1801+
}
1802+
1803+
#[kani::proof_for_contract(Duration::subsec_millis)]
1804+
fn duration_subsec_millis() {
1805+
let dur = safe_duration();
1806+
let _ = dur.subsec_millis();
1807+
}
1808+
1809+
#[kani::proof_for_contract(Duration::subsec_millis)]
1810+
#[kani::should_panic]
1811+
fn duration_subsec_millis_panics() {
1812+
let dur = kani::any::<Duration>();
1813+
let _ = dur.subsec_millis();
1814+
}
1815+
1816+
#[kani::proof_for_contract(Duration::subsec_micros)]
1817+
fn duration_subsec_micros() {
1818+
let dur = safe_duration();
1819+
let _ = dur.subsec_micros();
1820+
}
1821+
1822+
#[kani::proof_for_contract(Duration::subsec_micros)]
1823+
#[kani::should_panic]
1824+
fn duration_subsec_micros_panics() {
1825+
let dur = kani::any::<Duration>();
1826+
let _ = dur.subsec_micros();
1827+
}
1828+
1829+
#[kani::proof_for_contract(Duration::subsec_nanos)]
1830+
fn duration_subsec_nanos() {
1831+
let dur = safe_duration();
1832+
let _ = dur.subsec_nanos();
1833+
}
1834+
1835+
#[kani::proof_for_contract(Duration::subsec_nanos)]
1836+
#[kani::should_panic]
1837+
fn duration_subsec_nanos_panics() {
1838+
let dur = kani::any::<Duration>();
1839+
let _ = dur.subsec_nanos();
1840+
}
1841+
1842+
#[kani::proof_for_contract(Duration::as_millis)]
1843+
fn duration_as_millis() {
1844+
let dur = safe_duration();
1845+
let _ = dur.as_millis();
1846+
}
1847+
1848+
#[kani::proof_for_contract(Duration::as_millis)]
1849+
#[kani::should_panic]
1850+
fn duration_as_millis_panics() {
1851+
let dur = kani::any::<Duration>();
1852+
let _ = dur.as_millis();
1853+
}
1854+
1855+
#[kani::proof_for_contract(Duration::as_micros)]
1856+
fn duration_as_micros() {
1857+
let dur = safe_duration();
1858+
let _ = dur.as_micros();
1859+
}
1860+
1861+
#[kani::proof_for_contract(Duration::as_micros)]
1862+
#[kani::should_panic]
1863+
fn duration_as_micros_panics() {
1864+
let dur = kani::any::<Duration>();
1865+
let _ = dur.as_micros();
1866+
}
1867+
1868+
#[kani::proof]
1869+
fn duration_as_nanos() {
1870+
let dur = safe_duration();
1871+
let _ = dur.as_nanos();
1872+
}
1873+
1874+
#[kani::proof]
1875+
#[kani::should_panic]
1876+
fn duration_as_nanos_panics() {
1877+
let dur = kani::any::<Duration>();
1878+
let _ = dur.as_nanos();
1879+
}
1880+
1881+
#[kani::proof_for_contract(Duration::checked_add)]
1882+
fn duration_checked_add() {
1883+
let d0 = safe_duration();
1884+
let d1 = safe_duration();
1885+
let _ = d0.checked_add(d1);
1886+
}
1887+
1888+
#[kani::proof_for_contract(Duration::checked_add)]
1889+
#[kani::should_panic]
1890+
fn duration_checked_add_panics() {
1891+
let d0 = kani::any::<Duration>();
1892+
let d1 = kani::any::<Duration>();
1893+
let _ = d0.checked_add(d1);
1894+
}
1895+
1896+
#[kani::proof_for_contract(Duration::checked_sub)]
1897+
fn duration_checked_sub() {
1898+
let d0 = safe_duration();
1899+
let d1 = safe_duration();
1900+
let _ = d0.checked_sub(d1);
1901+
}
1902+
1903+
#[kani::proof_for_contract(Duration::checked_sub)]
1904+
#[kani::should_panic]
1905+
fn duration_checked_sub_panics() {
1906+
let d0 = kani::any::<Duration>();
1907+
let d1 = kani::any::<Duration>();
1908+
let _ = d0.checked_sub(d1);
1909+
}
1910+
1911+
#[kani::proof_for_contract(Duration::checked_mul)]
1912+
fn duration_checked_mul() {
1913+
let d0 = safe_duration();
1914+
let amt = kani::any::<u32>();
1915+
let _ = d0.checked_mul(amt);
1916+
}
1917+
1918+
#[kani::proof_for_contract(Duration::checked_mul)]
1919+
#[kani::should_panic]
1920+
fn duration_checked_mul_panics() {
1921+
let d0 = kani::any::<Duration>();
1922+
let amt = kani::any::<u32>();
1923+
let _ = d0.checked_mul(amt);
1924+
}
1925+
1926+
#[kani::proof_for_contract(Duration::checked_div)]
1927+
fn duration_checked_div() {
1928+
let d0 = safe_duration();
1929+
let amt = kani::any::<u32>();
1930+
let _ = d0.checked_div(amt);
1931+
}
1932+
1933+
#[kani::proof_for_contract(Duration::checked_div)]
1934+
#[kani::should_panic]
1935+
fn duration_checked_div_panics() {
1936+
let d0 = kani::any::<Duration>();
1937+
let amt = kani::any::<u32>();
1938+
let _ = d0.checked_div(amt);
1939+
}
1940+
}

0 commit comments

Comments
 (0)