Skip to content

Commit 61f17d6

Browse files
SUPERCILEXAlex Saveau
andauthored
Use MaybeUninit in DecInt (#1201)
* Use MaybeUninit in DecInt * Fix DecInt::new panicking on integers greater than 64 bits and optimize out any panics in DecInt::new * Add tests so people messing with DecInt can catch UB with miri * Use new itoa --------- Co-authored-by: Alex Saveau <saveau.alexandre+tmp@gmail.com>
1 parent 41b26db commit 61f17d6

File tree

3 files changed

+76
-47
lines changed

3 files changed

+76
-47
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ rust-version = "1.63"
1717

1818
[dependencies]
1919
bitflags = { version = "2.4.0", default-features = false }
20-
itoa = { version = "1.0.1", default-features = false, optional = true }
20+
itoa = { version = "1.0.13", default-features = false, optional = true }
2121

2222
# Special dependencies used in rustc-dep-of-std mode.
2323
core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" }

src/path/dec_int.rs

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
use crate::backend::fd::{AsFd, AsRawFd};
1010
use crate::ffi::CStr;
11-
use core::fmt::Write;
11+
use core::mem::{self, MaybeUninit};
1212
use itoa::{Buffer, Integer};
1313
#[cfg(all(feature = "std", unix))]
1414
use std::os::unix::ffi::OsStrExt;
@@ -36,23 +36,36 @@ use {core::fmt, std::ffi::OsStr, std::path::Path};
3636
/// ```
3737
#[derive(Clone)]
3838
pub struct DecInt {
39-
// 20 `u8`s is enough to hold the decimal ASCII representation of any
40-
// `u64`, and we add one for a NUL terminator for `as_c_str`.
41-
buf: [u8; 20 + 1],
39+
// Enough to hold an {u,i}64 and NUL terminator.
40+
buf: [MaybeUninit<u8>; u64::MAX_STR_LEN + 1],
4241
len: usize,
4342
}
43+
const _: () = assert!(u64::MAX_STR_LEN == i64::MAX_STR_LEN);
4444

4545
impl DecInt {
4646
/// Construct a new path component from an integer.
4747
#[inline]
48-
pub fn new<Int: Integer>(i: Int) -> Self {
49-
let mut me = DecIntWriter(Self {
50-
buf: [0; 20 + 1],
51-
len: 0,
48+
pub fn new<Int: Integer + 'static>(i: Int) -> Self {
49+
let mut buf = [MaybeUninit::uninit(); 21];
50+
51+
let mut str_buf = Buffer::new();
52+
let str_buf = str_buf.format(i);
53+
assert!(
54+
str_buf.len() < buf.len(),
55+
"{str_buf}{} unsupported.",
56+
core::any::type_name::<Int>()
57+
);
58+
59+
buf[..str_buf.len()].copy_from_slice(unsafe {
60+
// SAFETY: you can always go from init to uninit
61+
mem::transmute::<&[u8], &[MaybeUninit<u8>]>(str_buf.as_bytes())
5262
});
53-
let mut buf = Buffer::new();
54-
me.write_str(buf.format(i)).unwrap();
55-
me.0
63+
buf[str_buf.len()] = MaybeUninit::new(0);
64+
65+
Self {
66+
buf,
67+
len: str_buf.len(),
68+
}
5669
}
5770

5871
/// Construct a new path component from a file descriptor.
@@ -72,44 +85,35 @@ impl DecInt {
7285
/// Return the raw byte buffer as a `&CStr`.
7386
#[inline]
7487
pub fn as_c_str(&self) -> &CStr {
75-
let bytes_with_nul = &self.buf[..=self.len];
88+
let bytes_with_nul = self.as_bytes_with_nul();
7689
debug_assert!(CStr::from_bytes_with_nul(bytes_with_nul).is_ok());
7790

7891
// SAFETY: `self.buf` holds a single decimal ASCII representation and
7992
// at least one extra NUL byte.
8093
unsafe { CStr::from_bytes_with_nul_unchecked(bytes_with_nul) }
8194
}
8295

83-
/// Return the raw byte buffer.
96+
/// Return the raw byte buffer including the NUL byte.
8497
#[inline]
85-
pub fn as_bytes(&self) -> &[u8] {
86-
&self.buf[..self.len]
98+
pub fn as_bytes_with_nul(&self) -> &[u8] {
99+
let init = &self.buf[..=self.len];
100+
// SAFETY: we're guaranteed to have initialized len+1 bytes.
101+
unsafe { mem::transmute::<&[MaybeUninit<u8>], &[u8]>(init) }
87102
}
88-
}
89-
90-
/// A wrapper around `DecInt` that implements `Write` without exposing this
91-
/// implementation to `DecInt`'s public API.
92-
struct DecIntWriter(DecInt);
93103

94-
impl core::fmt::Write for DecIntWriter {
104+
/// Return the raw byte buffer.
95105
#[inline]
96-
fn write_str(&mut self, s: &str) -> core::fmt::Result {
97-
match self.0.buf.get_mut(self.0.len..self.0.len + s.len()) {
98-
Some(slice) => {
99-
slice.copy_from_slice(s.as_bytes());
100-
self.0.len += s.len();
101-
Ok(())
102-
}
103-
None => Err(core::fmt::Error),
104-
}
106+
pub fn as_bytes(&self) -> &[u8] {
107+
let bytes = self.as_bytes_with_nul();
108+
&bytes[..bytes.len() - 1]
105109
}
106110
}
107111

108112
#[cfg(feature = "std")]
109113
impl AsRef<Path> for DecInt {
110114
#[inline]
111115
fn as_ref(&self) -> &Path {
112-
let as_os_str: &OsStr = OsStrExt::from_bytes(&self.buf[..self.len]);
116+
let as_os_str: &OsStr = OsStrExt::from_bytes(self.as_bytes());
113117
Path::new(as_os_str)
114118
}
115119
}

tests/path/dec_int.rs

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,45 @@
11
use rustix::path::DecInt;
22

3+
macro_rules! check {
4+
($i:expr) => {
5+
let i = $i;
6+
assert_eq!(DecInt::new(i).as_ref().to_str().unwrap(), i.to_string());
7+
};
8+
}
9+
310
#[test]
411
fn test_dec_int() {
5-
assert_eq!(DecInt::new(0).as_ref().to_str().unwrap(), "0");
6-
assert_eq!(DecInt::new(-1).as_ref().to_str().unwrap(), "-1");
7-
assert_eq!(DecInt::new(789).as_ref().to_str().unwrap(), "789");
8-
assert_eq!(
9-
DecInt::new(i64::MIN).as_ref().to_str().unwrap(),
10-
i64::MIN.to_string()
11-
);
12-
assert_eq!(
13-
DecInt::new(i64::MAX).as_ref().to_str().unwrap(),
14-
i64::MAX.to_string()
15-
);
16-
assert_eq!(
17-
DecInt::new(u64::MAX).as_ref().to_str().unwrap(),
18-
u64::MAX.to_string()
19-
);
12+
check!(0);
13+
check!(-1);
14+
check!(789);
15+
16+
check!(u8::MAX);
17+
check!(i8::MIN);
18+
check!(u16::MAX);
19+
check!(i16::MIN);
20+
check!(u32::MAX);
21+
check!(i32::MIN);
22+
check!(u64::MAX);
23+
check!(i64::MIN);
24+
#[cfg(any(
25+
target_pointer_width = "16",
26+
target_pointer_width = "32",
27+
target_pointer_width = "64"
28+
))]
29+
{
30+
check!(usize::MAX);
31+
check!(isize::MIN);
32+
}
33+
}
34+
35+
#[test]
36+
#[should_panic]
37+
fn test_unsupported_max_u128_dec_int() {
38+
check!(u128::MAX);
39+
}
40+
41+
#[test]
42+
#[should_panic]
43+
fn test_unsupported_min_u128_dec_int() {
44+
check!(i128::MIN);
2045
}

0 commit comments

Comments
 (0)