@@ -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,22 @@ pub(crate) enum MinMax {
5657 MaximumNumberNsz ,
5758}
5859
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`.
62+ enum VarArgCompatible {
63+ /// `T` and `U` are compatible, e.g.
64+ ///
65+ /// - They're the same type.
66+ /// - One is `usize`/`isize`, the other an integer type of the same width
67+ /// and sign on the current target.
68+ /// - They are compatible pointer types (see the exact rules below).
69+ Compatible ,
70+ /// `T` and `U` are definitely not compatible.
71+ Incompatible ,
72+ /// `T` and `U` are corresponding signed and unsigned integer types.
73+ CastIntTo { source_is_signed : bool } ,
74+ }
75+
5976/// Directly returns an `Allocation` containing an absolute path representation of the given type.
6077pub ( crate ) fn alloc_type_name < ' tcx > ( tcx : TyCtxt < ' tcx > , ty : Ty < ' tcx > ) -> ( AllocId , u64 ) {
6178 let path = crate :: util:: type_name ( tcx, ty) ;
@@ -739,18 +756,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
739756 throw_ub ! ( VaArgOutOfBounds ) ;
740757 } ;
741758
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) ?;
759+ // Error when the caller's argument is not c-variadic compatible with the type
760+ // requested by the callee.
761+ self . validate_c_variadic_argument ( & arg_mplace, dest. layout ) ?;
762+
763+ // Copy the argument, allowing a transmute and relying on the compatibility check
764+ // rejecting conversions between types of different size.
765+ self . copy_op_allow_transmute ( & arg_mplace, dest) ?;
754766
755767 // Update the VaList pointer.
756768 let new_key = self . va_list_ptr ( varargs) ;
@@ -766,6 +778,130 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
766778 interp_ok ( true )
767779 }
768780
781+ /// Validate whether the value and type passed by the caller are compatible with the type
782+ /// requested by the callee. Based on section 7.16.1.1 of the C23 specification.
783+ ///
784+ /// The callee requesting a value of a type is valid when that type is compatible with the type
785+ /// provided by the caller (see `validate_c_variadic_compatible_ty`) and, if both types are
786+ /// integers of the same size but different signedness, the passed value must be representable
787+ /// in both types.
788+ fn validate_c_variadic_argument (
789+ & mut self ,
790+ arg_mplace : & MPlaceTy < ' tcx , M :: Provenance > ,
791+ callee_type : TyAndLayout < ' tcx > ,
792+ ) -> InterpResult < ' tcx > {
793+ let callee_ty = callee_type. ty ;
794+ let caller_ty = arg_mplace. layout . ty ;
795+
796+ // Identical types are clearly compatible.
797+ if caller_ty == callee_ty {
798+ return interp_ok ( ( ) ) ;
799+ }
800+
801+ // Types of different sizes can never be compatible.
802+ if arg_mplace. layout . size != callee_type. size {
803+ throw_ub_format ! (
804+ "va_arg type mismatch: requested `{}` is incompatible with next argument of type `{}`" ,
805+ callee_ty,
806+ caller_ty,
807+ )
808+ }
809+
810+ match self . validate_c_variadic_compatible_ty ( arg_mplace. layout . ty , callee_type. ty ) ? {
811+ VarArgCompatible :: Compatible => interp_ok ( ( ) ) ,
812+ VarArgCompatible :: Incompatible => throw_ub_format ! (
813+ "va_arg type mismatch: requested `{}` is incompatible with next argument of type `{}`" ,
814+ callee_ty,
815+ caller_ty,
816+ ) ,
817+ VarArgCompatible :: CastIntTo { source_is_signed } => {
818+ // Check that the value can be represented in the target type.
819+ let size = arg_mplace. layout . size ;
820+ let scalar = self . read_scalar ( arg_mplace) ?;
821+ if scalar. to_int ( size) ? < 0 {
822+ throw_ub_format ! (
823+ "va_arg value mismatch: value `{value}_{caller_ty}` cannot be represented by type `{callee_ty}`" ,
824+ value = if source_is_signed {
825+ scalar. to_int( size) ?. to_string( )
826+ } else {
827+ scalar. to_uint( size) ?. to_string( )
828+ }
829+ )
830+ }
831+
832+ interp_ok ( ( ) )
833+ }
834+ }
835+ }
836+
837+ /// Check whether the caller and callee type are compatible for c-variadic calls. Further
838+ /// validation of the argument value may be needed to detect all UB.
839+ ///
840+ /// Types `T` and `U` are compatible when:
841+ ///
842+ /// - `T` and `U` are the same type.
843+ /// - `T` and `U` are integer types of the same size.
844+ /// - `T` and `U` are both pointers, and their target types are compatible.
845+ /// - `T` is a pointer to [`std::ffi::c_void`] and `U` is a pointer to [`i8`] or [`u8`],
846+ /// or vice versa.
847+ fn validate_c_variadic_compatible_ty (
848+ & mut self ,
849+ caller_type : Ty < ' tcx > ,
850+ callee_type : Ty < ' tcx > ,
851+ ) -> InterpResult < ' tcx , VarArgCompatible > {
852+ if caller_type == callee_type {
853+ return interp_ok ( VarArgCompatible :: Compatible ) ;
854+ }
855+
856+ if self . layout_of ( caller_type) ?. size != self . layout_of ( callee_type) ?. size {
857+ return interp_ok ( VarArgCompatible :: Incompatible ) ;
858+ }
859+
860+ // Any character type (`char`, `unsigned char` and `signed char`) is compatible with
861+ // `void*`, so the signedness of `c_char` is irrelevant here.
862+ let is_c_char = |ty : Ty < ' _ > | matches ! ( ty. kind( ) , ty:: Uint ( UintTy :: U8 ) | ty:: Int ( IntTy :: I8 ) ) ;
863+
864+ match ( caller_type. kind ( ) , callee_type. kind ( ) ) {
865+ ( ty:: RawPtr ( caller_target_ty, _) , ty:: RawPtr ( callee_target_ty, _) ) => {
866+ // In C, types can be qualified by a combination of `const`, `volatile` and
867+ // `restrict`. These properties are irrelevant for the ABI, and don't have an
868+ // equivalent in rust.
869+
870+ // Accept the cast if one type is pointer to void, and the other is a pointer to
871+ // a character type (`char`, `unsigned char` and `signed char`).
872+ if caller_target_ty. is_c_void ( self . tcx . tcx ) && is_c_char ( * callee_target_ty) {
873+ return interp_ok ( VarArgCompatible :: Compatible ) ;
874+ }
875+ if callee_target_ty. is_c_void ( self . tcx . tcx ) && is_c_char ( * caller_target_ty) {
876+ return interp_ok ( VarArgCompatible :: Compatible ) ;
877+ }
878+
879+ // Accept the cast if both types are pointers to compatible types.
880+ match self
881+ . validate_c_variadic_compatible_ty ( * caller_target_ty, * callee_target_ty) ?
882+ {
883+ VarArgCompatible :: Incompatible => interp_ok ( VarArgCompatible :: Incompatible ) ,
884+ VarArgCompatible :: Compatible => interp_ok ( VarArgCompatible :: Compatible ) ,
885+ VarArgCompatible :: CastIntTo { source_is_signed : _ } => {
886+ // The integer cast check is not needed when the value is behind a pointer.
887+ interp_ok ( VarArgCompatible :: Compatible )
888+ }
889+ }
890+ }
891+ ( ty:: Int ( _) , ty:: Uint ( _) ) => {
892+ interp_ok ( VarArgCompatible :: CastIntTo { source_is_signed : true } )
893+ }
894+ ( ty:: Uint ( _) , ty:: Int ( _) ) => {
895+ interp_ok ( VarArgCompatible :: CastIntTo { source_is_signed : false } )
896+ }
897+ ( ty:: Int ( _) , ty:: Int ( _) ) | ( ty:: Uint ( _) , ty:: Uint ( _) ) => {
898+ // E.g. cast between `usize` and `u64` on a 64-bit platform.
899+ interp_ok ( VarArgCompatible :: Compatible )
900+ }
901+ _ => interp_ok ( VarArgCompatible :: Incompatible ) ,
902+ }
903+ }
904+
769905 pub ( super ) fn eval_nondiverging_intrinsic (
770906 & mut self ,
771907 intrinsic : & NonDivergingIntrinsic < ' tcx > ,
0 commit comments