@@ -780,12 +780,70 @@ 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+ zend_string * str ;
789+ bool bval ;
790+
791+ ZEND_ASSERT (!Z_ISREF_P (arg ));
792+ ZEND_ASSERT (!Z_ISREF_P (coerce_arg ));
793+ if (UNEXPECTED (Z_ISUNDEF_P (coerce_arg ))) {
794+ ZVAL_COPY (coerce_arg , arg );
795+ }
796+
797+ /* Type preference order: int -> float -> string -> bool */
798+ if (type_mask & MAY_BE_LONG ) {
799+ /* For an int|float union type and string value,
800+ * determine chosen type by is_numeric_string() semantics. */
801+ if ((type_mask & MAY_BE_DOUBLE ) && Z_TYPE_P (arg ) == IS_STRING ) {
802+ uint8_t type = is_numeric_str_function (Z_STR_P (arg ), & lval , & dval );
803+ if (type == IS_LONG ) {
804+ zend_string_release (Z_STR_P (coerce_arg ));
805+ ZVAL_LONG (coerce_arg , lval );
806+ return true;
807+ }
808+ if (type == IS_DOUBLE ) {
809+ zend_string_release (Z_STR_P (coerce_arg ));
810+ ZVAL_DOUBLE (coerce_arg , dval );
811+ return true;
812+ }
813+ } else if (zend_parse_arg_long_weak (arg , & lval , 0 )) {
814+ zval_ptr_dtor (coerce_arg );
815+ ZVAL_LONG (coerce_arg , lval );
816+ return true;
817+ } else if (UNEXPECTED (EG (exception ))) {
818+ return false;
819+ }
820+ }
821+ if ((type_mask & MAY_BE_DOUBLE ) && zend_parse_arg_double_weak (arg , & dval , 0 )) {
822+ zval_ptr_dtor (coerce_arg );
823+ ZVAL_DOUBLE (coerce_arg , dval );
824+ return true;
825+ }
826+ if ((type_mask & MAY_BE_STRING ) && zend_parse_arg_str_weak (coerce_arg , & str , 0 )) {
827+ /* on success "arg" is converted to IS_STRING */
828+ return true;
829+ }
830+ if ((type_mask & MAY_BE_BOOL ) == MAY_BE_BOOL && zend_parse_arg_bool_weak (arg , & bval , 0 )) {
831+ zval_ptr_dtor (coerce_arg );
832+ ZVAL_BOOL (coerce_arg , bval );
833+ return true;
834+ }
835+ return false;
836+ }
837+
783838static zend_type_check_status zend_check_type_slow (
784839 const zend_type * type ,
785840 const zval * arg ,
786841 const zend_class_entry * scope ,
787842 bool strict_types ,
788- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
843+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
844+ const uint32_t callable_check_flag ,
845+ /* Usually the same pointer as arg */
846+ zval * coerce_arg
789847) {
790848 if (ZEND_TYPE_IS_COMPLEX (* type ) && EXPECTED (Z_TYPE_P (arg ) == IS_OBJECT )) {
791849 const zend_class_entry * arg_ce = Z_OBJCE_P (arg );
@@ -826,6 +884,10 @@ static zend_type_check_status zend_check_type_slow(
826884
827885 /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */
828886 if ((type_mask & MAY_BE_DOUBLE ) && Z_TYPE_P (arg ) == IS_LONG ) {
887+ if (coerce_arg ) {
888+ const double dval = (double )Z_LVAL_P (arg );
889+ ZVAL_DOUBLE (coerce_arg , dval );
890+ }
829891 return ZEND_TYPE_CHECK_MAY_COERCE ;
830892 }
831893 /* Need to suppress deprecation for internal functions, otherwise two deprecation notice would be emitted
@@ -844,10 +906,18 @@ static zend_type_check_status zend_check_type_slow(
844906 return ZEND_TYPE_CHECK_VALID ;
845907 }
846908
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 ) {
909+ /* Only scalar types may coerce to other scalar types */
910+ if (
911+ !strict_types
912+ && Z_TYPE_P (arg ) > IS_NULL
913+ && (type_mask & (MAY_BE_LONG |MAY_BE_DOUBLE |MAY_BE_STRING |MAY_BE_BOOL ))
914+ ) {
915+ if (coerce_arg ) {
916+ zend_type_check_status status = zend_coerce_weak_scalar_type_declaration (type_mask , arg , coerce_arg )
917+ ? ZEND_TYPE_CHECK_MAY_COERCE : ZEND_TYPE_CHECK_INVALID ;
918+ return status ;
919+ }
920+ if (Z_TYPE_P (arg ) <= IS_STRING ) {
851921 return ZEND_TYPE_CHECK_MAY_COERCE ;
852922 }
853923 /* Stringable object pass a string type check */
@@ -859,107 +929,64 @@ static zend_type_check_status zend_check_type_slow(
859929 return ZEND_TYPE_CHECK_INVALID ;
860930}
861931
862- static zend_type_check_status zend_check_type (
932+ static zend_always_inline zend_type_check_status zend_check_type_ex (
863933 const zend_type * type ,
864934 const zval * arg ,
865935 const zend_class_entry * scope ,
866936 bool strict_types ,
867- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
937+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
938+ const uint32_t callable_check_flag ,
939+ zval * coerce_arg
868940) {
869941 if (UNEXPECTED (Z_ISREF_P (arg ))) {
870942 /* Cannot coerce typed references */
871943 strict_types |= ZEND_REF_HAS_TYPE_SOURCES (Z_REF_P (arg ));
872- arg = Z_REFVAL_P (arg );
944+ if (arg == coerce_arg ) {
945+ arg = coerce_arg = Z_REFVAL_P (coerce_arg );
946+ } else {
947+ arg = Z_REFVAL_P (arg );
948+ }
873949 }
874950
875951 if (EXPECTED (ZEND_TYPE_CONTAINS_CODE (* type , Z_TYPE_P (arg )))) {
876952 return ZEND_TYPE_CHECK_VALID ;
877953 }
878- return zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag );
954+ return zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag , coerce_arg );
879955}
880956
881- static bool zend_coerce_weak_scalar_type_declaration (uint32_t type_mask , zval * arg )
882- {
883- zend_long lval ;
884- double dval ;
885- zend_string * str ;
886- bool bval ;
887-
888- ZVAL_DEREF (arg );
889- /* arg should only be of type int, float, string, bool, or Stringable */
890- /* Type preference order: int -> float -> string -> bool */
891- if (type_mask & MAY_BE_LONG ) {
892- /* For an int|float union type and string value,
893- * determine chosen type by is_numeric_string() semantics. */
894- if ((type_mask & MAY_BE_DOUBLE ) && Z_TYPE_P (arg ) == IS_STRING ) {
895- uint8_t type = is_numeric_str_function (Z_STR_P (arg ), & lval , & dval );
896- if (type == IS_LONG ) {
897- zend_string_release (Z_STR_P (arg ));
898- ZVAL_LONG (arg , lval );
899- return true;
900- }
901- if (type == IS_DOUBLE ) {
902- zend_string_release (Z_STR_P (arg ));
903- ZVAL_DOUBLE (arg , dval );
904- return true;
905- }
906- } else if (zend_parse_arg_long_weak (arg , & lval , 0 )) {
907- zval_ptr_dtor (arg );
908- ZVAL_LONG (arg , lval );
909- return true;
910- } else if (UNEXPECTED (EG (exception ))) {
911- return false;
912- }
913- }
914- if ((type_mask & MAY_BE_DOUBLE ) && zend_parse_arg_double_weak (arg , & dval , 0 )) {
915- zval_ptr_dtor (arg );
916- ZVAL_DOUBLE (arg , dval );
917- return true;
918- }
919- if ((type_mask & MAY_BE_STRING ) && zend_parse_arg_str_weak (arg , & str , 0 )) {
920- /* on success "arg" is converted to IS_STRING */
921- return true;
922- }
923- if ((type_mask & MAY_BE_BOOL ) == MAY_BE_BOOL && zend_parse_arg_bool_weak (arg , & bval , 0 )) {
924- zval_ptr_dtor (arg );
925- ZVAL_BOOL (arg , bval );
926- return true;
927- }
928- return false;
957+ static zend_always_inline zend_type_check_status zend_check_type (
958+ const zend_type * type ,
959+ const zval * arg ,
960+ const zend_class_entry * scope ,
961+ bool strict_types ,
962+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
963+ const uint32_t callable_check_flag
964+ ) {
965+ return zend_check_type_ex (type , arg , scope , strict_types , callable_check_flag , NULL );
929966}
930967
931968static bool zend_check_type_and_coerce (
932969 const zend_type * type ,
933970 zval * arg ,
934971 const zend_class_entry * scope ,
935972 bool strict_types ,
936- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
973+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
974+ const uint32_t callable_check_flag
937975) {
938- zend_type_check_status status = zend_check_type (type , arg , scope , strict_types , callable_check_flag );
939- if (EXPECTED (status == ZEND_TYPE_CHECK_VALID )) {
940- return true;
941- }
942- if (status == ZEND_TYPE_CHECK_MAY_COERCE ) {
943- return zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (* type ), arg );
944- }
945- return false;
976+ zend_type_check_status status = zend_check_type_ex (type , arg , scope , strict_types , callable_check_flag , arg );
977+ return status != ZEND_TYPE_CHECK_INVALID ;
946978}
947979
948980static bool zend_check_type_and_coerce_slow (
949981 const zend_type * type ,
950982 zval * arg ,
951983 const zend_class_entry * scope ,
952984 bool strict_types ,
953- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
985+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
986+ const uint32_t callable_check_flag
954987) {
955- zend_type_check_status status = zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag );
956- if (EXPECTED (status == ZEND_TYPE_CHECK_VALID )) {
957- return true;
958- }
959- if (status == ZEND_TYPE_CHECK_MAY_COERCE ) {
960- return zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (* type ), arg );
961- }
962- return false;
988+ zend_type_check_status status = zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag , arg );
989+ return status != ZEND_TYPE_CHECK_INVALID ;
963990}
964991
965992ZEND_API bool zend_check_user_type_slow (
@@ -996,14 +1023,8 @@ static zend_never_inline ZEND_COLD void zend_verify_property_type_error(const ze
9961023
9971024static zend_always_inline bool i_zend_verify_property_type (const zend_property_info * info , zval * property , bool strict )
9981025{
999- zend_type_check_status status = zend_check_type (& info -> type , property , info -> ce , strict , 0 );
1000- if (EXPECTED (status == ZEND_TYPE_CHECK_VALID )) {
1001- return true;
1002- }
1003- if (
1004- status == ZEND_TYPE_CHECK_MAY_COERCE
1005- && zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_PURE_MASK (info -> type ), property )
1006- ) {
1026+ bool status = zend_check_type_and_coerce (& info -> type , property , info -> ce , strict , 0 );
1027+ if (EXPECTED (status )) {
10071028 return true;
10081029 }
10091030
@@ -1186,7 +1207,7 @@ static zend_always_inline bool zend_verify_variadic_arg_type(
11861207}
11871208
11881209#if ZEND_DEBUG
1189- 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 )
1210+ 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 )
11901211{
11911212 if (UNEXPECTED (zend_check_type (
11921213 type ,
@@ -3861,22 +3882,12 @@ static zend_never_inline ZEND_COLD void zend_throw_conflicting_coercion_error(co
38613882}
38623883
38633884static zend_always_inline zend_type_check_status i_zend_verify_type_assignable_zval (
3864- const zend_property_info * info , const zval * zv , bool strict ) {
3865- //zend_type type = info->type;
3866- //uint32_t type_mask;
3867- //uint8_t zv_type = Z_TYPE_P(zv);
3868-
3869- zend_type_check_status status = zend_check_type (& info -> type , zv , info -> ce , strict , 0 );
3870-
3871- /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */
3872- // TODO How to handle this in zend_check_type()?
3873- //if (strict) {
3874- // if ((type_mask & MAY_BE_DOUBLE) && zv_type == IS_LONG) {
3875- // return -1;
3876- // }
3877- // return 0;
3878- //}
3879-
3885+ const zend_property_info * info ,
3886+ const zval * zv ,
3887+ bool strict ,
3888+ zval * coerced_value
3889+ ) {
3890+ zend_type_check_status status = zend_check_type_ex (& info -> type , zv , info -> ce , strict , 0 , coerced_value );
38803891 return status ;
38813892}
38823893
@@ -3892,40 +3903,35 @@ ZEND_API bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference *ref,
38923903
38933904 ZEND_ASSERT (Z_TYPE_P (zv ) != IS_REFERENCE );
38943905 ZEND_REF_FOREACH_TYPE_SOURCES (ref , prop ) {
3895- zend_type_check_status result = i_zend_verify_type_assignable_zval (prop , zv , strict );
3906+ zval tmp ;
3907+ ZVAL_UNDEF (& tmp );
3908+ zend_type_check_status result = i_zend_verify_type_assignable_zval (prop , zv , strict , & tmp );
38963909 if (result == ZEND_TYPE_CHECK_INVALID ) {
3897- type_error :
38983910 zend_throw_ref_type_error_zval (prop , zv );
3911+ zval_ptr_dtor (& tmp );
38993912 zval_ptr_dtor (& coerced_value );
39003913 return 0 ;
39013914 }
39023915
39033916 if (result == ZEND_TYPE_CHECK_MAY_COERCE ) {
39043917 if (!first_prop ) {
39053918 first_prop = prop ;
3906- ZVAL_COPY (& coerced_value , zv );
3907- if (!zend_coerce_weak_scalar_type_declaration (
3908- ZEND_TYPE_FULL_MASK (prop -> type ), & coerced_value )) {
3909- goto type_error ;
3910- }
3919+ ZVAL_COPY (& coerced_value , & tmp );
3920+ zval_ptr_dtor (& tmp );
39113921 } else if (Z_ISUNDEF (coerced_value )) {
39123922 /* A previous property did not require coercion, but this one does,
39133923 * so they are incompatible. */
3924+ zval_ptr_dtor (& tmp );
39143925 goto conflicting_coercion_error ;
39153926 } else {
3916- zval tmp ;
3917- ZVAL_COPY (& tmp , zv );
3918- if (!zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (prop -> type ), & tmp )) {
3919- zval_ptr_dtor (& tmp );
3920- goto type_error ;
3921- }
3922- if (!zend_is_identical (& coerced_value , & tmp )) {
3923- zval_ptr_dtor (& tmp );
3927+ bool is_identical = zend_is_identical (& coerced_value , & tmp );
3928+ zval_ptr_dtor (& tmp );
3929+ if (!is_identical ) {
39243930 goto conflicting_coercion_error ;
39253931 }
3926- zval_ptr_dtor (& tmp );
39273932 }
39283933 } else {
3934+ ZEND_ASSERT (Z_ISUNDEF (tmp ));
39293935 if (!first_prop ) {
39303936 first_prop = prop ;
39313937 } else if (!Z_ISUNDEF (coerced_value )) {
@@ -4003,26 +4009,22 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ui
40034009ZEND_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 ) {
40044010 zval * val = orig_val ;
40054011 if (Z_ISREF_P (val ) && ZEND_REF_HAS_TYPE_SOURCES (Z_REF_P (val ))) {
4006- int result ;
4007-
40084012 val = Z_REFVAL_P (val );
4009- result = i_zend_verify_type_assignable_zval (prop_info , val , strict );
4010- if (result == ZEND_TYPE_CHECK_VALID ) {
4013+ zval tmp ;
4014+
4015+ ZVAL_UNDEF (& tmp );
4016+ zend_type_check_status result = i_zend_verify_type_assignable_zval (prop_info , val , strict , & tmp );
4017+ if (EXPECTED (result == ZEND_TYPE_CHECK_VALID )) {
4018+ ZEND_ASSERT (Z_ISUNDEF (tmp ));
40114019 return true;
40124020 }
40134021
40144022 if (result == ZEND_TYPE_CHECK_MAY_COERCE ) {
4015- /* This is definitely an error, but we still need to determined why: Either because
4016- * the value is simply illegal for the type, or because or a conflicting coercion. */
4017- zval tmp ;
4018- ZVAL_COPY (& tmp , val );
4019- if (zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (prop_info -> type ), & tmp )) {
4020- const zend_property_info * ref_prop = ZEND_REF_FIRST_SOURCE (Z_REF_P (orig_val ));
4021- zend_throw_ref_type_error_type (ref_prop , prop_info , val );
4022- zval_ptr_dtor (& tmp );
4023- return false;
4024- }
4023+ /* This is an error, because or a conflicting coercion. */
4024+ const zend_property_info * ref_prop = ZEND_REF_FIRST_SOURCE (Z_REF_P (orig_val ));
4025+ zend_throw_ref_type_error_type (ref_prop , prop_info , val );
40254026 zval_ptr_dtor (& tmp );
4027+ return false;
40264028 }
40274029 } else {
40284030 ZVAL_DEREF (val );
0 commit comments