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: + +``` + +``` + + + + 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 diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 972b9bbfe1caf..b93a579df1526 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -2193,6 +2193,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..e594ceec1b78c --- /dev/null +++ b/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs @@ -0,0 +1,39 @@ +use super::apple_sdk_base::{opts, Arch}; +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: llvm_target, + 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_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/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 5b14795f5457c..ae803dbdb3117 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/library/alloc/src/collections/binary_heap.rs b/library/alloc/src/collections/binary_heap.rs index 8a36b2af76522..33bd98d467cec 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,84 @@ 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(). + // 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. + // 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 +611,54 @@ 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(). + // 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 + 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) }; } } diff --git a/library/alloc/src/collections/btree/map/tests.rs b/library/alloc/src/collections/btree/map/tests.rs index 1993c6e047d48..56d6ae57e04a5 100644 --- a/library/alloc/src/collections/btree/map/tests.rs +++ b/library/alloc/src/collections/btree/map/tests.rs @@ -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,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: @@ -1228,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(); } } @@ -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); + } } } 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..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::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] 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 new file mode 100644 index 0000000000000..7a094f8a59522 --- /dev/null +++ b/library/alloc/src/collections/btree/testing/mod.rs @@ -0,0 +1,3 @@ +pub mod crash_test; +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 + } +} 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/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, 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>( diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs index 9b9df36e7dc0e..3216c1af26730 100644 --- a/src/bootstrap/clean.rs +++ b/src/bootstrap/clean.rs @@ -51,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) + }) + }); } }; } @@ -73,12 +99,18 @@ 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| { + // Delete symlinked directories on Windows + #[cfg(windows)] + if m.file_type().is_symlink() && path.is_dir() && fs::remove_dir(path).is_ok() { + return; + } panic!("failed to {} {}: {}", desc, path.display(), e); - }) + }); } Err(e) => { panic!("failed to {} {}: {}", desc, path.display(), e); diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs index 0ae9f9712d569..d21e3408144fe 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) -> 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 @@ -17,17 +19,21 @@ 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 closure that'll wait for rustfmt's completion + 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); + } } } @@ -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 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 || { + 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(); } 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 diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 8198dbaa5278c..d21cf334442c8 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` | ? | | 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`