@@ -8,6 +8,7 @@ use std::assert_matches;
88
99use rustc_abi:: { FieldIdx , HasDataLayout , Size , VariantIdx } ;
1010use rustc_apfloat:: ieee:: { Double , Half , Quad , Single } ;
11+ use rustc_ast:: { IntTy , UintTy } ;
1112use rustc_middle:: mir:: interpret:: { CTFE_ALLOC_SALT , read_target_uint, write_target_uint} ;
1213use rustc_middle:: mir:: { self , BinOp , ConstValue , NonDivergingIntrinsic } ;
1314use rustc_middle:: ty:: layout:: TyAndLayout ;
@@ -21,9 +22,9 @@ use super::util::ensure_monomorphic_enough;
2122use super :: {
2223 AllocId , CheckInAllocMsg , ImmTy , InterpCx , InterpResult , Machine , OpTy , PlaceTy , Pointer ,
2324 PointerArithmetic , Projectable , Provenance , Scalar , err_ub_format, err_unsup_format, interp_ok,
24- throw_inval, throw_ub, throw_ub_format, throw_unsup_format ,
25+ throw_inval, throw_ub, throw_ub_format,
2526} ;
26- use crate :: interpret:: Writeable ;
27+ use crate :: interpret:: { MPlaceTy , Writeable } ;
2728
2829#[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
2930enum MulAddType {
@@ -56,6 +57,14 @@ pub(crate) enum MinMax {
5657 MaximumNumberNsz ,
5758}
5859
60+ enum VarArgCompatible {
61+ Identical ,
62+ Incompatible ,
63+ CastSignedToUnsigned ( IntTy ) ,
64+ CastUnsignedToSigned ( UintTy ) ,
65+ ValidPtrCast ,
66+ }
67+
5968/// Directly returns an `Allocation` containing an absolute path representation of the given type.
6069pub ( crate ) fn alloc_type_name < ' tcx > ( tcx : TyCtxt < ' tcx > , ty : Ty < ' tcx > ) -> ( AllocId , u64 ) {
6170 let path = crate :: util:: type_name ( tcx, ty) ;
@@ -739,18 +748,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
739748 throw_ub ! ( VaArgOutOfBounds ) ;
740749 } ;
741750
742- // NOTE: In C some type conversions are allowed (e.g. casting between signed and
743- // unsigned integers). For now we require c-variadic arguments to be read with the
744- // exact type they were passed as.
745- if arg_mplace. layout . ty != dest. layout . ty {
746- throw_unsup_format ! (
747- "va_arg type mismatch: requested `{}`, but next argument is `{}`" ,
748- dest. layout. ty,
749- arg_mplace. layout. ty
750- ) ;
751- }
752- // Copy the argument.
753- self . copy_op ( & arg_mplace, dest) ?;
751+ // Error when the caller's argument is not c-variadic compatible with the type
752+ // requested by the callee.
753+ self . validate_c_variadic_compatible ( & arg_mplace, dest. layout ) ?;
754+
755+ // Copy the argument, allowing a transmute and relying on the compatibility check
756+ // rejecting conversions between types of different size.
757+ self . copy_op_allow_transmute ( & arg_mplace, dest) ?;
754758
755759 // Update the VaList pointer.
756760 let new_key = self . va_list_ptr ( varargs) ;
@@ -766,6 +770,149 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
766770 interp_ok ( true )
767771 }
768772
773+ /// Validate whether the value and type passed by the caller are compatible with the type
774+ /// requested by the callee. Based on section 7.16.1.1 of the C23 specification.
775+ ///
776+ /// The caller requesting a value of a type is valid when that type is compatible with the type
777+ /// provided by the caller (see `validate_c_variadic_compatible_ty`) and, if both types are
778+ /// integers of different signedness, the passed value must be representable in both types.
779+ fn validate_c_variadic_compatible (
780+ & mut self ,
781+ arg_mplace : & MPlaceTy < ' tcx , M :: Provenance > ,
782+ callee_type : TyAndLayout < ' tcx > ,
783+ ) -> InterpResult < ' tcx > {
784+ // Identical types are clearly compatible.
785+ if arg_mplace. layout . ty == callee_type. ty {
786+ return interp_ok ( ( ) ) ;
787+ }
788+
789+ // Types of different sizes can never be compatible.
790+ if arg_mplace. layout . size != callee_type. size {
791+ throw_ub_format ! (
792+ "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}" ,
809+ )
810+ }
811+
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 ( ( ) ) ,
815+ VarArgCompatible :: Incompatible => throw_ub_format ! (
816+ "va_arg type mismatch: requested `{}` is incompatible with next argument of type `{}`" ,
817+ callee_type. ty,
818+ arg_mplace. layout. ty,
819+ ) ,
820+ VarArgCompatible :: CastSignedToUnsigned ( int_ty) => {
821+ // Check that the value can be represented in the target type.
822+ let callee_ty = callee_type. ty ;
823+ 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+ } ;
857+
858+ interp_ok ( ( ) )
859+ }
860+ }
861+ }
862+
863+ /// Check whether the caller and callee type are compatible for c-variadic calls. Further
864+ /// validation of the argument value may be needed to detect all UB.
865+ ///
866+ /// Types `T` and `U` are compatible when:
867+ ///
868+ /// - `T` and `U` are the same type.
869+ /// - `T` is a signed integer and `U` the corresponding unsigned integer,
870+ /// - `T` is an unsigned integer and `U` the corresponding signed integer,
871+ /// - `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`.
873+ /// - `T` is a pointer to `i8` or `u8` and `U` is a pointer to `c_void`.
874+ fn validate_c_variadic_compatible_ty (
875+ & mut self ,
876+ caller_type : Ty < ' tcx > ,
877+ callee_type : Ty < ' tcx > ,
878+ ) -> VarArgCompatible {
879+ if caller_type == callee_type {
880+ return VarArgCompatible :: Identical ;
881+ }
882+
883+ // The signedness of c_char is irrelevant here.
884+ let is_c_char = |ty : Ty < ' _ > | matches ! ( ty. kind( ) , ty:: Uint ( UintTy :: U8 ) | ty:: Int ( IntTy :: I8 ) ) ;
885+
886+ match ( caller_type. kind ( ) , callee_type. kind ( ) ) {
887+ ( 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.
890+ if caller_target_ty. is_c_void ( self . tcx . tcx ) && is_c_char ( * callee_target_ty) {
891+ return VarArgCompatible :: ValidPtrCast ;
892+ }
893+ if callee_target_ty. is_c_void ( self . tcx . tcx ) && is_c_char ( * caller_target_ty) {
894+ return VarArgCompatible :: ValidPtrCast ;
895+ }
896+
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 ,
902+ }
903+ }
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)
907+ }
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)
911+ }
912+ _ => VarArgCompatible :: Incompatible ,
913+ }
914+ }
915+
769916 pub ( super ) fn eval_nondiverging_intrinsic (
770917 & mut self ,
771918 intrinsic : & NonDivergingIntrinsic < ' tcx > ,
0 commit comments