| 1 | use crate::ffi::CStr; |
| 2 | use crate::fmt; |
| 3 | |
| 4 | /// A struct containing information about the location of a panic. |
| 5 | /// |
| 6 | /// This structure is created by [`PanicHookInfo::location()`] and [`PanicInfo::location()`]. |
| 7 | /// |
| 8 | /// [`PanicInfo::location()`]: crate::panic::PanicInfo::location |
| 9 | /// [`PanicHookInfo::location()`]: ../../std/panic/struct.PanicHookInfo.html#method.location |
| 10 | /// |
| 11 | /// # Examples |
| 12 | /// |
| 13 | /// ```should_panic |
| 14 | /// use std::panic; |
| 15 | /// |
| 16 | /// panic::set_hook(Box::new(|panic_info| { |
| 17 | /// if let Some(location) = panic_info.location() { |
| 18 | /// println!("panic occurred in file '{}' at line {}" , location.file(), location.line()); |
| 19 | /// } else { |
| 20 | /// println!("panic occurred but can't get location information..." ); |
| 21 | /// } |
| 22 | /// })); |
| 23 | /// |
| 24 | /// panic!("Normal panic" ); |
| 25 | /// ``` |
| 26 | /// |
| 27 | /// # Comparisons |
| 28 | /// |
| 29 | /// Comparisons for equality and ordering are made in file, line, then column priority. |
| 30 | /// Files are compared as strings, not `Path`, which could be unexpected. |
| 31 | /// See [`Location::file`]'s documentation for more discussion. |
| 32 | #[lang = "panic_location" ] |
| 33 | #[derive (Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
| 34 | #[stable (feature = "panic_hooks" , since = "1.10.0" )] |
| 35 | pub struct Location<'a> { |
| 36 | // Note: this filename will have exactly one nul byte at its end, but otherwise |
| 37 | // it must never contain interior nul bytes. This is relied on for the conversion |
| 38 | // to `CStr` below. |
| 39 | // |
| 40 | // The prefix of the string without the trailing nul byte will be a regular UTF8 `str`. |
| 41 | file_bytes_with_nul: &'a [u8], |
| 42 | line: u32, |
| 43 | col: u32, |
| 44 | } |
| 45 | |
| 46 | impl<'a> Location<'a> { |
| 47 | /// Returns the source location of the caller of this function. If that function's caller is |
| 48 | /// annotated then its call location will be returned, and so on up the stack to the first call |
| 49 | /// within a non-tracked function body. |
| 50 | /// |
| 51 | /// # Examples |
| 52 | /// |
| 53 | /// ```standalone_crate |
| 54 | /// use std::panic::Location; |
| 55 | /// |
| 56 | /// /// Returns the [`Location`] at which it is called. |
| 57 | /// #[track_caller] |
| 58 | /// fn get_caller_location() -> &'static Location<'static> { |
| 59 | /// Location::caller() |
| 60 | /// } |
| 61 | /// |
| 62 | /// /// Returns a [`Location`] from within this function's definition. |
| 63 | /// fn get_just_one_location() -> &'static Location<'static> { |
| 64 | /// get_caller_location() |
| 65 | /// } |
| 66 | /// |
| 67 | /// let fixed_location = get_just_one_location(); |
| 68 | /// assert_eq!(fixed_location.file(), file!()); |
| 69 | /// assert_eq!(fixed_location.line(), 14); |
| 70 | /// assert_eq!(fixed_location.column(), 5); |
| 71 | /// |
| 72 | /// // running the same untracked function in a different location gives us the same result |
| 73 | /// let second_fixed_location = get_just_one_location(); |
| 74 | /// assert_eq!(fixed_location.file(), second_fixed_location.file()); |
| 75 | /// assert_eq!(fixed_location.line(), second_fixed_location.line()); |
| 76 | /// assert_eq!(fixed_location.column(), second_fixed_location.column()); |
| 77 | /// |
| 78 | /// let this_location = get_caller_location(); |
| 79 | /// assert_eq!(this_location.file(), file!()); |
| 80 | /// assert_eq!(this_location.line(), 28); |
| 81 | /// assert_eq!(this_location.column(), 21); |
| 82 | /// |
| 83 | /// // running the tracked function in a different location produces a different value |
| 84 | /// let another_location = get_caller_location(); |
| 85 | /// assert_eq!(this_location.file(), another_location.file()); |
| 86 | /// assert_ne!(this_location.line(), another_location.line()); |
| 87 | /// assert_ne!(this_location.column(), another_location.column()); |
| 88 | /// ``` |
| 89 | #[must_use ] |
| 90 | #[stable (feature = "track_caller" , since = "1.46.0" )] |
| 91 | #[rustc_const_stable (feature = "const_caller_location" , since = "1.79.0" )] |
| 92 | #[track_caller ] |
| 93 | #[inline ] |
| 94 | pub const fn caller() -> &'static Location<'static> { |
| 95 | crate::intrinsics::caller_location() |
| 96 | } |
| 97 | |
| 98 | /// Returns the name of the source file from which the panic originated. |
| 99 | /// |
| 100 | /// # `&str`, not `&Path` |
| 101 | /// |
| 102 | /// The returned name refers to a source path on the compiling system, but it isn't valid to |
| 103 | /// represent this directly as a `&Path`. The compiled code may run on a different system with |
| 104 | /// a different `Path` implementation than the system providing the contents and this library |
| 105 | /// does not currently have a different "host path" type. |
| 106 | /// |
| 107 | /// The most surprising behavior occurs when "the same" file is reachable via multiple paths in |
| 108 | /// the module system (usually using the `#[path = "..."]` attribute or similar), which can |
| 109 | /// cause what appears to be identical code to return differing values from this function. |
| 110 | /// |
| 111 | /// # Cross-compilation |
| 112 | /// |
| 113 | /// This value is not suitable for passing to `Path::new` or similar constructors when the host |
| 114 | /// platform and target platform differ. |
| 115 | /// |
| 116 | /// # Examples |
| 117 | /// |
| 118 | /// ```should_panic |
| 119 | /// use std::panic; |
| 120 | /// |
| 121 | /// panic::set_hook(Box::new(|panic_info| { |
| 122 | /// if let Some(location) = panic_info.location() { |
| 123 | /// println!("panic occurred in file '{}'" , location.file()); |
| 124 | /// } else { |
| 125 | /// println!("panic occurred but can't get location information..." ); |
| 126 | /// } |
| 127 | /// })); |
| 128 | /// |
| 129 | /// panic!("Normal panic" ); |
| 130 | /// ``` |
| 131 | #[must_use ] |
| 132 | #[stable (feature = "panic_hooks" , since = "1.10.0" )] |
| 133 | #[rustc_const_stable (feature = "const_location_fields" , since = "1.79.0" )] |
| 134 | pub const fn file(&self) -> &str { |
| 135 | let str_len = self.file_bytes_with_nul.len() - 1; |
| 136 | // SAFETY: `file_bytes_with_nul` without the trailing nul byte is guaranteed to be |
| 137 | // valid UTF8. |
| 138 | unsafe { crate::str::from_raw_parts(self.file_bytes_with_nul.as_ptr(), str_len) } |
| 139 | } |
| 140 | |
| 141 | /// Returns the name of the source file as a nul-terminated `CStr`. |
| 142 | /// |
| 143 | /// This is useful for interop with APIs that expect C/C++ `__FILE__` or |
| 144 | /// `std::source_location::file_name`, both of which return a nul-terminated `const char*`. |
| 145 | #[must_use ] |
| 146 | #[unstable (feature = "file_with_nul" , issue = "141727" )] |
| 147 | #[inline ] |
| 148 | pub const fn file_with_nul(&self) -> &CStr { |
| 149 | // SAFETY: `file_bytes_with_nul` is guaranteed to have a trailing nul byte and no |
| 150 | // interior nul bytes. |
| 151 | unsafe { CStr::from_bytes_with_nul_unchecked(self.file_bytes_with_nul) } |
| 152 | } |
| 153 | |
| 154 | /// Returns the line number from which the panic originated. |
| 155 | /// |
| 156 | /// # Examples |
| 157 | /// |
| 158 | /// ```should_panic |
| 159 | /// use std::panic; |
| 160 | /// |
| 161 | /// panic::set_hook(Box::new(|panic_info| { |
| 162 | /// if let Some(location) = panic_info.location() { |
| 163 | /// println!("panic occurred at line {}" , location.line()); |
| 164 | /// } else { |
| 165 | /// println!("panic occurred but can't get location information..." ); |
| 166 | /// } |
| 167 | /// })); |
| 168 | /// |
| 169 | /// panic!("Normal panic" ); |
| 170 | /// ``` |
| 171 | #[must_use ] |
| 172 | #[stable (feature = "panic_hooks" , since = "1.10.0" )] |
| 173 | #[rustc_const_stable (feature = "const_location_fields" , since = "1.79.0" )] |
| 174 | #[inline ] |
| 175 | pub const fn line(&self) -> u32 { |
| 176 | self.line |
| 177 | } |
| 178 | |
| 179 | /// Returns the column from which the panic originated. |
| 180 | /// |
| 181 | /// # Examples |
| 182 | /// |
| 183 | /// ```should_panic |
| 184 | /// use std::panic; |
| 185 | /// |
| 186 | /// panic::set_hook(Box::new(|panic_info| { |
| 187 | /// if let Some(location) = panic_info.location() { |
| 188 | /// println!("panic occurred at column {}" , location.column()); |
| 189 | /// } else { |
| 190 | /// println!("panic occurred but can't get location information..." ); |
| 191 | /// } |
| 192 | /// })); |
| 193 | /// |
| 194 | /// panic!("Normal panic" ); |
| 195 | /// ``` |
| 196 | #[must_use ] |
| 197 | #[stable (feature = "panic_col" , since = "1.25.0" )] |
| 198 | #[rustc_const_stable (feature = "const_location_fields" , since = "1.79.0" )] |
| 199 | #[inline ] |
| 200 | pub const fn column(&self) -> u32 { |
| 201 | self.col |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | #[stable (feature = "panic_hook_display" , since = "1.26.0" )] |
| 206 | impl fmt::Display for Location<'_> { |
| 207 | #[inline ] |
| 208 | fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 209 | write!(formatter, " {}: {}: {}" , self.file(), self.line, self.col) |
| 210 | } |
| 211 | } |
| 212 | |