]> git.lizzy.rs Git - rust.git/blob - src/test/run-pass/backtrace-debuginfo.rs
8b2b26948824f08d89b9252cc024a2714d5e773b
[rust.git] / src / test / run-pass / backtrace-debuginfo.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // We disable tail merging here because it can't preserve debuginfo and thus
12 // potentially breaks the backtraces. Also, subtle changes can decide whether
13 // tail merging suceeds, so the test might work today but fail tomorrow due to a
14 // seemingly completely unrelated change.
15 // Unfortunately, LLVM has no "disable" option for this, so we have to set
16 // "enable" to 0 instead.
17
18 // compile-flags:-g -Cllvm-args=-enable-tail-merge=0
19 // ignore-pretty as this critically relies on line numbers
20
21 use std::io;
22 use std::io::prelude::*;
23 use std::env;
24
25 #[path = "backtrace-debuginfo-aux.rs"] mod aux;
26
27 macro_rules! pos {
28     () => ((file!(), line!()))
29 }
30
31 macro_rules! dump_and_die {
32     ($($pos:expr),*) => ({
33         // FIXME(#18285): we cannot include the current position because
34         // the macro span takes over the last frame's file/line.
35         if cfg!(target_os = "macos") ||
36            cfg!(target_os = "ios") ||
37            cfg!(target_os = "android") ||
38            cfg!(all(target_os = "linux", target_arch = "arm")) ||
39            cfg!(all(windows, target_env = "gnu")) {
40             // skip these platforms as this support isn't implemented yet.
41         } else {
42             dump_filelines(&[$($pos),*]);
43             panic!();
44         }
45     })
46 }
47
48 // we can't use a function as it will alter the backtrace
49 macro_rules! check {
50     ($counter:expr; $($pos:expr),*) => ({
51         if *$counter == 0 {
52             dump_and_die!($($pos),*)
53         } else {
54             *$counter -= 1;
55         }
56     })
57 }
58
59 type Pos = (&'static str, u32);
60
61 // this goes to stdout and each line has to be occurred
62 // in the following backtrace to stderr with a correct order.
63 fn dump_filelines(filelines: &[Pos]) {
64     // Skip top frame for MSVC, because it sees the macro rather than
65     // the containing function.
66     let skip = if cfg!(target_env = "msvc") {1} else {0};
67     for &(file, line) in filelines.iter().rev().skip(skip) {
68         // extract a basename
69         let basename = file.split(&['/', '\\'][..]).last().unwrap();
70         println!("{}:{}", basename, line);
71     }
72 }
73
74 #[inline(never)]
75 fn inner(counter: &mut i32, main_pos: Pos, outer_pos: Pos) {
76     check!(counter; main_pos, outer_pos);
77     check!(counter; main_pos, outer_pos);
78     let inner_pos = pos!(); aux::callback(|aux_pos| {
79         check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
80     });
81     let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
82         check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
83     });
84 }
85
86 // LLVM does not yet output the required debug info to support showing inlined
87 // function calls in backtraces when targetting MSVC, so disable inlining in
88 // this case.
89 #[cfg_attr(not(target_env = "msvc"), inline(always))]
90 #[cfg_attr(target_env = "msvc", inline(never))]
91 fn inner_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos) {
92     check!(counter; main_pos, outer_pos);
93     check!(counter; main_pos, outer_pos);
94
95     // Again, disable inlining for MSVC.
96     #[cfg_attr(not(target_env = "msvc"), inline(always))]
97     #[cfg_attr(target_env = "msvc", inline(never))]
98     fn inner_further_inlined(counter: &mut i32, main_pos: Pos, outer_pos: Pos, inner_pos: Pos) {
99         check!(counter; main_pos, outer_pos, inner_pos);
100     }
101     inner_further_inlined(counter, main_pos, outer_pos, pos!());
102
103     let inner_pos = pos!(); aux::callback(|aux_pos| {
104         check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
105     });
106     let inner_pos = pos!(); aux::callback_inlined(|aux_pos| {
107         check!(counter; main_pos, outer_pos, inner_pos, aux_pos);
108     });
109
110     // this tests a distinction between two independent calls to the inlined function.
111     // (un)fortunately, LLVM somehow merges two consecutive such calls into one node.
112     inner_further_inlined(counter, main_pos, outer_pos, pos!());
113 }
114
115 #[inline(never)]
116 fn outer(mut counter: i32, main_pos: Pos) {
117     inner(&mut counter, main_pos, pos!());
118     inner_inlined(&mut counter, main_pos, pos!());
119 }
120
121 fn check_trace(output: &str, error: &str) {
122     // reverse the position list so we can start with the last item (which was the first line)
123     let mut remaining: Vec<&str> = output.lines().map(|s| s.trim()).rev().collect();
124
125     assert!(error.contains("stack backtrace"), "no backtrace in the error: {}", error);
126     for line in error.lines() {
127         if !remaining.is_empty() && line.contains(remaining.last().unwrap()) {
128             remaining.pop();
129         }
130     }
131     assert!(remaining.is_empty(),
132             "trace does not match position list: {}\n---\n{}", error, output);
133 }
134
135 fn run_test(me: &str) {
136     use std::str;
137     use std::process::Command;
138
139     let mut template = Command::new(me);
140     template.env("RUST_BACKTRACE", "1");
141
142     let mut i = 0;
143     loop {
144         let out = Command::new(me)
145                           .env("RUST_BACKTRACE", "1")
146                           .arg(i.to_string()).output().unwrap();
147         let output = str::from_utf8(&out.stdout).unwrap();
148         let error = str::from_utf8(&out.stderr).unwrap();
149         if out.status.success() {
150             assert!(output.contains("done."), "bad output for successful run: {}", output);
151             break;
152         } else {
153             check_trace(output, error);
154         }
155         i += 1;
156     }
157 }
158
159 #[inline(never)]
160 fn main() {
161     let args: Vec<String> = env::args().collect();
162     if args.len() >= 2 {
163         let case = args[1].parse().unwrap();
164         writeln!(&mut io::stderr(), "test case {}", case).unwrap();
165         outer(case, pos!());
166         println!("done.");
167     } else {
168         run_test(&args[0]);
169     }
170 }
171