From 6d2247eac2256b3f6e8d10e3bc9c3b6068d74967 Mon Sep 17 00:00:00 2001 From: Stein Somers Date: Sat, 23 Jan 2021 15:25:56 +0100 Subject: [PATCH 01/18] BTreeMap/BTreeSet: separate off code supporting tests --- .../alloc/src/collections/btree/map/tests.rs | 6 ++-- library/alloc/src/collections/btree/mod.rs | 30 +------------------ .../alloc/src/collections/btree/set/tests.rs | 2 +- .../src/collections/btree/testing/mod.rs | 2 ++ .../btree/{map/tests => testing}/ord_chaos.rs | 0 .../src/collections/btree/testing/rng.rs | 28 +++++++++++++++++ 6 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 library/alloc/src/collections/btree/testing/mod.rs rename library/alloc/src/collections/btree/{map/tests => testing}/ord_chaos.rs (100%) create mode 100644 library/alloc/src/collections/btree/testing/rng.rs diff --git a/library/alloc/src/collections/btree/map/tests.rs b/library/alloc/src/collections/btree/map/tests.rs index 1993c6e047d48..73607a2a638f3 100644 --- a/library/alloc/src/collections/btree/map/tests.rs +++ b/library/alloc/src/collections/btree/map/tests.rs @@ -1,4 +1,5 @@ -use super::super::{node, DeterministicRng}; +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 +16,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; diff --git a/library/alloc/src/collections/btree/mod.rs b/library/alloc/src/collections/btree/mod.rs index cf91c17b511cc..421f842dab0ac 100644 --- a/library/alloc/src/collections/btree/mod.rs +++ b/library/alloc/src/collections/btree/mod.rs @@ -20,32 +20,4 @@ trait Recover { } #[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; diff --git a/library/alloc/src/collections/btree/set/tests.rs b/library/alloc/src/collections/btree/set/tests.rs index 3762af7236af2..9c6fb44af43c8 100644 --- a/library/alloc/src/collections/btree/set/tests.rs +++ b/library/alloc/src/collections/btree/set/tests.rs @@ -1,4 +1,4 @@ -use super::super::DeterministicRng; +use super::super::testing::rng::DeterministicRng; use super::*; use crate::vec::Vec; use std::cmp::Ordering; diff --git a/library/alloc/src/collections/btree/testing/mod.rs b/library/alloc/src/collections/btree/testing/mod.rs new file mode 100644 index 0000000000000..03880e7014e72 --- /dev/null +++ b/library/alloc/src/collections/btree/testing/mod.rs @@ -0,0 +1,2 @@ +pub mod ord_chaos; +pub mod rng; diff --git a/library/alloc/src/collections/btree/map/tests/ord_chaos.rs b/library/alloc/src/collections/btree/testing/ord_chaos.rs similarity index 100% rename from library/alloc/src/collections/btree/map/tests/ord_chaos.rs rename to library/alloc/src/collections/btree/testing/ord_chaos.rs diff --git a/library/alloc/src/collections/btree/testing/rng.rs b/library/alloc/src/collections/btree/testing/rng.rs new file mode 100644 index 0000000000000..ecf543bee035a --- /dev/null +++ b/library/alloc/src/collections/btree/testing/rng.rs @@ -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 + } +} From 3e1d602a6b3ed491e2188176addf41a059c1ff02 Mon Sep 17 00:00:00 2001 From: Stein Somers Date: Sat, 23 Jan 2021 15:25:56 +0100 Subject: [PATCH 02/18] BTreeMap: share panicky test code & test panic during clear, clone --- .../alloc/src/collections/btree/map/tests.rs | 279 ++++++++++-------- .../alloc/src/collections/btree/set/tests.rs | 87 ++---- .../collections/btree/testing/crash_test.rs | 119 ++++++++ .../src/collections/btree/testing/mod.rs | 1 + 4 files changed, 303 insertions(+), 183 deletions(-) create mode 100644 library/alloc/src/collections/btree/testing/crash_test.rs diff --git a/library/alloc/src/collections/btree/map/tests.rs b/library/alloc/src/collections/btree/map/tests.rs index 73607a2a638f3..56d6ae57e04a5 100644 --- a/library/alloc/src/collections/btree/map/tests.rs +++ b/library/alloc/src/collections/btree/map/tests.rs @@ -1,3 +1,4 @@ +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}; @@ -1134,91 +1135,62 @@ 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::>(); + 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::>(); - - 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::>(); + 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: @@ -1226,11 +1198,15 @@ mod test_drain_filter { 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(); } } @@ -1437,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(); @@ -1482,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(); @@ -1899,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] @@ -2048,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); + } } } diff --git a/library/alloc/src/collections/btree/set/tests.rs b/library/alloc/src/collections/btree/set/tests.rs index 9c6fb44af43c8..4cb6e3d6619dc 100644 --- a/library/alloc/src/collections/btree/set/tests.rs +++ b/library/alloc/src/collections/btree/set/tests.rs @@ -1,10 +1,10 @@ +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] diff --git a/library/alloc/src/collections/btree/testing/crash_test.rs b/library/alloc/src/collections/btree/testing/crash_test.rs new file mode 100644 index 0000000000000..b2527b95f5bba --- /dev/null +++ b/library/alloc/src/collections/btree/testing/crash_test.rs @@ -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(&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 { + 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<'_> {} diff --git a/library/alloc/src/collections/btree/testing/mod.rs b/library/alloc/src/collections/btree/testing/mod.rs index 03880e7014e72..7a094f8a59522 100644 --- a/library/alloc/src/collections/btree/testing/mod.rs +++ b/library/alloc/src/collections/btree/testing/mod.rs @@ -1,2 +1,3 @@ +pub mod crash_test; pub mod ord_chaos; pub mod rng; From c5b43aa2b32951f25d05dd3fb6ec1f5799598977 Mon Sep 17 00:00:00 2001 From: Christiaan Dirkx Date: Mon, 15 Feb 2021 17:39:56 +0100 Subject: [PATCH 03/18] Update RELEASES.md 1.50 to include methods stabilized in #79342 --- RELEASES.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index f5b71f295c629..25f9c802982c3 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -43,6 +43,23 @@ The following previously stable methods are now `const`. - [`IpAddr::is_ipv4`] - [`IpAddr::is_ipv6`] +- [`IpAddr::is_unspecified`] +- [`IpAddr::is_loopback`] +- [`IpAddr::is_multicast`] +- [`Ipv4Addr::octets`] +- [`Ipv4Addr::is_loopback`] +- [`Ipv4Addr::is_private`] +- [`Ipv4Addr::is_link_local`] +- [`Ipv4Addr::is_multicast`] +- [`Ipv4Addr::is_broadcast`] +- [`Ipv4Addr::is_documentation`] +- [`Ipv4Addr::to_ipv6_compatible`] +- [`Ipv4Addr::to_ipv6_mapped`] +- [`Ipv6Addr::segments`] +- [`Ipv6Addr::is_unspecified`] +- [`Ipv6Addr::is_loopback`] +- [`Ipv6Addr::is_multicast`] +- [`Ipv6Addr::to_ipv4`] - [`Layout::size`] - [`Layout::align`] - [`Layout::from_size_align`] @@ -104,6 +121,23 @@ Compatibility Notes [cargo/8725]: https://github.com/rust-lang/cargo/pull/8725 [`IpAddr::is_ipv4`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_ipv4 [`IpAddr::is_ipv6`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_ipv6 +[`IpAddr::is_unspecified`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_unspecified +[`IpAddr::is_loopback`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_loopback +[`IpAddr::is_multicast`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_multicast +[`Ipv4Addr::octets`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.octets +[`Ipv4Addr::is_loopback`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_loopback +[`Ipv4Addr::is_private`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_private +[`Ipv4Addr::is_link_local`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_link_local +[`Ipv4Addr::is_multicast`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_multicast +[`Ipv4Addr::is_broadcast`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_broadcast +[`Ipv4Addr::is_documentation`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_documentation +[`Ipv4Addr::to_ipv6_compatible`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.to_ipv6_compatible +[`Ipv4Addr::to_ipv6_mapped`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.to_ipv6_mapped +[`Ipv6Addr::segments`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.segments +[`Ipv6Addr::is_unspecified`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.is_unspecified +[`Ipv6Addr::is_loopback`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.is_loopback +[`Ipv6Addr::is_multicast`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.is_multicast +[`Ipv6Addr::to_ipv4`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.to_ipv4 [`Layout::align`]: https://doc.rust-lang.org/stable/std/alloc/struct.Layout.html#method.align [`Layout::from_size_align`]: https://doc.rust-lang.org/stable/std/alloc/struct.Layout.html#method.from_size_align [`Layout::size`]: https://doc.rust-lang.org/stable/std/alloc/struct.Layout.html#method.size From f52caa76f9204b1341b59adad3d6323f4ca35292 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Tue, 16 Feb 2021 10:35:15 +0100 Subject: [PATCH 04/18] Do not delete bootstrap.exe on Windows during clean Windows does not allow deleting currently running executables --- src/bootstrap/clean.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs index 9b9df36e7dc0e..6026a000c3004 100644 --- a/src/bootstrap/clean.rs +++ b/src/bootstrap/clean.rs @@ -21,6 +21,9 @@ pub fn clean(build: &Build, all: bool) { } else { rm_rf(&build.out.join("tmp")); rm_rf(&build.out.join("dist")); + // Only delete the bootstrap executable on non-Windows systems + // Windows does not allow deleting a currently running executable + #[cfg(not(windows))] rm_rf(&build.out.join("bootstrap")); for host in &build.hosts { From e18c79a4a9c06ae8dc282f1b4aae560a3076d9cd Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Tue, 16 Feb 2021 16:50:45 +0100 Subject: [PATCH 05/18] Work around various issues cleaning up bootstrap on Windows --- src/bootstrap/clean.rs | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs index 6026a000c3004..c2f01ecfd1ff9 100644 --- a/src/bootstrap/clean.rs +++ b/src/bootstrap/clean.rs @@ -21,9 +21,6 @@ pub fn clean(build: &Build, all: bool) { } else { rm_rf(&build.out.join("tmp")); rm_rf(&build.out.join("dist")); - // Only delete the bootstrap executable on non-Windows systems - // Windows does not allow deleting a currently running executable - #[cfg(not(windows))] rm_rf(&build.out.join("bootstrap")); for host in &build.hosts { @@ -54,14 +51,40 @@ fn rm_rf(path: &Path) { } Ok(metadata) => { if metadata.file_type().is_file() || metadata.file_type().is_symlink() { - do_op(path, "remove file", |p| fs::remove_file(p)); + do_op(path, "remove file", |p| { + fs::remove_file(p).or_else(|e| { + // Work around the fact that we cannot + // delete an executable while it runs on Windows. + #[cfg(windows)] + if e.kind() == std::io::ErrorKind::PermissionDenied + && p.file_name().and_then(std::ffi::OsStr::to_str) + == Some("bootstrap.exe") + { + eprintln!("warning: failed to delete '{}'.", p.display()); + return Ok(()); + } + Err(e) + }) + }); return; } for file in t!(fs::read_dir(path)) { rm_rf(&t!(file).path()); } - do_op(path, "remove dir", |p| fs::remove_dir(p)); + do_op(path, "remove dir", |p| { + fs::remove_dir(p).or_else(|e| { + // Check for dir not empty on Windows + #[cfg(windows)] + if matches!(e.kind(), std::io::ErrorKind::Other) + && e.raw_os_error() == Some(145) + { + return Ok(()); + } + + Err(e) + }) + }); } }; } @@ -80,8 +103,15 @@ where p.set_readonly(false); t!(fs::set_permissions(path, p)); f(path).unwrap_or_else(|e| { + // Deleting symlinked directories on Windows is non-trivial. + // Skip doing so for now. + #[cfg(windows)] + if e.kind() == ErrorKind::PermissionDenied && path.is_dir() { + eprintln!("warning: failed to delete '{}'.", path.display()); + return; + } panic!("failed to {} {}: {}", desc, path.display(), e); - }) + }); } Err(e) => { panic!("failed to {} {}: {}", desc, path.display(), e); From 5b0ed02bb9e0ee00c32cbf41eb2a12fe5476fe56 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Tue, 16 Feb 2021 17:08:11 +0100 Subject: [PATCH 06/18] Delete symlinked directories --- src/bootstrap/clean.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs index c2f01ecfd1ff9..3216c1af26730 100644 --- a/src/bootstrap/clean.rs +++ b/src/bootstrap/clean.rs @@ -99,15 +99,14 @@ where // As a result, we have some special logic to remove readonly files on windows. // This is also the reason that we can't use things like fs::remove_dir_all(). Err(ref e) if cfg!(windows) && e.kind() == ErrorKind::PermissionDenied => { - let mut p = t!(path.symlink_metadata()).permissions(); + let m = t!(path.symlink_metadata()); + let mut p = m.permissions(); p.set_readonly(false); t!(fs::set_permissions(path, p)); f(path).unwrap_or_else(|e| { - // Deleting symlinked directories on Windows is non-trivial. - // Skip doing so for now. + // Delete symlinked directories on Windows #[cfg(windows)] - if e.kind() == ErrorKind::PermissionDenied && path.is_dir() { - eprintln!("warning: failed to delete '{}'.", path.display()); + if m.file_type().is_symlink() && path.is_dir() && fs::remove_dir(path).is_ok() { return; } panic!("failed to {} {}: {}", desc, path.display(), e); From fcf6e6e80d723c7ee6d8734f944fcfb798be427b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 15 Feb 2021 10:18:26 +0100 Subject: [PATCH 07/18] Add check for ES5 in CI --- src/ci/docker/host-x86_64/mingw-check/Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ci/docker/host-x86_64/mingw-check/Dockerfile b/src/ci/docker/host-x86_64/mingw-check/Dockerfile index b2aa5844e4766..cb50e0c8f7438 100644 --- a/src/ci/docker/host-x86_64/mingw-check/Dockerfile +++ b/src/ci/docker/host-x86_64/mingw-check/Dockerfile @@ -17,6 +17,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ pkg-config \ mingw-w64 +RUN curl -sL https://nodejs.org/dist/v14.4.0/node-v14.4.0-linux-x64.tar.xz | tar -xJ +ENV PATH="/node-v14.4.0-linux-x64/bin:${PATH}" +# Install es-check +RUN npm install es-check -g + COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh @@ -29,4 +34,6 @@ ENV SCRIPT python3 ../x.py --stage 2 test src/tools/expand-yaml-anchors && \ python3 ../x.py test --stage 0 src/tools/compiletest && \ python3 ../x.py test --stage 2 src/tools/tidy && \ python3 ../x.py doc --stage 0 library/std && \ - /scripts/validate-toolstate.sh + /scripts/validate-toolstate.sh && \ + # Runs checks to ensure that there are no ES5 issues in our JS code. + es-check es5 ../src/librustdoc/html/static/*.js From 8ae05dfdf6014e8538ef11f989bc493363cc87ab Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Wed, 17 Feb 2021 19:13:51 +0000 Subject: [PATCH 08/18] try-back-block-type test: Use TryFromSliceError for From test Using `i32` is rather fragile because it has many implementations - and indeed I'm about to add one. TryFromSliceError is nice because it doesn't seem likely to grow new conversions. We still have one conversion, from Infallible. Signed-off-by: Ian Jackson --- src/test/ui/try-block/try-block-bad-type.rs | 2 +- src/test/ui/try-block/try-block-bad-type.stderr | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/test/ui/try-block/try-block-bad-type.rs b/src/test/ui/try-block/try-block-bad-type.rs index 496ba145810fb..ef6e690e1bd0e 100644 --- a/src/test/ui/try-block/try-block-bad-type.rs +++ b/src/test/ui/try-block/try-block-bad-type.rs @@ -3,7 +3,7 @@ #![feature(try_blocks)] pub fn main() { - let res: Result = try { + let res: Result = try { Err("")?; //~ ERROR `?` couldn't convert the error 5 }; diff --git a/src/test/ui/try-block/try-block-bad-type.stderr b/src/test/ui/try-block/try-block-bad-type.stderr index 2d1313d7d0e31..75a42c0d6b71b 100644 --- a/src/test/ui/try-block/try-block-bad-type.stderr +++ b/src/test/ui/try-block/try-block-bad-type.stderr @@ -1,16 +1,12 @@ -error[E0277]: `?` couldn't convert the error to `i32` +error[E0277]: `?` couldn't convert the error to `TryFromSliceError` --> $DIR/try-block-bad-type.rs:7:16 | LL | Err("")?; - | ^ the trait `From<&str>` is not implemented for `i32` + | ^ the trait `From<&str>` is not implemented for `TryFromSliceError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = help: the following implementations were found: - > - > - > - > - and 2 others + > = note: required by `from` error[E0271]: type mismatch resolving ` as Try>::Ok == &str` From 2380090f4995b3e156d325f765a023c49ca78a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Fri, 19 Feb 2021 00:00:00 +0000 Subject: [PATCH 09/18] Remove unsafe impl Send for CompletedTest & TestResult --- library/test/src/event.rs | 2 -- library/test/src/test_result.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/library/test/src/event.rs b/library/test/src/event.rs index 297bb72aecb2f..2103a0d10f4c6 100644 --- a/library/test/src/event.rs +++ b/library/test/src/event.rs @@ -24,8 +24,6 @@ impl CompletedTest { } } -unsafe impl Send for CompletedTest {} - #[derive(Debug, Clone)] pub enum TestEvent { TeFiltered(Vec), diff --git a/library/test/src/test_result.rs b/library/test/src/test_result.rs index 598fb670bb4bf..c5c56ca9c7ff9 100644 --- a/library/test/src/test_result.rs +++ b/library/test/src/test_result.rs @@ -24,8 +24,6 @@ pub enum TestResult { TrTimedFail, } -unsafe impl Send for TestResult {} - /// Creates a `TestResult` depending on the raw result of test execution /// and associated data. pub fn calc_result<'a>( From f10fbbbd534e459f98a3337d1f0bf2b23d607906 Mon Sep 17 00:00:00 2001 From: "Ricky (deg4uss3r)" Date: Thu, 4 Feb 2021 09:26:02 -0500 Subject: [PATCH 10/18] added aarch64_apple_ios_sim as a rustc target --- compiler/rustc_codegen_ssa/src/back/link.rs | 1 + .../src/spec/aarch64_apple_ios_sim.rs | 31 +++++++++++++++++++ .../rustc_target/src/spec/apple_sdk_base.rs | 6 +++- compiler/rustc_target/src/spec/mod.rs | 1 + src/doc/rustc/src/platform-support.md | 1 + 5 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 8bc4e64422370..b5339b455ac92 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -2187,6 +2187,7 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) { ("x86_64", "tvos") => "appletvsimulator", ("arm", "ios") => "iphoneos", ("aarch64", "ios") if llvm_target.contains("macabi") => "macosx", + ("aarch64", "ios") if llvm_target.contains("sim") => "iphonesimulator", ("aarch64", "ios") => "iphoneos", ("x86", "ios") => "iphonesimulator", ("x86_64", "ios") if llvm_target.contains("macabi") => "macosx", diff --git a/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs b/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs new file mode 100644 index 0000000000000..de4c6b44368da --- /dev/null +++ b/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs @@ -0,0 +1,31 @@ +use super::apple_sdk_base::{opts, Arch}; +use crate::spec::{Target, TargetOptions}; + +pub fn target() -> Target { + let base = opts("ios", Arch::Arm64_sim); + Target { + llvm_target: "arm64-apple-ios-simulator".to_string(), + pointer_width: 64, + data_layout: "e-m:o-i64:64-i128:128-n32:64-S128".to_string(), + arch: "aarch64".to_string(), + options: TargetOptions { + features: "+neon,+fp-armv8,+apple-a7".to_string(), + eliminate_frame_pointer: false, + max_atomic_width: Some(128), + unsupported_abis: super::arm_base::unsupported_abis(), + forces_embed_bitcode: true, + // Taken from a clang build on Xcode 11.4.1. + // These arguments are not actually invoked - they just have + // to look right to pass App Store validation. + bitcode_llvm_cmdline: "-triple\0\ + arm64-apple-ios14.0-simulator\0\ + -emit-obj\0\ + -disable-llvm-passes\0\ + -target-abi\0\ + darwinpcs\0\ + -Os\0" + .to_string(), + ..base + }, + } +} diff --git a/compiler/rustc_target/src/spec/apple_sdk_base.rs b/compiler/rustc_target/src/spec/apple_sdk_base.rs index d894f7599377e..538c4ca86974f 100644 --- a/compiler/rustc_target/src/spec/apple_sdk_base.rs +++ b/compiler/rustc_target/src/spec/apple_sdk_base.rs @@ -11,6 +11,7 @@ pub enum Arch { X86_64, X86_64_macabi, Arm64_macabi, + Arm64_sim, } fn target_cpu(arch: Arch) -> String { @@ -22,13 +23,16 @@ fn target_cpu(arch: Arch) -> String { X86_64 => "core2", X86_64_macabi => "core2", Arm64_macabi => "apple-a12", + Arm64_sim => "apple-a12", } .to_string() } fn link_env_remove(arch: Arch) -> Vec { match arch { - Armv7 | Armv7s | Arm64 | I386 | X86_64 => vec!["MACOSX_DEPLOYMENT_TARGET".to_string()], + Armv7 | Armv7s | Arm64 | I386 | X86_64 | Arm64_sim => { + vec!["MACOSX_DEPLOYMENT_TARGET".to_string()] + } X86_64_macabi | Arm64_macabi => vec!["IPHONEOS_DEPLOYMENT_TARGET".to_string()], } } diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 7a93bac72ca07..962374a21b2c7 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -727,6 +727,7 @@ supported_targets! { ("armv7s-apple-ios", armv7s_apple_ios), ("x86_64-apple-ios-macabi", x86_64_apple_ios_macabi), ("aarch64-apple-ios-macabi", aarch64_apple_ios_macabi), + ("aarch64-apple-ios-sim", aarch64_apple_ios_sim), ("aarch64-apple-tvos", aarch64_apple_tvos), ("x86_64-apple-tvos", x86_64_apple_tvos), diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index eb74041964701..641650b5b0942 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -153,6 +153,7 @@ not available. target | std | host | notes -------|-----|------|------- `aarch64-apple-ios-macabi` | ? | | Apple Catalyst on ARM64 +`aarch64-apple-ios-sim` | ? | | Apple iOS Simulator on ARM64 `aarch64-apple-tvos` | * | | ARM64 tvOS `aarch64-unknown-freebsd` | ✓ | ✓ | ARM64 FreeBSD `aarch64-unknown-hermit` | ? | | From 9b4e61255c4112c774194e62728b4d575abfff26 Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Wed, 3 Feb 2021 12:45:55 +0100 Subject: [PATCH 11/18] Document BinaryHeap unsafe functions --- library/alloc/src/collections/binary_heap.rs | 162 +++++++++++++------ 1 file changed, 113 insertions(+), 49 deletions(-) diff --git a/library/alloc/src/collections/binary_heap.rs b/library/alloc/src/collections/binary_heap.rs index 8a36b2af76522..87184f90d571a 100644 --- a/library/alloc/src/collections/binary_heap.rs +++ b/library/alloc/src/collections/binary_heap.rs @@ -275,7 +275,8 @@ impl fmt::Debug for PeekMut<'_, T> { impl Drop for PeekMut<'_, T> { fn drop(&mut self) { if self.sift { - self.heap.sift_down(0); + // SAFETY: PeekMut is only instantiated for non-empty heaps. + unsafe { self.heap.sift_down(0) }; } } } @@ -431,7 +432,8 @@ impl BinaryHeap { self.data.pop().map(|mut item| { if !self.is_empty() { swap(&mut item, &mut self.data[0]); - self.sift_down_to_bottom(0); + // SAFETY: !self.is_empty() means that self.len() > 0 + unsafe { self.sift_down_to_bottom(0) }; } item }) @@ -473,7 +475,9 @@ impl BinaryHeap { pub fn push(&mut self, item: T) { let old_len = self.len(); self.data.push(item); - self.sift_up(0, old_len); + // SAFETY: Since we pushed a new item it means that + // old_len = self.len() - 1 < self.len() + unsafe { self.sift_up(0, old_len) }; } /// Consumes the `BinaryHeap` and returns a vector in sorted @@ -506,7 +510,10 @@ impl BinaryHeap { let ptr = self.data.as_mut_ptr(); ptr::swap(ptr, ptr.add(end)); } - self.sift_down_range(0, end); + // SAFETY: `end` goes from `self.len() - 1` to 1 (both included) so: + // 0 < 1 <= end <= self.len() - 1 < self.len() + // Which means 0 < end and end < self.len(). + unsafe { self.sift_down_range(0, end) }; } self.into_vec() } @@ -519,47 +526,82 @@ impl BinaryHeap { // the hole is filled back at the end of its scope, even on panic. // Using a hole reduces the constant factor compared to using swaps, // which involves twice as many moves. - fn sift_up(&mut self, start: usize, pos: usize) -> usize { - unsafe { - // Take out the value at `pos` and create a hole. - let mut hole = Hole::new(&mut self.data, pos); - - while hole.pos() > start { - let parent = (hole.pos() - 1) / 2; - if hole.element() <= hole.get(parent) { - break; - } - hole.move_to(parent); + + /// # Safety + /// + /// The caller must guarantee that `pos < self.len()`. + unsafe fn sift_up(&mut self, start: usize, pos: usize) -> usize { + // Take out the value at `pos` and create a hole. + // SAFETY: The caller guarantees that pos < self.len() + let mut hole = unsafe { Hole::new(&mut self.data, pos) }; + + while hole.pos() > start { + let parent = (hole.pos() - 1) / 2; + + // SAFETY: hole.pos() > start >= 0, which means hole.pos() > 0 + // and so hole.pos() - 1 can't underflow. + // This guarantees that parent < hole.pos() so + // it's a valid index and also != hole.pos(). + if hole.element() <= unsafe { hole.get(parent) } { + break; } - hole.pos() + + // SAFETY: Same as above + unsafe { hole.move_to(parent) }; } + + hole.pos() } /// Take an element at `pos` and move it down the heap, /// while its children are larger. - fn sift_down_range(&mut self, pos: usize, end: usize) { - unsafe { - let mut hole = Hole::new(&mut self.data, pos); - let mut child = 2 * pos + 1; - while child < end - 1 { - // compare with the greater of the two children - child += (hole.get(child) <= hole.get(child + 1)) as usize; - // if we are already in order, stop. - if hole.element() >= hole.get(child) { - return; - } - hole.move_to(child); - child = 2 * hole.pos() + 1; - } - if child == end - 1 && hole.element() < hole.get(child) { - hole.move_to(child); + /// + /// # Safety + /// + /// The caller must guarantee that `pos < end <= self.len()`. + unsafe fn sift_down_range(&mut self, pos: usize, end: usize) { + // SAFETY: The caller guarantees that pos < end <= self.len(). + let mut hole = unsafe { Hole::new(&mut self.data, pos) }; + let mut child = 2 * hole.pos() + 1; + + // Loop invariant: child == 2 * hole.pos() + 1. + while child < end - 1 { + // compare with the greater of the two children + // SAFETY: child < end - 1 < self.len() and + // child + 1 < end <= self.len(), so they're valid indexes. + // child == 2 * hole.pos() + 1 != hole.pos() and + // child + 1 == 2 * hole.pos() + 2 != hole.pos(). + child += unsafe { hole.get(child) <= hole.get(child + 1) } as usize; + + // if we are already in order, stop. + // SAFETY: child is now either the old child or the old child+1 + // We already proven that both are < self.len() and != hole.pos() + if hole.element() >= unsafe { hole.get(child) } { + return; } + + // SAFETY: same as above. + unsafe { hole.move_to(child) }; + child = 2 * hole.pos() + 1; + } + + // SAFETY: && short circuit, which means that in the + // second condition it's already true that child == end - 1 < self.len(). + if child == end - 1 && hole.element() < unsafe { hole.get(child) } { + // SAFETY: child is already proven to be a valid index and + // child == 2 * hole.pos() + 1 != hole.pos(). + unsafe { hole.move_to(child) }; } } - fn sift_down(&mut self, pos: usize) { + /// # Safety + /// + /// The caller must guarantee that `pos < self.len()`. + unsafe fn sift_down(&mut self, pos: usize) { let len = self.len(); - self.sift_down_range(pos, len); + // SAFETY: pos < len is guaranteed by the caller and + // obviously len = self.len() <= self.len(). + unsafe { self.sift_down_range(pos, len) }; } /// Take an element at `pos` and move it all the way down the heap, @@ -567,30 +609,52 @@ impl BinaryHeap { /// /// Note: This is faster when the element is known to be large / should /// be closer to the bottom. - fn sift_down_to_bottom(&mut self, mut pos: usize) { + /// + /// # Safety + /// + /// The caller must guarantee that `pos < self.len()`. + unsafe fn sift_down_to_bottom(&mut self, mut pos: usize) { let end = self.len(); let start = pos; - unsafe { - let mut hole = Hole::new(&mut self.data, pos); - let mut child = 2 * pos + 1; - while child < end - 1 { - child += (hole.get(child) <= hole.get(child + 1)) as usize; - hole.move_to(child); - child = 2 * hole.pos() + 1; - } - if child == end - 1 { - hole.move_to(child); - } - pos = hole.pos; + + // SAFETY: The caller guarantees that pos < self.len(). + let mut hole = unsafe { Hole::new(&mut self.data, pos) }; + let mut child = 2 * hole.pos() + 1; + + // Loop invariant: child == 2 * hole.pos() + 1. + while child < end - 1 { + // SAFETY: child < end - 1 < self.len() and + // child + 1 < end <= self.len(), so they're valid indexes. + // child == 2 * hole.pos() + 1 != hole.pos() and + // child + 1 == 2 * hole.pos() + 2 != hole.pos(). + child += unsafe { hole.get(child) <= hole.get(child + 1) } as usize; + + // SAFETY: Same as above + unsafe { hole.move_to(child) }; + child = 2 * hole.pos() + 1; } - self.sift_up(start, pos); + + if child == end - 1 { + // SAFETY: child == end - 1 < self.len(), so it's a valid index + // and child == 2 * hole.pos() + 1 != hole.pos(). + unsafe { hole.move_to(child) }; + } + pos = hole.pos(); + drop(hole); + + // SAFETY: pos is the position in the hole and was already proven + // to be a valid index. + unsafe { self.sift_up(start, pos) }; } fn rebuild(&mut self) { let mut n = self.len() / 2; while n > 0 { n -= 1; - self.sift_down(n); + // SAFETY: n starts from self.len() / 2 and goes down to 0. + // The only case when !(n < self.len()) is if + // self.len() == 0, but it's ruled out by the loop condition. + unsafe { self.sift_down(n) }; } } From 3ec1a28418472d64518efcb72e25ff976d6ff140 Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Fri, 12 Feb 2021 10:50:16 +0100 Subject: [PATCH 12/18] Add FIXME for safety comments that are invalid when T is a ZST --- library/alloc/src/collections/binary_heap.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/alloc/src/collections/binary_heap.rs b/library/alloc/src/collections/binary_heap.rs index 87184f90d571a..33bd98d467cec 100644 --- a/library/alloc/src/collections/binary_heap.rs +++ b/library/alloc/src/collections/binary_heap.rs @@ -571,6 +571,8 @@ impl BinaryHeap { // child + 1 < end <= self.len(), so they're valid indexes. // child == 2 * hole.pos() + 1 != hole.pos() and // child + 1 == 2 * hole.pos() + 2 != hole.pos(). + // FIXME: 2 * hole.pos() + 1 or 2 * hole.pos() + 2 could overflow + // if T is a ZST child += unsafe { hole.get(child) <= hole.get(child + 1) } as usize; // if we are already in order, stop. @@ -627,6 +629,8 @@ impl BinaryHeap { // child + 1 < end <= self.len(), so they're valid indexes. // child == 2 * hole.pos() + 1 != hole.pos() and // child + 1 == 2 * hole.pos() + 2 != hole.pos(). + // FIXME: 2 * hole.pos() + 1 or 2 * hole.pos() + 2 could overflow + // if T is a ZST child += unsafe { hole.get(child) <= hole.get(child + 1) } as usize; // SAFETY: Same as above From 8d6ad11ab2490e95e6cf6daa18c2684e7382bb22 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Sun, 14 Feb 2021 16:33:03 +0100 Subject: [PATCH 13/18] iOS simulator: pick the target based on the environment variable LLVM picks the right things to put into the compiled object file based on the target deployment version. We need to communicate it through the target triple. Only with that LLVM will use the right commands in the file to make it look and behave like code compiled for the arm64 iOS simulator target. --- .../src/spec/aarch64_apple_ios_sim.rs | 10 ++++++++- compiler/rustc_target/src/spec/apple_base.rs | 21 ++++++++++++++----- src/doc/rustc/src/platform-support.md | 2 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs b/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs index de4c6b44368da..e594ceec1b78c 100644 --- a/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs +++ b/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs @@ -3,8 +3,16 @@ use crate::spec::{Target, TargetOptions}; pub fn target() -> Target { let base = opts("ios", Arch::Arm64_sim); + + // Clang automatically chooses a more specific target based on + // IPHONEOS_DEPLOYMENT_TARGET. + // This is required for the simulator target to pick the right + // MACH-O commands, so we do too. + let arch = "arm64"; + let llvm_target = super::apple_base::ios_sim_llvm_target(arch); + Target { - llvm_target: "arm64-apple-ios-simulator".to_string(), + llvm_target: llvm_target, pointer_width: 64, data_layout: "e-m:o-i64:64-i128:128-n32:64-S128".to_string(), arch: "aarch64".to_string(), diff --git a/compiler/rustc_target/src/spec/apple_base.rs b/compiler/rustc_target/src/spec/apple_base.rs index 3b458962b3d07..23f1357af163f 100644 --- a/compiler/rustc_target/src/spec/apple_base.rs +++ b/compiler/rustc_target/src/spec/apple_base.rs @@ -54,14 +54,16 @@ pub fn opts(os: &str) -> TargetOptions { } } -fn macos_deployment_target() -> (u32, u32) { - let deployment_target = env::var("MACOSX_DEPLOYMENT_TARGET").ok(); - let version = deployment_target +fn deployment_target(var_name: &str) -> Option<(u32, u32)> { + let deployment_target = env::var(var_name).ok(); + deployment_target .as_ref() .and_then(|s| s.split_once('.')) - .and_then(|(a, b)| a.parse::().and_then(|a| b.parse::().map(|b| (a, b))).ok()); + .and_then(|(a, b)| a.parse::().and_then(|a| b.parse::().map(|b| (a, b))).ok()) +} - version.unwrap_or((10, 7)) +fn macos_deployment_target() -> (u32, u32) { + deployment_target("MACOSX_DEPLOYMENT_TARGET").unwrap_or((10, 7)) } pub fn macos_llvm_target(arch: &str) -> String { @@ -84,3 +86,12 @@ pub fn macos_link_env_remove() -> Vec { env_remove.push("IPHONEOS_DEPLOYMENT_TARGET".to_string()); env_remove } + +fn ios_deployment_target() -> (u32, u32) { + deployment_target("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or((7, 0)) +} + +pub fn ios_sim_llvm_target(arch: &str) -> String { + let (major, minor) = ios_deployment_target(); + format!("{}-apple-ios{}.{}.0-simulator", arch, major, minor) +} diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 641650b5b0942..ef8fe480fc4bb 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -153,7 +153,7 @@ not available. target | std | host | notes -------|-----|------|------- `aarch64-apple-ios-macabi` | ? | | Apple Catalyst on ARM64 -`aarch64-apple-ios-sim` | ? | | Apple iOS Simulator on ARM64 +`aarch64-apple-ios-sim` | ? | | Apple iOS Simulator on ARM64 `aarch64-apple-tvos` | * | | ARM64 tvOS `aarch64-unknown-freebsd` | ✓ | ✓ | ARM64 FreeBSD `aarch64-unknown-hermit` | ? | | From 211d49c73cccdcf10444c0db3b8ae1e91582c6cd Mon Sep 17 00:00:00 2001 From: The8472 Date: Sat, 6 Feb 2021 17:29:56 +0100 Subject: [PATCH 14/18] parallelize x.py test tidy old: ``` real 0m11.123s user 0m14.495s sys 0m5.227s ``` new: ``` real 0m2.767s user 0m13.014s sys 0m1.691s ``` --- src/bootstrap/format.rs | 83 +++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 19 deletions(-) diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs index 0ae9f9712d569..40043c6e31ad2 100644 --- a/src/bootstrap/format.rs +++ b/src/bootstrap/format.rs @@ -3,10 +3,12 @@ use crate::Build; use build_helper::{output, t}; use ignore::WalkBuilder; -use std::path::Path; +use std::collections::VecDeque; +use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; +use std::sync::mpsc::SyncSender; -fn rustfmt(src: &Path, rustfmt: &Path, path: &Path, check: bool) { +fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> Box { let mut cmd = Command::new(&rustfmt); // avoid the submodule config paths from coming into play, // we only allow a single global config for the workspace for now @@ -17,18 +19,22 @@ fn rustfmt(src: &Path, rustfmt: &Path, path: &Path, check: bool) { if check { cmd.arg("--check"); } - cmd.arg(&path); + cmd.args(paths); let cmd_debug = format!("{:?}", cmd); - let status = cmd.status().expect("executing rustfmt"); - if !status.success() { - eprintln!( - "Running `{}` failed.\nIf you're running `tidy`, \ - try again with `--bless`. Or, if you just want to format \ - code, run `./x.py fmt` instead.", - cmd_debug, - ); - std::process::exit(1); - } + let mut cmd = cmd.spawn().expect("running rustfmt"); + // poor man's async: return a box that'll wait for rustfmt's completion + Box::new(move || { + let status = cmd.wait().unwrap(); + if !status.success() { + eprintln!( + "Running `{}` failed.\nIf you're running `tidy`, \ + try again with `--bless`. Or, if you just want to format \ + code, run `./x.py fmt` instead.", + cmd_debug, + ); + std::process::exit(1); + } + }) } #[derive(serde::Deserialize)] @@ -101,19 +107,58 @@ pub fn format(build: &Build, check: bool) { } let ignore_fmt = ignore_fmt.build().unwrap(); - let rustfmt_path = build.config.initial_rustfmt.as_ref().unwrap_or_else(|| { - eprintln!("./x.py fmt is not supported on this channel"); - std::process::exit(1); + let rustfmt_path = build + .config + .initial_rustfmt + .as_ref() + .unwrap_or_else(|| { + eprintln!("./x.py fmt is not supported on this channel"); + std::process::exit(1); + }) + .to_path_buf(); + let src = build.src.clone(); + let (tx, rx): (SyncSender, _) = std::sync::mpsc::sync_channel(128); + let walker = + WalkBuilder::new(src.clone()).types(matcher).overrides(ignore_fmt).build_parallel(); + + // there is a lot of blocking involved in spawning a child process and reading files to format. + // spawn more processes than available cores to keep the CPU busy + let max_processes = num_cpus::get() * 2; + + // spawn child processes on a separate thread so we can batch entries we have received from ignore + let thread = std::thread::spawn(move || { + let mut children = VecDeque::new(); + while let Ok(path) = rx.recv() { + // try getting a few more paths from the channel to amortize the overhead of spawning processes + let paths: Vec<_> = rx.try_iter().take(7).chain(std::iter::once(path)).collect(); + + let child = rustfmt(&src, &rustfmt_path, paths.as_slice(), check); + children.push_back(child); + + if children.len() > max_processes { + // await oldest child + children.pop_front().unwrap()(); + } + } + + // await remaining children + for mut child in children { + child(); + } }); - let src = &build.src; - let walker = WalkBuilder::new(src).types(matcher).overrides(ignore_fmt).build_parallel(); + walker.run(|| { + let tx = tx.clone(); Box::new(move |entry| { let entry = t!(entry); if entry.file_type().map_or(false, |t| t.is_file()) { - rustfmt(src, &rustfmt_path, &entry.path(), check); + t!(tx.send(entry.into_path())); } ignore::WalkState::Continue }) }); + + drop(tx); + + thread.join().unwrap(); } From 6dc948e7238780dbe3d8e19b03f720c7c1e53449 Mon Sep 17 00:00:00 2001 From: The8472 Date: Sat, 20 Feb 2021 22:52:44 +0100 Subject: [PATCH 15/18] limit rustfmt parallelism by taking -j into account --- src/bootstrap/format.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs index 40043c6e31ad2..3c9b66e5a017d 100644 --- a/src/bootstrap/format.rs +++ b/src/bootstrap/format.rs @@ -122,8 +122,8 @@ pub fn format(build: &Build, check: bool) { WalkBuilder::new(src.clone()).types(matcher).overrides(ignore_fmt).build_parallel(); // there is a lot of blocking involved in spawning a child process and reading files to format. - // spawn more processes than available cores to keep the CPU busy - let max_processes = num_cpus::get() * 2; + // spawn more processes than available concurrency to keep the CPU busy + let max_processes = build.jobs() as usize * 2; // spawn child processes on a separate thread so we can batch entries we have received from ignore let thread = std::thread::spawn(move || { @@ -135,7 +135,7 @@ pub fn format(build: &Build, check: bool) { let child = rustfmt(&src, &rustfmt_path, paths.as_slice(), check); children.push_back(child); - if children.len() > max_processes { + if children.len() >= max_processes { // await oldest child children.pop_front().unwrap()(); } From c07197046d40f973ce2217328444426214193e7e Mon Sep 17 00:00:00 2001 From: The8472 Date: Fri, 12 Feb 2021 21:27:16 +0100 Subject: [PATCH 16/18] remove redundant box wrapper --- src/bootstrap/format.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs index 3c9b66e5a017d..d21e3408144fe 100644 --- a/src/bootstrap/format.rs +++ b/src/bootstrap/format.rs @@ -8,7 +8,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::sync::mpsc::SyncSender; -fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> Box { +fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl FnMut() { let mut cmd = Command::new(&rustfmt); // avoid the submodule config paths from coming into play, // we only allow a single global config for the workspace for now @@ -22,8 +22,8 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> Box Box Date: Wed, 17 Feb 2021 09:47:31 -0800 Subject: [PATCH 17/18] Add A-diagnostics bug report template --- .github/ISSUE_TEMPLATE/diagnostics.md | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/diagnostics.md diff --git a/.github/ISSUE_TEMPLATE/diagnostics.md b/.github/ISSUE_TEMPLATE/diagnostics.md new file mode 100644 index 0000000000000..044979f3baec3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/diagnostics.md @@ -0,0 +1,46 @@ +--- +name: Diagnostic issue +about: Create a bug report or feature request for a change to `rustc`'s error output +labels: A-diagnostics, T-compiler +--- + + +Given the following code: + +```rust + +``` + +The current output is: + +``` + +``` + + +Ideally the output should look like: + +``` + +``` + + + + From 88753cead814a0830acdb527f513957fb9fe620a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Sun, 21 Feb 2021 00:00:00 +0000 Subject: [PATCH 18/18] test: Print test name only once on timeout Pretty formatter when using multiple test threads displays test name twice on timeout event. This implicitly suggest that those are two different events, while in fact they are always printed together. Print test name only once. Before: ``` running 3 tests test src/lib.rs - c (line 16) ... ok test src/lib.rs - a (line 3) ... ok test src/lib.rs - b (line 9) ... test src/lib.rs - b (line 9) has been running for over 60 seconds test src/lib.rs - b (line 9) ... ok ``` After: ``` running 3 tests test src/lib.rs - c (line 16) ... ok test src/lib.rs - a (line 3) ... ok test src/lib.rs - b (line 9) has been running for over 60 seconds test src/lib.rs - b (line 9) ... ok ``` --- library/test/src/formatters/pretty.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/library/test/src/formatters/pretty.rs b/library/test/src/formatters/pretty.rs index 6fa369298416b..5e41d6d969234 100644 --- a/library/test/src/formatters/pretty.rs +++ b/library/test/src/formatters/pretty.rs @@ -222,10 +222,6 @@ impl OutputFormatter for PrettyFormatter { } fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { - if self.is_multithreaded { - self.write_test_name(desc)?; - } - self.write_plain(&format!( "test {} has been running for over {} seconds\n", desc.name,