diff options
Diffstat (limited to '')
-rw-r--r-- | src/test/ui/runtime/backtrace-debuginfo.rs | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/src/test/ui/runtime/backtrace-debuginfo.rs b/src/test/ui/runtime/backtrace-debuginfo.rs new file mode 100644 index 000000000..7c9f1a7f2 --- /dev/null +++ b/src/test/ui/runtime/backtrace-debuginfo.rs @@ -0,0 +1,189 @@ +// run-pass +// We disable tail merging here because it can't preserve debuginfo and thus +// potentially breaks the backtraces. Also, subtle changes can decide whether +// tail merging succeeds, so the test might work today but fail tomorrow due to a +// seemingly completely unrelated change. +// Unfortunately, LLVM has no "disable" option for this, so we have to set +// "enable" to 0 instead. + +// compile-flags:-g -Copt-level=0 -Cllvm-args=-enable-tail-merge=0 +// compile-flags:-Cforce-frame-pointers=yes +// compile-flags:-Cstrip=none +// ignore-pretty issue #37195 +// ignore-emscripten spawning processes is not supported +// ignore-sgx no processes + +use std::env; + +#[path = "backtrace-debuginfo-aux.rs"] mod aux; + +macro_rules! pos { + () => ((file!(), line!())) +} + +macro_rules! dump_and_die { + ($($pos:expr),*) => ({ + // FIXME(#18285): we cannot include the current position because + // the macro span takes over the last frame's file/line. + // + // You might also be wondering why a major platform, + // i686-pc-windows-msvc, is located in here. Some of the saga can be + // found on #62897, but the tl;dr; is that it appears that if the + // standard library doesn't have debug information or frame pointers, + // which it doesn't by default on the test builders, then the stack + // walking routines in dbghelp will randomly terminate the stack trace + // in libstd without going further. Presumably the addition of frame + // pointers and/or debuginfo fixes this since tests always work with + // nightly compilers (which have debuginfo). In general though this test + // is replicated in rust-lang/backtrace-rs and has extensive coverage + // there, even on i686-pc-windows-msvc. We do the best we can in + // rust-lang/rust to test it as well, but sometimes we just gotta keep + // landing PRs. + if cfg!(any(target_os = "android", + all(target_os = "linux", target_arch = "arm"), + all(target_env = "msvc", target_arch = "x86"), + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd")) { + // skip these platforms as this support isn't implemented yet. + } else { + dump_filelines(&[$($pos),*]); + panic!(); + } + }) +} + +// we can't use a function as it will alter the backtrace +macro_rules! check { + ($counter:expr; $($pos:expr),*) => ({ + if *$counter == 0 { + dump_and_die!($($pos),*) + } else { + *$counter -= 1; + } + }) +} + +type Pos = (&'static str, u32); + +// this goes to stdout and each line has to be occurred +// in the following backtrace to stderr with a correct order. +fn dump_filelines(filelines: &[Pos]) { + for &(file, line) in filelines.iter().rev() { + // extract a basename + let basename = file.split(&['/', '\\'][..]).last().unwrap(); + println!("{}:{}", basename, line); + } +} + +#[inline(never)] +fn inner(counter: &mut i32, main_pos: Pos, outer_pos: Pos) { + check!(counter; main_pos, outer_pos); + check!(counter; main_pos, outer_pos); + let inner_pos = pos!(); aux::callback(|aux_pos| { + check!(counter; main_pos, outer_pos, inner_pos, aux_pos); + }); + let inner_pos = pos!(); aux::callback_inlined(|aux_pos| { + check!(counter; main_pos, outer_pos, inner_pos, aux_pos); + }); +} + +// We emit the wrong location for the caller here when inlined on MSVC +#[cfg_attr(not(target_env = "msvc"), inline(always))] +#[cfg_attr(target_env = "msvc", inline(never))] +fn inner_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos) { + check!(counter; main_pos, outer_pos); + check!(counter; main_pos, outer_pos); + + // Again, disable inlining for MSVC. + #[cfg_attr(not(target_env = "msvc"), inline(always))] + #[cfg_attr(target_env = "msvc", inline(never))] + fn inner_further_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos, inner_pos: Pos) { + check!(counter; main_pos, outer_pos, inner_pos); + } + inner_further_inlined(counter, main_pos, outer_pos, pos!()); + + let inner_pos = pos!(); aux::callback(|aux_pos| { + check!(counter; main_pos, outer_pos, inner_pos, aux_pos); + }); + let inner_pos = pos!(); aux::callback_inlined(|aux_pos| { + check!(counter; main_pos, outer_pos, inner_pos, aux_pos); + }); + + // this tests a distinction between two independent calls to the inlined function. + // (un)fortunately, LLVM somehow merges two consecutive such calls into one node. + inner_further_inlined(counter, main_pos, outer_pos, pos!()); +} + +#[inline(never)] +fn outer(mut counter: i32, main_pos: Pos) { + inner(&mut counter, main_pos, pos!()); + inner_inlined(&mut counter, main_pos, pos!()); +} + +fn check_trace(output: &str, error: &str) -> Result<(), String> { + // reverse the position list so we can start with the last item (which was the first line) + let mut remaining: Vec<&str> = output.lines().map(|s| s.trim()).rev().collect(); + + if !error.contains("stack backtrace") { + return Err(format!("no backtrace found in stderr:\n{}", error)) + } + for line in error.lines() { + if !remaining.is_empty() && line.contains(remaining.last().unwrap()) { + remaining.pop(); + } + } + if !remaining.is_empty() { + return Err(format!("trace does not match position list\n\ + still need to find {:?}\n\n\ + --- stdout\n{}\n\ + --- stderr\n{}", + remaining, output, error)) + } + Ok(()) +} + +fn run_test(me: &str) { + use std::str; + use std::process::Command; + + let mut i = 0; + let mut errors = Vec::new(); + loop { + let out = Command::new(me) + .env("RUST_BACKTRACE", "full") + .arg(i.to_string()).output().unwrap(); + let output = str::from_utf8(&out.stdout).unwrap(); + let error = str::from_utf8(&out.stderr).unwrap(); + if out.status.success() { + assert!(output.contains("done."), "bad output for successful run: {}", output); + break; + } else { + if let Err(e) = check_trace(output, error) { + errors.push(e); + } + } + i += 1; + } + if errors.len() > 0 { + for error in errors { + println!("---------------------------------------"); + println!("{}", error); + } + + panic!("found some errors"); + } +} + +#[inline(never)] +fn main() { + let args: Vec<String> = env::args().collect(); + if args.len() >= 2 { + let case = args[1].parse().unwrap(); + eprintln!("test case {}", case); + outer(case, pos!()); + println!("done."); + } else { + run_test(&args[0]); + } +} |