Skip to content

Commit b9c6b33

Browse files
authored
Rollup merge of #141341 - folkertdev:limit-VaArgSafe-impls, r=workingjubilee
limit impls of `VaArgSafe` to just types that are actually safe tracking issue: #44930 Retrieving 8- or 16-bit integer arguments from a `VaList` is not safe, because such types are subject to upcasting. See #61275 (comment) for more detail. This PR also makes the instances of `VaArgSafe` visible in the documentation, and uses a private sealed trait to make sure users cannot create additional impls of `VaArgSafe`, which would almost certainly cause UB. r? `@workingjubilee`
2 parents b5edec2 + d8a22a2 commit b9c6b33

File tree

4 files changed

+52
-34
lines changed

4 files changed

+52
-34
lines changed

library/core/src/ffi/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub mod c_str;
2828
issue = "44930",
2929
reason = "the `c_variadic` feature has not been properly tested on all supported platforms"
3030
)]
31-
pub use self::va_list::{VaList, VaListImpl};
31+
pub use self::va_list::{VaArgSafe, VaList, VaListImpl};
3232

3333
#[unstable(
3434
feature = "c_variadic",

library/core/src/ffi/va_list.rs

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -223,39 +223,57 @@ impl<'a, 'f: 'a> DerefMut for VaList<'a, 'f> {
223223
}
224224
}
225225

226-
// The VaArgSafe trait needs to be used in public interfaces, however, the trait
227-
// itself must not be allowed to be used outside this module. Allowing users to
228-
// implement the trait for a new type (thereby allowing the va_arg intrinsic to
229-
// be used on a new type) is likely to cause undefined behavior.
230-
//
231-
// FIXME(dlrobertson): In order to use the VaArgSafe trait in a public interface
232-
// but also ensure it cannot be used elsewhere, the trait needs to be public
233-
// within a private module. Once RFC 2145 has been implemented look into
234-
// improving this.
235-
mod sealed_trait {
236-
/// Trait which permits the allowed types to be used with [super::VaListImpl::arg].
237-
pub unsafe trait VaArgSafe {}
238-
}
226+
mod sealed {
227+
pub trait Sealed {}
239228

240-
macro_rules! impl_va_arg_safe {
241-
($($t:ty),+) => {
242-
$(
243-
unsafe impl sealed_trait::VaArgSafe for $t {}
244-
)+
245-
}
229+
impl Sealed for i32 {}
230+
impl Sealed for i64 {}
231+
impl Sealed for isize {}
232+
233+
impl Sealed for u32 {}
234+
impl Sealed for u64 {}
235+
impl Sealed for usize {}
236+
237+
impl Sealed for f64 {}
238+
239+
impl<T> Sealed for *mut T {}
240+
impl<T> Sealed for *const T {}
246241
}
247242

248-
impl_va_arg_safe! {i8, i16, i32, i64, usize}
249-
impl_va_arg_safe! {u8, u16, u32, u64, isize}
250-
impl_va_arg_safe! {f64}
243+
/// Trait which permits the allowed types to be used with [`VaListImpl::arg`].
244+
///
245+
/// # Safety
246+
///
247+
/// This trait must only be implemented for types that C passes as varargs without implicit promotion.
248+
///
249+
/// In C varargs, integers smaller than [`c_int`] and floats smaller than [`c_double`]
250+
/// are implicitly promoted to [`c_int`] and [`c_double`] respectively. Implementing this trait for
251+
/// types that are subject to this promotion rule is invalid.
252+
///
253+
/// [`c_int`]: core::ffi::c_int
254+
/// [`c_double`]: core::ffi::c_double
255+
pub unsafe trait VaArgSafe: sealed::Sealed {}
256+
257+
// i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
258+
unsafe impl VaArgSafe for i32 {}
259+
unsafe impl VaArgSafe for i64 {}
260+
unsafe impl VaArgSafe for isize {}
261+
262+
// u8 and u16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
263+
unsafe impl VaArgSafe for u32 {}
264+
unsafe impl VaArgSafe for u64 {}
265+
unsafe impl VaArgSafe for usize {}
266+
267+
// f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`.
268+
unsafe impl VaArgSafe for f64 {}
251269

252-
unsafe impl<T> sealed_trait::VaArgSafe for *mut T {}
253-
unsafe impl<T> sealed_trait::VaArgSafe for *const T {}
270+
unsafe impl<T> VaArgSafe for *mut T {}
271+
unsafe impl<T> VaArgSafe for *const T {}
254272

255273
impl<'f> VaListImpl<'f> {
256274
/// Advance to the next arg.
257275
#[inline]
258-
pub unsafe fn arg<T: sealed_trait::VaArgSafe>(&mut self) -> T {
276+
pub unsafe fn arg<T: VaArgSafe>(&mut self) -> T {
259277
// SAFETY: the caller must uphold the safety contract for `va_arg`.
260278
unsafe { va_arg(self) }
261279
}
@@ -317,4 +335,4 @@ unsafe fn va_copy<'f>(dest: *mut VaListImpl<'f>, src: &VaListImpl<'f>);
317335
/// argument `ap` points to.
318336
#[rustc_intrinsic]
319337
#[rustc_nounwind]
320-
unsafe fn va_arg<T: sealed_trait::VaArgSafe>(ap: &mut VaListImpl<'_>) -> T;
338+
unsafe fn va_arg<T: VaArgSafe>(ap: &mut VaListImpl<'_>) -> T;

library/std/src/ffi/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ pub use core::ffi::c_void;
172172
all supported platforms",
173173
issue = "44930"
174174
)]
175-
pub use core::ffi::{VaList, VaListImpl};
175+
pub use core::ffi::{VaArgSafe, VaList, VaListImpl};
176176
#[stable(feature = "core_ffi_c", since = "1.64.0")]
177177
pub use core::ffi::{
178178
c_char, c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint,

tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ pub unsafe extern "C" fn check_list_0(mut ap: VaList) -> usize {
3030
#[no_mangle]
3131
pub unsafe extern "C" fn check_list_1(mut ap: VaList) -> usize {
3232
continue_if!(ap.arg::<c_int>() == -1);
33-
continue_if!(ap.arg::<c_char>() == 'A' as c_char);
34-
continue_if!(ap.arg::<c_char>() == '4' as c_char);
35-
continue_if!(ap.arg::<c_char>() == ';' as c_char);
33+
continue_if!(ap.arg::<c_int>() == 'A' as c_int);
34+
continue_if!(ap.arg::<c_int>() == '4' as c_int);
35+
continue_if!(ap.arg::<c_int>() == ';' as c_int);
3636
continue_if!(ap.arg::<c_int>() == 0x32);
3737
continue_if!(ap.arg::<c_int>() == 0x10000001);
3838
continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Valid!"));
@@ -43,7 +43,7 @@ pub unsafe extern "C" fn check_list_1(mut ap: VaList) -> usize {
4343
pub unsafe extern "C" fn check_list_2(mut ap: VaList) -> usize {
4444
continue_if!(ap.arg::<c_double>().floor() == 3.14f64.floor());
4545
continue_if!(ap.arg::<c_long>() == 12);
46-
continue_if!(ap.arg::<c_char>() == 'a' as c_char);
46+
continue_if!(ap.arg::<c_int>() == 'a' as c_int);
4747
continue_if!(ap.arg::<c_double>().floor() == 6.18f64.floor());
4848
continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Hello"));
4949
continue_if!(ap.arg::<c_int>() == 42);
@@ -55,7 +55,7 @@ pub unsafe extern "C" fn check_list_2(mut ap: VaList) -> usize {
5555
pub unsafe extern "C" fn check_list_copy_0(mut ap: VaList) -> usize {
5656
continue_if!(ap.arg::<c_double>().floor() == 6.28f64.floor());
5757
continue_if!(ap.arg::<c_int>() == 16);
58-
continue_if!(ap.arg::<c_char>() == 'A' as c_char);
58+
continue_if!(ap.arg::<c_int>() == 'A' as c_int);
5959
continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Skip Me!"));
6060
ap.with_copy(
6161
|mut ap| {
@@ -75,7 +75,7 @@ pub unsafe extern "C" fn check_varargs_0(_: c_int, mut ap: ...) -> usize {
7575
pub unsafe extern "C" fn check_varargs_1(_: c_int, mut ap: ...) -> usize {
7676
continue_if!(ap.arg::<c_double>().floor() == 3.14f64.floor());
7777
continue_if!(ap.arg::<c_long>() == 12);
78-
continue_if!(ap.arg::<c_char>() == 'A' as c_char);
78+
continue_if!(ap.arg::<c_int>() == 'A' as c_int);
7979
continue_if!(ap.arg::<c_longlong>() == 1);
8080
0
8181
}

0 commit comments

Comments
 (0)