Skip to content

Commit 9e64ee5

Browse files
committed
impove integer check
1 parent 6aedccd commit 9e64ee5

4 files changed

Lines changed: 283 additions & 131 deletions

File tree

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 65 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,19 @@ pub(crate) enum MinMax {
5757
MaximumNumberNsz,
5858
}
5959

60+
/// Whether two types `T` and `U` are compatible when a value of type `T` is passed as a c-variadic
61+
/// argument and read as a value of type `U`.
6062
enum VarArgCompatible {
61-
Identical,
63+
/// `T` and `U` are compatible, e.g.
64+
///
65+
/// - They're the same type.
66+
/// - One is `usize`/`isize`, the other same-sized fixed-width integer on that target.
67+
/// - They are compatible pointer types.
68+
Compatible,
69+
/// `T` and `U` are definitely not compatible.
6270
Incompatible,
63-
CastSignedToUnsigned(IntTy),
64-
CastUnsignedToSigned(UintTy),
65-
ValidPtrCast,
71+
/// `T` and `U` are corresponding signed and unsigned integer types.
72+
CastIntTo { source_is_signed: bool },
6673
}
6774

6875
/// Directly returns an `Allocation` containing an absolute path representation of the given type.
@@ -781,79 +788,44 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
781788
arg_mplace: &MPlaceTy<'tcx, M::Provenance>,
782789
callee_type: TyAndLayout<'tcx>,
783790
) -> InterpResult<'tcx> {
791+
let callee_ty = callee_type.ty;
792+
let caller_ty = arg_mplace.layout.ty;
793+
784794
// Identical types are clearly compatible.
785-
if arg_mplace.layout.ty == callee_type.ty {
795+
if caller_ty == callee_ty {
786796
return interp_ok(());
787797
}
788798

789799
// Types of different sizes can never be compatible.
790800
if arg_mplace.layout.size != callee_type.size {
791801
throw_ub_format!(
792802
"va_arg type mismatch: requested `{}` is incompatible with next argument of type `{}`",
793-
callee_type.ty,
794-
arg_mplace.layout.ty,
795-
)
796-
}
797-
798-
fn validate_cast<'tcx, T, U>(x: T, target_ty: Ty<'tcx>) -> InterpResult<'tcx>
799-
where
800-
T: std::fmt::Display + Copy,
801-
U: std::fmt::Display + TryFrom<T>,
802-
{
803-
if U::try_from(x).is_ok() {
804-
return interp_ok(());
805-
}
806-
807-
throw_ub_format!(
808-
"va_arg value mismatch: value `{x}` cannot be represented by type {target_ty}",
803+
callee_ty,
804+
caller_ty,
809805
)
810806
}
811807

812-
match self.validate_c_variadic_compatible_ty(arg_mplace.layout.ty, callee_type.ty) {
813-
VarArgCompatible::Identical => interp_ok(()),
814-
VarArgCompatible::ValidPtrCast => interp_ok(()),
808+
match self.validate_c_variadic_compatible_ty(arg_mplace.layout.ty, callee_type.ty)? {
809+
VarArgCompatible::Compatible => interp_ok(()),
815810
VarArgCompatible::Incompatible => throw_ub_format!(
816811
"va_arg type mismatch: requested `{}` is incompatible with next argument of type `{}`",
817-
callee_type.ty,
818-
arg_mplace.layout.ty,
812+
callee_ty,
813+
caller_ty,
819814
),
820-
VarArgCompatible::CastSignedToUnsigned(int_ty) => {
815+
VarArgCompatible::CastIntTo { source_is_signed } => {
821816
// Check that the value can be represented in the target type.
822-
let callee_ty = callee_type.ty;
817+
let size = arg_mplace.layout.size;
823818
let scalar = self.read_scalar(arg_mplace)?;
824-
match int_ty {
825-
IntTy::Isize => match self.data_layout().pointer_size().bits() {
826-
16 => validate_cast::<i16, u16>(scalar.to_i16()?, callee_ty)?,
827-
32 => validate_cast::<i32, u32>(scalar.to_i32()?, callee_ty)?,
828-
64 => validate_cast::<i64, u64>(scalar.to_i64()?, callee_ty)?,
829-
_ => unreachable!("unexpected pointer size"),
830-
},
831-
IntTy::I8 => validate_cast::<i8, u8>(scalar.to_i8()?, callee_ty)?,
832-
IntTy::I16 => validate_cast::<i16, u16>(scalar.to_i16()?, callee_ty)?,
833-
IntTy::I32 => validate_cast::<i32, u32>(scalar.to_i32()?, callee_ty)?,
834-
IntTy::I64 => validate_cast::<i64, u64>(scalar.to_i64()?, callee_ty)?,
835-
IntTy::I128 => validate_cast::<i128, u128>(scalar.to_i128()?, callee_ty)?,
836-
};
837-
838-
interp_ok(())
839-
}
840-
VarArgCompatible::CastUnsignedToSigned(uint_ty) => {
841-
// Check that the value can be represented in the target type.
842-
let callee_ty = callee_type.ty;
843-
let scalar = self.read_scalar(arg_mplace)?;
844-
match uint_ty {
845-
UintTy::Usize => match self.data_layout().pointer_size().bits() {
846-
16 => validate_cast::<u16, i16>(scalar.to_u16()?, callee_ty)?,
847-
32 => validate_cast::<u32, i32>(scalar.to_u32()?, callee_ty)?,
848-
64 => validate_cast::<u64, i64>(scalar.to_u64()?, callee_ty)?,
849-
_ => unreachable!("unexpected pointer size"),
850-
},
851-
UintTy::U8 => validate_cast::<u8, i8>(scalar.to_u8()?, callee_ty)?,
852-
UintTy::U16 => validate_cast::<u16, i16>(scalar.to_u16()?, callee_ty)?,
853-
UintTy::U32 => validate_cast::<u32, i32>(scalar.to_u32()?, callee_ty)?,
854-
UintTy::U64 => validate_cast::<u64, i64>(scalar.to_u64()?, callee_ty)?,
855-
UintTy::U128 => validate_cast::<u128, i128>(scalar.to_u128()?, callee_ty)?,
856-
};
819+
if scalar.to_int(size)? < 0 {
820+
throw_ub_format!(
821+
"va_arg value mismatch: value `{value}_{caller_ty}` cannot be represented by type {callee_ty}",
822+
value = if source_is_signed {
823+
scalar.to_int(size)?.to_string()
824+
} else {
825+
scalar.to_uint(size)?.to_string()
826+
}
827+
)
828+
}
857829

858830
interp_ok(())
859831
}
@@ -869,47 +841,59 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
869841
/// - `T` is a signed integer and `U` the corresponding unsigned integer,
870842
/// - `T` is an unsigned integer and `U` the corresponding signed integer,
871843
/// - `T` and `U` are both pointers, and their target types are compatible.
872-
/// - `T` is a pointer to `c_void` and `U` is a pointer to `i8` or `u8`.
844+
/// - `T` is a pointer to `c_void` and `U` is a pointer to `i8` or `u8` (i.e., any "character type").
873845
/// - `T` is a pointer to `i8` or `u8` and `U` is a pointer to `c_void`.
874846
fn validate_c_variadic_compatible_ty(
875847
&mut self,
876848
caller_type: Ty<'tcx>,
877849
callee_type: Ty<'tcx>,
878-
) -> VarArgCompatible {
850+
) -> InterpResult<'tcx, VarArgCompatible> {
879851
if caller_type == callee_type {
880-
return VarArgCompatible::Identical;
852+
return interp_ok(VarArgCompatible::Compatible);
853+
}
854+
855+
if self.layout_of(caller_type)?.size != self.layout_of(callee_type)?.size {
856+
return interp_ok(VarArgCompatible::Incompatible);
881857
}
882858

883-
// The signedness of c_char is irrelevant here.
859+
// Any character type (`char`, `unsigned char` and `signed char`) is compatible with
860+
// `void*`, so the signedness of `c_char` is irrelevant here.
884861
let is_c_char = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(UintTy::U8) | ty::Int(IntTy::I8));
885862

886863
match (caller_type.kind(), callee_type.kind()) {
887864
(ty::RawPtr(caller_target_ty, _), ty::RawPtr(callee_target_ty, _)) => {
888-
// Accept the cast if one type is pointer to qualified or unqualified void,
889-
// and the other is a pointer to a qualified or unqualified character type.
865+
// In C types can be qualified by a combination of `const`, `volatile` and
866+
// `restrict`. These properties are irrelevant for the ABI, and don't have an
867+
// equivalent in rust.
868+
869+
// Accept the cast if one type is pointer to void, and the other is a pointer to
870+
// a character type (`char`, `unsigned char` and `signed char`).
890871
if caller_target_ty.is_c_void(self.tcx.tcx) && is_c_char(*callee_target_ty) {
891-
return VarArgCompatible::ValidPtrCast;
872+
return interp_ok(VarArgCompatible::Compatible);
892873
}
893874
if callee_target_ty.is_c_void(self.tcx.tcx) && is_c_char(*caller_target_ty) {
894-
return VarArgCompatible::ValidPtrCast;
875+
return interp_ok(VarArgCompatible::Compatible);
895876
}
896877

897-
// Accept the cast if both types are pointers to qualified or unqualified versions
898-
// of compatible types.
899-
match self.validate_c_variadic_compatible_ty(*caller_target_ty, *callee_target_ty) {
900-
VarArgCompatible::Incompatible => VarArgCompatible::Incompatible,
901-
_ => VarArgCompatible::ValidPtrCast,
878+
// Accept the cast if both types are pointers to compatible types.
879+
match self
880+
.validate_c_variadic_compatible_ty(*caller_target_ty, *callee_target_ty)?
881+
{
882+
VarArgCompatible::Incompatible => interp_ok(VarArgCompatible::Incompatible),
883+
_ => interp_ok(VarArgCompatible::Compatible),
902884
}
903885
}
904-
(ty::Int(int_ty), ty::Uint(uint_ty)) => {
905-
assert_eq!(int_ty.bit_width(), uint_ty.bit_width());
906-
VarArgCompatible::CastSignedToUnsigned(*int_ty)
886+
(ty::Int(_), ty::Uint(_)) => {
887+
interp_ok(VarArgCompatible::CastIntTo { source_is_signed: true })
888+
}
889+
(ty::Uint(_), ty::Int(_)) => {
890+
interp_ok(VarArgCompatible::CastIntTo { source_is_signed: false })
907891
}
908-
(ty::Uint(uint_ty), ty::Int(int_ty)) => {
909-
assert_eq!(uint_ty.bit_width(), int_ty.bit_width());
910-
VarArgCompatible::CastUnsignedToSigned(*uint_ty)
892+
(ty::Int(_), ty::Int(_)) | (ty::Uint(_), ty::Uint(_)) => {
893+
// E.g. cast between `usize` and `u64` on a 64-bit platform.
894+
interp_ok(VarArgCompatible::Compatible)
911895
}
912-
_ => VarArgCompatible::Incompatible,
896+
_ => interp_ok(VarArgCompatible::Incompatible),
913897
}
914898
}
915899

library/core/src/ffi/va_list.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ impl<'f> VaList<'f> {
406406
/// - `T` is a pointer to [`i8]` or [`u8`] and `U` is a pointer to [`c_void`].
407407
///
408408
/// When `T` and `U` are both integer types, the value that the caller passes must be
409-
/// representable by `U`.
409+
/// representable by `T` and `U`.
410410
///
411411
/// [`c_void`]: core::ffi::c_void
412412
#[inline] // Avoid codegen when not used to help backends that don't support VaList.

tests/ui/consts/const-eval/c-variadic-fail.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,36 @@ unsafe fn read_cast_numeric() {
5151
const { read_as::<u32>(1i32) };
5252
const { read_as::<i32>(1u32) };
5353

54+
type ConcreteUsize = cfg_select! {
55+
target_pointer_width = "16" => u16,
56+
target_pointer_width = "32" => u32,
57+
target_pointer_width = "64" => u64,
58+
};
59+
60+
type ConcreteIsize = cfg_select! {
61+
target_pointer_width = "16" => i16,
62+
target_pointer_width = "32" => i32,
63+
target_pointer_width = "64" => i64,
64+
};
65+
66+
const { read_as::<ConcreteUsize>(1usize) };
67+
const { read_as::<usize>(1 as ConcreteUsize) };
68+
69+
const { read_as::<ConcreteIsize>(-1isize) };
70+
const { read_as::<isize>(-1 as ConcreteIsize) };
71+
5472
const { read_as::<u32>(-1i32) };
55-
//~^ ERROR va_arg value mismatch: value `-1` cannot be represented by type u32
73+
//~^ ERROR va_arg value mismatch: value `-1_i32` cannot be represented by type u32
74+
const { read_as::<u32>(i32::MIN) };
75+
//~^ ERROR va_arg value mismatch: value `-2147483648_i32` cannot be represented by type u32
5676
const { read_as::<i32>(u32::MAX) };
57-
//~^ ERROR va_arg value mismatch: value `4294967295` cannot be represented by type i32
77+
//~^ ERROR va_arg value mismatch: value `4294967295_u32` cannot be represented by type i32
78+
const { read_as::<i32>(i32::MAX as u32 + 1) };
79+
//~^ ERROR va_arg value mismatch: value `2147483648_u32` cannot be represented by type i32
80+
const { read_as::<i64>(u64::MAX) };
81+
//~^ ERROR va_arg value mismatch: value `18446744073709551615_u64` cannot be represented by type i64
82+
const { read_as::<i64>(i64::MAX as u64 + 1) };
83+
//~^ ERROR va_arg value mismatch: value `9223372036854775808_u64` cannot be represented by type i64
5884

5985
const { read_as::<i32>(1u64) };
6086
//~^ ERROR va_arg type mismatch: requested `i32` is incompatible with next argument of type `u64`
@@ -86,6 +112,8 @@ unsafe fn read_cast_pointer() {
86112
//~^ ERROR va_arg type mismatch: requested `*const u16` is incompatible with next argument of type `*const c_void`
87113
const { read_as::<*const c_void>(std::ptr::dangling::<u16>()) };
88114
//~^ ERROR va_arg type mismatch: requested `*const c_void` is incompatible with next argument of type `*const u16`
115+
const { read_as::<*const u16>(std::ptr::dangling::<i32>()) };
116+
//~^ ERROR va_arg type mismatch: requested `*const u16` is incompatible with next argument of type `*const i32`
89117

90118
const { read_as::<*const u8>(1usize) };
91119
//~^ ERROR requested `*const u8` is incompatible with next argument of type `usize`

0 commit comments

Comments
 (0)