Skip to content

Commit b380086

Browse files
committed
Auto merge of #93101 - Mark-Simulacrum:library-backtrace, r=yaahc
Support configuring whether to capture backtraces at runtime Tracking issue: #93346 This adds a new API to the `std::panic` module which configures whether and how the default panic hook will emit a backtrace when a panic occurs. After discussion with `@yaahc` on [Zulip](https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/backtrace.20lib.20vs.2E.20panic), this PR chooses to avoid adjusting or seeking to provide a similar API for the (currently unstable) std::backtrace API. It seems likely that the users of that API may wish to expose more specific settings rather than just a global one (e.g., emulating the `env_logger`, `tracing` per-module configuration) to avoid the cost of capture in hot code. The API added here could plausibly be copied and/or re-exported directly from std::backtrace relatively easily, but I don't think that's the right call as of now. ```rust mod panic { #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum BacktraceStyle { Short, Full, Off, } fn set_backtrace_style(BacktraceStyle); fn get_backtrace_style() -> Option<BacktraceStyle>; } ``` Several unresolved questions: * Do we need to move to a thread-local or otherwise more customizable strategy for whether to capture backtraces? See [this comment](#79085 (comment)) for some potential use cases for this. * Proposed answer: no, leave this for third-party hooks. * Bikeshed on naming of all the options, as usual. * Should BacktraceStyle be moved into `std::backtrace`? * It's already somewhat annoying to import and/or re-type the `std::panic::` prefix necessary to use these APIs, probably adding a second module to the mix isn't worth it. Note that PR #79085 proposed a much simpler API, but particularly in light of the desire to fully replace setting environment variables via `env::set_var` to control the backtrace API, a more complete API seems preferable. This PR likely subsumes that one.
2 parents 27f5d83 + 85930c8 commit b380086

File tree

7 files changed

+165
-65
lines changed

7 files changed

+165
-65
lines changed

library/std/src/panic.rs

+114
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use crate::any::Any;
66
use crate::collections;
77
use crate::panicking;
8+
use crate::sync::atomic::{AtomicUsize, Ordering};
89
use crate::sync::{Mutex, RwLock};
910
use crate::thread::Result;
1011

@@ -202,5 +203,118 @@ pub fn always_abort() {
202203
crate::panicking::panic_count::set_always_abort();
203204
}
204205

206+
/// The configuration for whether and how the default panic hook will capture
207+
/// and display the backtrace.
208+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
209+
#[unstable(feature = "panic_backtrace_config", issue = "93346")]
210+
#[non_exhaustive]
211+
pub enum BacktraceStyle {
212+
/// Prints a terser backtrace which ideally only contains relevant
213+
/// information.
214+
Short,
215+
/// Prints a backtrace with all possible information.
216+
Full,
217+
/// Disable collecting and displaying backtraces.
218+
Off,
219+
}
220+
221+
impl BacktraceStyle {
222+
pub(crate) fn full() -> Option<Self> {
223+
if cfg!(feature = "backtrace") { Some(BacktraceStyle::Full) } else { None }
224+
}
225+
226+
fn as_usize(self) -> usize {
227+
match self {
228+
BacktraceStyle::Short => 1,
229+
BacktraceStyle::Full => 2,
230+
BacktraceStyle::Off => 3,
231+
}
232+
}
233+
234+
fn from_usize(s: usize) -> Option<Self> {
235+
Some(match s {
236+
0 => return None,
237+
1 => BacktraceStyle::Short,
238+
2 => BacktraceStyle::Full,
239+
3 => BacktraceStyle::Off,
240+
_ => unreachable!(),
241+
})
242+
}
243+
}
244+
245+
// Tracks whether we should/can capture a backtrace, and how we should display
246+
// that backtrace.
247+
//
248+
// Internally stores equivalent of an Option<BacktraceStyle>.
249+
static SHOULD_CAPTURE: AtomicUsize = AtomicUsize::new(0);
250+
251+
/// Configure whether the default panic hook will capture and display a
252+
/// backtrace.
253+
///
254+
/// The default value for this setting may be set by the `RUST_BACKTRACE`
255+
/// environment variable; see the details in [`get_backtrace_style`].
256+
#[unstable(feature = "panic_backtrace_config", issue = "93346")]
257+
pub fn set_backtrace_style(style: BacktraceStyle) {
258+
if !cfg!(feature = "backtrace") {
259+
// If the `backtrace` feature of this crate isn't enabled, skip setting.
260+
return;
261+
}
262+
SHOULD_CAPTURE.store(style.as_usize(), Ordering::Release);
263+
}
264+
265+
/// Checks whether the standard library's panic hook will capture and print a
266+
/// backtrace.
267+
///
268+
/// This function will, if a backtrace style has not been set via
269+
/// [`set_backtrace_style`], read the environment variable `RUST_BACKTRACE` to
270+
/// determine a default value for the backtrace formatting:
271+
///
272+
/// The first call to `get_backtrace_style` may read the `RUST_BACKTRACE`
273+
/// environment variable if `set_backtrace_style` has not been called to
274+
/// override the default value. After a call to `set_backtrace_style` or
275+
/// `get_backtrace_style`, any changes to `RUST_BACKTRACE` will have no effect.
276+
///
277+
/// `RUST_BACKTRACE` is read according to these rules:
278+
///
279+
/// * `0` for `BacktraceStyle::Off`
280+
/// * `full` for `BacktraceStyle::Full`
281+
/// * `1` for `BacktraceStyle::Short`
282+
/// * Other values are currently `BacktraceStyle::Short`, but this may change in
283+
/// the future
284+
///
285+
/// Returns `None` if backtraces aren't currently supported.
286+
#[unstable(feature = "panic_backtrace_config", issue = "93346")]
287+
pub fn get_backtrace_style() -> Option<BacktraceStyle> {
288+
if !cfg!(feature = "backtrace") {
289+
// If the `backtrace` feature of this crate isn't enabled quickly return
290+
// `Unsupported` so this can be constant propagated all over the place
291+
// to optimize away callers.
292+
return None;
293+
}
294+
if let Some(style) = BacktraceStyle::from_usize(SHOULD_CAPTURE.load(Ordering::Acquire)) {
295+
return Some(style);
296+
}
297+
298+
// Setting environment variables for Fuchsia components isn't a standard
299+
// or easily supported workflow. For now, display backtraces by default.
300+
let format = if cfg!(target_os = "fuchsia") {
301+
BacktraceStyle::Full
302+
} else {
303+
crate::env::var_os("RUST_BACKTRACE")
304+
.map(|x| {
305+
if &x == "0" {
306+
BacktraceStyle::Off
307+
} else if &x == "full" {
308+
BacktraceStyle::Full
309+
} else {
310+
BacktraceStyle::Short
311+
}
312+
})
313+
.unwrap_or(BacktraceStyle::Off)
314+
};
315+
set_backtrace_style(format);
316+
Some(format)
317+
}
318+
205319
#[cfg(test)]
206320
mod tests;

library/std/src/panicking.rs

+15-8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
1010
#![deny(unsafe_op_in_unsafe_fn)]
1111

12+
use crate::panic::BacktraceStyle;
1213
use core::panic::{BoxMeUp, Location, PanicInfo};
1314

1415
use crate::any::Any;
@@ -18,7 +19,7 @@ use crate::mem::{self, ManuallyDrop};
1819
use crate::process;
1920
use crate::sync::atomic::{AtomicBool, Ordering};
2021
use crate::sys::stdio::panic_output;
21-
use crate::sys_common::backtrace::{self, RustBacktrace};
22+
use crate::sys_common::backtrace;
2223
use crate::sys_common::rwlock::StaticRWLock;
2324
use crate::sys_common::thread_info;
2425
use crate::thread;
@@ -262,10 +263,10 @@ where
262263
fn default_hook(info: &PanicInfo<'_>) {
263264
// If this is a double panic, make sure that we print a backtrace
264265
// for this panic. Otherwise only print it if logging is enabled.
265-
let backtrace_env = if panic_count::get_count() >= 2 {
266-
backtrace::rust_backtrace_print_full()
266+
let backtrace = if panic_count::get_count() >= 2 {
267+
BacktraceStyle::full()
267268
} else {
268-
backtrace::rust_backtrace_env()
269+
crate::panic::get_backtrace_style()
269270
};
270271

271272
// The current implementation always returns `Some`.
@@ -286,17 +287,23 @@ fn default_hook(info: &PanicInfo<'_>) {
286287

287288
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
288289

289-
match backtrace_env {
290-
RustBacktrace::Print(format) => drop(backtrace::print(err, format)),
291-
RustBacktrace::Disabled => {}
292-
RustBacktrace::RuntimeDisabled => {
290+
match backtrace {
291+
Some(BacktraceStyle::Short) => {
292+
drop(backtrace::print(err, crate::backtrace_rs::PrintFmt::Short))
293+
}
294+
Some(BacktraceStyle::Full) => {
295+
drop(backtrace::print(err, crate::backtrace_rs::PrintFmt::Full))
296+
}
297+
Some(BacktraceStyle::Off) => {
293298
if FIRST_PANIC.swap(false, Ordering::SeqCst) {
294299
let _ = writeln!(
295300
err,
296301
"note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace"
297302
);
298303
}
299304
}
305+
// If backtraces aren't supported, do nothing.
306+
None => {}
300307
}
301308
};
302309

library/std/src/sys_common/backtrace.rs

-57
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use crate::fmt;
77
use crate::io;
88
use crate::io::prelude::*;
99
use crate::path::{self, Path, PathBuf};
10-
use crate::sync::atomic::{self, Ordering};
1110
use crate::sys_common::mutex::StaticMutex;
1211

1312
/// Max number of frames to print.
@@ -144,62 +143,6 @@ where
144143
result
145144
}
146145

147-
pub enum RustBacktrace {
148-
Print(PrintFmt),
149-
Disabled,
150-
RuntimeDisabled,
151-
}
152-
153-
// If the `backtrace` feature of this crate isn't enabled quickly return
154-
// `Disabled` so this can be constant propagated all over the place to
155-
// optimize away callers.
156-
#[cfg(not(feature = "backtrace"))]
157-
pub fn rust_backtrace_env() -> RustBacktrace {
158-
RustBacktrace::Disabled
159-
}
160-
161-
// For now logging is turned off by default, and this function checks to see
162-
// whether the magical environment variable is present to see if it's turned on.
163-
#[cfg(feature = "backtrace")]
164-
pub fn rust_backtrace_env() -> RustBacktrace {
165-
// Setting environment variables for Fuchsia components isn't a standard
166-
// or easily supported workflow. For now, always display backtraces.
167-
if cfg!(target_os = "fuchsia") {
168-
return RustBacktrace::Print(PrintFmt::Full);
169-
}
170-
171-
static ENABLED: atomic::AtomicIsize = atomic::AtomicIsize::new(0);
172-
match ENABLED.load(Ordering::SeqCst) {
173-
0 => {}
174-
1 => return RustBacktrace::RuntimeDisabled,
175-
2 => return RustBacktrace::Print(PrintFmt::Short),
176-
_ => return RustBacktrace::Print(PrintFmt::Full),
177-
}
178-
179-
let (format, cache) = env::var_os("RUST_BACKTRACE")
180-
.map(|x| {
181-
if &x == "0" {
182-
(RustBacktrace::RuntimeDisabled, 1)
183-
} else if &x == "full" {
184-
(RustBacktrace::Print(PrintFmt::Full), 3)
185-
} else {
186-
(RustBacktrace::Print(PrintFmt::Short), 2)
187-
}
188-
})
189-
.unwrap_or((RustBacktrace::RuntimeDisabled, 1));
190-
ENABLED.store(cache, Ordering::SeqCst);
191-
format
192-
}
193-
194-
/// Setting for printing the full backtrace, unless backtraces are completely disabled
195-
pub(crate) fn rust_backtrace_print_full() -> RustBacktrace {
196-
if cfg!(feature = "backtrace") {
197-
RustBacktrace::Print(PrintFmt::Full)
198-
} else {
199-
RustBacktrace::Disabled
200-
}
201-
}
202-
203146
/// Prints the filename of the backtrace frame.
204147
///
205148
/// See also `output`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
thread 'main' panicked at 'explicit panic', $DIR/runtime-switch.rs:24:5
2+
stack backtrace:
3+
0: std::panicking::begin_panic
4+
1: runtime_switch::main
5+
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

src/test/ui/panics/runtime-switch.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Test for std::panic::set_backtrace_style.
2+
3+
// compile-flags: -O
4+
// run-fail
5+
// check-run-results
6+
// exec-env:RUST_BACKTRACE=0
7+
8+
// ignore-msvc see #62897 and `backtrace-debuginfo.rs` test
9+
// ignore-android FIXME #17520
10+
// ignore-openbsd no support for libbacktrace without filename
11+
// ignore-wasm no panic or subprocess support
12+
// ignore-emscripten no panic or subprocess support
13+
// ignore-sgx no subprocess support
14+
15+
// NOTE(eddyb) output differs between symbol mangling schemes
16+
// revisions: legacy v0
17+
// [legacy] compile-flags: -Zunstable-options -Csymbol-mangling-version=legacy
18+
// [v0] compile-flags: -Csymbol-mangling-version=v0
19+
20+
#![feature(panic_backtrace_config)]
21+
22+
fn main() {
23+
std::panic::set_backtrace_style(std::panic::BacktraceStyle::Short);
24+
panic!()
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
thread 'main' panicked at 'explicit panic', $DIR/runtime-switch.rs:24:5
2+
stack backtrace:
3+
0: std::panicking::begin_panic::<&str>
4+
1: runtime_switch::main
5+
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

src/tools/tidy/src/pal.rs

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const EXCEPTION_PATHS: &[&str] = &[
5858
"library/std/src/path.rs",
5959
"library/std/src/sys_common", // Should only contain abstractions over platforms
6060
"library/std/src/net/test.rs", // Utility helpers for tests
61+
"library/std/src/panic.rs", // fuchsia-specific panic backtrace handling
6162
];
6263

6364
pub fn check(path: &Path, bad: &mut bool) {

0 commit comments

Comments
 (0)