diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index 0d97407161930..3dd7576f593d3 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -1013,30 +1013,50 @@ where ) } - /// Allocates a string in the interpreter's memory, returning it as a (wide) place. - /// This is allocated in immutable global memory and deduplicated. - pub fn allocate_str_dedup( - &mut self, - str: &str, - ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { - let bytes = str.as_bytes(); - let ptr = self.allocate_bytes_dedup(bytes)?; - + /// Combines pointer and metadata into a wide pointer. + fn slice_from_ptr_and_len( + &self, + ptr: Pointer, + len: usize, + slice_ty: Ty<'tcx>, + ) -> MPlaceTy<'tcx, M::Provenance> { // Create length metadata for the string. - let meta = Scalar::from_target_usize(u64::try_from(bytes.len()).unwrap(), self); + let meta = Scalar::from_target_usize(u64::try_from(len).unwrap(), self); // Get layout for Rust's str type. - let layout = self.layout_of(self.tcx.types.str_).unwrap(); + let layout = self.layout_of(slice_ty).unwrap(); - // Combine pointer and metadata into a wide pointer. - interp_ok(self.ptr_with_meta_to_mplace( + self.ptr_with_meta_to_mplace( ptr.into(), MemPlaceMeta::Meta(meta), layout, /*unaligned*/ false, + ) + } + + pub fn allocate_byte_slice_dedup( + &mut self, + bytes: &[u8], + ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { + let ptr = self.allocate_bytes_dedup(bytes)?; + interp_ok(self.slice_from_ptr_and_len( + ptr, + bytes.len(), + Ty::new_slice(*self.tcx, self.tcx.types.u8), )) } + /// Allocates a string in the interpreter's memory, returning it as a (wide) place. + /// This is allocated in immutable global memory and deduplicated. + pub fn allocate_str_dedup( + &mut self, + str: &str, + ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { + let bytes = str.as_bytes(); + let ptr = self.allocate_bytes_dedup(bytes)?; + interp_ok(self.slice_from_ptr_and_len(ptr, bytes.len(), self.tcx.types.str_)) + } + pub fn raw_const_to_mplace( &self, raw: mir::ConstAlloc<'tcx>, diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index 6dd9447cf5a5f..34ddb5ebbbbe0 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -16,13 +16,19 @@ 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() + let filename_with_nul = filename.as_str().to_owned() + "\0"; + ecx.allocate_byte_slice_dedup(filename_with_nul.as_bytes()).unwrap() } else { - ecx.allocate_str_dedup("").unwrap() + ecx.allocate_byte_slice_dedup("\0".as_bytes()).unwrap() }; let file = file.map_provenance(CtfeProvenance::as_immutable); let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) }; diff --git a/library/core/src/panic/location.rs b/library/core/src/panic/location.rs index 1ad5c07d15cd0..48c20480ada4e 100644 --- a/library/core/src/panic/location.rs +++ b/library/core/src/panic/location.rs @@ -1,3 +1,5 @@ +#[cfg(not(bootstrap))] +use crate::ffi::CStr; use crate::fmt; /// A struct containing information about the location of a panic. @@ -32,7 +34,16 @@ use crate::fmt; #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[stable(feature = "panic_hooks", since = "1.10.0")] pub struct Location<'a> { + #[cfg(bootstrap)] 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`. + #[cfg(not(bootstrap))] + file_bytes_with_nul: &'a [u8], line: u32, col: u32, } @@ -122,12 +133,43 @@ impl<'a> Location<'a> { /// /// panic!("Normal panic"); /// ``` + /// + /// # C/C++ compatibility + /// + /// Although `file` returns an `&str`, the characters of `file` are guaranteed to be followed + /// by a nul-terminator. This allows for greater interoperabilty with C and C++ code using + /// `__FILE__` or `std::source_location::file_name`, both of which return nul-terminated + /// `const char*`. #[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 + #[cfg(bootstrap)] + { + self.file + } + + #[cfg(not(bootstrap))] + { + 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*`. + #[cfg(not(bootstrap))] + #[must_use] + #[unstable(feature = "file_cstr", issue = "none")] + #[inline] + pub const fn file_cstr(&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 +223,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 0000000000000..902423094a31f --- /dev/null +++ b/tests/ui/rfcs/rfc-2091-track-caller/file-is-nul-terminated.rs @@ -0,0 +1,25 @@ +//@ run-pass +#![feature(file_cstr)] + +#[track_caller] +const fn assert_file_has_trailing_zero() { + let caller = core::panic::Location::caller(); + let file_str = caller.file(); + let file_cstr = caller.file_cstr(); + if file_str.len() != file_cstr.count_bytes() { + panic!("mismatched lengths"); + } + let trailing_byte: core::ffi::c_char = unsafe { + *file_cstr.as_ptr().offset(file_cstr.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(); +}