Skip to content

Commit f1b6278

Browse files
committed
Add Location::file_cstr
This is useful for C/C++ APIs which expect the const char* returned from __FILE__ or std::source_location::file_name.
1 parent 4363f9b commit f1b6278

File tree

4 files changed

+111
-30
lines changed

4 files changed

+111
-30
lines changed

compiler/rustc_const_eval/src/interpret/place.rs

+33-13
Original file line numberDiff line numberDiff line change
@@ -1013,30 +1013,50 @@ where
10131013
)
10141014
}
10151015

1016-
/// Allocates a string in the interpreter's memory, returning it as a (wide) place.
1017-
/// This is allocated in immutable global memory and deduplicated.
1018-
pub fn allocate_str_dedup(
1019-
&mut self,
1020-
str: &str,
1021-
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
1022-
let bytes = str.as_bytes();
1023-
let ptr = self.allocate_bytes_dedup(bytes)?;
1024-
1016+
/// Combines pointer and metadata into a wide pointer.
1017+
fn slice_from_ptr_and_len(
1018+
&self,
1019+
ptr: Pointer<M::Provenance>,
1020+
len: usize,
1021+
slice_ty: Ty<'tcx>,
1022+
) -> MPlaceTy<'tcx, M::Provenance> {
10251023
// Create length metadata for the string.
1026-
let meta = Scalar::from_target_usize(u64::try_from(bytes.len()).unwrap(), self);
1024+
let meta = Scalar::from_target_usize(u64::try_from(len).unwrap(), self);
10271025

10281026
// Get layout for Rust's str type.
1029-
let layout = self.layout_of(self.tcx.types.str_).unwrap();
1027+
let layout = self.layout_of(slice_ty).unwrap();
10301028

1031-
// Combine pointer and metadata into a wide pointer.
1032-
interp_ok(self.ptr_with_meta_to_mplace(
1029+
self.ptr_with_meta_to_mplace(
10331030
ptr.into(),
10341031
MemPlaceMeta::Meta(meta),
10351032
layout,
10361033
/*unaligned*/ false,
1034+
)
1035+
}
1036+
1037+
pub fn allocate_byte_slice_dedup(
1038+
&mut self,
1039+
bytes: &[u8],
1040+
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
1041+
let ptr = self.allocate_bytes_dedup(bytes)?;
1042+
interp_ok(self.slice_from_ptr_and_len(
1043+
ptr,
1044+
bytes.len(),
1045+
Ty::new_slice(*self.tcx, self.tcx.types.u8),
10371046
))
10381047
}
10391048

1049+
/// Allocates a string in the interpreter's memory, returning it as a (wide) place.
1050+
/// This is allocated in immutable global memory and deduplicated.
1051+
pub fn allocate_str_dedup(
1052+
&mut self,
1053+
str: &str,
1054+
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
1055+
let bytes = str.as_bytes();
1056+
let ptr = self.allocate_bytes_dedup(bytes)?;
1057+
interp_ok(self.slice_from_ptr_and_len(ptr, bytes.len(), self.tcx.types.str_))
1058+
}
1059+
10401060
pub fn raw_const_to_mplace(
10411061
&self,
10421062
raw: mir::ConstAlloc<'tcx>,

compiler/rustc_const_eval/src/util/caller_location.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@ fn alloc_caller_location<'tcx>(
1616
line: u32,
1717
col: u32,
1818
) -> MPlaceTy<'tcx> {
19+
// Ensure that the filename itself does not contain nul bytes.
20+
// This isn't possible via POSIX or Windows, but we should ensure no one
21+
// ever does such a thing.
22+
assert!(!filename.as_str().as_bytes().contains(&0));
23+
1924
let loc_details = ecx.tcx.sess.opts.unstable_opts.location_detail;
2025
// This can fail if rustc runs out of memory right here. Trying to emit an error would be
2126
// pointless, since that would require allocating more memory than these short strings.
2227
let file = if loc_details.file {
23-
ecx.allocate_str_dedup(filename.as_str()).unwrap()
28+
let filename_with_nul = filename.as_str().to_owned() + "\0";
29+
ecx.allocate_byte_slice_dedup(filename_with_nul.as_bytes()).unwrap()
2430
} else {
25-
ecx.allocate_str_dedup("<redacted>").unwrap()
31+
ecx.allocate_byte_slice_dedup("<redacted>\0".as_bytes()).unwrap()
2632
};
2733
let file = file.map_provenance(CtfeProvenance::as_immutable);
2834
let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) };

library/core/src/panic/location.rs

+45-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[cfg(not(bootstrap))]
2+
use crate::ffi::CStr;
13
use crate::fmt;
24

35
/// A struct containing information about the location of a panic.
@@ -32,7 +34,16 @@ use crate::fmt;
3234
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
3335
#[stable(feature = "panic_hooks", since = "1.10.0")]
3436
pub struct Location<'a> {
37+
#[cfg(bootstrap)]
3538
file: &'a str,
39+
40+
// Note: this filename will have exactly one nul byte at its end, but otherwise
41+
// it must never contain interior nul bytes. This is relied on for the conversion
42+
// to `CStr` below.
43+
//
44+
// The prefix of the string without the trailing nul byte will be a regular UTF8 `str`.
45+
#[cfg(not(bootstrap))]
46+
file_bytes_with_nul: &'a [u8],
3647
line: u32,
3748
col: u32,
3849
}
@@ -122,12 +133,43 @@ impl<'a> Location<'a> {
122133
///
123134
/// panic!("Normal panic");
124135
/// ```
136+
///
137+
/// # C/C++ compatibility
138+
///
139+
/// Although `file` returns an `&str`, the characters of `file` are guaranteed to be followed
140+
/// by a nul-terminator. This allows for greater interoperabilty with C and C++ code using
141+
/// `__FILE__` or `std::source_location::file_name`, both of which return nul-terminated
142+
/// `const char*`.
125143
#[must_use]
126144
#[stable(feature = "panic_hooks", since = "1.10.0")]
127145
#[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
128-
#[inline]
129146
pub const fn file(&self) -> &str {
130-
self.file
147+
#[cfg(bootstrap)]
148+
{
149+
self.file
150+
}
151+
152+
#[cfg(not(bootstrap))]
153+
{
154+
let str_len = self.file_bytes_with_nul.len() - 1;
155+
// SAFETY: `file_bytes_with_nul` without the trailing nul byte is guaranteed to be
156+
// valid UTF8.
157+
unsafe { crate::str::from_raw_parts(self.file_bytes_with_nul.as_ptr(), str_len) }
158+
}
159+
}
160+
161+
/// Returns the name of the source file as a nul-terminated `CStr`.
162+
///
163+
/// This is useful for interop with APIs that expect C/C++ `__FILE__` or
164+
/// `std::source_location::file_name`, both of which return a nul-terminated `const char*`.
165+
#[cfg(not(bootstrap))]
166+
#[must_use]
167+
#[unstable(feature = "file_cstr", issue = "none")]
168+
#[inline]
169+
pub const fn file_cstr(&self) -> &CStr {
170+
// SAFETY: `file_bytes_with_nul` is guaranteed to have a trailing nul byte and no
171+
// interior nul bytes.
172+
unsafe { CStr::from_bytes_with_nul_unchecked(self.file_bytes_with_nul) }
131173
}
132174

133175
/// Returns the line number from which the panic originated.
@@ -181,22 +223,10 @@ impl<'a> Location<'a> {
181223
}
182224
}
183225

184-
#[unstable(
185-
feature = "panic_internals",
186-
reason = "internal details of the implementation of the `panic!` and related macros",
187-
issue = "none"
188-
)]
189-
impl<'a> Location<'a> {
190-
#[doc(hidden)]
191-
pub const fn internal_constructor(file: &'a str, line: u32, col: u32) -> Self {
192-
Location { file, line, col }
193-
}
194-
}
195-
196226
#[stable(feature = "panic_hook_display", since = "1.26.0")]
197227
impl fmt::Display for Location<'_> {
198228
#[inline]
199229
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
200-
write!(formatter, "{}:{}:{}", self.file, self.line, self.col)
230+
write!(formatter, "{}:{}:{}", self.file(), self.line, self.col)
201231
}
202232
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@ run-pass
2+
#![feature(file_cstr)]
3+
4+
#[track_caller]
5+
const fn assert_file_has_trailing_zero() {
6+
let caller = core::panic::Location::caller();
7+
let file_str = caller.file();
8+
let file_cstr = caller.file_cstr();
9+
if file_str.len() != file_cstr.count_bytes() {
10+
panic!("mismatched lengths");
11+
}
12+
let trailing_byte: core::ffi::c_char = unsafe {
13+
*file_cstr.as_ptr().offset(file_cstr.count_bytes() as _)
14+
};
15+
if trailing_byte != 0 {
16+
panic!("trailing byte was nonzero")
17+
}
18+
}
19+
20+
#[allow(dead_code)]
21+
const _: () = assert_file_has_trailing_zero();
22+
23+
fn main() {
24+
assert_file_has_trailing_zero();
25+
}

0 commit comments

Comments
 (0)