Skip to content

Commit 398ff24

Browse files
Add Flux specifications to path/bstr/hash/time (#438)
This PR adds flux specifications for checking the following modules in core * pat * bstr * hash * time In particular, it verifies that `Nanoseconds` is correctly used with values within the range. 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: Ranjit Jhala <rjhala@ucsd.edu>
1 parent 88422c8 commit 398ff24

File tree

7 files changed

+121
-4
lines changed

7 files changed

+121
-4
lines changed

library/core/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,11 @@ check-cfg = [
4747

4848
[package.metadata.flux]
4949
enabled = true
50-
include = [ "src/ascii{*.rs,/**/*.rs}" ]
50+
check_overflow = "lazy"
51+
include = [ "src/ascii*.rs",
52+
"src/num/mod.rs",
53+
"src/pat.rs",
54+
"src/bstr/*.rs",
55+
"src/hash/*.rs",
56+
"src/time.rs",
57+
]

library/core/src/ascii/ascii_char.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ impl AsciiChar {
479479
/// `b` must be in `0..=127`, or else this is UB.
480480
#[unstable(feature = "ascii_char", issue = "110998")]
481481
#[inline]
482+
#[cfg_attr(flux, flux::spec(fn(b: u8{b <= 127}) -> Self))]
482483
#[requires(b <= 127)]
483484
#[ensures(|result| *result as u8 == b)]
484485
pub const unsafe fn from_u8_unchecked(b: u8) -> Self {
@@ -516,6 +517,10 @@ impl AsciiChar {
516517
/// when writing code using this method, since the implementation doesn't
517518
/// need something really specific, not to make those other arguments do
518519
/// something useful. It might be tightened before stabilization.)
520+
// Only `d < 64` is required for safety as described above, but we use `d < 10` as
521+
// in the `assert_unsafe_precondition` inside. See https://github.com/rust-lang/rust/pull/129374
522+
// for some context about the discrepancy.
523+
#[cfg_attr(flux, flux::spec(fn(d: u8{d < 10}) -> Self))]
519524
#[unstable(feature = "ascii_char", issue = "110998")]
520525
#[inline]
521526
#[track_caller]
@@ -536,8 +541,8 @@ impl AsciiChar {
536541
}
537542

538543
/// Gets this ASCII character as a byte.
544+
#[cfg_attr(flux, flux::spec(fn (Self) -> u8{v: v <= 127}))]
539545
#[unstable(feature = "ascii_char", issue = "110998")]
540-
#[cfg_attr(flux, flux::spec(fn(Self) -> u8{v: v <= 127}))]
541546
#[inline]
542547
pub const fn to_u8(self) -> u8 {
543548
self as u8

library/core/src/flux_info.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,87 @@
44
/// See the following link for more information on how extensible properties for primitive operations work:
55
/// <https://flux-rs.github.io/flux/guide/specifications.html#extensible-properties-for-primitive-ops>
66
#[flux::defs {
7+
fn char_to_int(x:char) -> int {
8+
cast(x)
9+
}
10+
711
property ShiftRightByFour[>>](x, y) {
812
16 * [>>](x, 4) == x
913
}
1014
11-
property MaskBy15[&](x, y) {
12-
[&](x, y) <= y
15+
property MaskLess[&](x, y) {
16+
[&](x, y) <= x && [&](x, y) <= y
17+
}
18+
19+
property ShiftLeft[<<](n, k) {
20+
0 < k => n <= [<<](n, k)
21+
}
22+
23+
fn is_ascii_uppercase(n: int) -> bool {
24+
cast('A') <= n && n <= cast('Z')
25+
}
26+
27+
fn is_ascii_lowercase(n: int) -> bool {
28+
cast('a') <= n && n <= cast('z')
29+
}
30+
31+
fn to_ascii_uppercase(n: int) -> int {
32+
n - (cast(is_ascii_lowercase(n)) * 32)
33+
}
34+
35+
fn to_ascii_lowercase(n: int) -> int {
36+
n + (cast(is_ascii_uppercase(n)) * 32)
37+
}
38+
39+
property BitXor0[^](x, y) {
40+
(y == 0) => [^](x, y) == x
41+
}
42+
43+
property BitXor32[^](x, y) {
44+
(is_ascii_lowercase(x) && y == 32) => [^](x, y) == x - 32
45+
}
46+
47+
property BitOr0[|](x, y) {
48+
(y == 0) => [|](x, y) == x
49+
}
50+
51+
property BitOr32[|](x, y) {
52+
(is_ascii_uppercase(x) && y == 32) => [|](x, y) == x + 32
53+
}
54+
55+
}]
56+
#[flux::specs {
57+
mod hash {
58+
mod sip {
59+
struct Hasher {
60+
k0: u64,
61+
k1: u64,
62+
length: usize, // how many bytes we've processed
63+
state: State, // hash State
64+
tail: u64, // unprocessed bytes le
65+
ntail: usize{v: v <= 8}, // how many bytes in tail are valid
66+
_marker: PhantomData<S>,
67+
}
68+
}
69+
70+
impl BuildHasherDefault {
71+
#[trusted(reason="https://github.com/flux-rs/flux/issues/1185")]
72+
fn new() -> Self;
73+
}
74+
}
75+
76+
impl Hasher for hash::sip::Hasher {
77+
fn write(self: &mut Self, msg: &[u8]) ensures self: Self; // mut-ref-unfolding
78+
}
79+
80+
impl Clone for hash::BuildHasherDefault {
81+
#[trusted(reason="https://github.com/flux-rs/flux/issues/1185")]
82+
fn clone(self: &Self) -> Self;
83+
}
84+
85+
impl Debug for time::Duration {
86+
#[trusted(reason="modular arithmetic invariant inside nested fmt_decimal")]
87+
fn fmt(self: &Self, f: &mut fmt::Formatter) -> fmt::Result;
1388
}
1489
}]
1590
const _: () = {};

library/core/src/num/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ impl u8 {
503503
/// # Safety
504504
///
505505
/// This byte must be valid ASCII, or else this is UB.
506+
#[cfg_attr(flux, flux::spec(fn({&Self[@n] | n <= 127}) -> _))]
506507
#[must_use]
507508
#[unstable(feature = "ascii_char", issue = "110998")]
508509
#[inline]
@@ -533,6 +534,7 @@ impl u8 {
533534
/// ```
534535
///
535536
/// [`make_ascii_uppercase`]: Self::make_ascii_uppercase
537+
#[cfg_attr(flux, flux::spec(fn(&u8[@n]) -> u8[to_ascii_uppercase(n)]))]
536538
#[must_use = "to uppercase the value in-place, use `make_ascii_uppercase()`"]
537539
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
538540
#[rustc_const_stable(feature = "const_ascii_methods_on_intrinsics", since = "1.52.0")]
@@ -558,6 +560,7 @@ impl u8 {
558560
/// ```
559561
///
560562
/// [`make_ascii_lowercase`]: Self::make_ascii_lowercase
563+
#[cfg_attr(flux, flux::spec(fn(&u8[@n]) -> u8[to_ascii_lowercase(n)]))]
561564
#[must_use = "to lowercase the value in-place, use `make_ascii_lowercase()`"]
562565
#[stable(feature = "ascii_methods_on_intrinsics", since = "1.23.0")]
563566
#[rustc_const_stable(feature = "const_ascii_methods_on_intrinsics", since = "1.52.0")]
@@ -706,6 +709,7 @@ impl u8 {
706709
/// assert!(!lf.is_ascii_uppercase());
707710
/// assert!(!esc.is_ascii_uppercase());
708711
/// ```
712+
#[cfg_attr(flux, flux::spec(fn(&Self[@n]) -> bool[is_ascii_uppercase(n)]))]
709713
#[must_use]
710714
#[stable(feature = "ascii_ctype_on_intrinsics", since = "1.24.0")]
711715
#[rustc_const_stable(feature = "const_ascii_ctype_on_intrinsics", since = "1.47.0")]
@@ -740,6 +744,7 @@ impl u8 {
740744
/// assert!(!lf.is_ascii_lowercase());
741745
/// assert!(!esc.is_ascii_lowercase());
742746
/// ```
747+
#[cfg_attr(flux, flux::spec(fn(&u8[@n]) -> bool[is_ascii_lowercase(n)]))]
743748
#[must_use]
744749
#[stable(feature = "ascii_ctype_on_intrinsics", since = "1.24.0")]
745750
#[rustc_const_stable(feature = "const_ascii_ctype_on_intrinsics", since = "1.47.0")]

library/core/src/num/niche_types.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ macro_rules! define_valid_range_type {
1414
$(#[$m:meta])*
1515
$vis:vis struct $name:ident($int:ident as $uint:ident in $low:literal..=$high:literal);
1616
)+) => {$(
17+
#[cfg_attr(flux, flux::opaque)]
18+
#[cfg_attr(flux, flux::refined_by(val: int))]
19+
#[cfg_attr(flux, flux::invariant($low <= cast(val) && cast(val) <= $high))]
1720
#[derive(Clone, Copy, Eq)]
1821
#[repr(transparent)]
1922
#[rustc_layout_scalar_valid_range_start($low)]
@@ -33,6 +36,7 @@ macro_rules! define_valid_range_type {
3336

3437
impl $name {
3538
#[inline]
39+
#[cfg_attr(flux, flux::spec(fn (val: $int) -> Option<Self[{val: cast(val)}]>))]
3640
pub const fn new(val: $int) -> Option<Self> {
3741
if (val as $uint) >= ($low as $uint) && (val as $uint) <= ($high as $uint) {
3842
// SAFETY: just checked the inclusive range
@@ -49,12 +53,14 @@ macro_rules! define_valid_range_type {
4953
/// Immediate language UB if `val` is not within the valid range for this
5054
/// type, as it violates the validity invariant.
5155
#[inline]
56+
#[cfg_attr(flux, flux::spec(fn (val: $int{ $low <= cast(val) && cast(val) <= $high }) -> Self[{val:cast(val)}]))]
5257
pub const unsafe fn new_unchecked(val: $int) -> Self {
5358
// SAFETY: Caller promised that `val` is within the valid range.
5459
unsafe { $name(val) }
5560
}
5661

5762
#[inline]
63+
#[cfg_attr(flux, flux::spec(fn (self: Self) -> $int[cast(self.val)] ensures $low <= cast(self.val) && cast(self.val) <= $high))]
5864
pub const fn as_inner(self) -> $int {
5965
// SAFETY: This is a transparent wrapper, so unwrapping it is sound
6066
// (Not using `.0` due to MCP#807.)

library/core/src/num/uint_macros.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ macro_rules! uint_impl {
629629
without modifying the original"]
630630
#[inline(always)]
631631
#[track_caller]
632+
#[cfg_attr(flux, flux::spec(fn(self: Self, rhs: Self{self + rhs <= $MaxV}) -> Self[self + rhs]))]
632633
#[requires(!self.overflowing_add(rhs).1)]
633634
pub const unsafe fn unchecked_add(self, rhs: Self) -> Self {
634635
assert_unsafe_precondition!(

library/core/src/pat.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,22 @@ macro_rules! pattern_type {
1313
};
1414
}
1515

16+
// The Flux spec for the `trait RangePattern` below uses
17+
// [associated refinements](https://flux-rs.github.io/flux/tutorial/08-traits.html)
18+
// The `sub_one` method may only be safe for certain values,
19+
// e.g. when the value is not the "minimum of the type" as in the
20+
// case of the `char` implementation below. To specify this precondition generically
21+
// 1. at the trait level, we introduce the predicate `sub_ok`
22+
// which characterizes the "valid" values at which `sub_one`
23+
// can be safely called, and by default, specify this predicate
24+
// is "true";
25+
// 2. at the impl level, we can provide a type-specific implementation
26+
// of `sub_ok` that permits the verification of the impl for that type.
27+
1628
/// A trait implemented for integer types and `char`.
1729
/// Useful in the future for generic pattern types, but
1830
/// used right now to simplify ast lowering of pattern type ranges.
31+
#[cfg_attr(flux, flux::assoc(fn sub_ok(self: Self) -> bool { true }))]
1932
#[unstable(feature = "pattern_type_range_trait", issue = "123646")]
2033
#[rustc_const_unstable(feature = "pattern_type_range_trait", issue = "123646")]
2134
#[const_trait]
@@ -33,6 +46,7 @@ pub trait RangePattern {
3346
const MAX: Self;
3447

3548
/// A compile-time helper to subtract 1 for exclusive ranges.
49+
#[cfg_attr(flux, flux::spec(fn (self: Self{<Self as RangePattern>::sub_ok(self)}) -> Self))]
3650
#[lang = "RangeSub"]
3751
#[track_caller]
3852
fn sub_one(self) -> Self;
@@ -61,12 +75,16 @@ impl_range_pat! {
6175
u8, u16, u32, u64, u128, usize,
6276
}
6377

78+
// The "associated refinement" `sub_ok` is defined as `non-zero` for `char`, to let Flux
79+
// verify that the `self as u32 -1` in the impl does not underflow.
80+
#[cfg_attr(flux, flux::assoc(fn sub_ok(self: char) -> bool { 0 < char_to_int(self)}))]
6481
#[rustc_const_unstable(feature = "pattern_type_range_trait", issue = "123646")]
6582
impl const RangePattern for char {
6683
const MIN: Self = char::MIN;
6784

6885
const MAX: Self = char::MAX;
6986

87+
#[cfg_attr(flux, flux::spec(fn (self: char{<char as RangePattern>::sub_ok(self)}) -> char))]
7088
fn sub_one(self) -> Self {
7189
match char::from_u32(self as u32 - 1) {
7290
None => panic!("exclusive range to start of valid chars"),

0 commit comments

Comments
 (0)