Skip to content

Commit 79c99b4

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

3 files changed

Lines changed: 139 additions & 136 deletions

File tree

Zend/zend_execute.c

Lines changed: 131 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
784839
static 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

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

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

966993
ZEND_API bool zend_check_user_type_slow(
@@ -978,14 +1005,8 @@ ZEND_API bool zend_check_user_type_slow(
9781005

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

38473868
static 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
39873992
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) {
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

Comments
 (0)