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