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", expect(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    pub const fn caller() -> &'static Location<'static> {
158        crate::intrinsics::caller_location()
159    }
160
161    /// Returns the name of the source file from which the panic originated.
162    ///
163    /// # `&str`, not `&Path`
164    ///
165    /// The returned name refers to a source path on the compiling system, but it isn't valid to
166    /// represent this directly as a `&Path`. The compiled code may run on a different system with
167    /// a different `Path` implementation than the system providing the contents and this library
168    /// does not currently have a different "host path" type.
169    ///
170    /// The most surprising behavior occurs when "the same" file is reachable via multiple paths in
171    /// the module system (usually using the `#[path = "..."]` attribute or similar), which can
172    /// cause what appears to be identical code to return differing values from this function.
173    ///
174    /// # Cross-compilation
175    ///
176    /// This value is not suitable for passing to `Path::new` or similar constructors when the host
177    /// platform and target platform differ.
178    ///
179    /// # Examples
180    ///
181    /// ```should_panic
182    /// use std::panic;
183    ///
184    /// panic::set_hook(Box::new(|panic_info| {
185    ///     if let Some(location) = panic_info.location() {
186    ///         println!("panic occurred in file '{}'", location.file());
187    ///     } else {
188    ///         println!("panic occurred but can't get location information...");
189    ///     }
190    /// }));
191    ///
192    /// panic!("Normal panic");
193    /// ```
194    #[must_use]
195    #[stable(feature = "panic_hooks", since = "1.10.0")]
196    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
197    #[cfg(not(feature = "ferrocene_certified"))]
198    pub const fn file(&self) -> &'a str {
199        // SAFETY: The filename is valid.
200        unsafe { self.filename.as_ref() }
201    }
202
203    /// Returns the name of the source file as a nul-terminated `CStr`.
204    ///
205    /// This is useful for interop with APIs that expect C/C++ `__FILE__` or
206    /// `std::source_location::file_name`, both of which return a nul-terminated `const char*`.
207    #[must_use]
208    #[inline]
209    #[stable(feature = "file_with_nul", since = "1.92.0")]
210    #[rustc_const_stable(feature = "file_with_nul", since = "1.92.0")]
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<'_> {}