Skip to content

Commit df42dc4

Browse files
authored
Fix compile probe and nightly backtraces (#160)
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
2 parents dded7de + 87ee15e commit df42dc4

11 files changed

+204
-157
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 = "4.0"
20+
autocfg = "1.0"
2021

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

README.md

+16-6
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
@@ -195,8 +207,6 @@ you need an error type that can be handled via match or reported. This is
195207
common in library crates where you don't know how your users will handle
196208
your errors.
197209

198-
[thiserror]: https://github.com/dtolnay/thiserror
199-
200210
## Compatibility with `anyhow`
201211

202212
This crate does its best to be usable as a drop in replacement of `anyhow` and

eyre/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ 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"

eyre/build.rs

+72-99
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-
"#;
47-
48-
fn main() {
49-
match compile_probe(BACKTRACE_PROBE) {
50-
Some(status) if status.success() => println!("cargo:rustc-cfg=backtrace"),
51-
_ => {}
52-
}
53-
54-
match compile_probe(TRACK_CALLER_PROBE) {
55-
Some(status) if status.success() => println!("cargo:rustc-cfg=track_caller"),
56-
_ => {}
57-
}
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()?;
5871

59-
let version = match rustc_version_info() {
60-
Some(version) => version,
61-
None => return,
62-
};
72+
let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty());
73+
let rustc_workspace_wrapper =
74+
env::var_os("RUSTC_WORKSPACE_WRAPPER").filter(|wrapper| !wrapper.is_empty());
75+
let mut rustc = rustc_wrapper
76+
.into_iter()
77+
.chain(rustc_workspace_wrapper)
78+
.chain(std::iter::once(rustc));
6379

64-
version.toolchain.set_feature();
80+
let mut cmd = Command::new(rustc.next().unwrap());
81+
cmd.args(rustc);
6582

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

@@ -139,9 +136,9 @@ where
139136
D: Display,
140137
E: StdError + 'static,
141138
{
142-
#[cfg(backtrace)]
143-
fn backtrace(&self) -> Option<&Backtrace> {
144-
self.error.backtrace()
139+
#[cfg(generic_member_access)]
140+
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
141+
self.error.provide(request);
145142
}
146143

147144
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

+10-5
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,
@@ -636,7 +636,7 @@ impl dyn EyreHandler {
636636
t == concrete
637637
}
638638

639-
/// Downcast the handler to a concrete type `T`
639+
/// Downcast the handler to a concrete type
640640
pub fn downcast_ref<T: EyreHandler>(&self) -> Option<&T> {
641641
if self.is::<T>() {
642642
unsafe { Some(&*(self as *const dyn EyreHandler as *const T)) }
@@ -645,7 +645,7 @@ impl dyn EyreHandler {
645645
}
646646
}
647647

648-
/// Downcast the handler to a concrete type `T`
648+
/// Downcast the handler to a concrete type
649649
pub fn downcast_mut<T: EyreHandler>(&mut self) -> Option<&mut T> {
650650
if self.is::<T>() {
651651
unsafe { Some(&mut *(self as *mut dyn EyreHandler as *mut T)) }
@@ -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
}

0 commit comments

Comments
 (0)