//! This code adds specifications for the standard-library types //! `std::collections::HashMap` and `std::collections::HashSet`. //! //! Most of the specification only applies if you use `HashMap` or `HashSet`. If you use some custom build hasher, //! e.g., with`HashMap`, the //! specification won't specify much. //! //! Likewise, the specification is only meaningful when the `Key` //! obeys our hash table model, i.e., (1) `Key::hash` is //! deterministic, (2) any two `Key`s are identical if and only if the //! executable `==` operator considers them equal, and (3) //! `Key::clone` produces a result equal to its input. We have an //! axiom that all primitive types and `Box`es thereof obey this //! model. But if you want to use some other key type `MyKey`, you //! need to explicitly state your assumption that it does so with //! `assume(vstd::std_specs::hash::obeys_key_model::());`. In //! the future, we plan to devise a way for you to prove that it does //! so, so that you don't have to make such an assumption. //! //! By default, the Verus standard library brings useful axioms //! about the behavior of `HashMap` and `HashSet` into the ambient //! reasoning context by broadcasting the group //! `vstd::std_specs::hash::group_hash_axioms`. use super::super::prelude::*; use super::iter::IteratorSpec; use core::alloc::Allocator; use core::borrow::Borrow; use core::hash::{BuildHasher, Hash, Hasher}; use core::marker::PhantomData; use core::option::Option; use core::option::Option::None; use std::collections::hash_map; use std::collections::hash_map::{ DefaultHasher, Entry, Keys, OccupiedEntry, RandomState, VacantEntry, Values, }; use std::collections::hash_set; use std::collections::{HashMap, HashSet}; verus! { /// Specifications for the behavior of /// [`std::collections::hash_map::DefaultHasher`](https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html). /// /// We model a `DefaultHasher` as having a view (i.e., an abstract /// state) of type `Seq>`. This reflects the sequence of write /// operations performed so far, where each write is modeled as having /// written a sequence of bytes. There's also a specification for /// how a view will be transformed by `finish` into a `u64`. #[verifier::external_type_specification] #[verifier::external_body] pub struct ExDefaultHasher(DefaultHasher); impl View for DefaultHasher { type V = Seq>; #[verifier::external_body] uninterp spec fn view(&self) -> Seq>; } pub trait DefaultHasherAdditionalSpecFns { spec fn spec_finish(s: Seq>) -> u64; } impl DefaultHasherAdditionalSpecFns for DefaultHasher { #[verifier::external_body] uninterp spec fn spec_finish(s: Seq>) -> u64; } // This is the specification of behavior for `DefaultHasher::new()`. pub assume_specification[ DefaultHasher::new ]() -> (result: DefaultHasher) ensures result@ == Seq::>::empty(), ; // This is the specification of behavior for `DefaultHasher::write(&[u8])`. pub assume_specification[ DefaultHasher::write ](state: &mut DefaultHasher, bytes: &[u8]) ensures final(state)@ == old(state)@.push(bytes@), ; // This is the specification of behavior for `DefaultHasher::finish()`. pub assume_specification[ DefaultHasher::finish ](state: &DefaultHasher) -> (result: u64) ensures result == DefaultHasher::spec_finish(state@), ; /// Specifies whether a type `Key` conforms to our requirements to be /// a key in our hash table (and hash set) model. /// /// The three requirements are (1) the hash function is deterministic, /// (2) any two keys of type `Key` are identical if and only if they /// are considered equal by the executable `==` operator, and (3) the /// executable `Key::clone` function produces a result identical to /// its input. Requirement (1) isn't satisfied by having `Key` /// implement `Hash`, since this trait doesn't mandate determinism. /// /// The standard library has axioms that all primitive types and `Box`es /// thereof obey this model. If you want to use some other key /// type `MyKey`, you need to explicitly state your assumption that it /// does so with /// `assume(vstd::std_specs::hash::obeys_key_model::())`. /// In the future, we plan to devise a way for you to prove that it /// does so, so that you don't have to make such an assumption. #[verifier::external_body] pub uninterp spec fn obeys_key_model() -> bool; // These axioms state that any primitive type, or `Box` thereof, // obeys the requirements to be a key in a hash table that // conforms to our hash-table model. // (Declare each separately to enable pruning of unused primitive types.) pub broadcast proof fn axiom_bool_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_u8_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_u16_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_u32_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_u64_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_u128_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_usize_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_i8_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_i16_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_i32_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_i64_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_i128_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_isize_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::(), { admit(); } pub broadcast proof fn axiom_box_bool_obeys_hash_table_key_model() ensures #[trigger] obeys_key_model::>(), { admit(); } pub broadcast proof fn axiom_box_integer_type_obeys_hash_table_key_model() requires obeys_key_model::(), ensures #[trigger] obeys_key_model::>(), { admit(); } #[verifier::external_trait_specification] pub trait ExHasher { type ExternalTraitSpecificationFor: Hasher; } // Our model for the external trait `BuildHasher` is that for any two // `Hasher`s it builds, if they're both given the same write sequence // then their states will match and they'll produce the same digest // when invoked with `finish()`. // // We don't expect that all types implementing the `BuildHasher` trait // will conform to our model, just the types T for which // `builds_valid_hashers::()` holds. #[verifier::external_trait_specification] pub trait ExBuildHasher { type ExternalTraitSpecificationFor: BuildHasher; type Hasher: Hasher; } /// Specifies whether a type conforms to our requirements to be a hash builder /// in our hash table (and hash set) model. /// /// Our model requires that for any two `Hasher`s that the `BuildHasher` builds, /// if they're both given the same write sequence /// then their states will match and they'll produce the same digest /// when invoked with `finish()`. /// /// The standard library has an axiom that `RandomState`, the default `BuildHasher` /// used by `HashMap` and `HashSet`, implements this model. /// If you want to use some other hash builder type `MyHashBuilder`, /// you need to explicitly state your assumption that it does so with /// `assume(vstd::std_specs::hash::builds_valid_hashers::())`. #[verifier::external_body] pub uninterp spec fn builds_valid_hashers() -> bool; /// Specifications for the behavior of /// [`std::hash::RandomState`](https://doc.rust-lang.org/std/hash/struct.RandomState.html). /// /// `RandomState` is the default `BuildHasher` used by Rust's `HashMap` and `HashSet` implementations. /// We have an axiom that `RandomState` satisfies [`builds_valid_hashers()`](https://verus-lang.github.io/verus/verusdoc/vstd/std_specs/hash/fn.builds_valid_hashers.html) /// and thereby conforms to our model of how `BuildHasher` behaves. #[verifier::external_type_specification] #[verifier::external_body] pub struct ExRandomState(RandomState); pub broadcast proof fn axiom_random_state_builds_valid_hashers() ensures #[trigger] builds_valid_hashers::(), { admit(); } /// Specifications for the behavior of /// [`std::collections::hash_map::Keys`](https://doc.rust-lang.org/std/collections/hash_map/struct.Keys.html). #[verifier::external_type_specification] #[verifier::external_body] #[verifier::accept_recursive_types(Key)] #[verifier::accept_recursive_types(Value)] pub struct ExKeys<'a, Key: 'a, Value: 'a>(Keys<'a, Key, Value>); // To allow reasoning about the "contents" of the Keys iterator, without using // a prophecy, we need a function that gives us the underlying sequence of the original keys. pub uninterp spec fn into_iter_keys<'a, Key, Value>(i: Keys<'a, Key, Value>) -> Seq; impl<'a, K, V> super::iter::IteratorSpecImpl for Keys<'a, K, V> { open spec fn obeys_prophetic_iter_laws(&self) -> bool { true } uninterp spec fn remaining(&self) -> Seq; #[verifier::prophetic] open spec fn produces(&self) -> Seq { Seq::new(IteratorSpec::remaining(self).len(), |i: int| true) } uninterp spec fn decrease(&self) -> Option; open spec fn peek(&self, index: int) -> Option { if 0 <= index < into_iter_keys(*self).len() { Some(&into_iter_keys(*self)[index]) } else { None } } } /// Specifications for the behavior of /// [`std::collections::hash_map::Values`](https://doc.rust-lang.org/std/collections/hash_map/struct.Values.html). #[verifier::external_type_specification] #[verifier::external_body] #[verifier::accept_recursive_types(Key)] #[verifier::accept_recursive_types(Value)] pub struct ExValues<'a, Key: 'a, Value: 'a>(Values<'a, Key, Value>); // To allow reasoning about the "contents" of the Values iterator, without using // a prophecy, we need a function that gives us the underlying sequence of the original values. pub uninterp spec fn into_iter_values<'a, Key, Value>(i: Values<'a, Key, Value>) -> Seq; impl<'a, K, V> super::iter::IteratorSpecImpl for Values<'a, K, V> { open spec fn obeys_prophetic_iter_laws(&self) -> bool { true } uninterp spec fn remaining(&self) -> Seq; #[verifier::prophetic] open spec fn produces(&self) -> Seq { Seq::new(IteratorSpec::remaining(self).len(), |i: int| true) } uninterp spec fn decrease(&self) -> Option; open spec fn peek(&self, index: int) -> Option { if 0 <= index < into_iter_values(*self).len() { Some(&into_iter_values(*self)[index]) } else { None } } } // The `iter` method of a `HashMap` returns an iterator of type `hash_map::Iter`, // so we specify that type here. #[verifier::external_type_specification] #[verifier::external_body] #[verifier::accept_recursive_types(Key)] #[verifier::accept_recursive_types(Value)] pub struct ExMapIter<'a, Key: 'a, Value: 'a>(hash_map::Iter<'a, Key, Value>); // To allow reasoning about the "contents" of the Iter iterator, without using // a prophecy, we need a function that gives us the underlying sequence of the original map. pub uninterp spec fn into_iter<'a, Key, Value>(i: hash_map::Iter<'a, Key, Value>) -> Seq< (Key, Value), >; impl<'a, K, V> super::iter::IteratorSpecImpl for hash_map::Iter<'a, K, V> { open spec fn obeys_prophetic_iter_laws(&self) -> bool { true } uninterp spec fn remaining(&self) -> Seq; #[verifier::prophetic] open spec fn produces(&self) -> Seq { Seq::new(IteratorSpec::remaining(self).len(), |i: int| true) } uninterp spec fn decrease(&self) -> Option; open spec fn peek(&self, index: int) -> Option { if 0 <= index < into_iter(*self).len() { let (k, v) = into_iter(*self)[index]; Some((&k, &v)) } else { None } } } pub assume_specification<'a, Key, Value, S, A: Allocator>[ HashMap::::iter ]( m: &'a HashMap, ) -> (iter: hash_map::Iter<'a, Key, Value>) ensures obeys_key_model::() && builds_valid_hashers::() ==> { &&& IteratorSpec::remaining(&iter).len() == m@.dom().len() &&& forall|i: int| #![trigger m@.contains_key(*IteratorSpec::remaining(&iter)[i].0)] #![trigger m@[*IteratorSpec::remaining(&iter)[i].0]] 0 <= i < IteratorSpec::remaining(&iter).len() ==> m@.contains_key( *IteratorSpec::remaining(&iter)[i].0, ) && m@[*IteratorSpec::remaining(&iter)[i].0] == *IteratorSpec::remaining( &iter, )[i].1 &&& forall|k: Key| #[trigger] m@.contains_key(k) ==> IteratorSpec::remaining(&iter).contains((&k, &m@[k])) &&& IteratorSpec::remaining(&iter).unref().to_set() == m@.kv_pairs() &&& iter.remaining().no_duplicates() &&& into_iter(iter) == IteratorSpec::remaining(&iter).unref() &&& IteratorSpec::decrease(&iter) is Some }, ; /// Specifications for the behavior of [`std::collections::HashMap`](https://doc.rust-lang.org/std/collections/struct.HashMap.html). /// /// We model a `HashMap` as having a view of type `Map`, which reflects the current state of the map. /// /// These specifications are only meaningful if `obeys_key_model::()` and `builds_valid_hashers::()` hold. /// See [`obeys_key_model()`](https://verus-lang.github.io/verus/verusdoc/vstd/std_specs/hash/fn.obeys_key_model.html) /// for information on use with primitive types and other types, /// and see [`builds_valid_hashers()`](https://verus-lang.github.io/verus/verusdoc/vstd/std_specs/hash/fn.builds_valid_hashers.html) /// for information on use with Rust's default implementation and custom implementations. /// /// Axioms about the behavior of HashMap are present in the broadcast group `vstd::std_specs::hash::group_hash_axioms`. #[verifier::external_type_specification] #[verifier::external_body] #[verifier::accept_recursive_types(Key)] #[verifier::accept_recursive_types(Value)] #[verifier::reject_recursive_types(S)] #[verifier::reject_recursive_types(A)] pub struct ExHashMap(HashMap); pub trait HashMapAdditionalSpecFns: View> { spec fn spec_index(&self, k: Key) -> Value recommends self@.contains_key(k), ; } impl HashMapAdditionalSpecFns for HashMap< Key, Value, S, A, > { #[verifier::inline] open spec fn spec_index(&self, k: Key) -> Value { self@.index(k) } } /// The actual definition of `HashMap::deep_view`. /// /// This is a separate function since it introduces a lot of quantifiers and revealing an opaque trait /// method is not supported. In most cases, it's easier to use one of the lemmas below instead /// of revealing this function directly. #[verifier::opaque] pub open spec fn hash_map_deep_view_impl< Key: DeepView, Value: DeepView, S, A: core::alloc::Allocator, >(m: HashMap) -> Map { Map::new( m@.dom().map(|k: Key| k.deep_view()), |dk: Key::V| { let k = choose|k: Key| m@.contains_key(k) && #[trigger] k.deep_view() == dk; m@[k].deep_view() }, ) } pub broadcast proof fn lemma_hashmap_deepview_dom(m: HashMap) ensures #[trigger] m.deep_view().dom() == m@.dom().map(|k: K| k.deep_view()), { reveal(hash_map_deep_view_impl); broadcast use group_hash_axioms; broadcast use crate::vstd::group_vstd_default; assert(m.deep_view().dom() =~= m@.dom().map(|k: K| k.deep_view())); } pub broadcast proof fn lemma_hashmap_deepview_properties(m: HashMap) requires crate::relations::injective(|k: K| k.deep_view()), ensures #![trigger m.deep_view()] // all elements in m.view() are present in m.deep_view() forall|k: K| #[trigger] m@.contains_key(k) ==> m.deep_view().contains_key(k.deep_view()) && m.deep_view()[k.deep_view()] == m@[k].deep_view(), // all elements in m.deep_view() are present in m.view() forall|dk: ::V| #[trigger] m.deep_view().contains_key(dk) ==> exists|k: K| k.deep_view() == dk && #[trigger] m@.contains_key(k), { reveal(hash_map_deep_view_impl); broadcast use group_hash_axioms; broadcast use crate::vstd::group_vstd_default; lemma_hashmap_deepview_dom(m); assert(m.deep_view().dom() == m@.dom().map(|k: K| k.deep_view())); assert forall|k: K| #[trigger] m@.contains_key(k) implies m.deep_view().contains_key( k.deep_view(), ) && m.deep_view()[k.deep_view()] == m@[k].deep_view() by { assert(m@.dom().contains(k)); assert(m@.dom().map(|k: K| k.deep_view()).contains(k.deep_view())); assert(m.deep_view().dom().contains(k.deep_view())); let k2 = choose|k2: K| m@.contains_key(k2) && #[trigger] k2.deep_view() == k.deep_view(); assert forall|k1: K, k2: K| #[trigger] k1.deep_view() == #[trigger] k2.deep_view() implies k1 == k2 by { let ghost k_deepview = |k: K| k.deep_view(); assert(crate::relations::injective(k_deepview)); assert(k_deepview(k1) == k_deepview(k2)); } assert(k2 == k); } assert forall|dk: K::V| #[trigger] m.deep_view().contains_key(dk) implies exists|k: K| k.deep_view() == dk && #[trigger] m@.contains_key(k) by { assert(m.deep_view().dom().contains(dk)); assert(m@.dom().map(|k: K| k.deep_view()).contains(dk)); let k = choose|k: K| #[trigger] m@.dom().contains(k) && k.deep_view() == dk; assert(m@.contains_key(k)); } } pub broadcast proof fn lemma_hashmap_deepview_values(m: HashMap) requires crate::relations::injective(|k: K| k.deep_view()), ensures #[trigger] m.deep_view().values() =~= m@.values().map(|v: V| v.deep_view()), { reveal(hash_map_deep_view_impl); broadcast use group_hash_axioms; broadcast use lemma_hashmap_deepview_properties; broadcast use crate::vstd::group_vstd_default; let lhs = m.deep_view().values(); let rhs = m@.values().map(|v: V| v.deep_view()); assert forall|v: V::V| #[trigger] lhs.contains(v) implies rhs.contains(v) by { let dk = choose|dk: K::V| #[trigger] m.deep_view().contains_key(dk) && m.deep_view()[dk] == v; let k = choose|k: K| #[trigger] m@.contains_key(k) && k.deep_view() == dk; let ov = choose|ov: V| #[trigger] m@.contains_key(k) && m@[k] == ov && ov.deep_view() == v; assert(v == ov.deep_view()); assert(m@.values().contains(ov)); } } /// Borrowing a key works the same way on deep_view as on view, /// if deep_view is injective; see `axiom_contains_deref_key`. pub broadcast proof fn axiom_hashmap_deepview_borrow< K: DeepView + Borrow, V: DeepView, Q: View::V> + Hash + Eq + ?Sized, >(m: HashMap, k: &Q) requires obeys_key_model::(), crate::relations::injective(|k: K| k.deep_view()), ensures #[trigger] contains_borrowed_key(m@, k) <==> m.deep_view().contains_key(k@), { admit(); } pub uninterp spec fn spec_hash_map_len( m: &HashMap, ) -> usize; pub broadcast proof fn axiom_spec_hash_map_len( m: &HashMap, ) ensures obeys_key_model::() && builds_valid_hashers::() ==> #[trigger] spec_hash_map_len(m) == m@.len(), { admit(); } #[verifier::when_used_as_spec(spec_hash_map_len)] pub assume_specification[ HashMap::::len ]( m: &HashMap, ) -> (len: usize) ensures len == spec_hash_map_len(m), ; pub assume_specification[ HashMap::::capacity ]( m: &HashMap, ) -> (cap: usize) ensures cap >= spec_hash_map_len(m), ; pub assume_specification[ HashMap::::is_empty ]( m: &HashMap, ) -> (res: bool) ensures res == m@.is_empty(), ; pub assume_specification[ as Clone>::clone ](this: &HashMap) -> (other: HashMap) ensures other@.dom() == this@.dom(), forall|key| #![trigger other@.dom().contains(key)] other@.dom().contains(key) ==> cloned(this@[key], #[trigger] other@[key]), ; pub assume_specification[ HashMap::::new ]() -> (m: HashMap< Key, Value, RandomState, >) ensures m@ == Map::::empty(), ; pub assume_specification[ as core::default::Default>::default ]() -> (m: HashMap) ensures m@ == Map::::empty(), ; pub assume_specification[ HashMap::::with_capacity ](capacity: usize) -> (m: HashMap) ensures m@ == Map::::empty(), ; pub assume_specification[ HashMap::::with_hasher ]( hash_builder: S, ) -> (m: HashMap) ensures m@ == Map::::empty(), ; pub assume_specification[ HashMap::< Key, Value, S, A, >::reserve ](m: &mut HashMap, additional: usize) ensures final(m)@ == old(m)@, ; pub assume_specification[ HashMap::< Key, Value, S, A, >::insert ](m: &mut HashMap, k: Key, v: Value) -> (result: Option) ensures obeys_key_model::() && builds_valid_hashers::() ==> { &&& final(m)@ == old(m)@.insert(k, v) &&& match result { Some(v) => old(m)@.contains_key(k) && v == old(m)[k], None => !old(m)@.contains_key(k), } }, ; // The specification for `contains_key` has a parameter `key: &Q` // where you'd expect to find `key: &Key`. This allows for the case // that `Key` can be borrowed as something other than `&Key`. For // instance, `Box` can be borrowed as `&u32` and `String` can be // borrowed as `&str`, so in those cases `Q` would be `u32` and `str` // respectively. // To deal with this, we have a specification function that opaquely // specifies what it means for a map to contain a borrowed key of type // `&Q`. And the postcondition of `contains_key` just says that its // result matches the output of that specification function. But this // isn't very helpful by itself, since there's no body to that // specification function. So we have special-case axioms that say // what this means in two important circumstances: (1) `Key = Q` and // (2) `Key = Box`. pub uninterp spec fn contains_borrowed_key( m: Map, k: &Q, ) -> bool; pub broadcast proof fn axiom_contains_deref_key(m: Map, k: &Q) ensures #[trigger] contains_borrowed_key::(m, k) <==> m.contains_key(*k), { admit(); } pub broadcast proof fn axiom_contains_box(m: Map, Value>, k: &Q) ensures #[trigger] contains_borrowed_key::, Value, Q>(m, k) <==> m.contains_key( Box::new(*k), ), { admit(); } pub assume_specification< Key: Borrow + Hash + Eq, Value, S: BuildHasher, A: Allocator, Q: Hash + Eq + ?Sized, >[ HashMap::::contains_key:: ]( m: &HashMap, k: &Q, ) -> (result: bool) ensures obeys_key_model::() && builds_valid_hashers::() ==> result == contains_borrowed_key( m@, k, ), ; // The specification for `get` has a parameter `key: &Q` where you'd // expect to find `key: &Key`. This allows for the case that `Key` can // be borrowed as something other than `&Key`. For instance, // `Box` can be borrowed as `&u32` and `String` can be borrowed // as `&str`, so in those cases `Q` would be `u32` and `str` // respectively. // To deal with this, we have a specification function that opaquely // specifies what it means for a map to map a borrowed key of type // `&Q` to a certain value. And the postcondition of `get` says that // its result matches the output of that specification function. (It // also says that its result corresponds to the output of // `contains_borrowed_key`, discussed above.) But this isn't very // helpful by itself, since there's no body to that specification // function. So we have special-case axioms that say what this means // in two important circumstances: (1) `Key = Q` and (2) `Key = // Box`. pub uninterp spec fn maps_borrowed_key_to_value( m: Map, k: &Q, v: Value, ) -> bool; pub broadcast proof fn axiom_maps_deref_key_to_value(m: Map, k: &Q, v: Value) ensures #[trigger] maps_borrowed_key_to_value::(m, k, v) <==> m.contains_key(*k) && m[*k] == v, { admit(); } pub broadcast proof fn axiom_maps_box_key_to_value(m: Map, Value>, q: &Q, v: Value) ensures #[trigger] maps_borrowed_key_to_value::, Value, Q>(m, q, v) <==> { let k = Box::new(*q); &&& m.contains_key(k) &&& m[k] == v }, { admit(); } pub assume_specification< 'a, Key: Borrow + Hash + Eq, Value, S: BuildHasher, A: Allocator, Q: Hash + Eq + ?Sized, >[ HashMap::::get:: ](m: &'a HashMap, k: &Q) -> (result: Option<&'a Value>) ensures obeys_key_model::() && builds_valid_hashers::() ==> match result { Some(v) => maps_borrowed_key_to_value(m@, k, *v), None => !contains_borrowed_key(m@, k), }, ; // The specification for `remove` has a parameter `key: &Q` where // you'd expect to find `key: &Key`. This allows for the case that // `Key` can be borrowed as something other than `&Key`. For instance, // `Box` can be borrowed as `&u32` and `String` can be borrowed // as `&str`, so in those cases `Q` would be `u32` and `str` // respectively. To deal with this, we have a specification function // that opaquely specifies what it means for two maps to be related by // a remove of a certain `&Q`. And the postcondition of `remove` says // that `old(self)@` and `self@` satisfy that relationship. (It also // says that its result corresponds to the output of // `contains_borrowed_key` and `maps_borrowed_key_to_value`, discussed // above.) But this isn't very helpful by itself, since there's no // body to that specification function. So we have special-case axioms // that say what this means in two important circumstances: (1) `Key = // Q` and (2) `Key = Box`. pub uninterp spec fn borrowed_key_removed( old_m: Map, new_m: Map, k: &Q, ) -> bool; pub broadcast proof fn axiom_deref_key_removed( old_m: Map, new_m: Map, k: &Q, ) ensures #[trigger] borrowed_key_removed::(old_m, new_m, k) <==> new_m == old_m.remove( *k, ), { admit(); } pub broadcast proof fn axiom_box_key_removed( old_m: Map, Value>, new_m: Map, Value>, q: &Q, ) ensures #[trigger] borrowed_key_removed::, Value, Q>(old_m, new_m, q) <==> new_m == old_m.remove(Box::new(*q)), { admit(); } pub assume_specification< Key: Borrow + Hash + Eq, Value, S: BuildHasher, A: Allocator, Q: Hash + Eq + ?Sized, >[ HashMap::::remove:: ](m: &mut HashMap, k: &Q) -> (result: Option) ensures obeys_key_model::() && builds_valid_hashers::() ==> { &&& borrowed_key_removed(old(m)@, final(m)@, k) &&& match result { Some(v) => maps_borrowed_key_to_value(old(m)@, k, v), None => !contains_borrowed_key(old(m)@, k), } }, ; pub assume_specification[ HashMap::::clear ]( m: &mut HashMap, ) ensures final(m)@ == Map::::empty(), ; pub assume_specification<'a, Key, Value, S, A: Allocator>[ HashMap::::keys ]( m: &'a HashMap, ) -> (keys: Keys<'a, Key, Value>) ensures obeys_key_model::() && builds_valid_hashers::() ==> { &&& IteratorSpec::remaining(&keys).unref().to_set() == m@.dom() &&& IteratorSpec::remaining(&keys).no_duplicates() &&& IteratorSpec::remaining(&keys).len() == m@.dom().len() &&& into_iter_keys(keys) == IteratorSpec::remaining(&keys).unref() &&& IteratorSpec::decrease(&keys) is Some }, ; pub assume_specification<'a, Key, Value, S, A: Allocator>[ HashMap::::values ]( m: &'a HashMap, ) -> (values: Values<'a, Key, Value>) ensures obeys_key_model::() && builds_valid_hashers::() ==> { &&& IteratorSpec::remaining(&values).unref().to_set() == m@.values() &&& IteratorSpec::remaining(&values).len() == m@.dom().len() &&& into_iter_values(values) == IteratorSpec::remaining(&values).unref() &&& IteratorSpec::decrease(&values) is Some }, ; pub broadcast proof fn axiom_hashmap_decreases(m: HashMap) ensures #[trigger] (decreases_to!(m => m@)), { admit(); } // The `iter` method of a `HashSet` returns an iterator of type `hash_set::Iter`, // so we specify that type here. #[verifier::external_type_specification] #[verifier::external_body] #[verifier::accept_recursive_types(Key)] pub struct ExSetIter<'a, Key: 'a>(hash_set::Iter<'a, Key>); // To allow reasoning about the "contents" of the HashSet iterator, without using // a prophecy, we need a function that gives us the underlying sequence of the original keys. pub uninterp spec fn into_iter_hash_keys<'a, Key>(i: hash_set::Iter::<'a, Key>) -> Seq; impl<'a, K> super::iter::IteratorSpecImpl for hash_set::Iter::<'a, K> { open spec fn obeys_prophetic_iter_laws(&self) -> bool { true } uninterp spec fn remaining(&self) -> Seq; #[verifier::prophetic] open spec fn produces(&self) -> Seq { Seq::new(IteratorSpec::remaining(self).len(), |i: int| true) } uninterp spec fn decrease(&self) -> Option; open spec fn peek(&self, index: int) -> Option { if 0 <= index < into_iter_hash_keys(*self).len() { Some(&into_iter_hash_keys(*self)[index]) } else { None } } } /// Specifications for the behavior of [`std::collections::HashSet`](https://doc.rust-lang.org/std/collections/struct.HashSet.html). /// /// We model a `HashSet` as having a view of type `Set`, which reflects the current state of the set. /// /// These specifications are only meaningful if `obeys_key_model::()` and `builds_valid_hashers::()` hold. /// See [`obeys_key_model()`](https://verus-lang.github.io/verus/verusdoc/vstd/std_specs/hash/fn.obeys_key_model.html) /// for information on use with primitive types and custom types, /// and see [`builds_valid_hashers()`](https://verus-lang.github.io/verus/verusdoc/vstd/std_specs/hash/fn.builds_valid_hashers.html) /// for information on use with Rust's default implementation and custom implementations. /// /// Axioms about the behavior of HashSet are present in the broadcast group `vstd::std_specs::hash::group_hash_axioms`. #[verifier::external_type_specification] #[verifier::external_body] #[verifier::accept_recursive_types(Key)] #[verifier::reject_recursive_types(S)] #[verifier::reject_recursive_types(A)] pub struct ExHashSet(HashSet); pub uninterp spec fn spec_hash_set_len(m: &HashSet) -> usize; pub broadcast proof fn axiom_spec_hash_set_len(m: &HashSet) ensures obeys_key_model::() && builds_valid_hashers::() ==> #[trigger] spec_hash_set_len(m) == m@.len(), { admit(); } #[verifier::when_used_as_spec(spec_hash_set_len)] pub assume_specification[ HashSet::::len ]( m: &HashSet, ) -> (len: usize) ensures len == spec_hash_set_len(m), ; pub assume_specification[ HashSet::::is_empty ]( m: &HashSet, ) -> (res: bool) ensures res == m@.is_empty(), ; pub assume_specification[ HashSet::::new ]() -> (m: HashSet) ensures m@ == Set::::empty(), ; pub assume_specification[ as core::default::Default>::default ]() -> (m: HashSet) ensures m@ == Set::::empty(), ; pub assume_specification[ HashSet::::with_capacity ](capacity: usize) -> (m: HashSet< Key, RandomState, >) ensures m@ == Set::::empty(), ; pub assume_specification[ HashSet::::with_hasher ](hash_builder: S) -> (m: HashSet< Key, S, >) ensures m@ == Set::::empty(), ; pub assume_specification[ HashSet::::with_capacity_and_hasher ]( capacity: usize, hash_builder: S, ) -> (m: HashSet) ensures m@ == Set::::empty(), ; pub assume_specification[ HashSet::< Key, S, A, >::reserve ](m: &mut HashSet, additional: usize) ensures final(m)@ == old(m)@, ; pub assume_specification[ HashSet::< Key, S, A, >::insert ](m: &mut HashSet, k: Key) -> (result: bool) ensures obeys_key_model::() && builds_valid_hashers::() ==> { &&& final(m)@ == old(m)@.insert(k) &&& result == !old(m)@.contains(k) }, ; // The specification for `contains` has a parameter `key: &Q` // where you'd expect to find `key: &Key`. This allows for the case // that `Key` can be borrowed as something other than `&Key`. For // instance, `Box` can be borrowed as `&u32` and `String` can be // borrowed as `&str`, so in those cases `Q` would be `u32` and `str` // respectively. // To deal with this, we have a specification function that opaquely // specifies what it means for a set to contain a borrowed key of type // `&Q`. And the postcondition of `contains` just says that its // result matches the output of that specification function. But this // isn't very helpful by itself, since there's no body to that // specification function. So we have special-case axioms that say // what this means in two important circumstances: (1) `Key = Q` and // (2) `Key = Box`. pub uninterp spec fn set_contains_borrowed_key(m: Set, k: &Q) -> bool; pub broadcast proof fn axiom_set_contains_deref_key(m: Set, k: &Q) ensures #[trigger] set_contains_borrowed_key::(m, k) <==> m.contains(*k), { admit(); } pub broadcast proof fn axiom_set_contains_box(m: Set>, k: &Q) ensures #[trigger] set_contains_borrowed_key::, Q>(m, k) <==> m.contains(Box::new(*k)), { admit(); } pub assume_specification< Key: Borrow + Hash + Eq, S: BuildHasher, A: Allocator, Q: Hash + Eq + ?Sized, >[ HashSet::::contains ](m: &HashSet, k: &Q) -> (result: bool) ensures obeys_key_model::() && builds_valid_hashers::() ==> result == set_contains_borrowed_key(m@, k), ; // The specification for `get` has a parameter `key: &Q` where you'd // expect to find `key: &Key`. This allows for the case that `Key` can // be borrowed as something other than `&Key`. For instance, // `Box` can be borrowed as `&u32` and `String` can be borrowed // as `&str`, so in those cases `Q` would be `u32` and `str` // respectively. // To deal with this, we have a specification function that opaquely // specifies what it means for a returned reference to point to an // element of a HashSet. And the postcondition of `get` says that // its result matches the output of that specification function. (It // also says that its result corresponds to the output of // `contains_borrowed_key`, discussed above.) But this isn't very // helpful by itself, since there's no body to that specification // function. So we have special-case axioms that say what this means // in two important circumstances: (1) `Key = Q` and (2) `Key = // Box`. pub uninterp spec fn sets_borrowed_key_to_key(m: Set, k: &Q, v: &Key) -> bool; pub broadcast proof fn axiom_set_deref_key_to_value(m: Set, k: &Q, v: &Q) ensures #[trigger] sets_borrowed_key_to_key::(m, k, v) <==> m.contains(*k) && k == v, { admit(); } pub broadcast proof fn axiom_set_box_key_to_value(m: Set>, q: &Q, v: &Box) ensures #[trigger] sets_borrowed_key_to_key::, Q>(m, q, v) <==> (m.contains(*v) && Box::new( *q, ) == v), { admit(); } pub assume_specification< 'a, Key: Borrow + Hash + Eq, S: BuildHasher, A: Allocator, Q: Hash + Eq + ?Sized, >[ HashSet::::get:: ](m: &'a HashSet, k: &Q) -> (result: Option<&'a Key>) ensures obeys_key_model::() && builds_valid_hashers::() ==> match result { Some(v) => sets_borrowed_key_to_key(m@, k, v), None => !set_contains_borrowed_key(m@, k), }, ; // The specification for `remove` has a parameter `key: &Q` where // you'd expect to find `key: &Key`. This allows for the case that // `Key` can be borrowed as something other than `&Key`. For instance, // `Box` can be borrowed as `&u32` and `String` can be borrowed // as `&str`, so in those cases `Q` would be `u32` and `str` // respectively. To deal with this, we have a specification function // that opaquely specifies what it means for two sets to be related by // a remove of a certain `&Q`. And the postcondition of `remove` says // that `old(self)@` and `self@` satisfy that relationship. (It also // says that its result corresponds to the output of // `set_contains_borrowed_key`, discussed above.) But this isn't very // helpful by itself, since there's no body to that specification // function. So we have special-case axioms that say what this means // in two important circumstances: (1) `Key = Q` and (2) `Key = Box`. pub uninterp spec fn sets_differ_by_borrowed_key( old_m: Set, new_m: Set, k: &Q, ) -> bool; pub broadcast proof fn axiom_set_deref_key_removed(old_m: Set, new_m: Set, k: &Q) ensures #[trigger] sets_differ_by_borrowed_key::(old_m, new_m, k) <==> new_m == old_m.remove( *k, ), { admit(); } pub broadcast proof fn axiom_set_box_key_removed(old_m: Set>, new_m: Set>, q: &Q) ensures #[trigger] sets_differ_by_borrowed_key::, Q>(old_m, new_m, q) <==> new_m == old_m.remove(Box::new(*q)), { admit(); } pub assume_specification< Key: Borrow + Hash + Eq, S: BuildHasher, A: Allocator, Q: Hash + Eq + ?Sized, >[ HashSet::::remove:: ](m: &mut HashSet, k: &Q) -> (result: bool) ensures obeys_key_model::() && builds_valid_hashers::() ==> { &&& sets_differ_by_borrowed_key(old(m)@, final(m)@, k) &&& result == set_contains_borrowed_key(old(m)@, k) }, ; pub assume_specification[ HashSet::::clear ]( m: &mut HashSet, ) ensures final(m)@ == Set::::empty(), ; pub assume_specification<'a, Key, S, A: Allocator>[ HashSet::::iter ]( m: &'a HashSet, ) -> (hash_keys: hash_set::Iter<'a, Key>) ensures obeys_key_model::() && builds_valid_hashers::() ==> { &&& IteratorSpec::remaining(&hash_keys).unref().to_set() == m@ &&& IteratorSpec::remaining(&hash_keys).no_duplicates() &&& IteratorSpec::remaining(&hash_keys).len() == m@.len() &&& into_iter_hash_keys(hash_keys) == IteratorSpec::remaining(&hash_keys).unref() &&& IteratorSpec::decrease(&hash_keys) is Some }, ; pub broadcast proof fn axiom_hashset_decreases(m: HashSet) ensures #[trigger] (decreases_to!(m => m@)), { admit(); } //////// Entry API // Specs for OccupiedEntry, VacantEntry, and the Entry enum // OccupiedEntry and VacantEntry are both opaque; // Entry is just an enum wrapper around the other 2, so we leave it transparent. #[verifier::reject_recursive_types_in_ground_variants(K)] #[verifier::reject_recursive_types_in_ground_variants(V)] #[verifier::reject_recursive_types(A)] #[verifier::external_body] #[verifier::external_type_specification] pub struct ExOccupiedEntry<'a, K: 'a, V: 'a, A: Allocator>(OccupiedEntry<'a, K, V, A>); #[verifier::reject_recursive_types_in_ground_variants(K)] #[verifier::accept_recursive_types(V)] #[verifier::reject_recursive_types(A)] #[verifier::external_body] #[verifier::external_type_specification] pub struct ExVacantEntry<'a, K: 'a, V: 'a, A: Allocator>(VacantEntry<'a, K, V, A>); #[verifier::external_type_specification] #[verifier::reject_recursive_types(A)] pub struct ExEntry<'a, K: 'a, V: 'a, A: Allocator>(Entry<'a, K, V, A>); /// Specification for an [`OccupiedEntry`]. /// Contains the current key and value in the entry, /// and prophesies the final value after this instantiation of the entry API is complete. /// The final value is optional, since the user might choose to remove the entry. pub trait OccupiedEntrySpecFns: Sized { spec fn spec_key(self) -> K; spec fn value(self) -> V; #[verifier::prophetic] spec fn final_value(self) -> Option; } impl<'a, K, V, A: Allocator> OccupiedEntrySpecFns for OccupiedEntry<'a, K, V, A> { uninterp spec fn spec_key(self) -> K; uninterp spec fn value(self) -> V; #[verifier::prophetic] uninterp spec fn final_value(self) -> Option; } /// Specification for a [`VacantEntry`]. /// Contains the current key for the entry, /// and prophesies the final value after this instantiation of the entry API is complete. /// The final value is optional, since the user may or may not choose to insert a value. pub trait VacantEntrySpecFns: Sized { spec fn spec_key(self) -> K; #[verifier::prophetic] spec fn final_value(self) -> Option; } impl<'a, K, V, A: Allocator> VacantEntrySpecFns for VacantEntry<'a, K, V, A> { uninterp spec fn spec_key(self) -> K; #[verifier::prophetic] uninterp spec fn final_value(self) -> Option; } /// Specification for an [`Entry`]. /// Contains the current key for the entry, /// and prophesies the final value after this instantiation of the entry API is complete. pub trait EntrySpecFns: Sized { spec fn spec_key(self) -> K; spec fn value(self) -> Option; #[verifier::prophetic] spec fn final_value(self) -> Option; } impl<'a, K, V, A: Allocator> EntrySpecFns for Entry<'a, K, V, A> { open spec fn spec_key(self) -> K { match self { Entry::Occupied(occupied_entry) => occupied_entry.spec_key(), Entry::Vacant(vacant_entry) => vacant_entry.spec_key(), } } open spec fn value(self) -> Option { match self { Entry::Occupied(occupied_entry) => Some(occupied_entry.value()), Entry::Vacant(vacant_entry) => None, } } #[verifier::prophetic] open spec fn final_value(self) -> Option { match self { Entry::Occupied(occupied_entry) => occupied_entry.final_value(), Entry::Vacant(vacant_entry) => vacant_entry.final_value(), } } } pub broadcast axiom fn axiom_has_resolved_occupied_entry( entry: OccupiedEntry, ) ensures #[trigger] has_resolved(entry) ==> entry.final_value() == Some(entry.value()), ; pub broadcast axiom fn axiom_has_resolved_vacant_entry( entry: VacantEntry, ) ensures #[trigger] has_resolved(entry) ==> entry.final_value() == None::, ; pub broadcast proof fn axiom_has_resolved_entry(entry: Entry) ensures #[trigger] has_resolved(entry) ==> entry.final_value() == entry.value(), { broadcast use axiom_has_resolved_occupied_entry; broadcast use axiom_has_resolved_vacant_entry; } pub assume_specification<'a, Key: Hash + Eq, Value, S: BuildHasher, A: Allocator>[ HashMap::< Key, Value, S, A, >::entry ](m: &'a mut HashMap, key: Key) -> (entry: Entry<'a, Key, Value, A>) ensures obeys_key_model::() && builds_valid_hashers::() ==> (entry.key() == key && entry.value() == old(m)@.get(key) && final(m)@ == (match entry.final_value() { Some(value) => old(m)@.insert(key, value), None => old(m)@.remove(key), })), ; //// Entry #[verifier::allow_in_spec] pub assume_specification<'a, 'b, K, V, A: Allocator>[ Entry::key ]( entry: &'b Entry::<'a, K, V, A>, ) -> (key: &'b K) returns &entry.spec_key(), ; pub assume_specification<'a, K, V, A: Allocator>[ Entry::or_insert ]( entry: Entry::<'a, K, V, A>, default: V, ) -> (value: &'a mut V) ensures *value == (match entry.value() { Some(v) => v, None => default, }), entry.final_value() == Some(*final(value)), ; pub assume_specification<'a, K, V, A: Allocator>[ Entry::insert_entry ]( entry: Entry::<'a, K, V, A>, value: V, ) -> (occ_entry: OccupiedEntry<'a, K, V, A>) ensures occ_entry.key() == entry.key(), occ_entry.value() == value, entry.final_value() == occ_entry.final_value(), ; //// OccupiedEntry // This module works around a bug with `allow_in_spec` that creates duplicate spec fn names mod m_occ { use super::*; #[verifier::allow_in_spec] pub assume_specification<'a, 'b, K, V, A: Allocator>[ OccupiedEntry::key ]( entry: &'b OccupiedEntry::<'a, K, V, A>, ) -> (key: &'b K) returns &entry.spec_key(), ; } pub assume_specification<'a, K, V, A: Allocator>[ OccupiedEntry::remove_entry ]( entry: OccupiedEntry::<'a, K, V, A>, ) -> (kv: (K, V)) ensures entry.final_value() == None, returns (*entry.key(), entry.value()), ; pub assume_specification<'a, 'b, K, V, A: Allocator>[ OccupiedEntry::get ]( entry: &'b OccupiedEntry::<'a, K, V, A>, ) -> (value: &'b V) ensures *value == entry.value(), ; pub assume_specification<'a, 'b, K, V, A: Allocator>[ OccupiedEntry::get_mut ]( entry: &'b mut OccupiedEntry::<'a, K, V, A>, ) -> (value: &'b mut V) ensures *value == old(entry).value(), final(entry).key() == old(entry).key(), final(entry).value() == *final(value), final(entry).final_value() == old(entry).final_value(), ; pub assume_specification<'a, K, V, A: Allocator>[ OccupiedEntry::into_mut ]( entry: OccupiedEntry::<'a, K, V, A>, ) -> (value: &mut V) ensures *value == entry.value(), entry.final_value() == Some(*final(value)), ; pub assume_specification<'a, K, V, A: Allocator>[ OccupiedEntry::insert ]( entry: &mut OccupiedEntry::<'a, K, V, A>, value: V, ) -> (old_value: V) ensures old_value == old(entry).value(), final(entry).key() == old(entry).key(), final(entry).value() == value, final(entry).final_value() == old(entry).final_value(), ; pub assume_specification<'a, K, V, A: Allocator>[ OccupiedEntry::remove ]( entry: OccupiedEntry::<'a, K, V, A>, ) -> (value: V) ensures value == entry.value(), entry.final_value() == None, ; //// VacantEntry // This module works around a bug with `allow_in_spec` that creates duplicate spec fn names mod m_vac { use super::*; #[verifier::allow_in_spec] pub assume_specification<'a, 'b, K: 'a, V: 'a, A: Allocator>[ VacantEntry::key ]( entry: &'b VacantEntry::<'a, K, V, A>, ) -> (key: &'b K) returns &entry.spec_key(), ; } pub assume_specification<'a, K: 'a, V: 'a, A: Allocator>[ VacantEntry::into_key ]( entry: VacantEntry::<'a, K, V, A>, ) -> (key: K) ensures key == entry.key(), entry.final_value() == None, ; pub assume_specification<'a, K: 'a, V: 'a, A: Allocator>[ VacantEntry::insert ]( entry: VacantEntry::<'a, K, V, A>, value: V, ) -> (value_ref: &mut V) ensures *value_ref == value, entry.final_value() == Some(*final(value_ref)), ; pub assume_specification<'a, K: 'a, V: 'a, A: Allocator>[ VacantEntry::insert_entry ]( entry: VacantEntry::<'a, K, V, A>, value: V, ) -> (occ_entry: OccupiedEntry::<'a, K, V, A>) ensures occ_entry.key() == entry.key(), occ_entry.value() == value, entry.final_value() == occ_entry.final_value(), ; pub broadcast group group_hash_axioms { axiom_box_key_removed, axiom_contains_deref_key, axiom_contains_box, axiom_deref_key_removed, axiom_maps_deref_key_to_value, axiom_maps_box_key_to_value, axiom_hashmap_deepview_borrow, axiom_bool_obeys_hash_table_key_model, axiom_u8_obeys_hash_table_key_model, axiom_u16_obeys_hash_table_key_model, axiom_u32_obeys_hash_table_key_model, axiom_u64_obeys_hash_table_key_model, axiom_u128_obeys_hash_table_key_model, axiom_usize_obeys_hash_table_key_model, axiom_i8_obeys_hash_table_key_model, axiom_i16_obeys_hash_table_key_model, axiom_i32_obeys_hash_table_key_model, axiom_i64_obeys_hash_table_key_model, axiom_i128_obeys_hash_table_key_model, axiom_isize_obeys_hash_table_key_model, axiom_box_bool_obeys_hash_table_key_model, axiom_box_integer_type_obeys_hash_table_key_model, axiom_random_state_builds_valid_hashers, axiom_spec_hash_map_len, axiom_set_box_key_removed, axiom_set_contains_deref_key, axiom_set_contains_box, axiom_set_deref_key_removed, axiom_set_deref_key_to_value, axiom_set_box_key_to_value, axiom_spec_hash_set_len, axiom_hashmap_decreases, axiom_hashset_decreases, axiom_has_resolved_occupied_entry, axiom_has_resolved_vacant_entry, axiom_has_resolved_entry, } } // verus!