]> git.lizzy.rs Git - rust.git/blob - src/test/ui/runtime/backtrace-debuginfo.rs
b976c3a13bd5927f37ab09a2d72c5dd5a5176b7c
[rust.git] / src / test / ui / runtime / backtrace-debuginfo.rs
1 // run-pass
2 // We disable tail merging here because it can't preserve debuginfo and thus
3 // potentially breaks the backtraces. Also, subtle changes can decide whether
4 // tail merging succeeds, so the test might work today but fail tomorrow due to a
5 // seemingly completely unrelated change.
6 // Unfortunately, LLVM has no "disable" option for this, so we have to set
7 // "enable" to 0 instead.
8
9 // compile-flags:-g -Copt-level=0 -Cllvm-args=-enable-tail-merge=0
10 // compile-flags:-Cforce-frame-pointers=yes
11 // ignore-pretty issue #37195
12 // ignore-emscripten spawning processes is not supported
13 // ignore-sgx no processes
14
15 use std::env;
16
17 #[path = "backtrace-debuginfo-aux.rs"] mod aux;
18
19 macro_rules! pos {
20     () => ((file!(), line!()))
21 }
22
23 macro_rules! dump_and_die {
24     ($($pos:expr),*) => ({
25         // FIXME(#18285): we cannot include the current position because
26         // the macro span takes over the last frame's file/line.
27         //
28         // You might also be wondering why a major platform,
29         // i686-pc-windows-msvc, is located in here. Some of the saga can be
30         // found on #62897, but the tl;dr; is that it appears that if the
31         // standard library doesn't have debug information or frame pointers,
32         // which it doesn't by default on the test builders, then the stack
33         // walking routines in dbghelp will randomly terminate the stack trace
34         // in libstd without going further. Presumably the addition of frame
35         // pointers and/or debuginfo fixes this since tests always work with
36         // nightly compilers (which have debuginfo). In general though this test
37         // is replicated in rust-lang/backtrace-rs and has extensive coverage
38         // there, even on i686-pc-windows-msvc. We do the best we can in
39         // rust-lang/rust to test it as well, but sometimes we just gotta keep
40         // landing PRs.
41         if cfg!(any(target_os = "android",
42                     all(target_os = "linux", target_arch = "arm"),
43                     all(target_env = "msvc", target_arch = "x86"),
44                     target_os = "freebsd",
45                     target_os = "dragonfly",
46                     target_os = "openbsd")) {
47             // skip these platforms as this support isn't implemented yet.
48         } else {
49             dump_filelines(&[$($pos),*]);
50             panic!();
51         }
52     })
53 }
54
55 // we can't use a function as it will alter the backtrace
56 macro_rules! check {
57     ($counter:expr; $($pos:expr),*) => ({
58         if *$counter == 0 {
59             dump_and_die!($($pos),*)
60         } else {
61             *$counter -= 1;
62         }
63     })
64 }
65
66 type Pos = (&'static str, u32);
67
68 // this goes to stdout and each line has to be occurred
69 // in the following backtrace to stderr with a correct order.
70 fn dump_filelines(filelines: &[Pos]) {
71     for &(file, line) in filelines.iter().rev() {
72         // extract a basename
73         let basename = file.split(&['/', '\\'][..]).last().unwrap();
74         println!("{}:{}", basename, line);
75     }
76 }
77
78 #[inline(never)]
79 fn inner(counter: &mut i32, main_pos: Pos, outer_pos: Pos) {
80     check!(counter; main_pos, outer_pos);
81     check!(counter; main_pos, outer_pos);
82     let inner_pos = pos!(); aux::callback(|aux_pos| {
83         check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
84     });
85     let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
86         check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
87     });
88 }
89
90 // We emit the wrong location for the caller here when inlined on MSVC
91 #[cfg_attr(not(target_env = "msvc"), inline(always))]
92 #[cfg_attr(target_env = "msvc", inline(never))]
93 fn inner_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos) {
94     check!(counter; main_pos, outer_pos);
95     check!(counter; main_pos, outer_pos);
96
97     // Again, disable inlining for MSVC.
98     #[cfg_attr(not(target_env = "msvc"), inline(always))]
99     #[cfg_attr(target_env = "msvc", inline(never))]
100     fn inner_further_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos, inner_pos: Pos) {
101         check!(counter; main_pos, outer_pos, inner_pos);
102     }
103     inner_further_inlined(counter, main_pos, outer_pos, pos!());
104
105     let inner_pos = pos!(); aux::callback(|aux_pos| {
106         check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
107     });
108     let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
109         check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
110     });
111
112     // this tests a distinction between two independent calls to the inlined function.
113     // (un)fortunately, LLVM somehow merges two consecutive such calls into one node.
114     inner_further_inlined(counter, main_pos, outer_pos, pos!());
115 }
116
117 #[inline(never)]
118 fn outer(mut counter: i32, main_pos: Pos) {
119     inner(&mut counter, main_pos, pos!());
120     inner_inlined(&mut counter, main_pos, pos!());
121 }
122
123 fn check_trace(output: &str, error: &str) -> Result<(), String> {
124     // reverse the position list so we can start with the last item (which was the first line)
125     let mut remaining: Vec<&str> = output.lines().map(|s| s.trim()).rev().collect();
126
127     if !error.contains("stack backtrace") {
128         return Err(format!("no backtrace found in stderr:\n{}", error))
129     }
130     for line in error.lines() {
131         if !remaining.is_empty() && line.contains(remaining.last().unwrap()) {
132             remaining.pop();
133         }
134     }
135     if !remaining.is_empty() {
136         return Err(format!("trace does not match position list\n\
137             still need to find {:?}\n\n\
138             --- stdout\n{}\n\
139             --- stderr\n{}",
140             remaining, output, error))
141     }
142     Ok(())
143 }
144
145 fn run_test(me: &str) {
146     use std::str;
147     use std::process::Command;
148
149     let mut i = 0;
150     let mut errors = Vec::new();
151     loop {
152         let out = Command::new(me)
153                           .env("RUST_BACKTRACE", "full")
154                           .arg(i.to_string()).output().unwrap();
155         let output = str::from_utf8(&out.stdout).unwrap();
156         let error = str::from_utf8(&out.stderr).unwrap();
157         if out.status.success() {
158             assert!(output.contains("done."), "bad output for successful run: {}", output);
159             break;
160         } else {
161             if let Err(e) = check_trace(output, error) {
162                 errors.push(e);
163             }
164         }
165         i += 1;
166     }
167     if errors.len() > 0 {
168         for error in errors {
169             println!("---------------------------------------");
170             println!("{}", error);
171         }
172
173         panic!("found some errors");
174     }
175 }
176
177 #[inline(never)]
178 fn main() {
179     let args: Vec<String> = env::args().collect();
180     if args.len() >= 2 {
181         let case = args[1].parse().unwrap();
182         eprintln!("test case {}", case);
183         outer(case, pos!());
184         println!("done.");
185     } else {
186         run_test(&args[0]);
187     }
188 }