Skip to content

Commit e39ae6f

Browse files
committed
Auto merge of #67459 - ssomers:#67438, r=RalfJung
prune ill-conceived BTreeMap iter_mut assertion and test its mutability Proposal to deal with #67438 (and I'm more sure now that this is the right thing to do). Passes testing with miri.
2 parents 2ee25da + e3c814e commit e39ae6f

File tree

3 files changed

+152
-25
lines changed

3 files changed

+152
-25
lines changed

src/liballoc/collections/btree/node.rs

+30-24
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ impl<'a, K, V, Type> NodeRef<marker::Mut<'a>, K, V, Type> {
493493

494494
/// Returns a raw ptr to avoid asserting exclusive access to the entire node.
495495
fn as_leaf_mut(&mut self) -> *mut LeafNode<K, V> {
496-
// We are mutable, so we cannot be the root, so accessing this as a leaf is okay.
496+
// We are mutable, so we cannot be the shared root, so accessing this as a leaf is okay.
497497
self.node.as_ptr()
498498
}
499499

@@ -514,33 +514,37 @@ impl<'a, K: 'a, V: 'a, Type> NodeRef<marker::Immut<'a>, K, V, Type> {
514514
// but we want to avoid that run-time check.
515515
// Instead, we create a slice pointing into the node whenever possible.
516516
// We can sometimes do this even for the shared root, as the slice will be
517-
// empty. We cannot *always* do this because if the type is too highly
518-
// aligned, the offset of `keys` in a "full node" might be outside the bounds
519-
// of the header! So we do an alignment check first, that will be
520-
// evaluated at compile-time, and only do any run-time check in the rare case
521-
// that the alignment is very big.
522-
if mem::align_of::<K>() > mem::align_of::<LeafNode<(), ()>>() && self.is_shared_root() {
517+
// empty and `NodeHeader` contains an empty `keys_start` array.
518+
// We cannot *always* do this because:
519+
// - `keys_start` is not correctly typed because we want `NodeHeader`'s size to
520+
// not depend on the alignment of `K` (needed because `as_header` should be safe).
521+
// For this reason, `NodeHeader` has this `K2` parameter (that's usually `()`
522+
// and hence just adds a size-0-align-1 field, not affecting layout).
523+
// If the correctly typed header is more highly aligned than the allocated header,
524+
// we cannot transmute safely.
525+
// - Even if we can transmute, the offset of a correctly typed `keys_start` might
526+
// be different and outside the bounds of the allocated header!
527+
// So we do an alignment check and a size check first, that will be evaluated
528+
// at compile-time, and only do any run-time check in the rare case that
529+
// the compile-time checks signal danger.
530+
if (mem::align_of::<NodeHeader<K, V, K>>() > mem::align_of::<NodeHeader<K, V>>()
531+
|| mem::size_of::<NodeHeader<K, V, K>>() != mem::size_of::<NodeHeader<K, V>>())
532+
&& self.is_shared_root()
533+
{
523534
&[]
524535
} else {
525-
// Thanks to the alignment check above, we know that `keys` will be
536+
// If we are a `LeafNode<K, V>`, we can always transmute to
537+
// `NodeHeader<K, V, K>` and `keys_start` always has the same offset
538+
// as the actual `keys`.
539+
// Thanks to the checks above, we know that we can transmute to
540+
// `NodeHeader<K, V, K>` and that `keys_start` will be
526541
// in-bounds of some allocation even if this is the shared root!
527542
// (We might be one-past-the-end, but that is allowed by LLVM.)
528-
// Getting the pointer is tricky though. `NodeHeader` does not have a `keys`
529-
// field because we want its size to not depend on the alignment of `K`
530-
// (needed because `as_header` should be safe). We cannot call `as_leaf`
531-
// because we might be the shared root.
532-
// For this reason, `NodeHeader` has this `K2` parameter (that's usually `()`
533-
// and hence just adds a size-0-align-1 field, not affecting layout).
534-
// We know that we can transmute `NodeHeader<K, V, ()>` to `NodeHeader<K, V, K>`
535-
// because we did the alignment check above, and hence `NodeHeader<K, V, K>`
536-
// is not bigger than `NodeHeader<K, V, ()>`! Then we can use `NodeHeader<K, V, K>`
543+
// Thus we can use `NodeHeader<K, V, K>`
537544
// to compute the pointer where the keys start.
538545
// This entire hack will become unnecessary once
539546
// <https://github.com/rust-lang/rfcs/pull/2582> lands, then we can just take a raw
540547
// pointer to the `keys` field of `*const InternalNode<K, V>`.
541-
542-
// This is a non-debug-assert because it can be completely compile-time evaluated.
543-
assert!(mem::size_of::<NodeHeader<K, V>>() == mem::size_of::<NodeHeader<K, V, K>>());
544548
let header = self.as_header() as *const _ as *const NodeHeader<K, V, K>;
545549
let keys = unsafe { &(*header).keys_start as *const _ as *const K };
546550
unsafe { slice::from_raw_parts(keys, self.len()) }
@@ -549,7 +553,7 @@ impl<'a, K: 'a, V: 'a, Type> NodeRef<marker::Immut<'a>, K, V, Type> {
549553

550554
fn into_val_slice(self) -> &'a [V] {
551555
debug_assert!(!self.is_shared_root());
552-
// We cannot be the root, so `as_leaf` is okay
556+
// We cannot be the shared root, so `as_leaf` is okay
553557
unsafe { slice::from_raw_parts(MaybeUninit::first_ptr(&self.as_leaf().vals), self.len()) }
554558
}
555559

@@ -567,9 +571,11 @@ impl<'a, K: 'a, V: 'a, Type> NodeRef<marker::Mut<'a>, K, V, Type> {
567571
}
568572

569573
fn into_key_slice_mut(mut self) -> &'a mut [K] {
570-
// Same as for `into_key_slice` above, we try to avoid a run-time check
571-
// (the alignment comparison will usually be performed at compile-time).
572-
if mem::align_of::<K>() > mem::align_of::<LeafNode<(), ()>>() && self.is_shared_root() {
574+
// Same as for `into_key_slice` above, we try to avoid a run-time check.
575+
if (mem::align_of::<NodeHeader<K, V, K>>() > mem::align_of::<NodeHeader<K, V>>()
576+
|| mem::size_of::<NodeHeader<K, V, K>>() != mem::size_of::<NodeHeader<K, V>>())
577+
&& self.is_shared_root()
578+
{
573579
&mut []
574580
} else {
575581
unsafe {

src/liballoc/tests/btree/map.rs

+113-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::collections::btree_map::Entry::{Occupied, Vacant};
22
use std::collections::BTreeMap;
3+
use std::convert::TryFrom;
4+
use std::fmt::Debug;
35
use std::iter::FromIterator;
46
use std::ops::Bound::{self, Excluded, Included, Unbounded};
57
use std::rc::Rc;
@@ -57,36 +59,65 @@ fn test_basic_large() {
5759
#[test]
5860
fn test_basic_small() {
5961
let mut map = BTreeMap::new();
62+
// Empty, shared root:
6063
assert_eq!(map.remove(&1), None);
6164
assert_eq!(map.len(), 0);
65+
assert_eq!(map.get(&1), None);
66+
assert_eq!(map.get_mut(&1), None);
6267
assert_eq!(map.first_key_value(), None);
6368
assert_eq!(map.last_key_value(), None);
64-
assert_eq!(map.get(&1), None);
69+
assert_eq!(map.keys().count(), 0);
70+
assert_eq!(map.values().count(), 0);
6571
assert_eq!(map.insert(1, 1), None);
72+
73+
// 1 key-value pair:
6674
assert_eq!(map.len(), 1);
6775
assert_eq!(map.get(&1), Some(&1));
76+
assert_eq!(map.get_mut(&1), Some(&mut 1));
6877
assert_eq!(map.first_key_value(), Some((&1, &1)));
6978
assert_eq!(map.last_key_value(), Some((&1, &1)));
79+
assert_eq!(map.keys().collect::<Vec<_>>(), vec![&1]);
80+
assert_eq!(map.values().collect::<Vec<_>>(), vec![&1]);
7081
assert_eq!(map.insert(1, 2), Some(1));
7182
assert_eq!(map.len(), 1);
7283
assert_eq!(map.get(&1), Some(&2));
84+
assert_eq!(map.get_mut(&1), Some(&mut 2));
7385
assert_eq!(map.first_key_value(), Some((&1, &2)));
7486
assert_eq!(map.last_key_value(), Some((&1, &2)));
87+
assert_eq!(map.keys().collect::<Vec<_>>(), vec![&1]);
88+
assert_eq!(map.values().collect::<Vec<_>>(), vec![&2]);
7589
assert_eq!(map.insert(2, 4), None);
90+
91+
// 2 key-value pairs:
7692
assert_eq!(map.len(), 2);
7793
assert_eq!(map.get(&2), Some(&4));
94+
assert_eq!(map.get_mut(&2), Some(&mut 4));
7895
assert_eq!(map.first_key_value(), Some((&1, &2)));
7996
assert_eq!(map.last_key_value(), Some((&2, &4)));
97+
assert_eq!(map.keys().collect::<Vec<_>>(), vec![&1, &2]);
98+
assert_eq!(map.values().collect::<Vec<_>>(), vec![&2, &4]);
8099
assert_eq!(map.remove(&1), Some(2));
100+
101+
// 1 key-value pair:
81102
assert_eq!(map.len(), 1);
82103
assert_eq!(map.get(&1), None);
104+
assert_eq!(map.get_mut(&1), None);
83105
assert_eq!(map.get(&2), Some(&4));
106+
assert_eq!(map.get_mut(&2), Some(&mut 4));
84107
assert_eq!(map.first_key_value(), Some((&2, &4)));
85108
assert_eq!(map.last_key_value(), Some((&2, &4)));
109+
assert_eq!(map.keys().collect::<Vec<_>>(), vec![&2]);
110+
assert_eq!(map.values().collect::<Vec<_>>(), vec![&4]);
86111
assert_eq!(map.remove(&2), Some(4));
112+
113+
// Empty but private root:
87114
assert_eq!(map.len(), 0);
115+
assert_eq!(map.get(&1), None);
116+
assert_eq!(map.get_mut(&1), None);
88117
assert_eq!(map.first_key_value(), None);
89118
assert_eq!(map.last_key_value(), None);
119+
assert_eq!(map.keys().count(), 0);
120+
assert_eq!(map.values().count(), 0);
90121
assert_eq!(map.remove(&1), None);
91122
}
92123

@@ -142,6 +173,87 @@ fn test_iter_rev() {
142173
test(size, map.into_iter().rev());
143174
}
144175

176+
/// Specifically tests iter_mut's ability to mutate the value of pairs in-line
177+
fn do_test_iter_mut_mutation<T>(size: usize)
178+
where
179+
T: Copy + Debug + Ord + TryFrom<usize>,
180+
<T as std::convert::TryFrom<usize>>::Error: std::fmt::Debug,
181+
{
182+
let zero = T::try_from(0).unwrap();
183+
let mut map: BTreeMap<T, T> = (0..size).map(|i| (T::try_from(i).unwrap(), zero)).collect();
184+
185+
// Forward and backward iteration sees enough pairs (also tested elsewhere)
186+
assert_eq!(map.iter_mut().count(), size);
187+
assert_eq!(map.iter_mut().rev().count(), size);
188+
189+
// Iterate forwards, trying to mutate to unique values
190+
for (i, (k, v)) in map.iter_mut().enumerate() {
191+
assert_eq!(*k, T::try_from(i).unwrap());
192+
assert_eq!(*v, zero);
193+
*v = T::try_from(i + 1).unwrap();
194+
}
195+
196+
// Iterate backwards, checking that mutations succeeded and trying to mutate again
197+
for (i, (k, v)) in map.iter_mut().rev().enumerate() {
198+
assert_eq!(*k, T::try_from(size - i - 1).unwrap());
199+
assert_eq!(*v, T::try_from(size - i).unwrap());
200+
*v = T::try_from(2 * size - i).unwrap();
201+
}
202+
203+
// Check that backward mutations succeeded
204+
for (i, (k, v)) in map.iter_mut().enumerate() {
205+
assert_eq!(*k, T::try_from(i).unwrap());
206+
assert_eq!(*v, T::try_from(size + i + 1).unwrap());
207+
}
208+
}
209+
210+
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
211+
#[repr(align(32))]
212+
struct Align32(usize);
213+
214+
impl TryFrom<usize> for Align32 {
215+
type Error = ();
216+
217+
fn try_from(s: usize) -> Result<Align32, ()> {
218+
Ok(Align32(s))
219+
}
220+
}
221+
222+
#[test]
223+
fn test_iter_mut_mutation() {
224+
// Check many alignments because various fields precede array in NodeHeader.
225+
// Check with size 0 which should not iterate at all.
226+
// Check with size 1 for a tree with one kind of node (root = leaf).
227+
// Check with size 12 for a tree with two kinds of nodes (root and leaves).
228+
// Check with size 144 for a tree with all kinds of nodes (root, internals and leaves).
229+
do_test_iter_mut_mutation::<u8>(0);
230+
do_test_iter_mut_mutation::<u8>(1);
231+
do_test_iter_mut_mutation::<u8>(12);
232+
do_test_iter_mut_mutation::<u8>(127); // not enough unique values to test 144
233+
do_test_iter_mut_mutation::<u16>(1);
234+
do_test_iter_mut_mutation::<u16>(12);
235+
do_test_iter_mut_mutation::<u16>(144);
236+
do_test_iter_mut_mutation::<u32>(1);
237+
do_test_iter_mut_mutation::<u32>(12);
238+
do_test_iter_mut_mutation::<u32>(144);
239+
do_test_iter_mut_mutation::<u64>(1);
240+
do_test_iter_mut_mutation::<u64>(12);
241+
do_test_iter_mut_mutation::<u64>(144);
242+
do_test_iter_mut_mutation::<u128>(1);
243+
do_test_iter_mut_mutation::<u128>(12);
244+
do_test_iter_mut_mutation::<u128>(144);
245+
do_test_iter_mut_mutation::<Align32>(1);
246+
do_test_iter_mut_mutation::<Align32>(12);
247+
do_test_iter_mut_mutation::<Align32>(144);
248+
}
249+
250+
#[test]
251+
fn test_into_key_slice_with_shared_root_past_bounds() {
252+
let mut map: BTreeMap<Align32, ()> = BTreeMap::new();
253+
assert_eq!(map.get(&Align32(1)), None);
254+
assert_eq!(map.get_mut(&Align32(1)), None);
255+
}
256+
145257
#[test]
146258
fn test_values_mut() {
147259
let mut a = BTreeMap::new();

src/liballoc/tests/btree/set.rs

+9
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,15 @@ fn test_is_subset() {
302302
assert_eq!(is_subset(&[99, 100], &large), false);
303303
}
304304

305+
#[test]
306+
fn test_clear() {
307+
let mut x = BTreeSet::new();
308+
x.insert(1);
309+
310+
x.clear();
311+
assert!(x.is_empty());
312+
}
313+
305314
#[test]
306315
fn test_zip() {
307316
let mut x = BTreeSet::new();

0 commit comments

Comments
 (0)