Skip to content

Commit c360e21

Browse files
committed
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: rust-lang/libs-team#466 Tracking issue: #141727
2 parents 425e142 + b541f93 commit c360e21

File tree

3 files changed

+63
-24
lines changed

3 files changed

+63
-24
lines changed

compiler/rustc_const_eval/src/util/caller_location.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@ fn alloc_caller_location<'tcx>(
1515
line: u32,
1616
col: u32,
1717
) -> MPlaceTy<'tcx> {
18+
// Ensure that the filename itself does not contain nul bytes.
19+
// This isn't possible via POSIX or Windows, but we should ensure no one
20+
// ever does such a thing.
21+
assert!(!filename.as_str().as_bytes().contains(&0));
22+
1823
let loc_details = ecx.tcx.sess.opts.unstable_opts.location_detail;
19-
// This can fail if rustc runs out of memory right here. Trying to emit an error would be
20-
// pointless, since that would require allocating more memory than these short strings.
21-
let file = if loc_details.file {
22-
ecx.allocate_str_dedup(filename.as_str()).unwrap()
23-
} else {
24-
ecx.allocate_str_dedup("<redacted>").unwrap()
24+
let file_wide_ptr = {
25+
let filename = if loc_details.file { filename.as_str() } else { "<redacted>" };
26+
let filename_with_nul = filename.to_owned() + "\0";
27+
// This can fail if rustc runs out of memory right here. Trying to emit an error would be
28+
// pointless, since that would require allocating more memory than these short strings.
29+
let file_ptr = ecx.allocate_bytes_dedup(filename_with_nul.as_bytes()).unwrap();
30+
Immediate::new_slice(file_ptr.into(), filename_with_nul.len().try_into().unwrap(), ecx)
2531
};
26-
let file = file.map_provenance(CtfeProvenance::as_immutable);
2732
let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) };
2833
let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) };
2934

@@ -36,7 +41,7 @@ fn alloc_caller_location<'tcx>(
3641
let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap();
3742

3843
// Initialize fields.
39-
ecx.write_immediate(file.to_ref(ecx), &ecx.project_field(&location, 0).unwrap())
44+
ecx.write_immediate(file_wide_ptr, &ecx.project_field(&location, 0).unwrap())
4045
.expect("writing to memory we just allocated cannot fail");
4146
ecx.write_scalar(line, &ecx.project_field(&location, 1).unwrap())
4247
.expect("writing to memory we just allocated cannot fail");

library/core/src/panic/location.rs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::ffi::CStr;
12
use crate::fmt;
23

34
/// A struct containing information about the location of a panic.
@@ -32,7 +33,12 @@ use crate::fmt;
3233
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
3334
#[stable(feature = "panic_hooks", since = "1.10.0")]
3435
pub struct Location<'a> {
35-
file: &'a str,
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],
3642
line: u32,
3743
col: u32,
3844
}
@@ -125,9 +131,24 @@ impl<'a> Location<'a> {
125131
#[must_use]
126132
#[stable(feature = "panic_hooks", since = "1.10.0")]
127133
#[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
128-
#[inline]
129134
pub const fn file(&self) -> &str {
130-
self.file
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) }
131152
}
132153

133154
/// Returns the line number from which the panic originated.
@@ -181,22 +202,10 @@ impl<'a> Location<'a> {
181202
}
182203
}
183204

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-
196205
#[stable(feature = "panic_hook_display", since = "1.26.0")]
197206
impl fmt::Display for Location<'_> {
198207
#[inline]
199208
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
200-
write!(formatter, "{}:{}:{}", self.file, self.line, self.col)
209+
write!(formatter, "{}:{}:{}", self.file(), self.line, self.col)
201210
}
202211
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@ run-pass
2+
#![feature(file_with_nul)]
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_with_nul = caller.file_with_nul();
9+
if file_str.len() != file_with_nul.count_bytes() {
10+
panic!("mismatched lengths");
11+
}
12+
let trailing_byte: core::ffi::c_char = unsafe {
13+
*file_with_nul.as_ptr().offset(file_with_nul.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)