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<'_> {}