@@ -781,12 +781,70 @@ static bool zend_check_intersection_type_from_list(
781781 return true;
782782}
783783
784+ /* Usually coerce_arg will be the same pointer as arg */
785+ static bool zend_coerce_weak_scalar_type_declaration (uint32_t type_mask , const zval * arg , zval * coerce_arg )
786+ {
787+ zend_long lval ;
788+ double dval ;
789+ zend_string * str ;
790+ bool bval ;
791+
792+ ZEND_ASSERT (!Z_ISREF_P (arg ));
793+ ZEND_ASSERT (!Z_ISREF_P (coerce_arg ));
794+ if (UNEXPECTED (Z_ISUNDEF_P (coerce_arg ))) {
795+ ZVAL_COPY (coerce_arg , arg );
796+ }
797+
798+ /* Type preference order: int -> float -> string -> bool */
799+ if (type_mask & MAY_BE_LONG ) {
800+ /* For an int|float union type and string value,
801+ * determine chosen type by is_numeric_string() semantics. */
802+ if ((type_mask & MAY_BE_DOUBLE ) && Z_TYPE_P (arg ) == IS_STRING ) {
803+ uint8_t type = is_numeric_str_function (Z_STR_P (arg ), & lval , & dval );
804+ if (type == IS_LONG ) {
805+ zend_string_release (Z_STR_P (coerce_arg ));
806+ ZVAL_LONG (coerce_arg , lval );
807+ return true;
808+ }
809+ if (type == IS_DOUBLE ) {
810+ zend_string_release (Z_STR_P (coerce_arg ));
811+ ZVAL_DOUBLE (coerce_arg , dval );
812+ return true;
813+ }
814+ } else if (zend_parse_arg_long_weak (arg , & lval , 0 )) {
815+ zval_ptr_dtor (coerce_arg );
816+ ZVAL_LONG (coerce_arg , lval );
817+ return true;
818+ } else if (UNEXPECTED (EG (exception ))) {
819+ return false;
820+ }
821+ }
822+ if ((type_mask & MAY_BE_DOUBLE ) && zend_parse_arg_double_weak (arg , & dval , 0 )) {
823+ zval_ptr_dtor (coerce_arg );
824+ ZVAL_DOUBLE (coerce_arg , dval );
825+ return true;
826+ }
827+ if ((type_mask & MAY_BE_STRING ) && zend_parse_arg_str_weak (coerce_arg , & str , 0 )) {
828+ /* on success "arg" is converted to IS_STRING */
829+ return true;
830+ }
831+ if ((type_mask & MAY_BE_BOOL ) == MAY_BE_BOOL && zend_parse_arg_bool_weak (arg , & bval , 0 )) {
832+ zval_ptr_dtor (coerce_arg );
833+ ZVAL_BOOL (coerce_arg , bval );
834+ return true;
835+ }
836+ return false;
837+ }
838+
784839static zend_type_check_status zend_check_type_slow (
785840 const zend_type * type ,
786841 const zval * arg ,
787842 const zend_class_entry * scope ,
788843 bool strict_types ,
789- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
844+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
845+ const uint32_t callable_check_flag ,
846+ /* Usually the same pointer as arg */
847+ zval * coerce_arg
790848) {
791849 if (ZEND_TYPE_IS_COMPLEX (* type ) && EXPECTED (Z_TYPE_P (arg ) == IS_OBJECT )) {
792850 const zend_class_entry * arg_ce = Z_OBJCE_P (arg );
@@ -827,6 +885,10 @@ static zend_type_check_status zend_check_type_slow(
827885
828886 /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */
829887 if ((type_mask & MAY_BE_DOUBLE ) && Z_TYPE_P (arg ) == IS_LONG ) {
888+ if (coerce_arg ) {
889+ const double dval = (double )Z_LVAL_P (arg );
890+ ZVAL_DOUBLE (coerce_arg , dval );
891+ }
830892 return ZEND_TYPE_CHECK_MAY_COERCE ;
831893 }
832894 /* Need to suppress deprecation for internal functions, otherwise two deprecation notice would be emitted
@@ -845,10 +907,18 @@ static zend_type_check_status zend_check_type_slow(
845907 return ZEND_TYPE_CHECK_VALID ;
846908 }
847909
848- if (!strict_types ) {
849- /* TODO: Move coercible checks here in PHP 9 when various type coercions have been removed? */
850- /* Only scalar types may coerce to other scalar types */
851- 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 ) {
910+ /* Only scalar types may coerce to other scalar types */
911+ if (
912+ !strict_types
913+ && Z_TYPE_P (arg ) > IS_NULL
914+ && (type_mask & (MAY_BE_LONG |MAY_BE_DOUBLE |MAY_BE_STRING |MAY_BE_BOOL ))
915+ ) {
916+ if (coerce_arg ) {
917+ zend_type_check_status status = zend_coerce_weak_scalar_type_declaration (type_mask , arg , coerce_arg )
918+ ? ZEND_TYPE_CHECK_MAY_COERCE : ZEND_TYPE_CHECK_INVALID ;
919+ return status ;
920+ }
921+ if (Z_TYPE_P (arg ) <= IS_STRING ) {
852922 return ZEND_TYPE_CHECK_MAY_COERCE ;
853923 }
854924 /* Stringable object pass a string type check */
@@ -860,107 +930,64 @@ static zend_type_check_status zend_check_type_slow(
860930 return ZEND_TYPE_CHECK_INVALID ;
861931}
862932
863- static zend_type_check_status zend_check_type (
933+ static zend_always_inline zend_type_check_status zend_check_type_ex (
864934 const zend_type * type ,
865935 const zval * arg ,
866936 const zend_class_entry * scope ,
867937 bool strict_types ,
868- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
938+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
939+ const uint32_t callable_check_flag ,
940+ zval * coerce_arg
869941) {
870942 if (UNEXPECTED (Z_ISREF_P (arg ))) {
871943 /* Cannot coerce typed references */
872944 strict_types |= ZEND_REF_HAS_TYPE_SOURCES (Z_REF_P (arg ));
873- arg = Z_REFVAL_P (arg );
945+ if (arg == coerce_arg ) {
946+ arg = coerce_arg = Z_REFVAL_P (coerce_arg );
947+ } else {
948+ arg = Z_REFVAL_P (arg );
949+ }
874950 }
875951
876952 if (EXPECTED (ZEND_TYPE_CONTAINS_CODE (* type , Z_TYPE_P (arg )))) {
877953 return ZEND_TYPE_CHECK_VALID ;
878954 }
879- return zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag );
955+ return zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag , coerce_arg );
880956}
881957
882- static bool zend_coerce_weak_scalar_type_declaration (uint32_t type_mask , zval * arg )
883- {
884- zend_long lval ;
885- double dval ;
886- zend_string * str ;
887- bool bval ;
888-
889- ZVAL_DEREF (arg );
890- /* arg should only be of type int, float, string, bool, or Stringable */
891- /* Type preference order: int -> float -> string -> bool */
892- if (type_mask & MAY_BE_LONG ) {
893- /* For an int|float union type and string value,
894- * determine chosen type by is_numeric_string() semantics. */
895- if ((type_mask & MAY_BE_DOUBLE ) && Z_TYPE_P (arg ) == IS_STRING ) {
896- uint8_t type = is_numeric_str_function (Z_STR_P (arg ), & lval , & dval );
897- if (type == IS_LONG ) {
898- zend_string_release (Z_STR_P (arg ));
899- ZVAL_LONG (arg , lval );
900- return true;
901- }
902- if (type == IS_DOUBLE ) {
903- zend_string_release (Z_STR_P (arg ));
904- ZVAL_DOUBLE (arg , dval );
905- return true;
906- }
907- } else if (zend_parse_arg_long_weak (arg , & lval , 0 )) {
908- zval_ptr_dtor (arg );
909- ZVAL_LONG (arg , lval );
910- return true;
911- } else if (UNEXPECTED (EG (exception ))) {
912- return false;
913- }
914- }
915- if ((type_mask & MAY_BE_DOUBLE ) && zend_parse_arg_double_weak (arg , & dval , 0 )) {
916- zval_ptr_dtor (arg );
917- ZVAL_DOUBLE (arg , dval );
918- return true;
919- }
920- if ((type_mask & MAY_BE_STRING ) && zend_parse_arg_str_weak (arg , & str , 0 )) {
921- /* on success "arg" is converted to IS_STRING */
922- return true;
923- }
924- if ((type_mask & MAY_BE_BOOL ) == MAY_BE_BOOL && zend_parse_arg_bool_weak (arg , & bval , 0 )) {
925- zval_ptr_dtor (arg );
926- ZVAL_BOOL (arg , bval );
927- return true;
928- }
929- return false;
958+ static zend_always_inline zend_type_check_status zend_check_type (
959+ const zend_type * type ,
960+ const zval * arg ,
961+ const zend_class_entry * scope ,
962+ bool strict_types ,
963+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
964+ const uint32_t callable_check_flag
965+ ) {
966+ return zend_check_type_ex (type , arg , scope , strict_types , callable_check_flag , NULL );
930967}
931968
932969static bool zend_check_type_and_coerce (
933970 const zend_type * type ,
934971 zval * arg ,
935972 const zend_class_entry * scope ,
936973 bool strict_types ,
937- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
974+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
975+ const uint32_t callable_check_flag
938976) {
939- zend_type_check_status status = zend_check_type (type , arg , scope , strict_types , callable_check_flag );
940- if (EXPECTED (status == ZEND_TYPE_CHECK_VALID )) {
941- return true;
942- }
943- if (status == ZEND_TYPE_CHECK_MAY_COERCE ) {
944- return zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (* type ), arg );
945- }
946- return false;
977+ zend_type_check_status status = zend_check_type_ex (type , arg , scope , strict_types , callable_check_flag , arg );
978+ return status != ZEND_TYPE_CHECK_INVALID ;
947979}
948980
949981static bool zend_check_type_and_coerce_slow (
950982 const zend_type * type ,
951983 zval * arg ,
952984 const zend_class_entry * scope ,
953985 bool strict_types ,
954- const uint32_t callable_check_flag /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
986+ /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */
987+ const uint32_t callable_check_flag
955988) {
956- zend_type_check_status status = zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag );
957- if (EXPECTED (status == ZEND_TYPE_CHECK_VALID )) {
958- return true;
959- }
960- if (status == ZEND_TYPE_CHECK_MAY_COERCE ) {
961- return zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (* type ), arg );
962- }
963- return false;
989+ zend_type_check_status status = zend_check_type_slow (type , arg , scope , strict_types , callable_check_flag , arg );
990+ return status != ZEND_TYPE_CHECK_INVALID ;
964991}
965992
966993ZEND_API bool zend_check_user_type_slow (
@@ -978,14 +1005,8 @@ ZEND_API bool zend_check_user_type_slow(
9781005
9791006static zend_always_inline bool i_zend_verify_property_type (const zend_property_info * info , zval * property , bool strict )
9801007{
981- zend_type_check_status status = zend_check_type (& info -> type , property , info -> ce , strict , 0 );
982- if (EXPECTED (status == ZEND_TYPE_CHECK_VALID )) {
983- return true;
984- }
985- if (
986- status == ZEND_TYPE_CHECK_MAY_COERCE
987- && zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_PURE_MASK (info -> type ), property )
988- ) {
1008+ bool status = zend_check_type_and_coerce (& info -> type , property , info -> ce , strict , 0 );
1009+ if (EXPECTED (status )) {
9891010 return true;
9901011 }
9911012
@@ -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 ,
@@ -3845,22 +3866,12 @@ ZEND_API ZEND_COLD void zend_throw_conflicting_coercion_error(const zend_propert
38453866}
38463867
38473868static zend_always_inline zend_type_check_status i_zend_verify_type_assignable_zval (
3848- const zend_property_info * info , const zval * zv , bool strict ) {
3849- //zend_type type = info->type;
3850- //uint32_t type_mask;
3851- //uint8_t zv_type = Z_TYPE_P(zv);
3852-
3853- zend_type_check_status status = zend_check_type (& info -> type , zv , info -> ce , strict , 0 );
3854-
3855- /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */
3856- // TODO How to handle this in zend_check_type()?
3857- //if (strict) {
3858- // if ((type_mask & MAY_BE_DOUBLE) && zv_type == IS_LONG) {
3859- // return -1;
3860- // }
3861- // return 0;
3862- //}
3863-
3869+ const zend_property_info * info ,
3870+ const zval * zv ,
3871+ bool strict ,
3872+ zval * coerced_value
3873+ ) {
3874+ zend_type_check_status status = zend_check_type_ex (& info -> type , zv , info -> ce , strict , 0 , coerced_value );
38643875 return status ;
38653876}
38663877
@@ -3876,9 +3887,10 @@ ZEND_API bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference *ref,
38763887
38773888 ZEND_ASSERT (Z_TYPE_P (zv ) != IS_REFERENCE );
38783889 ZEND_REF_FOREACH_TYPE_SOURCES (ref , prop ) {
3879- zend_type_check_status result = i_zend_verify_type_assignable_zval (prop , zv , strict );
3890+ zval tmp ;
3891+ ZVAL_UNDEF (& tmp );
3892+ zend_type_check_status result = i_zend_verify_type_assignable_zval (prop , zv , strict , & tmp );
38803893 if (result == ZEND_TYPE_CHECK_INVALID ) {
3881- type_error :
38823894 zend_throw_ref_type_error_zval (prop , zv );
38833895 zval_ptr_dtor (& coerced_value );
38843896 return 0 ;
@@ -3887,29 +3899,22 @@ ZEND_API bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference *ref,
38873899 if (result == ZEND_TYPE_CHECK_MAY_COERCE ) {
38883900 if (!first_prop ) {
38893901 first_prop = prop ;
3890- ZVAL_COPY (& coerced_value , zv );
3891- if (!zend_coerce_weak_scalar_type_declaration (
3892- ZEND_TYPE_FULL_MASK (prop -> type ), & coerced_value )) {
3893- goto type_error ;
3894- }
3902+ ZVAL_COPY (& coerced_value , & tmp );
3903+ zval_ptr_dtor (& tmp );
38953904 } else if (Z_ISUNDEF (coerced_value )) {
38963905 /* A previous property did not require coercion, but this one does,
38973906 * so they are incompatible. */
3907+ zval_ptr_dtor (& tmp );
38983908 goto conflicting_coercion_error ;
38993909 } else {
3900- zval tmp ;
3901- ZVAL_COPY (& tmp , zv );
3902- if (!zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (prop -> type ), & tmp )) {
3903- zval_ptr_dtor (& tmp );
3904- goto type_error ;
3905- }
3906- if (!zend_is_identical (& coerced_value , & tmp )) {
3907- zval_ptr_dtor (& tmp );
3910+ bool is_identical = zend_is_identical (& coerced_value , & tmp );
3911+ zval_ptr_dtor (& tmp );
3912+ if (!is_identical ) {
39083913 goto conflicting_coercion_error ;
39093914 }
3910- zval_ptr_dtor (& tmp );
39113915 }
39123916 } else {
3917+ ZEND_ASSERT (Z_ISUNDEF (tmp ));
39133918 if (!first_prop ) {
39143919 first_prop = prop ;
39153920 } else if (!Z_ISUNDEF (coerced_value )) {
@@ -3987,26 +3992,22 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ui
39873992ZEND_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 ) {
39883993 zval * val = orig_val ;
39893994 if (Z_ISREF_P (val ) && ZEND_REF_HAS_TYPE_SOURCES (Z_REF_P (val ))) {
3990- int result ;
3991-
39923995 val = Z_REFVAL_P (val );
3993- result = i_zend_verify_type_assignable_zval (prop_info , val , strict );
3994- if (result == ZEND_TYPE_CHECK_VALID ) {
3996+ zval tmp ;
3997+
3998+ ZVAL_UNDEF (& tmp );
3999+ zend_type_check_status result = i_zend_verify_type_assignable_zval (prop_info , val , strict , & tmp );
4000+ if (EXPECTED (result == ZEND_TYPE_CHECK_VALID )) {
4001+ ZEND_ASSERT (Z_ISUNDEF (tmp ));
39954002 return true;
39964003 }
39974004
39984005 if (result == ZEND_TYPE_CHECK_MAY_COERCE ) {
3999- /* This is definitely an error, but we still need to determined why: Either because
4000- * the value is simply illegal for the type, or because or a conflicting coercion. */
4001- zval tmp ;
4002- ZVAL_COPY (& tmp , val );
4003- if (zend_coerce_weak_scalar_type_declaration (ZEND_TYPE_FULL_MASK (prop_info -> type ), & tmp )) {
4004- const zend_property_info * ref_prop = ZEND_REF_FIRST_SOURCE (Z_REF_P (orig_val ));
4005- zend_throw_ref_type_error_type (ref_prop , prop_info , val );
4006- zval_ptr_dtor (& tmp );
4007- return false;
4008- }
4006+ /* This is an error, because or a conflicting coercion. */
4007+ const zend_property_info * ref_prop = ZEND_REF_FIRST_SOURCE (Z_REF_P (orig_val ));
4008+ zend_throw_ref_type_error_type (ref_prop , prop_info , val );
40094009 zval_ptr_dtor (& tmp );
4010+ return false;
40104011 }
40114012 } else {
40124013 ZVAL_DEREF (val );
0 commit comments