Skip to content

Commit

Permalink
Add Location::file_cstr
Browse files Browse the repository at this point in the history
This is useful for C/C++ APIs which expect the const char* returned
from __FILE__ or std::source_location::file_name.
  • Loading branch information
cramertj committed Jan 3, 2025
1 parent 4363f9b commit f1b6278
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 30 deletions.
46 changes: 33 additions & 13 deletions compiler/rustc_const_eval/src/interpret/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<M::Provenance>,
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>,
Expand Down
10 changes: 8 additions & 2 deletions compiler/rustc_const_eval/src/util/caller_location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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("<redacted>").unwrap()
ecx.allocate_byte_slice_dedup("<redacted>\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) };
Expand Down
60 changes: 45 additions & 15 deletions library/core/src/panic/location.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(not(bootstrap))]
use crate::ffi::CStr;
use crate::fmt;

/// A struct containing information about the location of a panic.
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
}
25 changes: 25 additions & 0 deletions tests/ui/rfcs/rfc-2091-track-caller/file-is-nul-terminated.rs
Original file line number Diff line number Diff line change
@@ -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();
}

0 comments on commit f1b6278

Please sign in to comment.