Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ rust-version = "1.39"
[features]
default = ["std"]
std = []
location = []

[dependencies]
# On compilers older than 1.65, features=["backtrace"] may be used to enable
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,39 @@ anyhow = "1.0"
bail!("Missing attribute: {}", missing);
```

- When the "location" feature is enabled, you can get detailed location
information about where errors occurred using the `where_info()` method.
This includes the file path, line number, and column number where the
error was created.

**Note:** The "location" feature requires Rust 1.46.0 or later.

```toml
[dependencies]
anyhow = { version = "1.0", features = ["location"] }
```

```rust
use anyhow::anyhow;

fn process_data() -> anyhow::Result<()> {
let error = anyhow!("Failed to process data");

// Get location information
if let Some(location_info) = error.where_info() {
println!("{}", location_info);
// Output: Error occurred: Failed to process data (at src/main.rs:10:23)
}

Ok(())
}
```

The `where_info()` method returns `Option<String>` containing formatted
location information when the "location" feature is enabled, or `None`
when the feature is disabled. Note that context operations may lose
location information due to implementation limitations.

<br>

## No-std support
Expand Down
16 changes: 16 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ compile_error! {
}

fn main() {
// Check if location feature is enabled and Rust version is sufficient
if cfg!(feature = "location") {
let rustc = match rustc_minor_version() {
Some(rustc) => rustc,
None => {
eprintln!("Failed to determine Rust version");
process::exit(1);
}
};

if rustc < 46 {
eprintln!("The 'location' feature requires Rust 1.46.0 or later");
process::exit(1);
}
}

let mut error_generic_member_access = false;
if cfg!(feature = "std") {
println!("cargo:rerun-if-changed=src/nightly.rs");
Expand Down
27 changes: 24 additions & 3 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ mod ext {
where
E: crate::StdError + Send + Sync + 'static,
{
#[track_caller]
fn ext_context<C>(self, context: C) -> Error
where
C: Display + Send + Sync + 'static,
{
let backtrace = backtrace_if_absent!(&self);
Error::construct_from_context(context, self, backtrace)
#[cfg(feature = "location")]
let location = Some(crate::location::Location::capture());
#[cfg(not(feature = "location"))]
let location = None;
Error::construct_from_context(context, self, backtrace, location)
}
}

Expand All @@ -43,6 +48,7 @@ impl<T, E> Context<T, E> for Result<T, E>
where
E: ext::StdError + Send + Sync + 'static,
{
#[track_caller]
fn context<C>(self, context: C) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
Expand All @@ -55,6 +61,7 @@ where
}
}

#[track_caller]
fn with_context<C, F>(self, context: F) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
Expand Down Expand Up @@ -88,6 +95,7 @@ where
/// }
/// ```
impl<T> Context<T, Infallible> for Option<T> {
#[track_caller]
fn context<C>(self, context: C) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
Expand All @@ -96,18 +104,31 @@ impl<T> Context<T, Infallible> for Option<T> {
// backtrace.
match self {
Some(ok) => Ok(ok),
None => Err(Error::construct_from_display(context, backtrace!())),
None => {
#[cfg(feature = "location")]
let location = Some(crate::location::Location::capture());
#[cfg(not(feature = "location"))]
let location = None;
Err(Error::construct_from_display(context, backtrace!(), location))
}
}
}

#[track_caller]
fn with_context<C, F>(self, context: F) -> Result<T, Error>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
match self {
Some(ok) => Ok(ok),
None => Err(Error::construct_from_display(context(), backtrace!())),
None => {
#[cfg(feature = "location")]
let location = Some(crate::location::Location::capture());
#[cfg(not(feature = "location"))]
let location = None;
Err(Error::construct_from_display(context(), backtrace!(), location))
}
}
}
}
Expand Down
93 changes: 83 additions & 10 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use crate::backtrace::Backtrace;
use crate::chain::Chain;
#[cfg(feature = "location")]
use crate::location::Location;
#[cfg(feature = "location")]
use alloc::string::String;
#[cfg(feature = "location")]
use alloc::format;
#[cfg(error_generic_member_access)]
use crate::nightly::{self, Request};
#[cfg(any(feature = "std", not(anyhow_no_core_error), anyhow_no_ptr_addr_of))]
Expand All @@ -10,8 +16,12 @@ use alloc::boxed::Box;
use core::any::TypeId;
use core::fmt::{self, Debug, Display};
use core::mem::ManuallyDrop;
#[cfg(feature = "location")]
use alloc::string::ToString;
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
use core::ops::{Deref, DerefMut};


#[cfg(not(anyhow_no_core_unwind_safe))]
use core::panic::{RefUnwindSafe, UnwindSafe};
#[cfg(not(anyhow_no_ptr_addr_of))]
Expand All @@ -31,12 +41,49 @@ impl Error {
#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
#[cold]
#[must_use]
#[track_caller]
pub fn new<E>(error: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
let backtrace = backtrace_if_absent!(&error);
Error::construct_from_std(error, backtrace)
#[cfg(feature = "location")]
let location = Some(crate::location::Location::capture());
#[cfg(not(feature = "location"))]
let location = None;
Error::construct_from_std(error, backtrace, location)
}

/// Get the error information including the error message and its location,
/// but without the full stack trace.
///
/// This method returns a string containing both the error message and where it
/// occurred (file path, line number, and column number).
///
/// # Example
///
/// ```
/// use anyhow::{Error, Result};
///
/// fn example() -> Result<()> {
/// Err(Error::msg("failed to read config"))?;
/// Ok(())
/// }
///
/// if let Err(e) = example() {
/// // Will print something like:
/// // "failed to read config (at src/main.rs:4:5)"
/// println!("{}", e.where_info().unwrap_or_default());
/// }
/// ```
#[cfg(feature = "location")]
pub fn where_info(&self) -> Option<String> {
let error_msg = self.to_string();
unsafe {
ErrorImpl::location(self.inner.by_ref()).map(|loc| {
format!("Error occurred: {} (at {})", error_msg, loc)
})
}
}

/// Create a new error object from a printable error message.
Expand Down Expand Up @@ -148,7 +195,7 @@ impl Error {

#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
#[cold]
pub(crate) fn construct_from_std<E>(error: E, backtrace: Option<Backtrace>) -> Self
pub(crate) fn construct_from_std<E>(error: E, backtrace: Option<Backtrace>, #[cfg(feature = "location")] location: Option<Location>, #[cfg(not(feature = "location"))] location: Option<()>) -> Self
where
E: StdError + Send + Sync + 'static,
{
Expand All @@ -173,10 +220,11 @@ impl Error {
};

// Safety: passing vtable that operates on the right type E.
unsafe { Error::construct(error, vtable, backtrace) }
unsafe { Error::construct(error, vtable, backtrace, location) }
}

#[cold]
#[track_caller]
pub(crate) fn construct_from_adhoc<M>(message: M, backtrace: Option<Backtrace>) -> Self
where
M: Display + Debug + Send + Sync + 'static,
Expand Down Expand Up @@ -205,11 +253,11 @@ impl Error {

// Safety: MessageError is repr(transparent) so it is okay for the
// vtable to allow casting the MessageError<M> to M.
unsafe { Error::construct(error, vtable, backtrace) }
unsafe { Error::construct(error, vtable, backtrace, None) }
}

#[cold]
pub(crate) fn construct_from_display<M>(message: M, backtrace: Option<Backtrace>) -> Self
pub(crate) fn construct_from_display<M>(message: M, backtrace: Option<Backtrace>, #[cfg(feature = "location")] location: Option<Location>, #[cfg(not(feature = "location"))] location: Option<()>) -> Self
where
M: Display + Send + Sync + 'static,
{
Expand Down Expand Up @@ -237,7 +285,7 @@ impl Error {

// Safety: DisplayError is repr(transparent) so it is okay for the
// vtable to allow casting the DisplayError<M> to M.
unsafe { Error::construct(error, vtable, backtrace) }
unsafe { Error::construct(error, vtable, backtrace, location) }
}

#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
Expand All @@ -246,6 +294,8 @@ impl Error {
context: C,
error: E,
backtrace: Option<Backtrace>,
#[cfg(feature = "location")] location: Option<Location>,
#[cfg(not(feature = "location"))] location: Option<()>,
) -> Self
where
C: Display + Send + Sync + 'static,
Expand Down Expand Up @@ -274,7 +324,7 @@ impl Error {
};

// Safety: passing vtable that operates on the right type.
unsafe { Error::construct(error, vtable, backtrace) }
unsafe { Error::construct(error, vtable, backtrace, location) }
}

#[cfg(any(feature = "std", not(anyhow_no_core_error)))]
Expand Down Expand Up @@ -307,7 +357,7 @@ impl Error {

// Safety: BoxedError is repr(transparent) so it is okay for the vtable
// to allow casting to Box<dyn StdError + Send + Sync>.
unsafe { Error::construct(error, vtable, backtrace) }
unsafe { Error::construct(error, vtable, backtrace, None) }
}

// Takes backtrace as argument rather than capturing it here so that the
Expand All @@ -320,13 +370,16 @@ impl Error {
error: E,
vtable: &'static ErrorVTable,
backtrace: Option<Backtrace>,
#[cfg(feature = "location")] location: Option<Location>,
#[cfg(not(feature = "location"))] location: Option<()>,
) -> Self
where
E: StdError + Send + Sync + 'static,
{
let inner: Box<ErrorImpl<E>> = Box::new(ErrorImpl {
vtable,
backtrace,
location,
_object: error,
});
// Erase the concrete type of E from the compile-time type system. This
Expand Down Expand Up @@ -428,7 +481,7 @@ impl Error {
let backtrace = None;

// Safety: passing vtable that operates on the right type.
unsafe { Error::construct(error, vtable, backtrace) }
unsafe { Error::construct(error, vtable, backtrace, None) }
}

/// Get the backtrace for this Error.
Expand Down Expand Up @@ -736,9 +789,14 @@ where
E: StdError + Send + Sync + 'static,
{
#[cold]
#[track_caller]
fn from(error: E) -> Self {
let backtrace = backtrace_if_absent!(&error);
Error::construct_from_std(error, backtrace)
#[cfg(feature = "location")]
let location = Some(Location::capture());
#[cfg(not(feature = "location"))]
let location = None;
Error::construct_from_std(error, backtrace, location)
}
}

Expand Down Expand Up @@ -1058,6 +1116,10 @@ where
pub(crate) struct ErrorImpl<E = ()> {
vtable: &'static ErrorVTable,
backtrace: Option<Backtrace>,
#[cfg(feature = "location")]
location: Option<Location>,
#[cfg(not(feature = "location"))]
location: Option<()>,
// NOTE: Don't use directly. Use only through vtable. Erased type may have
// different alignment.
_object: E,
Expand Down Expand Up @@ -1139,6 +1201,17 @@ impl ErrorImpl {
pub(crate) unsafe fn chain(this: Ref<Self>) -> Chain {
Chain::new(unsafe { Self::error(this) })
}

#[cfg(feature = "location")]
pub(crate) unsafe fn location(this: Ref<Self>) -> Option<&Location> {
unsafe { this.deref() }.location.as_ref()
}

#[cfg(not(feature = "location"))]
#[allow(dead_code)]
pub(crate) unsafe fn location(_this: Ref<Self>) -> Option<&()> {
None
}
}

impl<E> StdError for ErrorImpl<E>
Expand Down
Loading
Loading