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