Skip to content

Commit 1700561

Browse files
committed
Pass a coercible zval* to do the coercion during the type check if required
1 parent e6335f8 commit 1700561

3 files changed

Lines changed: 143 additions & 141 deletions

File tree

Zend/zend_execute.c

Lines changed: 137 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
783843
static 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

936973
static 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

953985
static 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

970997
ZEND_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

10021029
static 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

38683889
static 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
40084014
ZEND_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

Comments
 (0)