Skip to content

Commit 386abeb

Browse files
committed
Auto merge of #139988 - cuviper:beta-next, r=cuviper
[beta] backports - Fix 2024 edition doctest panic output #139328 - make `Arguments::as_statically_known_str` doc(hidden) #139389 - Revert "Deduplicate template parameter creation" #139878 - sync::mpsc: prevent double free on `Drop` #139553 r? cuviper
2 parents a594829 + f31cf5c commit 386abeb

File tree

9 files changed

+142
-30
lines changed

9 files changed

+142
-30
lines changed

compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs

+13-23
Original file line numberDiff line numberDiff line change
@@ -1314,31 +1314,21 @@ fn build_generic_type_param_di_nodes<'ll, 'tcx>(
13141314
ty: Ty<'tcx>,
13151315
) -> SmallVec<Option<&'ll DIType>> {
13161316
if let ty::Adt(def, args) = *ty.kind() {
1317-
let generics = cx.tcx.generics_of(def.did());
1318-
return get_template_parameters(cx, generics, args);
1319-
}
1320-
1321-
return smallvec![];
1322-
}
1323-
1324-
pub(super) fn get_template_parameters<'ll, 'tcx>(
1325-
cx: &CodegenCx<'ll, 'tcx>,
1326-
generics: &ty::Generics,
1327-
args: ty::GenericArgsRef<'tcx>,
1328-
) -> SmallVec<Option<&'ll DIType>> {
1329-
if args.types().next().is_some() {
1330-
let names = get_parameter_names(cx, generics);
1331-
let template_params: SmallVec<_> = iter::zip(args, names)
1332-
.filter_map(|(kind, name)| {
1333-
kind.as_type().map(|ty| {
1334-
let actual_type = cx.tcx.normalize_erasing_regions(cx.typing_env(), ty);
1335-
let actual_type_di_node = type_di_node(cx, actual_type);
1336-
Some(cx.create_template_type_parameter(name.as_str(), actual_type_di_node))
1317+
if args.types().next().is_some() {
1318+
let generics = cx.tcx.generics_of(def.did());
1319+
let names = get_parameter_names(cx, generics);
1320+
let template_params: SmallVec<_> = iter::zip(args, names)
1321+
.filter_map(|(kind, name)| {
1322+
kind.as_type().map(|ty| {
1323+
let actual_type = cx.tcx.normalize_erasing_regions(cx.typing_env(), ty);
1324+
let actual_type_di_node = type_di_node(cx, actual_type);
1325+
Some(cx.create_template_type_parameter(name.as_str(), actual_type_di_node))
1326+
})
13371327
})
1338-
})
1339-
.collect();
1328+
.collect();
13401329

1341-
return template_params;
1330+
return template_params;
1331+
}
13421332
}
13431333

13441334
return smallvec![];

compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,6 @@ fn build_coroutine_variant_struct_type_di_node<'ll, 'tcx>(
363363

364364
state_specific_fields.into_iter().chain(common_fields).collect()
365365
},
366-
// FIXME: this is a no-op. `build_generic_type_param_di_nodes` only works for Adts.
367366
|cx| build_generic_type_param_di_nodes(cx, coroutine_type_and_layout.ty),
368367
)
369368
.di_node

compiler/rustc_codegen_llvm/src/debuginfo/mod.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
use std::cell::{OnceCell, RefCell};
44
use std::ops::Range;
5-
use std::ptr;
65
use std::sync::Arc;
6+
use std::{iter, ptr};
77

88
use libc::c_uint;
99
use metadata::create_subroutine_type;
@@ -486,10 +486,40 @@ impl<'ll, 'tcx> DebugInfoCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> {
486486
generics: &ty::Generics,
487487
args: GenericArgsRef<'tcx>,
488488
) -> &'ll DIArray {
489-
let template_params = metadata::get_template_parameters(cx, generics, args);
489+
if args.types().next().is_none() {
490+
return create_DIArray(DIB(cx), &[]);
491+
}
492+
493+
// Again, only create type information if full debuginfo is enabled
494+
let template_params: Vec<_> = if cx.sess().opts.debuginfo == DebugInfo::Full {
495+
let names = get_parameter_names(cx, generics);
496+
iter::zip(args, names)
497+
.filter_map(|(kind, name)| {
498+
kind.as_type().map(|ty| {
499+
let actual_type = cx.tcx.normalize_erasing_regions(cx.typing_env(), ty);
500+
let actual_type_metadata = type_di_node(cx, actual_type);
501+
Some(cx.create_template_type_parameter(
502+
name.as_str(),
503+
actual_type_metadata,
504+
))
505+
})
506+
})
507+
.collect()
508+
} else {
509+
vec![]
510+
};
511+
490512
create_DIArray(DIB(cx), &template_params)
491513
}
492514

515+
fn get_parameter_names(cx: &CodegenCx<'_, '_>, generics: &ty::Generics) -> Vec<Symbol> {
516+
let mut names = generics.parent.map_or_else(Vec::new, |def_id| {
517+
get_parameter_names(cx, cx.tcx.generics_of(def_id))
518+
});
519+
names.extend(generics.own_params.iter().map(|param| param.name));
520+
names
521+
}
522+
493523
/// Returns a scope, plus `true` if that's a type scope for "class" methods,
494524
/// otherwise `false` for plain namespace scopes.
495525
fn get_containing_scope<'ll, 'tcx>(

library/core/src/fmt/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ impl<'a> Arguments<'a> {
743743
#[unstable(feature = "fmt_internals", reason = "internal to standard library", issue = "none")]
744744
#[must_use]
745745
#[inline]
746+
#[doc(hidden)]
746747
pub fn as_statically_known_str(&self) -> Option<&'static str> {
747748
let s = self.as_str();
748749
if core::intrinsics::is_val_statically_known(s.is_some()) { s } else { None }

library/std/src/sync/mpmc/list.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,11 @@ impl<T> Channel<T> {
213213
.compare_exchange(block, new, Ordering::Release, Ordering::Relaxed)
214214
.is_ok()
215215
{
216+
// This yield point leaves the channel in a half-initialized state where the
217+
// tail.block pointer is set but the head.block is not. This is used to
218+
// facilitate the test in src/tools/miri/tests/pass/issues/issue-139553.rs
219+
#[cfg(miri)]
220+
crate::thread::yield_now();
216221
self.head.block.store(new, Ordering::Release);
217222
block = new;
218223
} else {
@@ -564,9 +569,15 @@ impl<T> Channel<T> {
564569
// In that case, just wait until it gets initialized.
565570
while block.is_null() {
566571
backoff.spin_heavy();
567-
block = self.head.block.load(Ordering::Acquire);
572+
block = self.head.block.swap(ptr::null_mut(), Ordering::AcqRel);
568573
}
569574
}
575+
// After this point `head.block` is not modified again and it will be deallocated if it's
576+
// non-null. The `Drop` code of the channel, which runs after this function, also attempts
577+
// to deallocate `head.block` if it's non-null. Therefore this function must maintain the
578+
// invariant that if a deallocation of head.block is attemped then it must also be set to
579+
// NULL. Failing to do so will lead to the Drop code attempting a double free. For this
580+
// reason both reads above do an atomic swap instead of a simple atomic load.
570581

571582
unsafe {
572583
// Drop all messages between head and tail and deallocate the heap-allocated blocks.

src/librustdoc/doctest/runner.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ impl DocTestRunner {
113113
mod __doctest_mod {{
114114
use std::sync::OnceLock;
115115
use std::path::PathBuf;
116+
use std::process::ExitCode;
116117
117118
pub static BINARY_PATH: OnceLock<PathBuf> = OnceLock::new();
118119
pub const RUN_OPTION: &str = \"RUSTDOC_DOCTEST_RUN_NB_TEST\";
@@ -123,16 +124,17 @@ mod __doctest_mod {{
123124
}}
124125
125126
#[allow(unused)]
126-
pub fn doctest_runner(bin: &std::path::Path, test_nb: usize) -> Result<(), String> {{
127+
pub fn doctest_runner(bin: &std::path::Path, test_nb: usize) -> ExitCode {{
127128
let out = std::process::Command::new(bin)
128129
.env(self::RUN_OPTION, test_nb.to_string())
129130
.args(std::env::args().skip(1).collect::<Vec<_>>())
130131
.output()
131132
.expect(\"failed to run command\");
132133
if !out.status.success() {{
133-
Err(String::from_utf8_lossy(&out.stderr).to_string())
134+
eprint!(\"{{}}\", String::from_utf8_lossy(&out.stderr));
135+
ExitCode::FAILURE
134136
}} else {{
135-
Ok(())
137+
ExitCode::SUCCESS
136138
}}
137139
}}
138140
}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-compare-exchange-weak-failure-rate=0
2+
use std::sync::mpsc::channel;
3+
use std::thread;
4+
5+
/// This test aims to trigger a race condition that causes a double free in the unbounded channel
6+
/// implementation. The test relies on a particular thread scheduling to happen as annotated by the
7+
/// comments below.
8+
fn main() {
9+
let (s1, r) = channel::<u64>();
10+
let s2 = s1.clone();
11+
12+
let t1 = thread::spawn(move || {
13+
// 1. The first action executed is an attempt to send the first value in the channel. This
14+
// will begin to initialize the channel but will stop at a critical momement as
15+
// indicated by the `yield_now()` call in the `start_send` method of the implementation.
16+
let _ = s1.send(42);
17+
// 4. The sender is re-scheduled and it finishes the initialization of the channel by
18+
// setting head.block to the same value as tail.block. It then proceeds to publish its
19+
// value but observes that the channel has already disconnected (due to the concurrent
20+
// call of `discard_all_messages`) and aborts the send.
21+
});
22+
std::thread::yield_now();
23+
24+
// 2. A second sender attempts to send a value while the channel is in a half-initialized
25+
// state. Here, half-initialized means that the `tail.block` pointer points to a valid block
26+
// but `head.block` is still null. This condition is ensured by the yield of step 1. When
27+
// this call returns the channel state has tail.index != head.index, tail.block != NULL, and
28+
// head.block = NULL.
29+
s2.send(42).unwrap();
30+
// 3. This thread continues with dropping the one and only receiver. When all receivers are
31+
// gone `discard_all_messages` will attempt to drop all currently sent values and
32+
// de-allocate all the blocks. If `tail.block != NULL` but `head.block = NULL` the
33+
// implementation waits for the initializing sender to finish by spinning/yielding.
34+
drop(r);
35+
// 5. This thread is rescheduled and `discard_all_messages` observes the head.block pointer set
36+
// by step 4 and proceeds with deallocation. In the problematic version of the code
37+
// `head.block` is simply read via an `Acquire` load and not swapped with NULL. After this
38+
// call returns the channel state has tail.index = head.index, tail.block = NULL, and
39+
// head.block != NULL.
40+
t1.join().unwrap();
41+
// 6. The last sender (s2) is dropped here which also attempts to cleanup any data in the
42+
// channel. It observes `tail.index = head.index` and so it doesn't attempt to cleanup any
43+
// messages but it also observes that `head.block != NULL` and attempts to deallocate it.
44+
// This is however already deallocated by `discard_all_messages`, leading to a double free.
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// This is a regression test for <https://github.com/rust-lang/rust/issues/137970>.
2+
// The output must look nice and not like a `Debug` display of a `String`.
3+
4+
//@ edition: 2024
5+
//@ compile-flags: --test
6+
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
7+
//@ normalize-stdout: "panicked at .+rs:" -> "panicked at $$TMP:"
8+
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
9+
//@ rustc-env:RUST_BACKTRACE=0
10+
//@ failure-status: 101
11+
12+
//! ```rust
13+
//! assert_eq!(2 + 2, 5);
14+
//! ```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
running 1 test
3+
test $DIR/edition-2024-error-output.rs - (line 12) ... FAILED
4+
5+
failures:
6+
7+
---- $DIR/edition-2024-error-output.rs - (line 12) stdout ----
8+
9+
thread 'main' panicked at $TMP:6:1:
10+
assertion `left == right` failed
11+
left: 4
12+
right: 5
13+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
14+
15+
16+
failures:
17+
$DIR/edition-2024-error-output.rs - (line 12)
18+
19+
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
20+

0 commit comments

Comments
 (0)