blob: b6400c68f536739a068b52c4fbd5902d5f53d6e6 [file] [log] [blame]
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// 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 suceeds, 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 -Cllvm-args=-enable-tail-merge=0
// ignore-pretty as this critically relies on line numbers
use std::io;
use std::io::prelude::*;
use std::env;
#[path = "backtrace-debuginfo-aux.rs"] mod aux;
macro_rules! pos {
() => ((file!(), line!()))
}
#[cfg(any(all(unix,
not(target_os = "macos"),
not(target_os = "ios"),
not(target_os = "android"),
not(all(target_os = "linux", target_arch = "arm"))),
all(windows, not(target_arch = "x86"))))]
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.
dump_filelines(&[$($pos),*]);
panic!();
})
}
// this does not work on Windows, Android, OSX or iOS
#[cfg(not(any(all(unix,
not(target_os = "macos"),
not(target_os = "ios"),
not(target_os = "android"),
not(all(target_os = "linux", target_arch = "arm"))),
all(windows, not(target_arch = "x86")))))]
macro_rules! dump_and_die {
($($pos:expr),*) => ({ let _ = [$($pos),*]; })
}
// 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]) {
// Skip top frame for MSVC, because it sees the macro rather than
// the containing function.
let skip = if cfg!(target_env = "msvc") {1} else {0};
for &(file, line) in filelines.iter().rev().skip(skip) {
// 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);
});
}
// LLVM does not yet output the required debug info to support showing inlined
// function calls in backtraces when targetting MSVC, so disable inlining in
// this case.
#[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) {
// 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();
assert!(error.contains("stack backtrace"), "no backtrace in the error: {}", error);
for line in error.lines() {
if !remaining.is_empty() && line.contains(remaining.last().unwrap()) {
remaining.pop();
}
}
assert!(remaining.is_empty(),
"trace does not match position list: {}\n---\n{}", error, output);
}
fn run_test(me: &str) {
use std::str;
use std::process::Command;
let mut template = Command::new(me);
template.env("RUST_BACKTRACE", "1");
let mut i = 0;
loop {
let out = Command::new(me)
.env("RUST_BACKTRACE", "1")
.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 {
check_trace(output, error);
}
i += 1;
}
}
#[inline(never)]
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() >= 2 {
let case = args[1].parse().unwrap();
writeln!(&mut io::stderr(), "test case {}", case).unwrap();
outer(case, pos!());
println!("done.");
} else {
run_test(&args[0]);
}
}