Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prune ill-conceived BTreeMap iter_mut assertion and test its mutability #67459

Merged
merged 1 commit into from
Dec 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 30 additions & 24 deletions src/liballoc/collections/btree/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ impl<'a, K, V, Type> NodeRef<marker::Mut<'a>, K, V, Type> {

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

Expand All @@ -514,33 +514,37 @@ impl<'a, K: 'a, V: 'a, Type> NodeRef<marker::Immut<'a>, K, V, Type> {
// but we want to avoid that run-time check.
// Instead, we create a slice pointing into the node whenever possible.
// We can sometimes do this even for the shared root, as the slice will be
// empty. We cannot *always* do this because if the type is too highly
// aligned, the offset of `keys` in a "full node" might be outside the bounds
// of the header! So we do an alignment check first, that will be
// evaluated at compile-time, and only do any run-time check in the rare case
// that the alignment is very big.
if mem::align_of::<K>() > mem::align_of::<LeafNode<(), ()>>() && self.is_shared_root() {
// empty and `NodeHeader` contains an empty `keys_start` array.
// We cannot *always* do this because:
// - `keys_start` is not correctly typed because we want `NodeHeader`'s size to
// not depend on the alignment of `K` (needed because `as_header` should be safe).
// For this reason, `NodeHeader` has this `K2` parameter (that's usually `()`
// and hence just adds a size-0-align-1 field, not affecting layout).
// If the correctly typed header is more highly aligned than the allocated header,
// we cannot transmute safely.
// - Even if we can transmute, the offset of a correctly typed `keys_start` might
// be different and outside the bounds of the allocated header!
// So we do an alignment check and a size check first, that will be evaluated
// at compile-time, and only do any run-time check in the rare case that
// the compile-time checks signal danger.
if (mem::align_of::<NodeHeader<K, V, K>>() > mem::align_of::<NodeHeader<K, V>>()
|| mem::size_of::<NodeHeader<K, V, K>>() != mem::size_of::<NodeHeader<K, V>>())
&& self.is_shared_root()
{
&[]
} else {
// Thanks to the alignment check above, we know that `keys` will be
// If we are a `LeafNode<K, V>`, we can always transmute to
// `NodeHeader<K, V, K>` and `keys_start` always has the same offset
// as the actual `keys`.
// Thanks to the checks above, we know that we can transmute to
// `NodeHeader<K, V, K>` and that `keys_start` will be
// in-bounds of some allocation even if this is the shared root!
// (We might be one-past-the-end, but that is allowed by LLVM.)
// Getting the pointer is tricky though. `NodeHeader` does not have a `keys`
// field because we want its size to not depend on the alignment of `K`
// (needed because `as_header` should be safe). We cannot call `as_leaf`
// because we might be the shared root.
// For this reason, `NodeHeader` has this `K2` parameter (that's usually `()`
// and hence just adds a size-0-align-1 field, not affecting layout).
// We know that we can transmute `NodeHeader<K, V, ()>` to `NodeHeader<K, V, K>`
// because we did the alignment check above, and hence `NodeHeader<K, V, K>`
// is not bigger than `NodeHeader<K, V, ()>`! Then we can use `NodeHeader<K, V, K>`
// Thus we can use `NodeHeader<K, V, K>`
// to compute the pointer where the keys start.
// This entire hack will become unnecessary once
// <https://github.com/rust-lang/rfcs/pull/2582> lands, then we can just take a raw
// pointer to the `keys` field of `*const InternalNode<K, V>`.

// This is a non-debug-assert because it can be completely compile-time evaluated.
assert!(mem::size_of::<NodeHeader<K, V>>() == mem::size_of::<NodeHeader<K, V, K>>());
let header = self.as_header() as *const _ as *const NodeHeader<K, V, K>;
let keys = unsafe { &(*header).keys_start as *const _ as *const K };
unsafe { slice::from_raw_parts(keys, self.len()) }
Expand All @@ -549,7 +553,7 @@ impl<'a, K: 'a, V: 'a, Type> NodeRef<marker::Immut<'a>, K, V, Type> {

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

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

fn into_key_slice_mut(mut self) -> &'a mut [K] {
// Same as for `into_key_slice` above, we try to avoid a run-time check
// (the alignment comparison will usually be performed at compile-time).
if mem::align_of::<K>() > mem::align_of::<LeafNode<(), ()>>() && self.is_shared_root() {
// Same as for `into_key_slice` above, we try to avoid a run-time check.
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
if (mem::align_of::<NodeHeader<K, V, K>>() > mem::align_of::<NodeHeader<K, V>>()
|| mem::size_of::<NodeHeader<K, V, K>>() != mem::size_of::<NodeHeader<K, V>>())
&& self.is_shared_root()
{
&mut []
} else {
unsafe {
Expand Down
114 changes: 113 additions & 1 deletion src/liballoc/tests/btree/map.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::collections::btree_map::Entry::{Occupied, Vacant};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fmt::Debug;
use std::iter::FromIterator;
use std::ops::Bound::{self, Excluded, Included, Unbounded};
use std::rc::Rc;
Expand Down Expand Up @@ -57,36 +59,65 @@ fn test_basic_large() {
#[test]
fn test_basic_small() {
let mut map = BTreeMap::new();
// Empty, shared root:
assert_eq!(map.remove(&1), None);
assert_eq!(map.len(), 0);
assert_eq!(map.get(&1), None);
assert_eq!(map.get_mut(&1), None);
assert_eq!(map.first_key_value(), None);
assert_eq!(map.last_key_value(), None);
assert_eq!(map.get(&1), None);
assert_eq!(map.keys().count(), 0);
assert_eq!(map.values().count(), 0);
assert_eq!(map.insert(1, 1), None);

// 1 key-value pair:
assert_eq!(map.len(), 1);
assert_eq!(map.get(&1), Some(&1));
assert_eq!(map.get_mut(&1), Some(&mut 1));
assert_eq!(map.first_key_value(), Some((&1, &1)));
assert_eq!(map.last_key_value(), Some((&1, &1)));
assert_eq!(map.keys().collect::<Vec<_>>(), vec![&1]);
assert_eq!(map.values().collect::<Vec<_>>(), vec![&1]);
assert_eq!(map.insert(1, 2), Some(1));
assert_eq!(map.len(), 1);
assert_eq!(map.get(&1), Some(&2));
assert_eq!(map.get_mut(&1), Some(&mut 2));
assert_eq!(map.first_key_value(), Some((&1, &2)));
assert_eq!(map.last_key_value(), Some((&1, &2)));
assert_eq!(map.keys().collect::<Vec<_>>(), vec![&1]);
assert_eq!(map.values().collect::<Vec<_>>(), vec![&2]);
assert_eq!(map.insert(2, 4), None);

// 2 key-value pairs:
assert_eq!(map.len(), 2);
assert_eq!(map.get(&2), Some(&4));
assert_eq!(map.get_mut(&2), Some(&mut 4));
assert_eq!(map.first_key_value(), Some((&1, &2)));
assert_eq!(map.last_key_value(), Some((&2, &4)));
assert_eq!(map.keys().collect::<Vec<_>>(), vec![&1, &2]);
assert_eq!(map.values().collect::<Vec<_>>(), vec![&2, &4]);
assert_eq!(map.remove(&1), Some(2));

// 1 key-value pair:
assert_eq!(map.len(), 1);
assert_eq!(map.get(&1), None);
assert_eq!(map.get_mut(&1), None);
assert_eq!(map.get(&2), Some(&4));
assert_eq!(map.get_mut(&2), Some(&mut 4));
assert_eq!(map.first_key_value(), Some((&2, &4)));
assert_eq!(map.last_key_value(), Some((&2, &4)));
assert_eq!(map.keys().collect::<Vec<_>>(), vec![&2]);
assert_eq!(map.values().collect::<Vec<_>>(), vec![&4]);
assert_eq!(map.remove(&2), Some(4));

// Empty but private root:
assert_eq!(map.len(), 0);
assert_eq!(map.get(&1), None);
assert_eq!(map.get_mut(&1), None);
assert_eq!(map.first_key_value(), None);
assert_eq!(map.last_key_value(), None);
assert_eq!(map.keys().count(), 0);
assert_eq!(map.values().count(), 0);
assert_eq!(map.remove(&1), None);
}

Expand Down Expand Up @@ -142,6 +173,87 @@ fn test_iter_rev() {
test(size, map.into_iter().rev());
}

/// Specifically tests iter_mut's ability to mutate the value of pairs in-line
fn do_test_iter_mut_mutation<T>(size: usize)
where
T: Copy + Debug + Ord + TryFrom<usize>,
<T as std::convert::TryFrom<usize>>::Error: std::fmt::Debug,
{
let zero = T::try_from(0).unwrap();
let mut map: BTreeMap<T, T> = (0..size).map(|i| (T::try_from(i).unwrap(), zero)).collect();

// Forward and backward iteration sees enough pairs (also tested elsewhere)
assert_eq!(map.iter_mut().count(), size);
assert_eq!(map.iter_mut().rev().count(), size);

// Iterate forwards, trying to mutate to unique values
for (i, (k, v)) in map.iter_mut().enumerate() {
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
assert_eq!(*k, T::try_from(i).unwrap());
assert_eq!(*v, zero);
*v = T::try_from(i + 1).unwrap();
}

// Iterate backwards, checking that mutations succeeded and trying to mutate again
for (i, (k, v)) in map.iter_mut().rev().enumerate() {
assert_eq!(*k, T::try_from(size - i - 1).unwrap());
assert_eq!(*v, T::try_from(size - i).unwrap());
*v = T::try_from(2 * size - i).unwrap();
}

// Check that backward mutations succeeded
for (i, (k, v)) in map.iter_mut().enumerate() {
assert_eq!(*k, T::try_from(i).unwrap());
assert_eq!(*v, T::try_from(size + i + 1).unwrap());
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(align(32))]
struct Align32(usize);

impl TryFrom<usize> for Align32 {
type Error = ();

fn try_from(s: usize) -> Result<Align32, ()> {
Ok(Align32(s))
}
}

#[test]
fn test_iter_mut_mutation() {
// Check many alignments because various fields precede array in NodeHeader.
// Check with size 0 which should not iterate at all.
// Check with size 1 for a tree with one kind of node (root = leaf).
// Check with size 12 for a tree with two kinds of nodes (root and leaves).
// Check with size 144 for a tree with all kinds of nodes (root, internals and leaves).
do_test_iter_mut_mutation::<u8>(0);
do_test_iter_mut_mutation::<u8>(1);
do_test_iter_mut_mutation::<u8>(12);
do_test_iter_mut_mutation::<u8>(127); // not enough unique values to test 144
do_test_iter_mut_mutation::<u16>(1);
do_test_iter_mut_mutation::<u16>(12);
do_test_iter_mut_mutation::<u16>(144);
do_test_iter_mut_mutation::<u32>(1);
do_test_iter_mut_mutation::<u32>(12);
do_test_iter_mut_mutation::<u32>(144);
do_test_iter_mut_mutation::<u64>(1);
do_test_iter_mut_mutation::<u64>(12);
do_test_iter_mut_mutation::<u64>(144);
do_test_iter_mut_mutation::<u128>(1);
do_test_iter_mut_mutation::<u128>(12);
do_test_iter_mut_mutation::<u128>(144);
do_test_iter_mut_mutation::<Align32>(1);
do_test_iter_mut_mutation::<Align32>(12);
do_test_iter_mut_mutation::<Align32>(144);
}

#[test]
fn test_into_key_slice_with_shared_root_past_bounds() {
let mut map: BTreeMap<Align32, ()> = BTreeMap::new();
assert_eq!(map.get(&Align32(1)), None);
assert_eq!(map.get_mut(&Align32(1)), None);
}

#[test]
fn test_values_mut() {
let mut a = BTreeMap::new();
Expand Down
9 changes: 9 additions & 0 deletions src/liballoc/tests/btree/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,15 @@ fn test_is_subset() {
assert_eq!(is_subset(&[99, 100], &large), false);
}

#[test]
fn test_clear() {
let mut x = BTreeSet::new();
x.insert(1);

x.clear();
assert!(x.is_empty());
}

#[test]
fn test_zip() {
let mut x = BTreeSet::new();
Expand Down