Skip to content

Commit fdb7d84

Browse files
committed
fix: compile probe and nightly backtraces
This addresses the invalid compile probe testing for a non-longer existing feature by updating it and eyre to use the `error_generic_member_access` features instead. The report and errors have all been updated to accomodate this and the new backtrace provide API Fixes: #84 and #97
1 parent 7a5c32a commit fdb7d84

File tree

12 files changed

+203
-153
lines changed

12 files changed

+203
-153
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ rust-version = "1.65.0"
1717
indenter = "0.3.0"
1818
once_cell = "1.18.0"
1919
owo-colors = "3.2.0"
20+
autocfg = "1.0"
2021

2122
[profile.dev.package.backtrace]
2223
opt-level = 3

README.md

+16-4
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,12 @@ avoid using `eyre::Report` as your public error type.
122122
}
123123
```
124124

125-
- If using the nightly channel, a backtrace is captured and printed with the
126-
error if the underlying error type does not already provide its own. In order
127-
to see backtraces, they must be enabled through the environment variables
125+
- If using rust >1.65, a backtrace is captured and printed with the
126+
error.
127+
128+
On nightly eyre will use the underlying error's backtrace if it has one.
129+
130+
In order to see backtraces, they must be enabled through the environment variables
128131
described in [`std::backtrace`]:
129132

130133
- If you want panics and errors to both have backtraces, set
@@ -141,7 +144,7 @@ avoid using `eyre::Report` as your public error type.
141144
- Eyre works with any error type that has an impl of `std::error::Error`,
142145
including ones defined in your crate. We do not bundle a `derive(Error)` macro
143146
but you can write the impls yourself or use a standalone macro like
144-
[thiserror].
147+
[thiserror](https://github.com/dtolnay/thiserror).
145148

146149
```rust
147150
use thiserror::Error;
@@ -178,6 +181,15 @@ No-std support was removed in 2020 in [commit 608a16a] due to unaddressed upstre
178181
[commit 608a16a]:
179182
https://github.com/eyre-rs/eyre/pull/29/commits/608a16aa2c2c27eca6c88001cc94c6973c18f1d5
180183

184+
185+
## Backtrace support
186+
187+
The built in default handler has support for capturing backtrace using `rustc-1.65` or later.
188+
189+
Backtraces are captured when an error is converted to an `eyre::Report` (such as using `?` or `eyre!`).
190+
191+
If using the nightly toolchain, backtraces will also be captured and accessed from other errors using [error_generic_member_access](https://github.com/rust-lang/rfcs/pull/2895) if available.
192+
181193
## Comparison to failure
182194

183195
The `eyre::Report` type works something like `failure::Error`, but unlike

color-eyre/tests/theme.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ fn test_backwards_compatibility(target: String, file_name: &str) {
168168
fn normalize_backtrace(input: &str) -> String {
169169
input
170170
.lines()
171-
.take_while(|v| !v.contains("core::panic"))
171+
.take_while(|v| !v.contains("core::panic") && !v.contains("theme_test_helper::main"))
172172
.collect::<Vec<_>>()
173173
.join("\n")
174174
}

eyre/Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ indenter = { workspace = true }
2323
once_cell = { workspace = true }
2424
pyo3 = { version = "0.20", optional = true, default-features = false }
2525

26+
[build-dependencies]
27+
autocfg = { workspace = true }
28+
2629
[dev-dependencies]
2730
futures = { version = "0.3", default-features = false }
2831
rustversion = "1.0"
2932
thiserror = "1.0"
30-
trybuild = { version = "1.0.19", features = ["diff"] }
33+
# TODO: 1.0.90 uses rustc-1.70
34+
trybuild = { version = "=1.0.83", features = ["diff"] }
3135
backtrace = "0.3.46"
3236
anyhow = "1.0.28"
3337
syn = { version = "2.0", features = ["full"] }

eyre/build.rs

+70-97
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,53 @@
1-
use std::env;
2-
use std::ffi::OsString;
3-
use std::fs;
4-
use std::path::Path;
5-
use std::process::{Command, ExitStatus};
6-
use std::str;
7-
8-
// This code exercises the surface area that we expect of the std Backtrace
9-
// type. If the current toolchain is able to compile it, we go ahead and use
10-
// backtrace in eyre.
11-
const BACKTRACE_PROBE: &str = r#"
12-
#![feature(backtrace)]
1+
use std::{
2+
env, fs,
3+
path::Path,
4+
process::{Command, ExitStatus},
5+
};
6+
7+
fn main() {
8+
let ac = autocfg::new();
9+
10+
// https://github.com/rust-lang/rust/issues/99301 [nightly]
11+
//
12+
// Autocfg does currently not support custom probes, or `nightly` only features
13+
match compile_probe(GENERIC_MEMBER_ACCESS_PROBE) {
14+
Some(status) if status.success() => autocfg::emit("generic_member_access"),
15+
_ => {}
16+
}
17+
18+
// https://github.com/rust-lang/rust/issues/47809 [rustc-1.46]
19+
ac.emit_expression_cfg("std::panic::Location::caller", "track_caller");
20+
21+
if ac.probe_rustc_version(1, 52) {
22+
autocfg::emit("eyre_no_fmt_arguments_as_str");
23+
}
24+
25+
if ac.probe_rustc_version(1, 58) {
26+
autocfg::emit("eyre_no_fmt_args_capture");
27+
}
28+
29+
if ac.probe_rustc_version(1, 65) {
30+
autocfg::emit("backtrace")
31+
}
32+
}
33+
34+
// This code exercises the surface area or the generic member access feature for the `std::error::Error` trait.
35+
//
36+
// This is use to detect and supply backtrace information through different errors types.
37+
const GENERIC_MEMBER_ACCESS_PROBE: &str = r#"
38+
#![feature(error_generic_member_access)]
1339
#![allow(dead_code)]
1440
15-
use std::backtrace::{Backtrace, BacktraceStatus};
16-
use std::error::Error;
41+
use std::error::{Error, Request};
1742
use std::fmt::{self, Display};
1843
1944
#[derive(Debug)]
20-
struct E;
45+
struct E {
46+
backtrace: MyBacktrace,
47+
}
48+
49+
#[derive(Debug)]
50+
struct MyBacktrace;
2151
2252
impl Display for E {
2353
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
@@ -26,59 +56,44 @@ const BACKTRACE_PROBE: &str = r#"
2656
}
2757
2858
impl Error for E {
29-
fn backtrace(&self) -> Option<&Backtrace> {
30-
let backtrace = Backtrace::capture();
31-
match backtrace.status() {
32-
BacktraceStatus::Captured | BacktraceStatus::Disabled | _ => {}
33-
}
34-
unimplemented!()
59+
fn provide<'a>(&'a self, request: &mut Request<'a>) {
60+
request
61+
.provide_ref::<MyBacktrace>(&self.backtrace);
3562
}
3663
}
3764
"#;
3865

39-
const TRACK_CALLER_PROBE: &str = r#"
40-
#![allow(dead_code)]
41-
42-
#[track_caller]
43-
fn foo() {
44-
let _location = std::panic::Location::caller();
45-
}
46-
"#;
66+
fn compile_probe(probe: &str) -> Option<ExitStatus> {
67+
let rustc = env::var_os("RUSTC")?;
68+
let out_dir = env::var_os("OUT_DIR")?;
69+
let probefile = Path::new(&out_dir).join("probe.rs");
70+
fs::write(&probefile, probe).ok()?;
4771

48-
fn main() {
49-
match compile_probe(BACKTRACE_PROBE) {
50-
Some(status) if status.success() => println!("cargo:rustc-cfg=backtrace"),
51-
_ => {}
52-
}
72+
// Supports invoking rustc thrugh a wrapper
73+
let mut cmd = if let Some(wrapper) = env::var_os("RUSTC_WRAPPER") {
74+
let mut cmd = Command::new(wrapper);
5375

54-
match compile_probe(TRACK_CALLER_PROBE) {
55-
Some(status) if status.success() => println!("cargo:rustc-cfg=track_caller"),
56-
_ => {}
57-
}
76+
cmd.arg(rustc);
5877

59-
let version = match rustc_version_info() {
60-
Some(version) => version,
61-
None => return,
78+
cmd
79+
} else {
80+
Command::new(rustc)
6281
};
6382

64-
version.toolchain.set_feature();
65-
66-
if version.minor < 52 {
67-
println!("cargo:rustc-cfg=eyre_no_fmt_arguments_as_str");
83+
if let Some(target) = env::var_os("TARGET") {
84+
cmd.arg("--target").arg(target);
6885
}
6986

70-
if version.minor < 58 {
71-
println!("cargo:rustc-cfg=eyre_no_fmt_args_capture");
87+
// If Cargo wants to set RUSTFLAGS, use that.
88+
if let Ok(rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
89+
if !rustflags.is_empty() {
90+
for arg in rustflags.split('\x1f') {
91+
cmd.arg(arg);
92+
}
93+
}
7294
}
73-
}
7495

75-
fn compile_probe(probe: &str) -> Option<ExitStatus> {
76-
let rustc = env::var_os("RUSTC")?;
77-
let out_dir = env::var_os("OUT_DIR")?;
78-
let probefile = Path::new(&out_dir).join("probe.rs");
79-
fs::write(&probefile, probe).ok()?;
80-
Command::new(rustc)
81-
.arg("--edition=2018")
96+
cmd.arg("--edition=2018")
8297
.arg("--crate-name=eyre_build")
8398
.arg("--crate-type=lib")
8499
.arg("--emit=metadata")
@@ -88,45 +103,3 @@ fn compile_probe(probe: &str) -> Option<ExitStatus> {
88103
.status()
89104
.ok()
90105
}
91-
92-
// TODO factor this toolchain parsing and related tests into its own file
93-
#[derive(PartialEq)]
94-
enum Toolchain {
95-
Stable,
96-
Beta,
97-
Nightly,
98-
}
99-
impl Toolchain {
100-
fn set_feature(self) {
101-
match self {
102-
Toolchain::Nightly => println!("cargo:rustc-cfg=nightly"),
103-
Toolchain::Beta => println!("cargo:rustc-cfg=beta"),
104-
Toolchain::Stable => println!("cargo:rustc-cfg=stable"),
105-
}
106-
}
107-
}
108-
109-
struct VersionInfo {
110-
minor: u32,
111-
toolchain: Toolchain,
112-
}
113-
114-
fn rustc_version_info() -> Option<VersionInfo> {
115-
let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
116-
let output = Command::new(rustc).arg("--version").output().ok()?;
117-
let version = str::from_utf8(&output.stdout).ok()?;
118-
let mut pieces = version.split(['.', ' ', '-']);
119-
if pieces.next() != Some("rustc") {
120-
return None;
121-
}
122-
let _major: u32 = pieces.next()?.parse().ok()?;
123-
let minor = pieces.next()?.parse().ok()?;
124-
let _patch: u32 = pieces.next()?.parse().ok()?;
125-
let toolchain = match pieces.next() {
126-
Some("beta") => Toolchain::Beta,
127-
Some("nightly") => Toolchain::Nightly,
128-
_ => Toolchain::Stable,
129-
};
130-
let version = VersionInfo { minor, toolchain };
131-
Some(version)
132-
}

eyre/src/backtrace.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,32 @@ pub(crate) use std::backtrace::Backtrace;
55
pub(crate) enum Backtrace {}
66

77
#[cfg(backtrace)]
8+
macro_rules! capture_backtrace {
9+
() => {
10+
Some(Backtrace::capture())
11+
};
12+
}
13+
14+
#[cfg(not(backtrace))]
15+
macro_rules! capture_backtrace {
16+
() => {
17+
None
18+
};
19+
}
20+
/// Capture a backtrace iff there is not already a backtrace in the error chain
21+
#[cfg(generic_member_access)]
822
macro_rules! backtrace_if_absent {
923
($err:expr) => {
10-
match $err.backtrace() {
24+
match std::error::request_ref::<std::backtrace::Backtrace>($err as &dyn std::error::Error) {
1125
Some(_) => None,
12-
None => Some(Backtrace::capture()),
26+
None => capture_backtrace!(),
1327
}
1428
};
1529
}
1630

17-
#[cfg(not(backtrace))]
31+
#[cfg(not(generic_member_access))]
1832
macro_rules! backtrace_if_absent {
1933
($err:expr) => {
20-
None
34+
capture_backtrace!()
2135
};
2236
}

eyre/src/context.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ use crate::error::{ContextError, ErrorImpl};
22
use crate::{Report, StdError, WrapErr};
33
use core::fmt::{self, Debug, Display, Write};
44

5-
#[cfg(backtrace)]
6-
use std::backtrace::Backtrace;
7-
85
mod ext {
96
use super::*;
107

@@ -146,9 +143,9 @@ where
146143
D: Display,
147144
E: StdError + 'static,
148145
{
149-
#[cfg(backtrace)]
150-
fn backtrace(&self) -> Option<&Backtrace> {
151-
self.error.backtrace()
146+
#[cfg(generic_member_access)]
147+
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
148+
self.error.provide(request);
152149
}
153150

154151
fn source(&self) -> Option<&(dyn StdError + 'static)> {

eyre/src/error.rs

+5
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,11 @@ impl<E> StdError for ErrorImpl<E>
855855
where
856856
E: StdError,
857857
{
858+
#[cfg(generic_member_access)]
859+
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
860+
self._object.provide(request)
861+
}
862+
858863
fn source(&self) -> Option<&(dyn StdError + 'static)> {
859864
ErrorImpl::<()>::error(self.erase()).source()
860865
}

eyre/src/lib.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@
355355
unused_parens,
356356
while_true
357357
)]
358-
#![cfg_attr(backtrace, feature(backtrace))]
358+
#![cfg_attr(generic_member_access, feature(error_generic_member_access))]
359359
#![cfg_attr(doc_cfg, feature(doc_cfg))]
360360
#![allow(
361361
clippy::needless_doctest_main,
@@ -778,6 +778,7 @@ impl DefaultHandler {
778778
#[allow(unused_variables)]
779779
#[cfg_attr(not(feature = "auto-install"), allow(dead_code))]
780780
pub fn default_with(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
781+
// Capture the backtrace if the source error did not already capture one
781782
let backtrace = backtrace_if_absent!(error);
782783

783784
Box::new(Self {
@@ -837,15 +838,19 @@ impl EyreHandler for DefaultHandler {
837838
}
838839
}
839840

840-
#[cfg(backtrace)]
841+
#[cfg(generic_member_access)]
841842
{
842843
use std::backtrace::BacktraceStatus;
843844

845+
// The backtrace can be stored either in the handler instance, or the error itself.
846+
//
847+
// If the source error has a backtrace, the handler should not capture one
844848
let backtrace = self
845849
.backtrace
846850
.as_ref()
847-
.or_else(|| error.backtrace())
851+
.or_else(|| std::error::request_ref::<Backtrace>(error))
848852
.expect("backtrace capture failed");
853+
849854
if let BacktraceStatus::Captured = backtrace.status() {
850855
write!(f, "\n\nStack backtrace:\n{}", backtrace)?;
851856
}

eyre/src/wrapper.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ impl Display for BoxedError {
8282
}
8383

8484
impl StdError for BoxedError {
85-
#[cfg(backtrace)]
86-
fn backtrace(&self) -> Option<&crate::backtrace::Backtrace> {
87-
self.0.backtrace()
85+
#[cfg(generic_member_access)]
86+
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
87+
self.0.provide(request);
8888
}
8989

9090
fn source(&self) -> Option<&(dyn StdError + 'static)> {

0 commit comments

Comments
 (0)