Skip to content

BTree: share panicky test code & test panic during clear, clone #81300

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

Merged
merged 2 commits into from
Feb 21, 2021
Merged
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
285 changes: 154 additions & 131 deletions library/alloc/src/collections/btree/map/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use super::super::{node, DeterministicRng};
use super::super::testing::crash_test::{CrashTestDummy, Panic};
use super::super::testing::ord_chaos::{Cyclic3, Governed, Governor};
use super::super::testing::rng::DeterministicRng;
use super::Entry::{Occupied, Vacant};
use super::*;
use crate::boxed::Box;
@@ -15,9 +17,6 @@ use std::ops::RangeBounds;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};

mod ord_chaos;
use ord_chaos::{Cyclic3, Governed, Governor};

// Capacity of a tree with a single level,
// i.e., a tree who's root is a leaf node at height 0.
const NODE_CAPACITY: usize = node::CAPACITY;
@@ -1136,103 +1135,78 @@ mod test_drain_filter {

#[test]
fn drop_panic_leak() {
static PREDS: AtomicUsize = AtomicUsize::new(0);
static DROPS: AtomicUsize = AtomicUsize::new(0);

struct D;
impl Drop for D {
fn drop(&mut self) {
if DROPS.fetch_add(1, SeqCst) == 1 {
panic!("panic in `drop`");
}
}
}

// Keys are multiples of 4, so that each key is counted by a hexadecimal digit.
let mut map = (0..3).map(|i| (i * 4, D)).collect::<BTreeMap<_, _>>();
let a = CrashTestDummy::new(0);
let b = CrashTestDummy::new(1);
let c = CrashTestDummy::new(2);
let mut map = BTreeMap::new();
map.insert(a.spawn(Panic::Never), ());
map.insert(b.spawn(Panic::InDrop), ());
map.insert(c.spawn(Panic::Never), ());

catch_unwind(move || {
drop(map.drain_filter(|i, _| {
PREDS.fetch_add(1usize << i, SeqCst);
true
}))
})
.unwrap_err();
catch_unwind(move || drop(map.drain_filter(|dummy, _| dummy.query(true)))).unwrap_err();

assert_eq!(PREDS.load(SeqCst), 0x011);
assert_eq!(DROPS.load(SeqCst), 3);
assert_eq!(a.queried(), 1);
assert_eq!(b.queried(), 1);
assert_eq!(c.queried(), 0);
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 1);
assert_eq!(c.dropped(), 1);
}

#[test]
fn pred_panic_leak() {
static PREDS: AtomicUsize = AtomicUsize::new(0);
static DROPS: AtomicUsize = AtomicUsize::new(0);

struct D;
impl Drop for D {
fn drop(&mut self) {
DROPS.fetch_add(1, SeqCst);
}
}

// Keys are multiples of 4, so that each key is counted by a hexadecimal digit.
let mut map = (0..3).map(|i| (i * 4, D)).collect::<BTreeMap<_, _>>();

catch_unwind(AssertUnwindSafe(|| {
drop(map.drain_filter(|i, _| {
PREDS.fetch_add(1usize << i, SeqCst);
match i {
0 => true,
_ => panic!(),
}
}))
}))
.unwrap_err();

assert_eq!(PREDS.load(SeqCst), 0x011);
assert_eq!(DROPS.load(SeqCst), 1);
let a = CrashTestDummy::new(0);
let b = CrashTestDummy::new(1);
let c = CrashTestDummy::new(2);
let mut map = BTreeMap::new();
map.insert(a.spawn(Panic::Never), ());
map.insert(b.spawn(Panic::InQuery), ());
map.insert(c.spawn(Panic::InQuery), ());

catch_unwind(AssertUnwindSafe(|| drop(map.drain_filter(|dummy, _| dummy.query(true)))))
.unwrap_err();

assert_eq!(a.queried(), 1);
assert_eq!(b.queried(), 1);
assert_eq!(c.queried(), 0);
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 0);
assert_eq!(c.dropped(), 0);
assert_eq!(map.len(), 2);
assert_eq!(map.first_entry().unwrap().key(), &4);
assert_eq!(map.last_entry().unwrap().key(), &8);
assert_eq!(map.first_entry().unwrap().key().id(), 1);
assert_eq!(map.last_entry().unwrap().key().id(), 2);
map.check();
}

// Same as above, but attempt to use the iterator again after the panic in the predicate
#[test]
fn pred_panic_reuse() {
static PREDS: AtomicUsize = AtomicUsize::new(0);
static DROPS: AtomicUsize = AtomicUsize::new(0);

struct D;
impl Drop for D {
fn drop(&mut self) {
DROPS.fetch_add(1, SeqCst);
}
}

// Keys are multiples of 4, so that each key is counted by a hexadecimal digit.
let mut map = (0..3).map(|i| (i * 4, D)).collect::<BTreeMap<_, _>>();
let a = CrashTestDummy::new(0);
let b = CrashTestDummy::new(1);
let c = CrashTestDummy::new(2);
let mut map = BTreeMap::new();
map.insert(a.spawn(Panic::Never), ());
map.insert(b.spawn(Panic::InQuery), ());
map.insert(c.spawn(Panic::InQuery), ());

{
let mut it = map.drain_filter(|i, _| {
PREDS.fetch_add(1usize << i, SeqCst);
match i {
0 => true,
_ => panic!(),
}
});
let mut it = map.drain_filter(|dummy, _| dummy.query(true));
catch_unwind(AssertUnwindSafe(|| while it.next().is_some() {})).unwrap_err();
// Iterator behaviour after a panic is explicitly unspecified,
// so this is just the current implementation:
let result = catch_unwind(AssertUnwindSafe(|| it.next()));
assert!(matches!(result, Ok(None)));
}

assert_eq!(PREDS.load(SeqCst), 0x011);
assert_eq!(DROPS.load(SeqCst), 1);
assert_eq!(a.queried(), 1);
assert_eq!(b.queried(), 1);
assert_eq!(c.queried(), 0);
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 0);
assert_eq!(c.dropped(), 0);
assert_eq!(map.len(), 2);
assert_eq!(map.first_entry().unwrap().key(), &4);
assert_eq!(map.last_entry().unwrap().key(), &8);
assert_eq!(map.first_entry().unwrap().key().id(), 1);
assert_eq!(map.last_entry().unwrap().key().id(), 2);
map.check();
}
}
@@ -1439,6 +1413,43 @@ fn test_bad_zst() {
m.check();
}

#[test]
fn test_clear() {
let mut map = BTreeMap::new();
for &len in &[MIN_INSERTS_HEIGHT_1, MIN_INSERTS_HEIGHT_2, 0, NODE_CAPACITY] {
for i in 0..len {
map.insert(i, ());
}
assert_eq!(map.len(), len);
map.clear();
map.check();
assert!(map.is_empty());
}
}

#[test]
fn test_clear_drop_panic_leak() {
let a = CrashTestDummy::new(0);
let b = CrashTestDummy::new(1);
let c = CrashTestDummy::new(2);

let mut map = BTreeMap::new();
map.insert(a.spawn(Panic::Never), ());
map.insert(b.spawn(Panic::InDrop), ());
map.insert(c.spawn(Panic::Never), ());

catch_unwind(AssertUnwindSafe(|| map.clear())).unwrap_err();
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 1);
assert_eq!(c.dropped(), 1);
assert_eq!(map.len(), 0);

drop(map);
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 1);
assert_eq!(c.dropped(), 1);
}

#[test]
fn test_clone() {
let mut map = BTreeMap::new();
@@ -1484,6 +1495,35 @@ fn test_clone() {
map.check();
}

#[test]
fn test_clone_panic_leak() {
let a = CrashTestDummy::new(0);
let b = CrashTestDummy::new(1);
let c = CrashTestDummy::new(2);

let mut map = BTreeMap::new();
map.insert(a.spawn(Panic::Never), ());
map.insert(b.spawn(Panic::InClone), ());
map.insert(c.spawn(Panic::Never), ());

catch_unwind(|| map.clone()).unwrap_err();
assert_eq!(a.cloned(), 1);
assert_eq!(b.cloned(), 1);
assert_eq!(c.cloned(), 0);
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 0);
assert_eq!(c.dropped(), 0);
assert_eq!(map.len(), 3);

drop(map);
assert_eq!(a.cloned(), 1);
assert_eq!(b.cloned(), 1);
assert_eq!(c.cloned(), 0);
assert_eq!(a.dropped(), 2);
assert_eq!(b.dropped(), 1);
assert_eq!(c.dropped(), 1);
}

#[test]
fn test_clone_from() {
let mut map1 = BTreeMap::new();
@@ -1901,29 +1941,21 @@ create_append_test!(test_append_1700, 1700);

#[test]
fn test_append_drop_leak() {
static DROPS: AtomicUsize = AtomicUsize::new(0);

struct D;

impl Drop for D {
fn drop(&mut self) {
if DROPS.fetch_add(1, SeqCst) == 0 {
panic!("panic in `drop`");
}
}
}

let a = CrashTestDummy::new(0);
let b = CrashTestDummy::new(1);
let c = CrashTestDummy::new(2);
let mut left = BTreeMap::new();
let mut right = BTreeMap::new();
left.insert(0, D);
left.insert(1, D); // first to be dropped during append
left.insert(2, D);
right.insert(1, D);
right.insert(2, D);
left.insert(a.spawn(Panic::Never), ());
left.insert(b.spawn(Panic::InDrop), ()); // first duplicate key, dropped during append
left.insert(c.spawn(Panic::Never), ());
right.insert(b.spawn(Panic::Never), ());
right.insert(c.spawn(Panic::Never), ());

catch_unwind(move || left.append(&mut right)).unwrap_err();

assert_eq!(DROPS.load(SeqCst), 4); // Rust issue #47949 ate one little piggy
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 1); // should be 2 were it not for Rust issue #47949
assert_eq!(c.dropped(), 2);
}

#[test]
@@ -2050,51 +2082,42 @@ fn test_split_off_large_random_sorted() {

#[test]
fn test_into_iter_drop_leak_height_0() {
static DROPS: AtomicUsize = AtomicUsize::new(0);

struct D;

impl Drop for D {
fn drop(&mut self) {
if DROPS.fetch_add(1, SeqCst) == 3 {
panic!("panic in `drop`");
}
}
}

let a = CrashTestDummy::new(0);
let b = CrashTestDummy::new(1);
let c = CrashTestDummy::new(2);
let d = CrashTestDummy::new(3);
let e = CrashTestDummy::new(4);
let mut map = BTreeMap::new();
map.insert("a", D);
map.insert("b", D);
map.insert("c", D);
map.insert("d", D);
map.insert("e", D);
map.insert("a", a.spawn(Panic::Never));
map.insert("b", b.spawn(Panic::Never));
map.insert("c", c.spawn(Panic::Never));
map.insert("d", d.spawn(Panic::InDrop));
map.insert("e", e.spawn(Panic::Never));

catch_unwind(move || drop(map.into_iter())).unwrap_err();

assert_eq!(DROPS.load(SeqCst), 5);
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 1);
assert_eq!(c.dropped(), 1);
assert_eq!(d.dropped(), 1);
assert_eq!(e.dropped(), 1);
}

#[test]
fn test_into_iter_drop_leak_height_1() {
let size = MIN_INSERTS_HEIGHT_1;
static DROPS: AtomicUsize = AtomicUsize::new(0);
static PANIC_POINT: AtomicUsize = AtomicUsize::new(0);

struct D;
impl Drop for D {
fn drop(&mut self) {
if DROPS.fetch_add(1, SeqCst) == PANIC_POINT.load(SeqCst) {
panic!("panic in `drop`");
}
}
}

for panic_point in vec![0, 1, size - 2, size - 1] {
DROPS.store(0, SeqCst);
PANIC_POINT.store(panic_point, SeqCst);
let map: BTreeMap<_, _> = (0..size).map(|i| (i, D)).collect();
let dummies: Vec<_> = (0..size).map(|i| CrashTestDummy::new(i)).collect();
let map: BTreeMap<_, _> = (0..size)
.map(|i| {
let panic = if i == panic_point { Panic::InDrop } else { Panic::Never };
(dummies[i].spawn(Panic::Never), dummies[i].spawn(panic))
})
.collect();
catch_unwind(move || drop(map.into_iter())).unwrap_err();
assert_eq!(DROPS.load(SeqCst), size);
for i in 0..size {
assert_eq!(dummies[i].dropped(), 2);
}
}
}

30 changes: 1 addition & 29 deletions library/alloc/src/collections/btree/mod.rs
Original file line number Diff line number Diff line change
@@ -20,32 +20,4 @@ trait Recover<Q: ?Sized> {
}

#[cfg(test)]
/// XorShiftRng
struct DeterministicRng {
count: usize,
x: u32,
y: u32,
z: u32,
w: u32,
}

#[cfg(test)]
impl DeterministicRng {
fn new() -> Self {
DeterministicRng { count: 0, x: 0x193a6754, y: 0xa8a7d469, z: 0x97830e05, w: 0x113ba7bb }
}

/// Guarantees that each returned number is unique.
fn next(&mut self) -> u32 {
self.count += 1;
assert!(self.count <= 70029);
let x = self.x;
let t = x ^ (x << 11);
self.x = self.y;
self.y = self.z;
self.z = self.w;
let w_ = self.w;
self.w = w_ ^ (w_ >> 19) ^ (t ^ (t >> 8));
self.w
}
}
mod testing;
89 changes: 32 additions & 57 deletions library/alloc/src/collections/btree/set/tests.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use super::super::DeterministicRng;
use super::super::testing::crash_test::{CrashTestDummy, Panic};
use super::super::testing::rng::DeterministicRng;
use super::*;
use crate::vec::Vec;
use std::cmp::Ordering;
use std::iter::FromIterator;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU32, Ordering::SeqCst};

#[test]
fn test_clone_eq() {
@@ -349,70 +349,45 @@ fn test_drain_filter() {

#[test]
fn test_drain_filter_drop_panic_leak() {
static PREDS: AtomicU32 = AtomicU32::new(0);
static DROPS: AtomicU32 = AtomicU32::new(0);

#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct D(i32);
impl Drop for D {
fn drop(&mut self) {
if DROPS.fetch_add(1, SeqCst) == 1 {
panic!("panic in `drop`");
}
}
}

let a = CrashTestDummy::new(0);
let b = CrashTestDummy::new(1);
let c = CrashTestDummy::new(2);
let mut set = BTreeSet::new();
set.insert(D(0));
set.insert(D(4));
set.insert(D(8));
set.insert(a.spawn(Panic::Never));
set.insert(b.spawn(Panic::InDrop));
set.insert(c.spawn(Panic::Never));

catch_unwind(move || {
drop(set.drain_filter(|d| {
PREDS.fetch_add(1u32 << d.0, SeqCst);
true
}))
})
.ok();
catch_unwind(move || drop(set.drain_filter(|dummy| dummy.query(true)))).ok();

assert_eq!(PREDS.load(SeqCst), 0x011);
assert_eq!(DROPS.load(SeqCst), 3);
assert_eq!(a.queried(), 1);
assert_eq!(b.queried(), 1);
assert_eq!(c.queried(), 0);
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 1);
assert_eq!(c.dropped(), 1);
}

#[test]
fn test_drain_filter_pred_panic_leak() {
static PREDS: AtomicU32 = AtomicU32::new(0);
static DROPS: AtomicU32 = AtomicU32::new(0);

#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct D(i32);
impl Drop for D {
fn drop(&mut self) {
DROPS.fetch_add(1, SeqCst);
}
}

let a = CrashTestDummy::new(0);
let b = CrashTestDummy::new(1);
let c = CrashTestDummy::new(2);
let mut set = BTreeSet::new();
set.insert(D(0));
set.insert(D(4));
set.insert(D(8));

catch_unwind(AssertUnwindSafe(|| {
drop(set.drain_filter(|d| {
PREDS.fetch_add(1u32 << d.0, SeqCst);
match d.0 {
0 => true,
_ => panic!(),
}
}))
}))
.ok();

assert_eq!(PREDS.load(SeqCst), 0x011);
assert_eq!(DROPS.load(SeqCst), 1);
set.insert(a.spawn(Panic::Never));
set.insert(b.spawn(Panic::InQuery));
set.insert(c.spawn(Panic::InQuery));

catch_unwind(AssertUnwindSafe(|| drop(set.drain_filter(|dummy| dummy.query(true))))).ok();

assert_eq!(a.queried(), 1);
assert_eq!(b.queried(), 1);
assert_eq!(c.queried(), 0);
assert_eq!(a.dropped(), 1);
assert_eq!(b.dropped(), 0);
assert_eq!(c.dropped(), 0);
assert_eq!(set.len(), 2);
assert_eq!(set.first().unwrap().0, 4);
assert_eq!(set.last().unwrap().0, 8);
assert_eq!(set.first().unwrap().id(), 1);
assert_eq!(set.last().unwrap().id(), 2);
}

#[test]
119 changes: 119 additions & 0 deletions library/alloc/src/collections/btree/testing/crash_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use crate::fmt::Debug;
use std::cmp::Ordering;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};

/// A blueprint for crash test dummy instances that monitor particular events.
/// Some instances may be configured to panic at some point.
/// Events are `clone`, `drop` or some anonymous `query`.
///
/// Crash test dummies are identified and ordered by an id, so they can be used
/// as keys in a BTreeMap. The implementation intentionally uses does not rely
/// on anything defined in the crate, apart from the `Debug` trait.
#[derive(Debug)]
pub struct CrashTestDummy {
id: usize,
cloned: AtomicUsize,
dropped: AtomicUsize,
queried: AtomicUsize,
}

impl CrashTestDummy {
/// Creates a crash test dummy design. The `id` determines order and equality of instances.
pub fn new(id: usize) -> CrashTestDummy {
CrashTestDummy {
id,
cloned: AtomicUsize::new(0),
dropped: AtomicUsize::new(0),
queried: AtomicUsize::new(0),
}
}

/// Creates an instance of a crash test dummy that records what events it experiences
/// and optionally panics.
pub fn spawn(&self, panic: Panic) -> Instance<'_> {
Instance { origin: self, panic }
}

/// Returns how many times instances of the dummy have been cloned.
pub fn cloned(&self) -> usize {
self.cloned.load(SeqCst)
}

/// Returns how many times instances of the dummy have been dropped.
pub fn dropped(&self) -> usize {
self.dropped.load(SeqCst)
}

/// Returns how many times instances of the dummy have had their `query` member invoked.
pub fn queried(&self) -> usize {
self.queried.load(SeqCst)
}
}

#[derive(Debug)]
pub struct Instance<'a> {
origin: &'a CrashTestDummy,
panic: Panic,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Panic {
Never,
InClone,
InDrop,
InQuery,
}

impl Instance<'_> {
pub fn id(&self) -> usize {
self.origin.id
}

/// Some anonymous query, the result of which is already given.
pub fn query<R>(&self, result: R) -> R {
self.origin.queried.fetch_add(1, SeqCst);
if self.panic == Panic::InQuery {
panic!("panic in `query`");
}
result
}
}

impl Clone for Instance<'_> {
fn clone(&self) -> Self {
self.origin.cloned.fetch_add(1, SeqCst);
if self.panic == Panic::InClone {
panic!("panic in `clone`");
}
Self { origin: self.origin, panic: Panic::Never }
}
}

impl Drop for Instance<'_> {
fn drop(&mut self) {
self.origin.dropped.fetch_add(1, SeqCst);
if self.panic == Panic::InDrop {
panic!("panic in `drop`");
}
}
}

impl PartialOrd for Instance<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.id().partial_cmp(&other.id())
}
}

impl Ord for Instance<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.id().cmp(&other.id())
}
}

impl PartialEq for Instance<'_> {
fn eq(&self, other: &Self) -> bool {
self.id().eq(&other.id())
}
}

impl Eq for Instance<'_> {}
3 changes: 3 additions & 0 deletions library/alloc/src/collections/btree/testing/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod crash_test;
pub mod ord_chaos;
pub mod rng;
28 changes: 28 additions & 0 deletions library/alloc/src/collections/btree/testing/rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// XorShiftRng
pub struct DeterministicRng {
count: usize,
x: u32,
y: u32,
z: u32,
w: u32,
}

impl DeterministicRng {
pub fn new() -> Self {
DeterministicRng { count: 0, x: 0x193a6754, y: 0xa8a7d469, z: 0x97830e05, w: 0x113ba7bb }
}

/// Guarantees that each returned number is unique.
pub fn next(&mut self) -> u32 {
self.count += 1;
assert!(self.count <= 70029);
let x = self.x;
let t = x ^ (x << 11);
self.x = self.y;
self.y = self.z;
self.z = self.w;
let w_ = self.w;
self.w = w_ ^ (w_ >> 19) ^ (t ^ (t >> 8));
self.w
}
}