about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-06-05 10:21:20 +0000
committerbors <bors@rust-lang.org>2025-06-05 10:21:20 +0000
commitc360e219f5a56631baa46065d28e9852ca7d4ce3 (patch)
tree7193d135b42af795cea72addaba2df1de3ca1b15
parent425e142686242c7e73f5e32c79071ae266f0f355 (diff)
parentb541f93372c24bcbf036efc2b454cff6f6b8f8c5 (diff)
downloadrust-c360e219f5a56631baa46065d28e9852ca7d4ce3.tar.gz
rust-c360e219f5a56631baa46065d28e9852ca7d4ce3.zip
Auto merge of #135054 - cramertj:file-cstr, r=m-ou-se
Add Location::file_with_nul

This is useful for C/C++ APIs which expect the const char* returned from __FILE__ or std::source_location::file_name.

ACP: https://github.com/rust-lang/libs-team/issues/466
Tracking issue: https://github.com/rust-lang/rust/issues/141727
-rw-r--r--compiler/rustc_const_eval/src/util/caller_location.rs21
-rw-r--r--library/core/src/panic/location.rs41
-rw-r--r--tests/ui/rfcs/rfc-2091-track-caller/file-is-nul-terminated.rs25
3 files changed, 63 insertions, 24 deletions
diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs
index e926040e9ba..9c867cc615e 100644
--- a/compiler/rustc_const_eval/src/util/caller_location.rs
+++ b/compiler/rustc_const_eval/src/util/caller_location.rs
@@ -15,15 +15,20 @@ fn alloc_caller_location<'tcx>(
     line: u32,
     col: u32,
 ) -> MPlaceTy<'tcx> {
+    // Ensure that the filename itself does not contain nul bytes.
+    // This isn't possible via POSIX or Windows, but we should ensure no one
+    // ever does such a thing.
+    assert!(!filename.as_str().as_bytes().contains(&0));
+
     let loc_details = ecx.tcx.sess.opts.unstable_opts.location_detail;
-    // This can fail if rustc runs out of memory right here. Trying to emit an error would be
-    // pointless, since that would require allocating more memory than these short strings.
-    let file = if loc_details.file {
-        ecx.allocate_str_dedup(filename.as_str()).unwrap()
-    } else {
-        ecx.allocate_str_dedup("<redacted>").unwrap()
+    let file_wide_ptr = {
+        let filename = if loc_details.file { filename.as_str() } else { "<redacted>" };
+        let filename_with_nul = filename.to_owned() + "\0";
+        // This can fail if rustc runs out of memory right here. Trying to emit an error would be
+        // pointless, since that would require allocating more memory than these short strings.
+        let file_ptr = ecx.allocate_bytes_dedup(filename_with_nul.as_bytes()).unwrap();
+        Immediate::new_slice(file_ptr.into(), filename_with_nul.len().try_into().unwrap(), ecx)
     };
-    let file = file.map_provenance(CtfeProvenance::as_immutable);
     let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) };
     let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) };
 
@@ -36,7 +41,7 @@ fn alloc_caller_location<'tcx>(
     let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap();
 
     // Initialize fields.
-    ecx.write_immediate(file.to_ref(ecx), &ecx.project_field(&location, 0).unwrap())
+    ecx.write_immediate(file_wide_ptr, &ecx.project_field(&location, 0).unwrap())
         .expect("writing to memory we just allocated cannot fail");
     ecx.write_scalar(line, &ecx.project_field(&location, 1).unwrap())
         .expect("writing to memory we just allocated cannot fail");
diff --git a/library/core/src/panic/location.rs b/library/core/src/panic/location.rs
index 1ad5c07d15c..94cfd667ffa 100644
--- a/library/core/src/panic/location.rs
+++ b/library/core/src/panic/location.rs
@@ -1,3 +1,4 @@
+use crate::ffi::CStr;
 use crate::fmt;
 
 /// A struct containing information about the location of a panic.
@@ -32,7 +33,12 @@ use crate::fmt;
 #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
 #[stable(feature = "panic_hooks", since = "1.10.0")]
 pub struct Location<'a> {
-    file: &'a str,
+    // Note: this filename will have exactly one nul byte at its end, but otherwise
+    // it must never contain interior nul bytes. This is relied on for the conversion
+    // to `CStr` below.
+    //
+    // The prefix of the string without the trailing nul byte will be a regular UTF8 `str`.
+    file_bytes_with_nul: &'a [u8],
     line: u32,
     col: u32,
 }
@@ -125,9 +131,24 @@ impl<'a> Location<'a> {
     #[must_use]
     #[stable(feature = "panic_hooks", since = "1.10.0")]
     #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
-    #[inline]
     pub const fn file(&self) -> &str {
-        self.file
+        let str_len = self.file_bytes_with_nul.len() - 1;
+        // SAFETY: `file_bytes_with_nul` without the trailing nul byte is guaranteed to be
+        // valid UTF8.
+        unsafe { crate::str::from_raw_parts(self.file_bytes_with_nul.as_ptr(), str_len) }
+    }
+
+    /// Returns the name of the source file as a nul-terminated `CStr`.
+    ///
+    /// This is useful for interop with APIs that expect C/C++ `__FILE__` or
+    /// `std::source_location::file_name`, both of which return a nul-terminated `const char*`.
+    #[must_use]
+    #[unstable(feature = "file_with_nul", issue = "141727")]
+    #[inline]
+    pub const fn file_with_nul(&self) -> &CStr {
+        // SAFETY: `file_bytes_with_nul` is guaranteed to have a trailing nul byte and no
+        // interior nul bytes.
+        unsafe { CStr::from_bytes_with_nul_unchecked(self.file_bytes_with_nul) }
     }
 
     /// Returns the line number from which the panic originated.
@@ -181,22 +202,10 @@ impl<'a> Location<'a> {
     }
 }
 
-#[unstable(
-    feature = "panic_internals",
-    reason = "internal details of the implementation of the `panic!` and related macros",
-    issue = "none"
-)]
-impl<'a> Location<'a> {
-    #[doc(hidden)]
-    pub const fn internal_constructor(file: &'a str, line: u32, col: u32) -> Self {
-        Location { file, line, col }
-    }
-}
-
 #[stable(feature = "panic_hook_display", since = "1.26.0")]
 impl fmt::Display for Location<'_> {
     #[inline]
     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(formatter, "{}:{}:{}", self.file, self.line, self.col)
+        write!(formatter, "{}:{}:{}", self.file(), self.line, self.col)
     }
 }
diff --git a/tests/ui/rfcs/rfc-2091-track-caller/file-is-nul-terminated.rs b/tests/ui/rfcs/rfc-2091-track-caller/file-is-nul-terminated.rs
new file mode 100644
index 00000000000..65e61a21f1a
--- /dev/null
+++ b/tests/ui/rfcs/rfc-2091-track-caller/file-is-nul-terminated.rs
@@ -0,0 +1,25 @@
+//@ run-pass
+#![feature(file_with_nul)]
+
+#[track_caller]
+const fn assert_file_has_trailing_zero() {
+    let caller = core::panic::Location::caller();
+    let file_str = caller.file();
+    let file_with_nul = caller.file_with_nul();
+    if file_str.len() != file_with_nul.count_bytes() {
+        panic!("mismatched lengths");
+    }
+    let trailing_byte: core::ffi::c_char = unsafe {
+        *file_with_nul.as_ptr().offset(file_with_nul.count_bytes() as _)
+    };
+    if trailing_byte != 0 {
+        panic!("trailing byte was nonzero")
+    }
+}
+
+#[allow(dead_code)]
+const _: () = assert_file_has_trailing_zero();
+
+fn main() {
+    assert_file_has_trailing_zero();
+}