diff options
Diffstat (limited to 'Zend/zend_execute.c')
-rw-r--r-- | Zend/zend_execute.c | 542 |
1 files changed, 323 insertions, 219 deletions
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 097137aa5c..153d8e3bc4 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -39,6 +39,7 @@ #include "zend_dtrace.h" #include "zend_inheritance.h" #include "zend_type_info.h" +#include "zend_smart_str.h" /* Virtual current working directory support */ #include "zend_virtual_cwd.h" @@ -642,14 +643,30 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_throw_non_object_erro } } +/* Test used to preserve old error messages for non-union types. + * We might want to canonicalize all type errors instead. */ +static zend_bool is_union_type(zend_type type) { + if (ZEND_TYPE_HAS_LIST(type)) { + return 1; + } + uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); + if (ZEND_TYPE_HAS_CLASS(type)) { + return type_mask_without_null != 0; + } + if (type_mask_without_null == MAY_BE_BOOL) { + return 0; + } + /* Check that only one bit is set. */ + return (type_mask_without_null & (type_mask_without_null - 1)) != 0; +} + static ZEND_COLD void zend_verify_type_error_common( const zend_function *zf, const zend_arg_info *arg_info, void **cache_slot, zval *value, const char **fname, const char **fsep, const char **fclass, - const char **need_msg, const char **need_kind, const char **need_or_null, - const char **given_msg, const char **given_kind) + zend_string **need_msg, const char **given_msg, const char **given_kind) { - zend_bool is_interface = 0; + smart_str str = {0}; *fname = ZSTR_VAL(zf->common.function_name); if (zf->common.scope) { *fsep = "::"; @@ -659,58 +676,69 @@ static ZEND_COLD void zend_verify_type_error_common( *fclass = ""; } - if (ZEND_TYPE_IS_CLASS(arg_info->type)) { + if (is_union_type(arg_info->type)) { + zend_string *type_str = zend_type_to_string(arg_info->type); + smart_str_appends(&str, "be of type "); + smart_str_append(&str, type_str); + zend_string_release(type_str); + } else if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { + zend_bool is_interface = 0; zend_class_entry *ce = *cache_slot; + if (!ce) { + ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), + (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); + } if (ce) { if (ce->ce_flags & ZEND_ACC_INTERFACE) { - *need_msg = "implement interface "; + smart_str_appends(&str, "implement interface "); is_interface = 1; } else { - *need_msg = "be an instance of "; + smart_str_appends(&str, "be an instance of "); } - *need_kind = ZSTR_VAL(ce->name); + smart_str_append(&str, ce->name); } else { /* We don't know whether it's a class or interface, assume it's a class */ + smart_str_appends(&str, "be an instance of "); + smart_str_append(&str, ZEND_TYPE_NAME(arg_info->type)); + } - *need_msg = "be an instance of "; - *need_kind = ZSTR_VAL(ZEND_TYPE_NAME(arg_info->type)); + if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { + smart_str_appends(&str, is_interface ? " or be null" : " or null"); } } else { uint32_t type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(arg_info->type); switch (type_mask) { case MAY_BE_OBJECT: - *need_msg = "be an "; - *need_kind = "object"; + smart_str_appends(&str, "be an object"); break; case MAY_BE_CALLABLE: - *need_msg = "be callable"; - *need_kind = ""; + smart_str_appends(&str, "be callable"); break; case MAY_BE_ITERABLE: - *need_msg = "be iterable"; - *need_kind = ""; + smart_str_appends(&str, "be iterable"); break; default: { - /* TODO: The zend_type_to_string() result is guaranteed interned here. - * It would be beter to switch all this code to use zend_string though. */ + /* Hack to print the type without null */ zend_type type = arg_info->type; ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL; - *need_msg = "be of the type "; - *need_kind = ZSTR_VAL(zend_type_to_string(type)); + zend_string *type_str = zend_type_to_string(type); + smart_str_appends(&str, "be of the type "); + smart_str_append(&str, type_str); + zend_string_release(type_str); break; } } - } - if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { - *need_or_null = is_interface ? " or be null" : " or null"; - } else { - *need_or_null = ""; + if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { + smart_str_appends(&str, " or null"); + } } + *need_msg = smart_str_extract(&str); + if (value) { - if (ZEND_TYPE_IS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) { + if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) { *given_msg = "instance of "; *given_kind = ZSTR_VAL(Z_OBJCE_P(value)->name); } else { @@ -729,7 +757,8 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( { zend_execute_data *ptr = EG(current_execute_data)->prev_execute_data; const char *fname, *fsep, *fclass; - const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind; + zend_string *need_msg; + const char *given_msg, *given_kind; if (EG(exception)) { /* The type verification itself might have already thrown an exception @@ -740,19 +769,21 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( if (value) { zend_verify_type_error_common( zf, arg_info, cache_slot, value, - &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind); + &fname, &fsep, &fclass, &need_msg, &given_msg, &given_kind); if (zf->common.type == ZEND_USER_FUNCTION) { if (ptr && ptr->func && ZEND_USER_CODE(ptr->func->common.type)) { - zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given, called in %s on line %d", - arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind, + zend_type_error("Argument %d passed to %s%s%s() must %s, %s%s given, called in %s on line %d", + arg_num, fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind, ZSTR_VAL(ptr->func->op_array.filename), ptr->opline->lineno); } else { - zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); + zend_type_error("Argument %d passed to %s%s%s() must %s, %s%s given", arg_num, fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind); } } else { - zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); + zend_type_error("Argument %d passed to %s%s%s() must %s, %s%s given", arg_num, fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind); } + + zend_string_release(need_msg); } else { zend_missing_arg_error(ptr); } @@ -760,42 +791,47 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( static zend_bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg) { - if (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE)) { - zend_bool dest; + zend_long lval; + double dval; + zend_string *str; + zend_bool bval; - if (!zend_parse_arg_bool_weak(arg, &dest)) { - return 0; + /* Type preference order: int -> float -> string -> bool */ + if (type_mask & MAY_BE_LONG) { + /* For an int|float union type and string value, + * determine chosen type by is_numeric_string() semantics. */ + if ((type_mask & MAY_BE_DOUBLE) && Z_TYPE_P(arg) == IS_STRING) { + zend_uchar type = is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), &lval, &dval, -1); + if (type == IS_LONG) { + zend_string_release(Z_STR_P(arg)); + ZVAL_LONG(arg, lval); + return 1; + } + if (type == IS_DOUBLE) { + zend_string_release(Z_STR_P(arg)); + ZVAL_DOUBLE(arg, dval); + return 1; + } + } else if (zend_parse_arg_long_weak(arg, &lval)) { + zval_ptr_dtor(arg); + ZVAL_LONG(arg, lval); + return 1; } + } + if ((type_mask & MAY_BE_DOUBLE) && zend_parse_arg_double_weak(arg, &dval)) { zval_ptr_dtor(arg); - ZVAL_BOOL(arg, dest); + ZVAL_DOUBLE(arg, dval); return 1; } - if (type_mask & MAY_BE_LONG) { - zend_long dest; - - if (!zend_parse_arg_long_weak(arg, &dest)) { - return 0; - } - zval_ptr_dtor(arg); - ZVAL_LONG(arg, dest); + if ((type_mask & MAY_BE_STRING) && zend_parse_arg_str_weak(arg, &str)) { + /* on success "arg" is converted to IS_STRING */ return 1; } - if (type_mask & MAY_BE_DOUBLE) { - double dest; - - if (!zend_parse_arg_double_weak(arg, &dest)) { - return 0; - } + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval)) { zval_ptr_dtor(arg); - ZVAL_DOUBLE(arg, dest); + ZVAL_BOOL(arg, bval); return 1; } - if (type_mask & MAY_BE_STRING) { - zend_string *dest; - - /* on success "arg" is converted to IS_STRING */ - return zend_parse_arg_str_weak(arg, &dest); - } return 0; } @@ -803,40 +839,48 @@ static zend_bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg /* Used to sanity-check internal arginfo types without performing any actual type conversions. */ static zend_bool zend_verify_weak_scalar_type_hint_no_sideeffect(uint32_t type_mask, zval *arg) { - if (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE)) { - zend_bool dest; - return zend_parse_arg_bool_weak(arg, &dest); - } + zend_long lval; + double dval; + zend_bool bval; + if (type_mask & MAY_BE_LONG) { - zend_long dest; if (Z_TYPE_P(arg) == IS_STRING) { /* Handle this case separately to avoid the "non well-formed" warning */ - double dval; zend_uchar type = is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, &dval, 1); if (type == IS_LONG) { return 1; } if (type == IS_DOUBLE) { - return !zend_isnan(dval) && ZEND_DOUBLE_FITS_LONG(dval); + if ((type_mask & MAY_BE_DOUBLE) + || (!zend_isnan(dval) && ZEND_DOUBLE_FITS_LONG(dval))) { + return 1; + } } - return 0; } - return zend_parse_arg_long_weak(arg, &dest); + if (zend_parse_arg_long_weak(arg, &lval)) { + return 1; + } } if (type_mask & MAY_BE_DOUBLE) { - double dest; if (Z_TYPE_P(arg) == IS_STRING) { /* Handle this case separately to avoid the "non well-formed" warning */ - return is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, NULL, 1) != 0; + if (is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, NULL, 1) != 0) { + return 1; + } + } + if (zend_parse_arg_double_weak(arg, &dval)) { + return 1; } - return zend_parse_arg_double_weak(arg, &dest); } - if (type_mask & MAY_BE_STRING) { - /* We don't call cast_object here, because this check must be side-effect free. As this - * is only used for a sanity check of arginfo/zpp consistency, it's okay if we accept - * more than actually allowed here. */ - return Z_TYPE_P(arg) < IS_STRING || Z_TYPE_P(arg) == IS_OBJECT; + /* We don't call cast_object here, because this check must be side-effect free. As this + * is only used for a sanity check of arginfo/zpp consistency, it's okay if we accept + * more than actually allowed here. */ + if ((type_mask & MAY_BE_STRING) && (Z_TYPE_P(arg) < IS_STRING || Z_TYPE_P(arg) == IS_OBJECT)) { + return 1; + } + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval)) { + return 1; } return 0; } @@ -850,12 +894,10 @@ ZEND_API zend_bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, z return 0; } } else if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) { - /* NULL may be accepted only by nullable hints (this is already checked) */ - if (is_internal_arg && (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))) { - /* As an exception, null is allowed for scalar types in weak mode. */ - return 1; - } - return 0; + /* NULL may be accepted only by nullable hints (this is already checked). + * As an exception for internal functions, null is allowed for scalar types in weak mode. */ + return is_internal_arg + && (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING)); } #if ZEND_DEBUG if (is_internal_arg) { @@ -883,59 +925,77 @@ ZEND_COLD zend_never_inline void zend_verify_property_type_error(zend_property_i zend_string_release(type_str); } -static zend_bool zend_resolve_class_type(zend_type *type, zend_class_entry *self_ce) { - zend_class_entry *ce; - zend_string *name = ZEND_TYPE_NAME(*type); +static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class_entry *self_ce) { if (zend_string_equals_literal_ci(name, "self")) { /* We need to explicitly check for this here, to avoid updating the type in the trait and * later using the wrong "self" when the trait is used in a class. */ if (UNEXPECTED((self_ce->ce_flags & ZEND_ACC_TRAIT) != 0)) { - zend_throw_error(NULL, "Cannot write a%s value to a 'self' typed static property of a trait", ZEND_TYPE_ALLOW_NULL(*type) ? " non-null" : ""); - return 0; + return NULL; } - ce = self_ce; + return self_ce; } else if (zend_string_equals_literal_ci(name, "parent")) { - if (UNEXPECTED(!self_ce->parent)) { - zend_throw_error(NULL, "Cannot access parent:: when current class scope has no parent"); - return 0; - } - ce = self_ce->parent; + return self_ce->parent; } else { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - return 0; - } + return zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); } - - zend_string_release(name); - *type = (zend_type) ZEND_TYPE_INIT_CE(ce, ZEND_TYPE_ALLOW_NULL(*type), 0); - return 1; } +static zend_bool zend_check_and_resolve_property_class_type( + zend_property_info *info, zend_class_entry *object_ce) { + zend_class_entry *ce; + if (ZEND_TYPE_HAS_LIST(info->type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(info->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry); + ce = resolve_single_class_type(name, info->ce); + if (!ce) { + continue; + } + zend_string_release(name); + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } else { + ce = ZEND_TYPE_LIST_GET_CE(*entry); + } + if (instanceof_function(object_ce, ce)) { + return 1; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return 0; + } else { + if (UNEXPECTED(ZEND_TYPE_HAS_NAME(info->type))) { + zend_string *name = ZEND_TYPE_NAME(info->type); + ce = resolve_single_class_type(name, info->ce); + if (UNEXPECTED(!ce)) { + return 0; + } + + zend_string_release(name); + ZEND_TYPE_SET_CE(info->type, ce); + } else { + ce = ZEND_TYPE_CE(info->type); + } + return instanceof_function(object_ce, ce); + } +} static zend_always_inline zend_bool i_zend_check_property_type(zend_property_info *info, zval *property, zend_bool strict) { ZEND_ASSERT(!Z_ISREF_P(property)); - if (ZEND_TYPE_IS_CLASS(info->type)) { - if (UNEXPECTED(Z_TYPE_P(property) != IS_OBJECT)) { - return Z_TYPE_P(property) == IS_NULL && ZEND_TYPE_ALLOW_NULL(info->type); - } - - if (UNEXPECTED(!ZEND_TYPE_IS_CE(info->type)) && UNEXPECTED(!zend_resolve_class_type(&info->type, info->ce))) { - return 0; - } + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) { + return 1; + } - return instanceof_function(Z_OBJCE_P(property), ZEND_TYPE_CE(info->type)); + if (ZEND_TYPE_HAS_CLASS(info->type) && Z_TYPE_P(property) == IS_OBJECT + && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property))) { + return 1; } ZEND_ASSERT(!(ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_CALLABLE)); - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) { + if ((ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_ITERABLE) && zend_is_iterable(property)) { return 1; - } else if (ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_ITERABLE) { - return zend_is_iterable(property); - } else { - return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0); } + return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0); } static zend_bool zend_always_inline i_zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict) @@ -981,43 +1041,70 @@ static zend_always_inline zend_bool zend_check_type( arg = Z_REFVAL_P(arg); } - if (ZEND_TYPE_IS_CLASS(type)) { + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(arg)))) { + return 1; + } + + if (ZEND_TYPE_HAS_CLASS(type) && Z_TYPE_P(arg) == IS_OBJECT) { zend_class_entry *ce; - if (EXPECTED(*cache_slot)) { - ce = (zend_class_entry *) *cache_slot; + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + if (*cache_slot) { + ce = *cache_slot; + } else { + ce = zend_fetch_class(ZEND_TYPE_LIST_GET_NAME(entry), + (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); + if (!ce) { + cache_slot++; + continue; + } + *cache_slot = ce; + } + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + return 1; + } + cache_slot++; + } ZEND_TYPE_LIST_FOREACH_END(); } else { - ce = zend_fetch_class(ZEND_TYPE_NAME(type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); - if (UNEXPECTED(!ce)) { - return Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type); + if (EXPECTED(*cache_slot)) { + ce = (zend_class_entry *) *cache_slot; + } else { + ce = zend_fetch_class(ZEND_TYPE_NAME(type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); + if (UNEXPECTED(!ce)) { + goto builtin_types; + } + *cache_slot = (void *) ce; + } + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + return 1; } - *cache_slot = (void *) ce; - } - if (EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { - return instanceof_function(Z_OBJCE_P(arg), ce); } - return Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type); - } else if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(arg)))) { - return 1; } +builtin_types: type_mask = ZEND_TYPE_FULL_MASK(type); - if (type_mask & MAY_BE_CALLABLE) { - return zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL); - } else if (type_mask & MAY_BE_ITERABLE) { - return zend_is_iterable(arg); - } else if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) { - return 0; /* we cannot have conversions for typed refs */ - } else if (is_internal && is_return_type) { + if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { + return 1; + } + if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { + return 1; + } + if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) { + /* We cannot have conversions for typed refs. */ + return 0; + } + if (is_internal && is_return_type) { /* For internal returns, the type has to match exactly, because we're not * going to check it for non-debug builds, and there will be no chance to * apply coercions. */ return 0; - } else { - return zend_verify_scalar_type_hint(type_mask, arg, - is_return_type ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES(), - is_internal); } + return zend_verify_scalar_type_hint(type_mask, arg, + is_return_type ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES(), + is_internal); + /* Special handling for IS_VOID is not necessary (for return types), * because this case is already checked at compile-time. */ } @@ -1139,14 +1226,17 @@ static ZEND_COLD void zend_verify_return_error( { const zend_arg_info *arg_info = &zf->common.arg_info[-1]; const char *fname, *fsep, *fclass; - const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind; + zend_string *need_msg; + const char *given_msg, *given_kind; zend_verify_type_error_common( zf, arg_info, cache_slot, value, - &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind); + &fname, &fsep, &fclass, &need_msg, &given_msg, &given_kind); + + zend_type_error("Return value of %s%s%s() must %s, %s%s returned", + fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind); - zend_type_error("Return value of %s%s%s() must %s%s%s, %s%s returned", - fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); + zend_string_release(need_msg); } #if ZEND_DEBUG @@ -1155,14 +1245,15 @@ static ZEND_COLD void zend_verify_internal_return_error( { const zend_arg_info *arg_info = &zf->common.arg_info[-1]; const char *fname, *fsep, *fclass; - const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind; + zend_string *need_msg; + const char *given_msg, *given_kind; zend_verify_type_error_common( zf, arg_info, cache_slot, value, - &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind); + &fname, &fsep, &fclass, &need_msg, &given_msg, &given_kind); - zend_error_noreturn(E_CORE_ERROR, "Return value of %s%s%s() must %s%s%s, %s%s returned", - fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); + zend_error_noreturn(E_CORE_ERROR, "Return value of %s%s%s() must %s, %s%s returned", + fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind); } static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, const char *returned_msg, const char *returned_kind) @@ -1217,18 +1308,6 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * static ZEND_COLD int zend_verify_missing_return_type(const zend_function *zf, void **cache_slot) { /* VERIFY_RETURN_TYPE is not emitted for "void" functions, so this is always an error. */ - zend_arg_info *ret_info = zf->common.arg_info - 1; - - // TODO: Eliminate this! - zend_class_entry *ce = NULL; - if (ZEND_TYPE_IS_CLASS(ret_info->type)) { - if (UNEXPECTED(!*cache_slot)) { - zend_class_entry *ce = zend_fetch_class(ZEND_TYPE_NAME(ret_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); - if (ce) { - *cache_slot = (void *) ce; - } - } - } zend_verify_return_error(zf, cache_slot, NULL); return 0; } @@ -1572,25 +1651,25 @@ static zend_property_info *zend_get_prop_not_accepting_double(zend_reference *re return NULL; } -static ZEND_COLD zend_long zend_throw_incdec_ref_error(zend_reference *ref OPLINE_DC) +static ZEND_COLD zend_long zend_throw_incdec_ref_error( + zend_reference *ref, zend_property_info *error_prop OPLINE_DC) { - zend_property_info *error_prop = zend_get_prop_not_accepting_double(ref); - /* Currently there should be no way for a typed reference to accept both int and double. - * Generalize this and the related property code once this becomes possible. */ - ZEND_ASSERT(error_prop); + zend_string *type_str = zend_type_to_string(error_prop->type); if (ZEND_IS_INCREMENT(opline->opcode)) { zend_type_error( - "Cannot increment a reference held by property %s::$%s of type %sint past its maximal value", + "Cannot increment a reference held by property %s::$%s of type %s past its maximal value", ZSTR_VAL(error_prop->ce->name), zend_get_unmangled_property_name(error_prop->name), - ZEND_TYPE_ALLOW_NULL(error_prop->type) ? "?" : ""); + ZSTR_VAL(type_str)); + zend_string_release(type_str); return ZEND_LONG_MAX; } else { zend_type_error( - "Cannot decrement a reference held by property %s::$%s of type %sint past its minimal value", + "Cannot decrement a reference held by property %s::$%s of type %s past its minimal value", ZSTR_VAL(error_prop->ce->name), zend_get_unmangled_property_name(error_prop->name), - ZEND_TYPE_ALLOW_NULL(error_prop->type) ? "?" : ""); + ZSTR_VAL(type_str)); + zend_string_release(type_str); return ZEND_LONG_MIN; } } @@ -1632,8 +1711,11 @@ static void zend_incdec_typed_ref(zend_reference *ref, zval *copy OPLINE_DC EXEC } if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(copy) == IS_LONG) { - zend_long val = zend_throw_incdec_ref_error(ref OPLINE_CC); - ZVAL_LONG(var_ptr, val); + zend_property_info *error_prop = zend_get_prop_not_accepting_double(ref); + if (UNEXPECTED(error_prop)) { + zend_long val = zend_throw_incdec_ref_error(ref, error_prop OPLINE_CC); + ZVAL_LONG(var_ptr, val); + } } else if (UNEXPECTED(!zend_verify_ref_assignable_zval(ref, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, copy); @@ -1660,8 +1742,10 @@ static void zend_incdec_typed_prop(zend_property_info *prop_info, zval *var_ptr, } if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(copy) == IS_LONG) { - zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); - ZVAL_LONG(var_ptr, val); + if (!(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); + ZVAL_LONG(var_ptr, val); + } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, copy); @@ -1679,7 +1763,8 @@ static void zend_pre_incdec_property_zval(zval *prop, zend_property_info *prop_i } else { fast_long_decrement_function(prop); } - if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)) { + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) + && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); ZVAL_LONG(prop, val); } @@ -1717,7 +1802,8 @@ static void zend_post_incdec_property_zval(zval *prop, zend_property_info *prop_ } else { fast_long_decrement_function(prop); } - if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)) { + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) + && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); ZVAL_LONG(prop, val); } @@ -2942,29 +3028,22 @@ ZEND_API ZEND_COLD void zend_throw_conflicting_coercion_error(zend_property_info /* 1: valid, 0: invalid, -1: may be valid after type coercion */ static zend_always_inline int i_zend_verify_type_assignable_zval( - zend_type *type_ptr, zend_class_entry *self_ce, zval *zv, zend_bool strict) { - zend_type type = *type_ptr; + zend_property_info *info, zval *zv, zend_bool strict) { + zend_type type = info->type; uint32_t type_mask; zend_uchar zv_type = Z_TYPE_P(zv); - if (ZEND_TYPE_IS_CLASS(type)) { - if (ZEND_TYPE_ALLOW_NULL(type) && zv_type == IS_NULL) { - return 1; - } - if (!ZEND_TYPE_IS_CE(type)) { - if (!zend_resolve_class_type(type_ptr, self_ce)) { - return 0; - } - type = *type_ptr; - } - return zv_type == IS_OBJECT && instanceof_function(Z_OBJCE_P(zv), ZEND_TYPE_CE(type)); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, zv_type))) { + return 1; } - if (ZEND_TYPE_CONTAINS_CODE(type, zv_type)) { + if (ZEND_TYPE_HAS_CLASS(type) && zv_type == IS_OBJECT + && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv))) { return 1; } type_mask = ZEND_TYPE_FULL_MASK(type); + ZEND_ASSERT(!(type_mask & MAY_BE_CALLABLE)); if (type_mask & MAY_BE_ITERABLE) { return zend_is_iterable(zv); } @@ -2977,13 +3056,14 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( return 0; } - /* No weak conversions for arrays and objects */ - if (type_mask & (MAY_BE_ARRAY|MAY_BE_OBJECT)) { + /* NULL may be accepted only by nullable hints (this is already checked) */ + if (zv_type == IS_NULL) { return 0; } - /* NULL may be accepted only by nullable hints (this is already checked) */ - if (zv_type == IS_NULL) { + /* Does not contain any type to which a coercion is possible */ + if (!(type_mask & (MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING)) + && (type_mask & MAY_BE_BOOL) != MAY_BE_BOOL) { return 0; } @@ -2996,39 +3076,62 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference zend_property_info *prop; /* The value must satisfy each property type, and coerce to the same value for each property - * type. Right now, the latter rule means that *if* coercion is necessary, then all types - * must be the same (modulo nullability). To handle this, remember the first type we see and - * compare against it when coercion becomes necessary. */ - zend_property_info *seen_prop = NULL; - uint32_t seen_type_mask; - zend_bool needs_coercion = 0; + * type. Remember the first coerced type and value we've seen for this purpose. */ + zend_property_info *first_prop = NULL; + zval coerced_value; + ZVAL_UNDEF(&coerced_value); ZEND_ASSERT(Z_TYPE_P(zv) != IS_REFERENCE); ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { - int result = i_zend_verify_type_assignable_zval(&prop->type, prop->ce, zv, strict); + int result = i_zend_verify_type_assignable_zval(prop, zv, strict); if (result == 0) { +type_error: zend_throw_ref_type_error_zval(prop, zv); + zval_ptr_dtor(&coerced_value); return 0; } if (result < 0) { - needs_coercion = 1; - } - - if (!seen_prop) { - seen_prop = prop; - seen_type_mask = ZEND_TYPE_IS_CLASS(prop->type) - ? MAY_BE_OBJECT : ZEND_TYPE_PURE_MASK_WITHOUT_NULL(prop->type); - } else if (needs_coercion - && seen_type_mask != ZEND_TYPE_PURE_MASK_WITHOUT_NULL(prop->type)) { - zend_throw_conflicting_coercion_error(seen_prop, prop, zv); - return 0; + if (!first_prop) { + first_prop = prop; + ZVAL_COPY(&coerced_value, zv); + if (!zend_verify_weak_scalar_type_hint( + ZEND_TYPE_FULL_MASK(prop->type), &coerced_value)) { + goto type_error; + } + } else if (Z_ISUNDEF(coerced_value)) { + /* A previous property did not require coercion, but this one does, + * so they are incompatible. */ + goto conflicting_coercion_error; + } else { + zval tmp; + ZVAL_COPY(&tmp, zv); + if (!zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop->type), &tmp)) { + zval_ptr_dtor(&tmp); + goto type_error; + } + if (!zend_is_identical(&coerced_value, &tmp)) { + zval_ptr_dtor(&tmp); + goto conflicting_coercion_error; + } + } + } else { + if (!first_prop) { + first_prop = prop; + } else if (!Z_ISUNDEF(coerced_value)) { + /* A previous property required coercion, but this one doesn't, + * so they are incompatible. */ +conflicting_coercion_error: + zend_throw_conflicting_coercion_error(first_prop, prop, zv); + zval_ptr_dtor(&coerced_value); + return 0; + } } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); - if (UNEXPECTED(needs_coercion && !zend_verify_weak_scalar_type_hint(seen_type_mask, zv))) { - zend_throw_ref_type_error_zval(seen_prop, zv); - return 0; + if (!Z_ISUNDEF(coerced_value)) { + zval_ptr_dtor(zv); + ZVAL_COPY_VALUE(zv, &coerced_value); } return 1; @@ -3080,22 +3183,23 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_propert int result; val = Z_REFVAL_P(val); - result = i_zend_verify_type_assignable_zval(&prop_info->type, prop_info->ce, val, strict); + result = i_zend_verify_type_assignable_zval(prop_info, val, strict); if (result > 0) { return 1; } if (result < 0) { - zend_property_info *ref_prop = ZEND_REF_FIRST_SOURCE(Z_REF_P(orig_val)); - if (ZEND_TYPE_PURE_MASK_WITHOUT_NULL(prop_info->type) - != ZEND_TYPE_PURE_MASK_WITHOUT_NULL(ref_prop->type)) { - /* Invalid due to conflicting coercion */ + /* This is definitely an error, but we still need to determined why: Either because + * the value is simply illegal for the type, or because or a conflicting coercion. */ + zval tmp; + ZVAL_COPY(&tmp, val); + if (zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop_info->type), &tmp)) { + zend_property_info *ref_prop = ZEND_REF_FIRST_SOURCE(Z_REF_P(orig_val)); zend_throw_ref_type_error_type(ref_prop, prop_info, val); + zval_ptr_dtor(&tmp); return 0; } - if (zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop_info->type), val)) { - return 1; - } + zval_ptr_dtor(&tmp); } } else { ZVAL_DEREF(val); |