core/panic/
location.rs

1#[cfg(not(feature = "ferrocene_certified"))]
2use crate::cmp::Ordering;
3#[cfg(not(feature = "ferrocene_certified"))]
4use crate::ffi::CStr;
5#[cfg(not(feature = "ferrocene_certified"))]
6use crate::fmt;
7#[cfg(not(feature = "ferrocene_certified"))]
8use crate::hash::{Hash, Hasher};
9use crate::marker::PhantomData;
10use crate::ptr::NonNull;
11
12/// A struct containing information about the location of a panic.
13///
14/// This structure is created by [`PanicHookInfo::location()`] and [`PanicInfo::location()`].
15///
16/// [`PanicInfo::location()`]: crate::panic::PanicInfo::location
17/// [`PanicHookInfo::location()`]: ../../std/panic/struct.PanicHookInfo.html#method.location
18///
19/// # Examples
20///
21/// ```should_panic
22/// use std::panic;
23///
24/// panic::set_hook(Box::new(|panic_info| {
25///     if let Some(location) = panic_info.location() {
26///         println!("panic occurred in file '{}' at line {}", location.file(), location.line());
27///     } else {
28///         println!("panic occurred but can't get location information...");
29///     }
30/// }));
31///
32/// panic!("Normal panic");
33/// ```
34///
35/// # Comparisons
36///
37/// Comparisons for equality and ordering are made in file, line, then column priority.
38/// Files are compared as strings, not `Path`, which could be unexpected.
39/// See [`Location::file`]'s documentation for more discussion.
40#[lang = "panic_location"]
41#[stable(feature = "panic_hooks", since = "1.10.0")]
42#[cfg_attr(feature = "ferrocene_certified", allow(dead_code))]
43#[cfg_attr(not(feature = "ferrocene_certified"), derive(Copy, Clone))]
44pub struct Location<'a> {
45    // A raw pointer is used rather than a reference because the pointer is valid for one more byte
46    // than the length stored in this pointer; the additional byte is the NUL-terminator used by
47    // `Location::file_as_c_str`.
48    filename: NonNull<str>,
49    line: u32,
50    col: u32,
51    _filename: PhantomData<&'a str>,
52}
53
54#[stable(feature = "panic_hooks", since = "1.10.0")]
55#[cfg(not(feature = "ferrocene_certified"))]
56impl PartialEq for Location<'_> {
57    fn eq(&self, other: &Self) -> bool {
58        // Compare col / line first as they're cheaper to compare and more likely to differ,
59        // while not impacting the result.
60        self.col == other.col && self.line == other.line && self.file() == other.file()
61    }
62}
63
64#[stable(feature = "panic_hooks", since = "1.10.0")]
65#[cfg(not(feature = "ferrocene_certified"))]
66impl Eq for Location<'_> {}
67
68#[stable(feature = "panic_hooks", since = "1.10.0")]
69#[cfg(not(feature = "ferrocene_certified"))]
70impl Ord for Location<'_> {
71    fn cmp(&self, other: &Self) -> Ordering {
72        self.file()
73            .cmp(other.file())
74            .then_with(|| self.line.cmp(&other.line))
75            .then_with(|| self.col.cmp(&other.col))
76    }
77}
78
79#[stable(feature = "panic_hooks", since = "1.10.0")]
80#[cfg(not(feature = "ferrocene_certified"))]
81impl PartialOrd for Location<'_> {
82    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
83        Some(self.cmp(other))
84    }
85}
86
87#[stable(feature = "panic_hooks", since = "1.10.0")]
88#[cfg(not(feature = "ferrocene_certified"))]
89impl Hash for Location<'_> {
90    fn hash<H: Hasher>(&self, state: &mut H) {
91        self.file().hash(state);
92        self.line.hash(state);
93        self.col.hash(state);
94    }
95}
96
97#[stable(feature = "panic_hooks", since = "1.10.0")]
98#[cfg(not(feature = "ferrocene_certified"))]
99impl fmt::Debug for Location<'_> {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        f.debug_struct("Location")
102            .field("file", &self.file())
103            .field("line", &self.line)
104            .field("column", &self.col)
105            .finish()
106    }
107}
108
109impl<'a> Location<'a> {
110    /// Returns the source location of the caller of this function. If that function's caller is
111    /// annotated then its call location will be returned, and so on up the stack to the first call
112    /// within a non-tracked function body.
113    ///
114    /// # Examples
115    ///
116    /// ```standalone_crate
117    /// use std::panic::Location;
118    ///
119    /// /// Returns the [`Location`] at which it is called.
120    /// #[track_caller]
121    /// fn get_caller_location() -> &'static Location<'static> {
122    ///     Location::caller()
123    /// }
124    ///
125    /// /// Returns a [`Location`] from within this function's definition.
126    /// fn get_just_one_location() -> &'static Location<'static> {
127    ///     get_caller_location()
128    /// }
129    ///
130    /// let fixed_location = get_just_one_location();
131    /// assert_eq!(fixed_location.file(), file!());
132    /// assert_eq!(fixed_location.line(), 14);
133    /// assert_eq!(fixed_location.column(), 5);
134    ///
135    /// // running the same untracked function in a different location gives us the same result
136    /// let second_fixed_location = get_just_one_location();
137    /// assert_eq!(fixed_location.file(), second_fixed_location.file());
138    /// assert_eq!(fixed_location.line(), second_fixed_location.line());
139    /// assert_eq!(fixed_location.column(), second_fixed_location.column());
140    ///
141    /// let this_location = get_caller_location();
142    /// assert_eq!(this_location.file(), file!());
143    /// assert_eq!(this_location.line(), 28);
144    /// assert_eq!(this_location.column(), 21);
145    ///
146    /// // running the tracked function in a different location produces a different value
147    /// let another_location = get_caller_location();
148    /// assert_eq!(this_location.file(), another_location.file());
149    /// assert_ne!(this_location.line(), another_location.line());
150    /// assert_ne!(this_location.column(), another_location.column());
151    /// ```
152    #[must_use]
153    #[stable(feature = "track_caller", since = "1.46.0")]
154    #[rustc_const_stable(feature = "const_caller_location", since = "1.79.0")]
155    #[track_caller]
156    #[inline]
157    #[cfg(not(feature = "ferrocene_certified"))]
158    pub const fn caller() -> &'static Location<'static> {
159        crate::intrinsics::caller_location()
160    }
161
162    /// Returns the name of the source file from which the panic originated.
163    ///
164    /// # `&str`, not `&Path`
165    ///
166    /// The returned name refers to a source path on the compiling system, but it isn't valid to
167    /// represent this directly as a `&Path`. The compiled code may run on a different system with
168    /// a different `Path` implementation than the system providing the contents and this library
169    /// does not currently have a different "host path" type.
170    ///
171    /// The most surprising behavior occurs when "the same" file is reachable via multiple paths in
172    /// the module system (usually using the `#[path = "..."]` attribute or similar), which can
173    /// cause what appears to be identical code to return differing values from this function.
174    ///
175    /// # Cross-compilation
176    ///
177    /// This value is not suitable for passing to `Path::new` or similar constructors when the host
178    /// platform and target platform differ.
179    ///
180    /// # Examples
181    ///
182    /// ```should_panic
183    /// use std::panic;
184    ///
185    /// panic::set_hook(Box::new(|panic_info| {
186    ///     if let Some(location) = panic_info.location() {
187    ///         println!("panic occurred in file '{}'", location.file());
188    ///     } else {
189    ///         println!("panic occurred but can't get location information...");
190    ///     }
191    /// }));
192    ///
193    /// panic!("Normal panic");
194    /// ```
195    #[must_use]
196    #[stable(feature = "panic_hooks", since = "1.10.0")]
197    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
198    #[cfg(not(feature = "ferrocene_certified"))]
199    pub const fn file(&self) -> &'a str {
200        // SAFETY: The filename is valid.
201        unsafe { self.filename.as_ref() }
202    }
203
204    /// Returns the name of the source file as a nul-terminated `CStr`.
205    ///
206    /// This is useful for interop with APIs that expect C/C++ `__FILE__` or
207    /// `std::source_location::file_name`, both of which return a nul-terminated `const char*`.
208    #[must_use]
209    #[unstable(feature = "file_with_nul", issue = "141727")]
210    #[inline]
211    #[cfg(not(feature = "ferrocene_certified"))]
212    pub const fn file_as_c_str(&self) -> &'a CStr {
213        let filename = self.filename.as_ptr();
214
215        // SAFETY: The filename is valid for `filename_len+1` bytes, so this addition can't
216        // overflow.
217        let cstr_len = unsafe { crate::mem::size_of_val_raw(filename).unchecked_add(1) };
218
219        // SAFETY: The filename is valid for `filename_len+1` bytes.
220        let slice = unsafe { crate::slice::from_raw_parts(filename.cast(), cstr_len) };
221
222        // SAFETY: The filename is guaranteed to have a trailing nul byte and no interior nul bytes.
223        unsafe { CStr::from_bytes_with_nul_unchecked(slice) }
224    }
225
226    /// Returns the line number from which the panic originated.
227    ///
228    /// # Examples
229    ///
230    /// ```should_panic
231    /// use std::panic;
232    ///
233    /// panic::set_hook(Box::new(|panic_info| {
234    ///     if let Some(location) = panic_info.location() {
235    ///         println!("panic occurred at line {}", location.line());
236    ///     } else {
237    ///         println!("panic occurred but can't get location information...");
238    ///     }
239    /// }));
240    ///
241    /// panic!("Normal panic");
242    /// ```
243    #[must_use]
244    #[stable(feature = "panic_hooks", since = "1.10.0")]
245    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
246    #[inline]
247    pub const fn line(&self) -> u32 {
248        self.line
249    }
250
251    /// Returns the column from which the panic originated.
252    ///
253    /// # Examples
254    ///
255    /// ```should_panic
256    /// use std::panic;
257    ///
258    /// panic::set_hook(Box::new(|panic_info| {
259    ///     if let Some(location) = panic_info.location() {
260    ///         println!("panic occurred at column {}", location.column());
261    ///     } else {
262    ///         println!("panic occurred but can't get location information...");
263    ///     }
264    /// }));
265    ///
266    /// panic!("Normal panic");
267    /// ```
268    #[must_use]
269    #[stable(feature = "panic_col", since = "1.25.0")]
270    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
271    #[inline]
272    pub const fn column(&self) -> u32 {
273        self.col
274    }
275}
276
277#[stable(feature = "panic_hook_display", since = "1.26.0")]
278#[cfg(not(feature = "ferrocene_certified"))]
279impl fmt::Display for Location<'_> {
280    #[inline]
281    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
282        write!(formatter, "{}:{}:{}", self.file(), self.line, self.col)
283    }
284}
285
286#[stable(feature = "panic_hooks", since = "1.10.0")]
287unsafe impl Send for Location<'_> {}
288#[stable(feature = "panic_hooks", since = "1.10.0")]
289unsafe impl Sync for Location<'_> {}