Skip to content

Commit a59ed77

Browse files
committed
feat: add informative BTree cursor model
1 parent 1880b07 commit a59ed77

1 file changed

Lines changed: 150 additions & 24 deletions

File tree

  • verified_libs/vstd_extra/src/external

verified_libs/vstd_extra/src/external/btree.rs

Lines changed: 150 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ use alloc::{
33
alloc::Allocator,
44
collections::{BTreeMap, btree_map::CursorMut},
55
};
6-
use core::{borrow::Borrow, ops::Bound};
6+
use core::{borrow::Borrow, cmp::Ordering, ops::Bound};
77
use vstd::{
88
laws_cmp::obeys_cmp,
99
prelude::*,
10-
std_specs::btree::{borrowed_key_removed, contains_borrowed_key, maps_borrowed_key_to_value},
10+
std_specs::{
11+
btree::{
12+
borrowed_key_removed, contains_borrowed_key, increasing_seq, maps_borrowed_key_to_value,
13+
},
14+
cmp::OrdSpec,
15+
},
1116
};
1217

1318
verus! {
@@ -20,13 +25,41 @@ verus! {
2025
#[verifier::reject_recursive_types(A)]
2126
pub struct ExCursorMut<'a, K: 'a, V: 'a, A>(CursorMut<'a, K, V, A>);
2227

23-
/// Additional ghost state used to remember the keys matching an excluded lower bound.
24-
pub trait CursorMutAdditionalSpecFns<Key> {
25-
spec fn excluded_keys(&self) -> ISet<Key>;
28+
/// The abstract state of a mutable B-tree cursor.
29+
///
30+
/// A cursor points at the gap immediately before `keys[position]`. Therefore `peek_next`
31+
/// accesses `keys[position]`, while `peek_prev` accesses `keys[position - 1]`.
32+
pub ghost struct CursorMutModel<Key, Value> {
33+
/// All keys in the underlying map, in strictly increasing order.
34+
pub keys: Seq<Key>,
35+
/// The index of the element immediately after the cursor.
36+
pub position: int,
37+
/// The current contents of the complete map borrowed by the cursor.
38+
pub map: Map<Key, Value>,
2639
}
2740

28-
impl<'a, Key, Value, A> CursorMutAdditionalSpecFns<Key> for CursorMut<'a, Key, Value, A> {
29-
uninterp spec fn excluded_keys(&self) -> ISet<Key>;
41+
/// Additional abstract and prophetic state for mutable B-tree cursors.
42+
pub trait CursorMutAdditionalSpecFns<Key, Value>: Sized {
43+
spec fn model(self) -> CursorMutModel<Key, Value>;
44+
45+
/// The contents of the borrowed map when this cursor's borrow is resolved.
46+
#[verifier::prophetic]
47+
spec fn final_map(self) -> Map<Key, Value>;
48+
}
49+
50+
impl<'a, Key, Value, A> CursorMutAdditionalSpecFns<Key, Value> for CursorMut<'a, Key, Value, A> {
51+
uninterp spec fn model(self) -> CursorMutModel<Key, Value>;
52+
53+
#[verifier::prophetic]
54+
uninterp spec fn final_map(self) -> Map<Key, Value>;
55+
}
56+
57+
/// Whether a cursor model consistently represents an ordered map and a gap in that map.
58+
pub open spec fn cursor_model_wf<Key, Value>(model: CursorMutModel<Key, Value>) -> bool {
59+
&&& 0 <= model.position <= model.keys.len()
60+
&&& model.keys.no_duplicates()
61+
&&& model.keys.to_set() == model.map.dom()
62+
&&& increasing_seq(model.keys)
3063
}
3164

3265
/// Whether a borrowed lookup key's ordering agrees with the ordering of stored keys.
@@ -35,12 +68,71 @@ impl<'a, Key, Value, A> CursorMutAdditionalSpecFns<Key> for CursorMut<'a, Key, V
3568
/// borrowed-key `BTreeMap` operations.
3669
pub uninterp spec fn borrowed_key_ordering_matches<Key, Q: ?Sized>(key: &Q) -> bool;
3770

71+
/// The ordering of a stored key relative to a borrowed lookup key.
72+
pub uninterp spec fn borrowed_key_cmp<Key, Q: ?Sized>(stored_key: Key, key: &Q) -> Ordering;
73+
3874
/// A key type has the same ordering as itself.
3975
pub broadcast axiom fn axiom_deref_key_ordering_matches<Key>(key: &Key)
4076
ensures
4177
#[trigger] borrowed_key_ordering_matches::<Key, Key>(key),
4278
;
4379

80+
/// Comparing a stored key against a borrowed key of the same type agrees with `Ord`'s model.
81+
pub broadcast axiom fn axiom_deref_key_cmp<Key: Ord>(stored_key: Key, key: &Key)
82+
ensures
83+
#[trigger] borrowed_key_cmp::<Key, Key>(stored_key, key) == stored_key.cmp_spec(key),
84+
;
85+
86+
/// Whether a key occurs before the gap returned by `lower_bound_mut`.
87+
pub open spec fn before_lower_bound<Key, Q: ?Sized>(key: Key, bound: Bound<&Q>) -> bool {
88+
match bound {
89+
Bound::Included(bound_key) => borrowed_key_cmp(key, bound_key) is Less,
90+
Bound::Excluded(bound_key) => !(borrowed_key_cmp(key, bound_key) is Greater),
91+
Bound::Unbounded => false,
92+
}
93+
}
94+
95+
/// Whether a key occurs before the gap returned by `upper_bound_mut`.
96+
pub open spec fn before_upper_bound<Key, Q: ?Sized>(key: Key, bound: Bound<&Q>) -> bool {
97+
match bound {
98+
Bound::Included(bound_key) => !(borrowed_key_cmp(key, bound_key) is Greater),
99+
Bound::Excluded(bound_key) => borrowed_key_cmp(key, bound_key) is Less,
100+
Bound::Unbounded => true,
101+
}
102+
}
103+
104+
/// Whether a cursor is at the gap selected by `lower_bound_mut`.
105+
pub open spec fn positioned_at_lower_bound<Key, Value, Q: ?Sized>(
106+
model: CursorMutModel<Key, Value>,
107+
bound: Bound<&Q>,
108+
) -> bool {
109+
&&& forall|i: int|
110+
#![trigger before_lower_bound(model.keys[i], bound)]
111+
0 <= i < model.position ==> before_lower_bound(model.keys[i], bound)
112+
&&& forall|i: int|
113+
#![trigger before_lower_bound(model.keys[i], bound)]
114+
model.position <= i < model.keys.len() ==> !before_lower_bound(model.keys[i], bound)
115+
}
116+
117+
/// Whether a cursor is at the gap selected by `upper_bound_mut`.
118+
pub open spec fn positioned_at_upper_bound<Key, Value, Q: ?Sized>(
119+
model: CursorMutModel<Key, Value>,
120+
bound: Bound<&Q>,
121+
) -> bool {
122+
&&& forall|i: int|
123+
#![trigger before_upper_bound(model.keys[i], bound)]
124+
0 <= i < model.position ==> before_upper_bound(model.keys[i], bound)
125+
&&& forall|i: int|
126+
#![trigger before_upper_bound(model.keys[i], bound)]
127+
model.position <= i < model.keys.len() ==> !before_upper_bound(model.keys[i], bound)
128+
}
129+
130+
/// Once the cursor has been dropped, its prophesied map is its current map.
131+
pub broadcast axiom fn axiom_has_resolved_cursor<Key, Value, A>(cursor: CursorMut<Key, Value, A>)
132+
ensures
133+
#[trigger] has_resolved(cursor) ==> cursor.final_map() == cursor.model().map,
134+
;
135+
44136
/// Relates a map before and after mutating the value selected by a borrowed key.
45137
pub open spec fn borrowed_key_mutated<Key, Value, Q: ?Sized>(
46138
old_map: Map<Key, Value>,
@@ -63,6 +155,8 @@ pub open spec fn borrowed_key_mutated<Key, Value, Q: ?Sized>(
63155
/// Additional axioms for mutable B-tree operations.
64156
pub broadcast group group_btree_extra_axioms {
65157
axiom_deref_key_ordering_matches,
158+
axiom_deref_key_cmp,
159+
axiom_has_resolved_cursor,
66160
}
67161

68162
/// Specification for [`BTreeMap::get_mut`].
@@ -86,10 +180,6 @@ pub assume_specification<
86180
;
87181

88182
/// Specification for [`BTreeMap::lower_bound_mut`].
89-
///
90-
/// This deliberately over-approximates the cursor position until vstd exposes a reusable ordered
91-
/// cursor model. It still provides a sound verified boundary for callers that do not rely on the
92-
/// selected key in their postconditions.
93183
pub assume_specification<
94184
'a,
95185
Key: Borrow<Q> + Ord,
@@ -108,13 +198,11 @@ pub assume_specification<
108198
Bound::Unbounded => true,
109199
},
110200
ensures
111-
final(map)@.dom() == old(map)@.dom(),
112-
cursor.excluded_keys() == match bound {
113-
Bound::Excluded(key) => ISet::new(
114-
|stored_key: Key|
115-
contains_borrowed_key(Map::<Key, ()>::empty().insert(stored_key, ()), key),
116-
),
117-
_ => ISet::empty(),
201+
obeys_cmp::<Key>() ==> {
202+
&&& cursor_model_wf(cursor.model())
203+
&&& cursor.model().map == old(map)@
204+
&&& final(map)@ == cursor.final_map()
205+
&&& positioned_at_lower_bound(cursor.model(), bound)
118206
},
119207
;
120208

@@ -137,27 +225,65 @@ pub assume_specification<
137225
Bound::Unbounded => true,
138226
},
139227
ensures
140-
final(map)@.dom() == old(map)@.dom(),
141-
cursor.excluded_keys() == ISet::empty(),
228+
obeys_cmp::<Key>() ==> {
229+
&&& cursor_model_wf(cursor.model())
230+
&&& cursor.model().map == old(map)@
231+
&&& final(map)@ == cursor.final_map()
232+
&&& positioned_at_upper_bound(cursor.model(), bound)
233+
},
142234
;
143235

144236
/// Specification for [`CursorMut::peek_prev`].
145237
pub assume_specification<'a, 'b, Key, Value, A>[ CursorMut::<'a, Key, Value, A>::peek_prev ](
146238
cursor: &'b mut CursorMut<'a, Key, Value, A>,
147239
) -> (result: Option<(&'b Key, &'b mut Value)>)
148240
ensures
149-
final(cursor).excluded_keys() == old(cursor).excluded_keys(),
241+
final(cursor).final_map() == old(cursor).final_map(),
242+
cursor_model_wf(old(cursor).model()) ==> cursor_model_wf(final(cursor).model()),
243+
match result {
244+
Some((key, value)) => {
245+
let old_model = old(cursor).model();
246+
let new_model = final(cursor).model();
247+
&&& old_model.position > 0
248+
&&& *key == old_model.keys[old_model.position - 1]
249+
&&& *value == old_model.map[*key]
250+
&&& new_model.keys == old_model.keys
251+
&&& new_model.position == old_model.position
252+
&&& new_model.map == old_model.map.insert(*key, *final(value))
253+
},
254+
None => {
255+
&&& old(cursor).model().position == 0
256+
&&& final(cursor).model().keys == old(cursor).model().keys
257+
&&& final(cursor).model().position == old(cursor).model().position
258+
&&& final(cursor).model().map == old(cursor).model().map
259+
},
260+
},
150261
;
151262

152263
/// Specification for [`CursorMut::peek_next`].
153264
pub assume_specification<'a, 'b, Key, Value, A>[ CursorMut::<'a, Key, Value, A>::peek_next ](
154265
cursor: &'b mut CursorMut<'a, Key, Value, A>,
155266
) -> (result: Option<(&'b Key, &'b mut Value)>)
156267
ensures
157-
final(cursor).excluded_keys() == old(cursor).excluded_keys(),
268+
final(cursor).final_map() == old(cursor).final_map(),
269+
cursor_model_wf(old(cursor).model()) ==> cursor_model_wf(final(cursor).model()),
158270
match result {
159-
Some((key, _)) => !old(cursor).excluded_keys().contains(*key),
160-
None => true,
271+
Some((key, value)) => {
272+
let old_model = old(cursor).model();
273+
let new_model = final(cursor).model();
274+
&&& old_model.position < old_model.keys.len()
275+
&&& *key == old_model.keys[old_model.position]
276+
&&& *value == old_model.map[*key]
277+
&&& new_model.keys == old_model.keys
278+
&&& new_model.position == old_model.position
279+
&&& new_model.map == old_model.map.insert(*key, *final(value))
280+
},
281+
None => {
282+
&&& old(cursor).model().position == old(cursor).model().keys.len()
283+
&&& final(cursor).model().keys == old(cursor).model().keys
284+
&&& final(cursor).model().position == old(cursor).model().position
285+
&&& final(cursor).model().map == old(cursor).model().map
286+
},
161287
},
162288
;
163289

0 commit comments

Comments
 (0)