Skip to content

Commit 2a23b94

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

3 files changed

Lines changed: 138 additions & 136 deletions

File tree

Zend/zend_execute.c

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

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

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

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

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

38633884
static 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
40034009
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) {
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

Comments
 (0)