@@ -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`.
6062enum 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
0 commit comments