@@ -780,12 +780,75 @@ static bool zend_check_intersection_type_from_list(
780780 return true;
781781}
782782
783+ /* Usually coerce_arg will be the same pointer as arg */
784+ static bool zend_coerce_weak_scalar_type_declaration (uint32_t type_mask , const zval * arg , zval * coerce_arg )
785+ {
786+ zend_long lval ;
787+ double dval ;
788+
789+ ZEND_ASSERT (!Z_ISREF_P (arg ));
790+ ZEND_ASSERT (!Z_ISREF_P (coerce_arg ));
791+ if (UNEXPECTED (Z_ISUNDEF_P (coerce_arg ))) {
792+ ZVAL_COPY (coerce_arg , arg );
793+ }
794+
795+ /* Type preference order: int -> float -> string -> bool */
796+ if (type_mask & MAY_BE_LONG ) {
797+ /* For an int|float union type and string value,
798+ * determine chosen type by is_numeric_string() semantics. */
799+ if ((type_mask & MAY_BE_DOUBLE ) && Z_TYPE_P (arg ) == IS_STRING ) {
800+ uint8_t type = is_numeric_str_function (Z_STR_P (arg ), & lval , & dval );
801+ if (type == IS_LONG ) {
802+ zend_string_release (Z_STR_P (coerce_arg ));
803+ ZVAL_LONG (coerce_arg , lval );
804+ return true;
805+ }
806+ if (type == IS_DOUBLE ) {
807+ zend_string_release (Z_STR_P (coerce_arg ));
808+ ZVAL_DOUBLE (coerce_arg , dval );
809+ return true;
810+ }
811+ } else if (zend_parse_arg_long_weak (arg , & lval , 0 )) {
812+ zval_ptr_dtor (coerce_arg );
813+ ZVAL_LONG (coerce_arg , lval );
814+ return true;
815+ } else if (UNEXPECTED (EG (exception ))) {
816+ return false;
817+ }
818+ }
819+ if (type_mask & MAY_BE_DOUBLE ) {
820+ dval = zend_parse_arg_double_weak (arg , 0 );
821+ if (EXPECTED (!zend_isnan (dval ))) {
822+ zval_ptr_dtor (coerce_arg );
823+ ZVAL_DOUBLE (coerce_arg , dval );
824+ return true;
825+ }
826+ }
827+ if ((type_mask & MAY_BE_STRING ) && zend_parse_arg_str_weak (coerce_arg , 0 )) {
828+ /* on success "coerce_arg" is converted to IS_STRING */
829+ return true;
830+ }
831+ if ((type_mask & MAY_BE_BOOL ) == MAY_BE_BOOL ) {
832+ zpp_parse_bool_status bval = zend_parse_arg_bool_weak (arg , 0 );
833+ if (UNEXPECTED (bval == ZPP_PARSE_BOOL_STATUS_ERROR )) {
834+ return false;
835+ }
836+ zval_ptr_dtor (coerce_arg );
837+ ZVAL_BOOL (coerce_arg , bval );
838+ return true;
839+ }
840+ return false;
841+ }
842+
783843static zend_type_check_status zend_check_type_slow (
784844 const zend_type * type ,
785845 const zval * arg ,
786846 const zend_class_entry * scope ,
787847 bool strict_types ,
788- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
848+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
849+ const uint32_t callable_check_flag ,
850+ /* Usually the same pointer as arg */
851+ zval * coerce_arg
789852) {
790853 if (ZEND_TYPE_IS_COMPLEX (* type ) && EXPECTED (Z_TYPE_P (arg ) == IS_OBJECT )) {
791854 const zend_class_entry * arg_ce = Z_OBJCE_P (arg );
@@ -826,6 +889,10 @@ static zend_type_check_status zend_check_type_slow(
826889
827890 /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */
828891 if ((type_mask & MAY_BE_DOUBLE ) && Z_TYPE_P (arg ) == IS_LONG ) {
892+ if (coerce_arg ) {
893+ const double dval = (double )Z_LVAL_P (arg );
894+ ZVAL_DOUBLE (coerce_arg , dval );
895+ }
829896 return ZEND_TYPE_CHECK_MAY_COERCE ;
830897 }
831898 /* Need to suppress deprecation for internal functions, otherwise two deprecation notice would be emitted
@@ -844,10 +911,18 @@ static zend_type_check_status zend_check_type_slow(
844911 return ZEND_TYPE_CHECK_VALID ;
845912 }
846913
847- if (!strict_types ) {
848- /* TODO: Move coercible checks here in PHP 9 when various type coercions have been removed? */
849- /* Only scalar types may coerce to other scalar types */
850- if (type_mask & (MAY_BE_LONG |MAY_BE_DOUBLE |MAY_BE_STRING |MAY_BE_BOOL ) && Z_TYPE_P (arg ) > IS_NULL && Z_TYPE_P (arg ) <= IS_STRING ) {
914+ /* Only scalar types may coerce to other scalar types */
915+ if (
916+ !strict_types
917+ && Z_TYPE_P (arg ) > IS_NULL
918+ && (type_mask & (MAY_BE_LONG |MAY_BE_DOUBLE |MAY_BE_STRING |MAY_BE_BOOL ))
919+ ) {
920+ if (coerce_arg ) {
921+ zend_type_check_status status = zend_coerce_weak_scalar_type_declaration (type_mask , arg , coerce_arg )
922+ ? ZEND_TYPE_CHECK_MAY_COERCE : ZEND_TYPE_CHECK_INVALID ;
923+ return status ;
924+ }
925+ if (Z_TYPE_P (arg ) <= IS_STRING ) {
851926 return ZEND_TYPE_CHECK_MAY_COERCE ;
852927 }
853928 /* Stringable object pass a string type check */
@@ -859,112 +934,64 @@ static zend_type_check_status zend_check_type_slow(
859934 return ZEND_TYPE_CHECK_INVALID ;
860935}
861936
862- static zend_type_check_status zend_check_type (
937+ static zend_always_inline zend_type_check_status zend_check_type_ex (
863938 const zend_type * type ,
864939 const zval * arg ,
865940 const zend_class_entry * scope ,
866941 bool strict_types ,
867- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
942+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
943+ const uint32_t callable_check_flag ,
944+ zval * coerce_arg
868945) {
869946 if (UNEXPECTED (Z_ISREF_P (arg ))) {
870947 /* Cannot coerce typed references */
871948 strict_types |= ZEND_REF_HAS_TYPE_SOURCES (Z_REF_P (arg ));
872- arg = Z_REFVAL_P (arg );
949+ if (arg == coerce_arg ) {
950+ arg = coerce_arg = Z_REFVAL_P (coerce_arg );
951+ } else {
952+ arg = Z_REFVAL_P (arg );
953+ }
873954 }
874955
875956 if (EXPECTED (ZEND_TYPE_CONTAINS_CODE (* type , Z_TYPE_P (arg )))) {
876957 return ZEND_TYPE_CHECK_VALID ;
877958 }
878- return zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag );
959+ return zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag , coerce_arg );
879960}
880961
881- static bool zend_coerce_weak_scalar_type_declaration (uint32_t type_mask , zval * arg )
882- {
883- zend_long lval ;
884- double dval ;
885-
886- ZVAL_DEREF (arg );
887- /* arg should only be of type int, float, string, bool, or Stringable */
888- /* Type preference order: int -> float -> string -> bool */
889- if (type_mask & MAY_BE_LONG ) {
890- /* For an int|float union type and string value,
891- * determine chosen type by is_numeric_string() semantics. */
892- if ((type_mask & MAY_BE_DOUBLE ) && Z_TYPE_P (arg ) == IS_STRING ) {
893- uint8_t type = is_numeric_str_function (Z_STR_P (arg ), & lval , & dval );
894- if (type == IS_LONG ) {
895- zend_string_release (Z_STR_P (arg ));
896- ZVAL_LONG (arg , lval );
897- return true;
898- }
899- if (type == IS_DOUBLE ) {
900- zend_string_release (Z_STR_P (arg ));
901- ZVAL_DOUBLE (arg , dval );
902- return true;
903- }
904- } else if (zend_parse_arg_long_weak (arg , & lval , 0 )) {
905- zval_ptr_dtor (arg );
906- ZVAL_LONG (arg , lval );
907- return true;
908- } else if (UNEXPECTED (EG (exception ))) {
909- return false;
910- }
911- }
912- if (type_mask & MAY_BE_DOUBLE ) {
913- dval = zend_parse_arg_double_weak (arg , 0 );
914- if (EXPECTED (!zend_isnan (dval ))) {
915- zval_ptr_dtor (arg );
916- ZVAL_DOUBLE (arg , dval );
917- return true;
918- }
919- }
920- if ((type_mask & MAY_BE_STRING ) && zend_parse_arg_str_weak (arg , 0 )) {
921- /* on success "arg" is converted to IS_STRING */
922- return true;
923- }
924- if ((type_mask & MAY_BE_BOOL ) == MAY_BE_BOOL ) {
925- zpp_parse_bool_status bval = zend_parse_arg_bool_weak (arg , 0 );
926- if (UNEXPECTED (bval == ZPP_PARSE_BOOL_STATUS_ERROR )) {
927- return false;
928- }
929- zval_ptr_dtor (arg );
930- ZVAL_BOOL (arg , bval );
931- return true;
932- }
933- return false;
962+ static zend_always_inline zend_type_check_status zend_check_type (
963+ const zend_type * type ,
964+ const zval * arg ,
965+ const zend_class_entry * scope ,
966+ bool strict_types ,
967+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
968+ const uint32_t callable_check_flag
969+ ) {
970+ return zend_check_type_ex (type , arg , scope , strict_types , callable_check_flag , NULL );
934971}
935972
936973static bool zend_check_type_and_coerce (
937974 const zend_type * type ,
938975 zval * arg ,
939976 const zend_class_entry * scope ,
940977 bool strict_types ,
941- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
978+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
979+ const uint32_t callable_check_flag
942980) {
943- zend_type_check_status status = zend_check_type (type , arg , scope , strict_types , callable_check_flag );
944- if (EXPECTED (status == ZEND_TYPE_CHECK_VALID )) {
945- return true;
946- }
947- if (status == ZEND_TYPE_CHECK_MAY_COERCE ) {
948- return zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (* type ), arg );
949- }
950- return false;
981+ zend_type_check_status status = zend_check_type_ex (type , arg , scope , strict_types , callable_check_flag , arg );
982+ return status != ZEND_TYPE_CHECK_INVALID ;
951983}
952984
953985static bool zend_check_type_and_coerce_slow (
954986 const zend_type * type ,
955987 zval * arg ,
956988 const zend_class_entry * scope ,
957989 bool strict_types ,
958- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
990+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
991+ const uint32_t callable_check_flag
959992) {
960- zend_type_check_status status = zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag );
961- if (EXPECTED (status == ZEND_TYPE_CHECK_VALID )) {
962- return true;
963- }
964- if (status == ZEND_TYPE_CHECK_MAY_COERCE ) {
965- return zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (* type ), arg );
966- }
967- return false;
993+ zend_type_check_status status = zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag , arg );
994+ return status != ZEND_TYPE_CHECK_INVALID ;
968995}
969996
970997ZEND_API bool zend_check_user_type_slow (
@@ -1001,14 +1028,8 @@ static zend_never_inline ZEND_COLD void zend_verify_property_type_error(const ze
10011028
10021029static zend_always_inline bool i_zend_verify_property_type (const zend_property_info * info , zval * property , bool strict )
10031030{
1004- zend_type_check_status status = zend_check_type (& info -> type , property , info -> ce , strict , 0 );
1005- if (EXPECTED (status == ZEND_TYPE_CHECK_VALID )) {
1006- return true;
1007- }
1008- if (
1009- status == ZEND_TYPE_CHECK_MAY_COERCE
1010- && zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_PURE_MASK (info -> type ), property )
1011- ) {
1031+ bool status = zend_check_type_and_coerce (& info -> type , property , info -> ce , strict , 0 );
1032+ if (EXPECTED (status )) {
10121033 return true;
10131034 }
10141035
@@ -1191,7 +1212,7 @@ static zend_always_inline bool zend_verify_variadic_arg_type(
11911212}
11921213
11931214#if ZEND_DEBUG
1194- static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_check_type_for_internal_parameter (const zend_type * type , const zval * arg , zend_execute_data * call )
1215+ static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_check_type_for_internal_parameter (const zend_type * type , const zval * arg , const zend_execute_data * call )
11951216{
11961217 if (UNEXPECTED (zend_check_type (
11971218 type ,
@@ -3866,22 +3887,12 @@ static zend_never_inline ZEND_COLD void zend_throw_conflicting_coercion_error(co
38663887}
38673888
38683889static zend_always_inline zend_type_check_status i_zend_verify_type_assignable_zval (
3869- const zend_property_info * info , const zval * zv , bool strict ) {
3870- //zend_type type = info->type;
3871- //uint32_t type_mask;
3872- //uint8_t zv_type = Z_TYPE_P(zv);
3873-
3874- zend_type_check_status status = zend_check_type (& info -> type , zv , info -> ce , strict , 0 );
3875-
3876- /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */
3877- // TODO How to handle this in zend_check_type()?
3878- //if (strict) {
3879- // if ((type_mask & MAY_BE_DOUBLE) && zv_type == IS_LONG) {
3880- // return -1;
3881- // }
3882- // return 0;
3883- //}
3884-
3890+ const zend_property_info * info ,
3891+ const zval * zv ,
3892+ bool strict ,
3893+ zval * coerced_value
3894+ ) {
3895+ zend_type_check_status status = zend_check_type_ex (& info -> type , zv , info -> ce , strict , 0 , coerced_value );
38853896 return status ;
38863897}
38873898
@@ -3897,40 +3908,35 @@ ZEND_API bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference *ref,
38973908
38983909 ZEND_ASSERT (Z_TYPE_P (zv ) != IS_REFERENCE );
38993910 ZEND_REF_FOREACH_TYPE_SOURCES (ref , prop ) {
3900- zend_type_check_status result = i_zend_verify_type_assignable_zval (prop , zv , strict );
3911+ zval tmp ;
3912+ ZVAL_UNDEF (& tmp );
3913+ zend_type_check_status result = i_zend_verify_type_assignable_zval (prop , zv , strict , & tmp );
39013914 if (result == ZEND_TYPE_CHECK_INVALID ) {
3902- type_error :
39033915 zend_throw_ref_type_error_zval (prop , zv );
3916+ zval_ptr_dtor (& tmp );
39043917 zval_ptr_dtor (& coerced_value );
39053918 return 0 ;
39063919 }
39073920
39083921 if (result == ZEND_TYPE_CHECK_MAY_COERCE ) {
39093922 if (!first_prop ) {
39103923 first_prop = prop ;
3911- ZVAL_COPY (& coerced_value , zv );
3912- if (!zend_coerce_weak_scalar_type_declaration (
3913- ZEND_TYPE_FULL_MASK (prop -> type ), & coerced_value )) {
3914- goto type_error ;
3915- }
3924+ ZVAL_COPY (& coerced_value , & tmp );
3925+ zval_ptr_dtor (& tmp );
39163926 } else if (Z_ISUNDEF (coerced_value )) {
39173927 /* A previous property did not require coercion, but this one does,
39183928 * so they are incompatible. */
3929+ zval_ptr_dtor (& tmp );
39193930 goto conflicting_coercion_error ;
39203931 } else {
3921- zval tmp ;
3922- ZVAL_COPY (& tmp , zv );
3923- if (!zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (prop -> type ), & tmp )) {
3924- zval_ptr_dtor (& tmp );
3925- goto type_error ;
3926- }
3927- if (!zend_is_identical (& coerced_value , & tmp )) {
3928- zval_ptr_dtor (& tmp );
3932+ bool is_identical = zend_is_identical (& coerced_value , & tmp );
3933+ zval_ptr_dtor (& tmp );
3934+ if (!is_identical ) {
39293935 goto conflicting_coercion_error ;
39303936 }
3931- zval_ptr_dtor (& tmp );
39323937 }
39333938 } else {
3939+ ZEND_ASSERT (Z_ISUNDEF (tmp ));
39343940 if (!first_prop ) {
39353941 first_prop = prop ;
39363942 } else if (!Z_ISUNDEF (coerced_value )) {
@@ -4008,26 +4014,22 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ui
40084014ZEND_API bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref_ex (const zend_property_info * prop_info , zval * orig_val , bool strict , zend_verify_prop_assignable_by_ref_context context ) {
40094015 zval * val = orig_val ;
40104016 if (Z_ISREF_P (val ) && ZEND_REF_HAS_TYPE_SOURCES (Z_REF_P (val ))) {
4011- int result ;
4012-
40134017 val = Z_REFVAL_P (val );
4014- result = i_zend_verify_type_assignable_zval (prop_info , val , strict );
4015- if (result == ZEND_TYPE_CHECK_VALID ) {
4018+ zval tmp ;
4019+
4020+ ZVAL_UNDEF (& tmp );
4021+ zend_type_check_status result = i_zend_verify_type_assignable_zval (prop_info , val , strict , & tmp );
4022+ if (EXPECTED (result == ZEND_TYPE_CHECK_VALID )) {
4023+ ZEND_ASSERT (Z_ISUNDEF (tmp ));
40164024 return true;
40174025 }
40184026
40194027 if (result == ZEND_TYPE_CHECK_MAY_COERCE ) {
4020- /* This is definitely an error, but we still need to determined why: Either because
4021- * the value is simply illegal for the type, or because or a conflicting coercion. */
4022- zval tmp ;
4023- ZVAL_COPY (& tmp , val );
4024- if (zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (prop_info -> type ), & tmp )) {
4025- const zend_property_info * ref_prop = ZEND_REF_FIRST_SOURCE (Z_REF_P (orig_val ));
4026- zend_throw_ref_type_error_type (ref_prop , prop_info , val );
4027- zval_ptr_dtor (& tmp );
4028- return false;
4029- }
4028+ /* This is an error, because or a conflicting coercion. */
4029+ const zend_property_info * ref_prop = ZEND_REF_FIRST_SOURCE (Z_REF_P (orig_val ));
4030+ zend_throw_ref_type_error_type (ref_prop , prop_info , val );
40304031 zval_ptr_dtor (& tmp );
4032+ return false;
40314033 }
40324034 } else {
40334035 ZVAL_DEREF (val );
0 commit comments