diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..b43a9787 --- /dev/null +++ b/build.rs @@ -0,0 +1,69 @@ +use std::env; +use std::fs; +use std::path::Path; +use std::process::{Command, ExitStatus, Stdio}; + +// This build script is copied from +// [anyhow](https://github.com/dtolnay/anyhow/blob/master/build.rs), +// and is a type of feature detection to determine if the current rust +// toolchain has backtraces available. +// +// It exercises the surface area that we expect of the std Backtrace +// type. If the current toolchain is able to compile it, we enable a +// backtrace compiler configuration flag in http-types. We then +// conditionally require the compiler feature in src/lib.rs with +// `#![cfg_attr(backtrace, feature(backtrace))]` +// and gate our backtrace code behind `#[cfg(backtrace)]` + +const PROBE: &str = r#" + #![feature(backtrace)] + #![allow(dead_code)] + + use std::backtrace::{Backtrace, BacktraceStatus}; + use std::error::Error; + use std::fmt::{self, Display}; + + #[derive(Debug)] + struct E; + + impl Display for E { + fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { + unimplemented!() + } + } + + impl Error for E { + fn backtrace(&self) -> Option<&Backtrace> { + let backtrace = Backtrace::capture(); + match backtrace.status() { + BacktraceStatus::Captured | BacktraceStatus::Disabled | _ => {} + } + unimplemented!() + } + } +"#; + +fn main() { + match compile_probe() { + Some(status) if status.success() => println!("cargo:rustc-cfg=backtrace"), + _ => {} + } +} + +fn compile_probe() -> Option { + let rustc = env::var_os("RUSTC")?; + let out_dir = env::var_os("OUT_DIR")?; + let probefile = Path::new(&out_dir).join("probe.rs"); + fs::write(&probefile, PROBE).ok()?; + Command::new(rustc) + .stderr(Stdio::null()) + .arg("--edition=2018") + .arg("--crate-name=http_types_build") + .arg("--crate-type=lib") + .arg("--emit=metadata") + .arg("--out-dir") + .arg(out_dir) + .arg(probefile) + .status() + .ok() +} diff --git a/src/error.rs b/src/error.rs index 8b887dce..edbb8a8f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -70,9 +70,26 @@ impl Error { /// capturing them all over the place all the time. /// /// [tracking]: https://github.com/rust-lang/rust/issues/53487 + /// + /// Note: This function can be called whether or not backtraces + /// are enabled and available. It will return a `None` variant if + /// compiled on a toolchain that does not support backtraces, or + /// if executed without backtraces enabled with + /// `RUST_LIB_BACKTRACE=1`. #[cfg(backtrace)] - pub fn backtrace(&self) -> &std::backtrace::Backtrace { - self.error.downcast_ref::() + pub fn backtrace(&self) -> Option<&std::backtrace::Backtrace> { + let backtrace = self.error.backtrace(); + if let std::backtrace::BacktraceStatus::Captured = backtrace.status() { + Some(backtrace) + } else { + None + } + } + + #[cfg(not(backtrace))] + #[allow(missing_docs)] + pub fn backtrace(&self) -> Option<()> { + None } /// Attempt to downcast the error object to a concrete type. @@ -106,13 +123,13 @@ impl Error { impl Display for Error { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "{}", self.error) + Display::fmt(&self.error, formatter) } } impl Debug for Error { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "{}", self.error) + Debug::fmt(&self.error, formatter) } } diff --git a/src/lib.rs b/src/lib.rs index 213cf885..1e482127 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,7 @@ #![deny(missing_debug_implementations, nonstandard_style)] #![warn(missing_docs, unreachable_pub)] #![allow(clippy::new_without_default)] +#![cfg_attr(backtrace, feature(backtrace))] #![cfg_attr(test, deny(warnings))] #![cfg_attr(feature = "docs", feature(doc_cfg))] #![doc(html_favicon_url = "https://yoshuawuyts.com/assets/http-rs/favicon.ico")]