diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 211a5d14e6c3a..ccb48a779b124 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -552,11 +552,7 @@ ZEND_API bool ZEND_FASTCALL zend_parse_arg_long_weak(const zval *arg, zend_long } else { zend_long lval = zend_dval_to_lval(Z_DVAL_P(arg)); if (UNEXPECTED(!zend_is_long_compatible(Z_DVAL_P(arg), lval))) { - /* Check arg_num is not (uint32_t)-1, as otherwise its called by - * zend_verify_weak_scalar_type_hint_no_sideeffect() */ - if (arg_num != (uint32_t)-1) { - zend_incompatible_double_to_long_error(Z_DVAL_P(arg)); - } + zend_incompatible_double_to_long_error(Z_DVAL_P(arg)); if (UNEXPECTED(EG(exception))) { return 0; } @@ -580,11 +576,7 @@ ZEND_API bool ZEND_FASTCALL zend_parse_arg_long_weak(const zval *arg, zend_long lval = zend_dval_to_lval(d); /* This only checks for a fractional part as if doesn't fit it already throws a TypeError */ if (UNEXPECTED(!zend_is_long_compatible(d, lval))) { - /* Check arg_num is not (uint32_t)-1, as otherwise its called by - * zend_verify_weak_scalar_type_hint_no_sideeffect() */ - if (arg_num != (uint32_t)-1) { - zend_incompatible_string_to_long_error(Z_STR_P(arg)); - } + zend_incompatible_string_to_long_error(Z_STR_P(arg)); if (UNEXPECTED(EG(exception))) { return 0; } @@ -4030,7 +4022,7 @@ static zend_always_inline bool zend_is_callable_check_func(const zval *callable, } /* }}} */ -ZEND_API zend_string *zend_get_callable_name_ex(zval *callable, const zend_object *object) /* {{{ */ +ZEND_API zend_string *zend_get_callable_name_ex(const zval *callable, const zend_object *object) /* {{{ */ { try_again: switch (Z_TYPE_P(callable)) { @@ -4088,7 +4080,7 @@ ZEND_API zend_string *zend_get_callable_name_ex(zval *callable, const zend_objec } /* }}} */ -ZEND_API zend_string *zend_get_callable_name(zval *callable) /* {{{ */ +ZEND_API zend_string *zend_get_callable_name(const zval *callable) /* {{{ */ { return zend_get_callable_name_ex(callable, NULL); } @@ -4205,7 +4197,7 @@ ZEND_API bool zend_is_callable_at_frame( } /* }}} */ -ZEND_API bool zend_is_callable_ex(zval *callable, zend_object *object, uint32_t check_flags, zend_string **callable_name, zend_fcall_info_cache *fcc, char **error) /* {{{ */ +ZEND_API bool zend_is_callable_ex(const zval *callable, zend_object *object, uint32_t check_flags, zend_string **callable_name, zend_fcall_info_cache *fcc, char **error) /* {{{ */ { /* Determine callability at the first parent user frame. */ const zend_execute_data *frame = EG(current_execute_data); @@ -4220,13 +4212,13 @@ ZEND_API bool zend_is_callable_ex(zval *callable, zend_object *object, uint32_t return ret; } -ZEND_API bool zend_is_callable(zval *callable, uint32_t check_flags, zend_string **callable_name) /* {{{ */ +ZEND_API bool zend_is_callable(const zval *callable, uint32_t check_flags, zend_string **callable_name) /* {{{ */ { return zend_is_callable_ex(callable, NULL, check_flags, callable_name, NULL, NULL); } /* }}} */ -ZEND_API zend_result zend_fcall_info_init(zval *callable, uint32_t check_flags, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string **callable_name, char **error) /* {{{ */ +ZEND_API zend_result zend_fcall_info_init(const zval *callable, uint32_t check_flags, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string **callable_name, char **error) /* {{{ */ { if (!zend_is_callable_ex(callable, NULL, check_flags, callable_name, fcc, error)) { return FAILURE; diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 60acbd63044d7..e56ded4e8f1b5 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -410,13 +410,13 @@ ZEND_API ZEND_COLD void zend_wrong_property_read(const zval *object, zval *prope #define IS_CALLABLE_SUPPRESS_DEPRECATIONS (1<<1) ZEND_API void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc); -ZEND_API zend_string *zend_get_callable_name_ex(zval *callable, const zend_object *object); -ZEND_API zend_string *zend_get_callable_name(zval *callable); +ZEND_API zend_string *zend_get_callable_name_ex(const zval *callable, const zend_object *object); +ZEND_API zend_string *zend_get_callable_name(const zval *callable); ZEND_API bool zend_is_callable_at_frame( const zval *callable, zend_object *object, const zend_execute_data *frame, uint32_t check_flags, zend_fcall_info_cache *fcc, char **error); -ZEND_API bool zend_is_callable_ex(zval *callable, zend_object *object, uint32_t check_flags, zend_string **callable_name, zend_fcall_info_cache *fcc, char **error); -ZEND_API bool zend_is_callable(zval *callable, uint32_t check_flags, zend_string **callable_name); +ZEND_API bool zend_is_callable_ex(const zval *callable, zend_object *object, uint32_t check_flags, zend_string **callable_name, zend_fcall_info_cache *fcc, char **error); +ZEND_API bool zend_is_callable(const zval *callable, uint32_t check_flags, zend_string **callable_name); ZEND_API const char *zend_get_module_version(const char *module_name); ZEND_API zend_result zend_get_module_started(const char *module_name); @@ -701,7 +701,7 @@ ZEND_API zend_result _call_user_function_impl(zval *object, zval *function_name, * fci->params = NULL; * The callable_name argument may be NULL. */ -ZEND_API zend_result zend_fcall_info_init(zval *callable, uint32_t check_flags, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string **callable_name, char **error); +ZEND_API zend_result zend_fcall_info_init(const zval *callable, uint32_t check_flags, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string **callable_name, char **error); /** Clear arguments connected with zend_fcall_info *fci * If free_mem is not zero then the params array gets free'd as well @@ -2482,7 +2482,7 @@ static zend_always_inline bool zend_parse_arg_resource(zval *arg, zval **dest, b return 1; } -static zend_always_inline bool zend_parse_arg_func(zval *arg, zend_fcall_info *dest_fci, zend_fcall_info_cache *dest_fcc, bool check_null, char **error, bool free_trampoline) +static zend_always_inline bool zend_parse_arg_func(const zval *arg, zend_fcall_info *dest_fci, zend_fcall_info_cache *dest_fcc, bool check_null, char **error, bool free_trampoline) { if (check_null && UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) { dest_fci->size = 0; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 37278c5cb9a23..54511b0527577 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -730,13 +730,71 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( zend_string_release(need_msg); } -static bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg) +typedef enum { + ZEND_TYPE_CHECK_VALID, + ZEND_TYPE_CHECK_INVALID, + ZEND_TYPE_CHECK_MAY_COERCE, +} zend_type_check_status; + +static const zend_class_entry *resolve_single_class_type( + zend_string *name, + const zend_class_entry *scope) { + if (zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_SELF))) { + /* If we don't have a scope, returning the NULL pointer is fine as the error handling is done on the call site */ + return scope; + } else if (UNEXPECTED(zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_PARENT)))) { // Parent as a type is extremely uncommon + return scope ? scope->parent : NULL; + } else { + return zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + } +} + +static zend_always_inline const zend_class_entry *zend_ce_from_type( + const zend_type *type, + const zend_class_entry *scope +) { + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*type)); + zend_string *name = ZEND_TYPE_NAME(*type); + if (ZSTR_HAS_CE_CACHE(name)) { + zend_class_entry *ce = ZSTR_GET_CE_CACHE(name); + if (EXPECTED(ce)) { + return ce; + } + } + return resolve_single_class_type(name, scope); +} + +static bool zend_check_intersection_type_from_list( + const zend_type_list *intersection_type_list, + const zend_class_entry *arg_ce, + const zend_class_entry *scope +) { + const zend_type *list_type; + ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) { + const zend_class_entry *ce = zend_ce_from_type(list_type, scope); + /* If type is not an instance of one of the types taking part in the + * intersection it cannot be a valid instance of the whole intersection type. */ + if (UNEXPECTED(!ce || !instanceof_function(arg_ce, ce))) { + return false; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return true; +} + +/* Usually coerce_arg will be the same pointer as arg */ +static bool zend_coerce_weak_scalar_type_declaration(uint32_t type_mask, const zval *arg, zval *coerce_arg) { zend_long lval; double dval; zend_string *str; bool bval; + ZEND_ASSERT(!Z_ISREF_P(arg)); + ZEND_ASSERT(!Z_ISREF_P(coerce_arg)); + if (UNEXPECTED(Z_ISUNDEF_P(coerce_arg))) { + ZVAL_COPY(coerce_arg, arg); + } + /* Type preference order: int -> float -> string -> bool */ if (type_mask & MAY_BE_LONG) { /* For an int|float union type and string value, @@ -744,96 +802,220 @@ static bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg) if ((type_mask & MAY_BE_DOUBLE) && Z_TYPE_P(arg) == IS_STRING) { uint8_t type = is_numeric_str_function(Z_STR_P(arg), &lval, &dval); if (type == IS_LONG) { - zend_string_release(Z_STR_P(arg)); - ZVAL_LONG(arg, lval); + zend_string_release(Z_STR_P(coerce_arg)); + ZVAL_LONG(coerce_arg, lval); return true; } if (type == IS_DOUBLE) { - zend_string_release(Z_STR_P(arg)); - ZVAL_DOUBLE(arg, dval); + zend_string_release(Z_STR_P(coerce_arg)); + ZVAL_DOUBLE(coerce_arg, dval); return true; } } else if (zend_parse_arg_long_weak(arg, &lval, 0)) { - zval_ptr_dtor(arg); - ZVAL_LONG(arg, lval); + zval_ptr_dtor(coerce_arg); + ZVAL_LONG(coerce_arg, lval); return true; } else if (UNEXPECTED(EG(exception))) { return false; } } if ((type_mask & MAY_BE_DOUBLE) && zend_parse_arg_double_weak(arg, &dval, 0)) { - zval_ptr_dtor(arg); - ZVAL_DOUBLE(arg, dval); + zval_ptr_dtor(coerce_arg); + ZVAL_DOUBLE(coerce_arg, dval); return true; } - if ((type_mask & MAY_BE_STRING) && zend_parse_arg_str_weak(arg, &str, 0)) { + if ((type_mask & MAY_BE_STRING) && zend_parse_arg_str_weak(coerce_arg, &str, 0)) { /* on success "arg" is converted to IS_STRING */ return true; } if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval, 0)) { - zval_ptr_dtor(arg); - ZVAL_BOOL(arg, bval); + zval_ptr_dtor(coerce_arg); + ZVAL_BOOL(coerce_arg, bval); return true; } return false; } -#if ZEND_DEBUG -static bool can_convert_to_string(const zval *zv) { - /* 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 (Z_TYPE_P(zv) == IS_OBJECT) { - return Z_OBJ_HT_P(zv)->cast_object != zend_std_cast_object_tostring - || Z_OBJCE_P(zv)->__tostring; +static zend_type_check_status zend_check_type_slow( + const zend_type *type, + const zval *arg, + const zend_class_entry *scope, + bool strict_types, + /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */ + const uint32_t callable_check_flag, + /* Usually the same pointer as arg */ + zval *coerce_arg +) { + if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { + const zend_class_entry *arg_ce = Z_OBJCE_P(arg); + if (EXPECTED(ZEND_TYPE_HAS_NAME(*type))) { + const zend_class_entry *ce = zend_ce_from_type(type, scope); + /* If we have a CE we check if it satisfies the type constraint, + * otherwise it will check if a standard type satisfies it. */ + if (ce && instanceof_function(arg_ce, ce)) { + return ZEND_TYPE_CHECK_VALID; + } + } else { + ZEND_ASSERT(ZEND_TYPE_HAS_LIST(*type)); + if (ZEND_TYPE_IS_INTERSECTION(*type)) { + return zend_check_intersection_type_from_list(ZEND_TYPE_LIST(*type), arg_ce, scope) + ? ZEND_TYPE_CHECK_VALID : ZEND_TYPE_CHECK_INVALID; + } else { + /* In a union type may be of simple atomic types or a DNF type */ + const zend_type *list_type; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { + if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { + if (zend_check_intersection_type_from_list(ZEND_TYPE_LIST(*list_type), arg_ce, scope)) { + return ZEND_TYPE_CHECK_VALID; + } + } else { + ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); + const zend_class_entry *ce = zend_ce_from_type(list_type, scope); + /* Instance of a single type part of a union is sufficient to pass the type check */ + if (ce && instanceof_function(arg_ce, ce)) { + return ZEND_TYPE_CHECK_VALID; + } + } + } ZEND_TYPE_LIST_FOREACH_END(); + } + } } - return Z_TYPE_P(zv) <= IS_STRING; -} -/* Used to sanity-check internal arginfo types without performing any actual type conversions. */ -static bool zend_verify_weak_scalar_type_hint_no_sideeffect(uint32_t type_mask, const zval *arg) -{ - zend_long lval; - double dval; - bool bval; + const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type); - /* Pass (uint32_t)-1 as arg_num to indicate to ZPP not to emit any deprecation notice, - * this is needed because the version with side effects also uses 0 (e.g. for typed properties) */ - if ((type_mask & MAY_BE_LONG) && zend_parse_arg_long_weak(arg, &lval, (uint32_t)-1)) { - return true; + /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */ + if ((type_mask & MAY_BE_DOUBLE) && Z_TYPE_P(arg) == IS_LONG) { + if (coerce_arg) { + const double dval = (double)Z_LVAL_P(arg); + ZVAL_DOUBLE(coerce_arg, dval); + } + return ZEND_TYPE_CHECK_MAY_COERCE; } - if ((type_mask & MAY_BE_DOUBLE) && zend_parse_arg_double_weak(arg, &dval, (uint32_t)-1)) { - return true; + /* Need to suppress deprecation for internal functions, otherwise two deprecation notice would be emitted + * when ZPP parses the zval into an FCI/FCC pair. */ + if ((type_mask & MAY_BE_CALLABLE) && + zend_is_callable(arg, callable_check_flag, NULL)) { + return ZEND_TYPE_CHECK_VALID; } - if ((type_mask & MAY_BE_STRING) && can_convert_to_string(arg)) { - return true; + /* TODO: move to return type check? */ + if ( + (type_mask & MAY_BE_STATIC) + && scope != NULL + && Z_TYPE_P(arg) == IS_OBJECT + && instanceof_function(Z_OBJCE_P(arg), scope) + ) { + return ZEND_TYPE_CHECK_VALID; } - if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval, (uint32_t)-1)) { - return true; + + /* Only scalar types may coerce to other scalar types */ + if ( + !strict_types + && Z_TYPE_P(arg) > IS_NULL + && (type_mask & (MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_BOOL)) + ) { + if (coerce_arg) { + zend_type_check_status status = zend_coerce_weak_scalar_type_declaration(type_mask, arg, coerce_arg) + ? ZEND_TYPE_CHECK_MAY_COERCE : ZEND_TYPE_CHECK_INVALID; + return status; + } + if (Z_TYPE_P(arg) <= IS_STRING) { + return ZEND_TYPE_CHECK_MAY_COERCE; + } + /* Stringable object pass a string type check */ + // TODO: Need to fix GMP and COM variant classes + if (Z_TYPE_P(arg) == IS_OBJECT && (type_mask & MAY_BE_STRING) && Z_OBJCE_P(arg)->__tostring != NULL) { + return ZEND_TYPE_CHECK_MAY_COERCE; + } } - return false; + return ZEND_TYPE_CHECK_INVALID; } -#endif -ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg) -{ - if (UNEXPECTED(strict)) { - /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */ - if (!(type_mask & MAY_BE_DOUBLE) || Z_TYPE_P(arg) != IS_LONG) { - return 0; +static zend_always_inline zend_type_check_status zend_check_type_ex( + const zend_type *type, + const zval *arg, + const zend_class_entry *scope, + bool strict_types, + /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */ + const uint32_t callable_check_flag, + zval *coerce_arg +) { + if (UNEXPECTED(Z_ISREF_P(arg))) { + /* Cannot coerce typed references */ + strict_types |= ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(arg)); + if (arg == coerce_arg) { + arg = coerce_arg = Z_REFVAL_P(coerce_arg); + } else { + arg = Z_REFVAL_P(arg); } - } else if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) { - /* 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) { - return zend_verify_weak_scalar_type_hint_no_sideeffect(type_mask, arg); + + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(*type, Z_TYPE_P(arg)))) { + return ZEND_TYPE_CHECK_VALID; } -#endif - return zend_verify_weak_scalar_type_hint(type_mask, arg); + return zend_check_type_slow(type, arg, scope, strict_types, callable_check_flag, coerce_arg); +} + +static zend_always_inline zend_type_check_status zend_check_type( + const zend_type *type, + const zval *arg, + const zend_class_entry *scope, + bool strict_types, + /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */ + const uint32_t callable_check_flag +) { + return zend_check_type_ex(type, arg, scope, strict_types, callable_check_flag, NULL); +} + +static bool zend_check_type_and_coerce( + const zend_type *type, + zval *arg, + const zend_class_entry *scope, + bool strict_types, + /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */ + const uint32_t callable_check_flag +) { + zend_type_check_status status = zend_check_type_ex(type, arg, scope, strict_types, callable_check_flag, arg); + return status != ZEND_TYPE_CHECK_INVALID; +} + +static bool zend_check_type_and_coerce_slow( + const zend_type *type, + zval *arg, + const zend_class_entry *scope, + bool strict_types, + /* This is needed to pass IS_CALLABLE_SUPPRESS_DEPRECATIONS for internal functions */ + const uint32_t callable_check_flag +) { + zend_type_check_status status = zend_check_type_slow(type, arg, scope, strict_types, callable_check_flag, arg); + return status != ZEND_TYPE_CHECK_INVALID; +} + +ZEND_API bool zend_check_user_type_slow( + const zend_type *type, zval *arg, const zend_reference *ref, bool is_return_type) +{ + bool strict = ref || (is_return_type ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES()); + return zend_check_type_and_coerce_slow( + type, + arg, + zend_get_called_scope(EG(current_execute_data)), + strict, + 0 + ); +} + +static zend_always_inline bool i_zend_verify_property_type(const zend_property_info *info, zval *property, bool strict) +{ + bool status = zend_check_type_and_coerce(&info->type, property, info->ce, strict, 0); + if (EXPECTED(status)) { + return true; + } + + zend_verify_property_type_error(info, property); + return false; +} + +ZEND_API bool zend_never_inline zend_verify_property_type(const zend_property_info *info, zval *property, bool strict) { + return i_zend_verify_property_type(info, property, strict); } ZEND_COLD zend_never_inline void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant) @@ -959,115 +1141,6 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_asymmetric_visibility_property_modifi scope ? "scope " : "global scope", scope ? ZSTR_VAL(scope->name) : ""); } -static const zend_class_entry *resolve_single_class_type(zend_string *name, const zend_class_entry *self_ce) { - if (zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_SELF))) { - return self_ce; - } else if (zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_PARENT))) { - return self_ce->parent; - } else { - return zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - } -} - -static zend_always_inline const zend_class_entry *zend_ce_from_type( - const zend_class_entry *scope, const zend_type *type) { - ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*type)); - zend_string *name = ZEND_TYPE_NAME(*type); - if (ZSTR_HAS_CE_CACHE(name)) { - zend_class_entry *ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - } - return ce; - } - return resolve_single_class_type(name, scope); -} - -static bool zend_check_intersection_for_property_or_class_constant_class_type( - const zend_class_entry *scope, const zend_type_list *intersection_type_list, const zend_class_entry *value_ce) -{ - const zend_type *list_type; - - ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) { - ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); - const zend_class_entry *ce = zend_ce_from_type(scope, list_type); - if (!ce || !instanceof_function(value_ce, ce)) { - return false; - } - } ZEND_TYPE_LIST_FOREACH_END(); - return true; -} - -static bool zend_check_and_resolve_property_or_class_constant_class_type( - const zend_class_entry *scope, const zend_type member_type, const zend_class_entry *value_ce) { - if (ZEND_TYPE_HAS_LIST(member_type)) { - if (ZEND_TYPE_IS_INTERSECTION(member_type)) { - return zend_check_intersection_for_property_or_class_constant_class_type( - scope, ZEND_TYPE_LIST(member_type), value_ce); - } else { - const zend_type *list_type; - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(member_type), list_type) { - if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { - if (zend_check_intersection_for_property_or_class_constant_class_type( - scope, ZEND_TYPE_LIST(*list_type), value_ce)) { - return true; - } - continue; - } - ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); - const zend_class_entry *ce = zend_ce_from_type(scope, list_type); - if (ce && instanceof_function(value_ce, ce)) { - return true; - } - } ZEND_TYPE_LIST_FOREACH_END(); - - if ((ZEND_TYPE_PURE_MASK(member_type) & MAY_BE_STATIC)) { - return value_ce == scope; - } - - return false; - } - } else if ((ZEND_TYPE_PURE_MASK(member_type) & MAY_BE_STATIC) && value_ce == scope) { - return true; - } else if (ZEND_TYPE_HAS_NAME(member_type)) { - const zend_class_entry *ce = zend_ce_from_type(scope, &member_type); - return ce && instanceof_function(value_ce, ce); - } - - return false; -} - -static zend_always_inline bool i_zend_check_property_type(const zend_property_info *info, zval *property, bool strict) -{ - ZEND_ASSERT(!Z_ISREF_P(property)); - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) { - return 1; - } - - if (ZEND_TYPE_IS_COMPLEX(info->type) && Z_TYPE_P(property) == IS_OBJECT - && zend_check_and_resolve_property_or_class_constant_class_type(info->ce, info->type, Z_OBJCE_P(property))) { - return 1; - } - - uint32_t type_mask = ZEND_TYPE_FULL_MASK(info->type); - ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC|MAY_BE_NEVER|MAY_BE_VOID))); - return zend_verify_scalar_type_hint(type_mask, property, strict, false); -} - -static zend_always_inline bool i_zend_verify_property_type(const zend_property_info *info, zval *property, bool strict) -{ - if (i_zend_check_property_type(info, property, strict)) { - return 1; - } - - zend_verify_property_type_error(info, property); - return 0; -} - -ZEND_API bool zend_never_inline zend_verify_property_type(const zend_property_info *info, zval *property, bool strict) { - return i_zend_verify_property_type(info, property, strict); -} - static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_info *info, zval *property_val, zval *value, zend_refcounted **garbage_ptr EXECUTE_DATA_DC) { zval tmp; @@ -1096,182 +1169,65 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf return zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr); } -static zend_always_inline bool zend_value_instanceof_static(const zval *zv) { - if (Z_TYPE_P(zv) != IS_OBJECT) { - return 0; - } - - zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data)); - if (!called_scope) { - return 0; - } - return instanceof_function(Z_OBJCE_P(zv), called_scope); -} - -static zend_always_inline zend_class_entry *zend_fetch_ce_from_type( - const zend_type *type) -{ - zend_string *name = ZEND_TYPE_NAME(*type); - zend_class_entry *ce; - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - /* Cannot resolve */ - return NULL; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (UNEXPECTED(!ce)) { - return NULL; - } - } - return ce; -} - -static bool zend_check_intersection_type_from_list( - const zend_type_list *intersection_type_list, - zend_class_entry *arg_ce) -{ - zend_class_entry *ce; - const zend_type *list_type; - ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) { - ce = zend_fetch_ce_from_type(list_type); - /* If type is not an instance of one of the types taking part in the - * intersection it cannot be a valid instance of the whole intersection type. */ - if (!ce || !instanceof_function(arg_ce, ce)) { - return false; - } - } ZEND_TYPE_LIST_FOREACH_END(); - return true; -} - -static zend_always_inline bool zend_check_type_slow( - const zend_type *type, zval *arg, const zend_reference *ref, - bool is_return_type, bool is_internal) -{ - if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { - zend_class_entry *ce; - if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) { - if (ZEND_TYPE_IS_INTERSECTION(*type)) { - return zend_check_intersection_type_from_list(ZEND_TYPE_LIST(*type), Z_OBJCE_P(arg)); - } else { - const zend_type *list_type; - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { - if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { - if (zend_check_intersection_type_from_list(ZEND_TYPE_LIST(*list_type), Z_OBJCE_P(arg))) { - return true; - } - } else { - ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); - ce = zend_fetch_ce_from_type(list_type); - /* Instance of a single type part of a union is sufficient to pass the type check */ - if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { - return true; - } - } - } ZEND_TYPE_LIST_FOREACH_END(); - } - } else { - ce = zend_fetch_ce_from_type(type); - /* If we have a CE we check if it satisfies the type constraint, - * otherwise it will check if a standard type satisfies it. */ - if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { - return true; - } - } - } - - const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type); - if ((type_mask & MAY_BE_CALLABLE) && - zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) { - return 1; - } - if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(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; - } - - 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. */ -} - -static zend_always_inline bool zend_check_type( - const zend_type *type, zval *arg, bool is_return_type, bool is_internal) -{ - const zend_reference *ref = NULL; - ZEND_ASSERT(ZEND_TYPE_IS_SET(*type)); - - if (UNEXPECTED(Z_ISREF_P(arg))) { - ref = Z_REF_P(arg); - arg = Z_REFVAL_P(arg); - } - - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(*type, Z_TYPE_P(arg)))) { - return 1; - } - - return zend_check_type_slow(type, arg, ref, is_return_type, is_internal); -} - -ZEND_API bool zend_check_user_type_slow( - const zend_type *type, zval *arg, const zend_reference *ref, bool is_return_type) -{ - return zend_check_type_slow( - type, arg, ref, is_return_type, /* is_internal */ false); -} - static zend_always_inline bool zend_verify_recv_arg_type(const zend_function *zf, uint32_t arg_num, zval *arg) { + // Pass scope scope = EX(func)->op_array.scope; const zend_arg_info *cur_arg_info; ZEND_ASSERT(arg_num <= zf->common.num_args); cur_arg_info = &zf->common.arg_info[arg_num-1]; if (ZEND_TYPE_IS_SET(cur_arg_info->type) - && UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, false, false))) { + && UNEXPECTED(!zend_check_type_and_coerce(&cur_arg_info->type, arg, + /* TODO: Pass scope to zend_verify_recv_arg_type()? */ + zend_get_called_scope(EG(current_execute_data)), + ZEND_ARG_USES_STRICT_TYPES(), 0))) { zend_verify_arg_error(zf, cur_arg_info, arg_num, arg); - return 0; + return false; } - return 1; + return true; } static zend_always_inline bool zend_verify_variadic_arg_type( const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *arg) { ZEND_ASSERT(ZEND_TYPE_IS_SET(arg_info->type)); - if (UNEXPECTED(!zend_check_type(&arg_info->type, arg, false, false))) { + if (UNEXPECTED(!zend_check_type_and_coerce(&arg_info->type, arg, + /* TODO: Pass scope to zend_verify_variadic_arg_type()? */ + zend_get_called_scope(EG(current_execute_data)), + ZEND_ARG_USES_STRICT_TYPES(), + 0 + ))) { zend_verify_arg_error(zf, arg_info, arg_num, arg); - return 0; + return false; } - return 1; + return true; +} + +#if ZEND_DEBUG +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) +{ + if (UNEXPECTED(zend_check_type( + type, + arg, + zend_get_called_scope(call), + ZEND_ARG_USES_STRICT_TYPES(), + IS_CALLABLE_SUPPRESS_DEPRECATIONS + ) == ZEND_TYPE_CHECK_INVALID)) { + /* Internal function allow coercion from null for scalar types */ + return Z_TYPE_P(arg) == IS_NULL && (ZEND_TYPE_PURE_MASK(*type) & (MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING)); + } + return true; } static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_verify_internal_arg_types(const zend_function *fbc, zend_execute_data *call) { - uint32_t i; uint32_t num_args = ZEND_CALL_NUM_ARGS(call); - zval *arg = ZEND_CALL_ARG(call, 1); + const zval *arg = ZEND_CALL_ARG(call, 1); - for (i = 0; i < num_args; ++i) { + for (uint32_t i = 0; i < num_args; ++i) { zend_arg_info *cur_arg_info; if (EXPECTED(i < fbc->common.num_args)) { cur_arg_info = &fbc->common.arg_info[i]; @@ -1281,16 +1237,14 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_verify_internal_arg_typ break; } - if (ZEND_TYPE_IS_SET(cur_arg_info->type) - && UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, false, /* is_internal */ true))) { - return 0; + if (ZEND_TYPE_IS_SET(cur_arg_info->type) && UNEXPECTED(!zend_check_type_for_internal_parameter(&cur_arg_info->type, arg, call))) { + return false; } arg++; } - return 1; + return true; } -#if ZEND_DEBUG /* Determine whether an internal call should throw, because the passed arguments violate * an arginfo constraint. This is only checked in debug builds. In release builds, we * trust that arginfo matches what is enforced by zend_parse_parameters. */ @@ -1477,24 +1431,24 @@ static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, con fclass, fsep, fname, returned_msg, returned_kind); } -ZEND_API bool zend_verify_internal_return_type(const zend_function *zf, zval *ret) +ZEND_API bool zend_verify_internal_return_type(const zend_function *zf, const zval *ret) { const zend_arg_info *ret_info = zf->internal_function.arg_info - 1; if (ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_VOID) { if (UNEXPECTED(Z_TYPE_P(ret) != IS_NULL)) { zend_verify_void_return_error(zf, zend_zval_value_name(ret), ""); - return 0; + return false; } - return 1; + return true; } - if (UNEXPECTED(!zend_check_type(&ret_info->type, ret, true, /* is_internal */ true))) { + if (UNEXPECTED(zend_check_type(&ret_info->type, ret, zf->common.scope, true, 0)) != ZEND_TYPE_CHECK_VALID) { zend_verify_internal_return_error(zf, ret); - return 0; + return false; } - return 1; + return true; } #endif @@ -1504,31 +1458,20 @@ static ZEND_COLD void zend_verify_missing_return_type(const zend_function *zf) zend_verify_return_error(zf, NULL); } -static zend_always_inline bool zend_check_class_constant_type(const zend_class_constant *c, zval *constant) +static zend_always_inline bool zend_check_class_constant_type(const zend_class_constant *c, const zval *constant) { ZEND_ASSERT(!Z_ISREF_P(constant)); - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(c->type, Z_TYPE_P(constant)))) { - return 1; - } - - if (((ZEND_TYPE_PURE_MASK(c->type) & MAY_BE_STATIC) || ZEND_TYPE_IS_COMPLEX(c->type)) && Z_TYPE_P(constant) == IS_OBJECT - && zend_check_and_resolve_property_or_class_constant_class_type(c->ce, c->type, Z_OBJCE_P(constant))) { - return 1; - } - - uint32_t type_mask = ZEND_TYPE_FULL_MASK(c->type); - ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_NEVER|MAY_BE_VOID))); - return zend_verify_scalar_type_hint(type_mask, constant, true, false); + return zend_check_type(&c->type, constant, c->ce, true, 0) == ZEND_TYPE_CHECK_VALID; } -ZEND_API bool zend_never_inline zend_verify_class_constant_type(const zend_class_constant *c, const zend_string *name, zval *constant) +ZEND_API bool zend_never_inline zend_verify_class_constant_type(const zend_class_constant *c, const zend_string *name, const zval *constant) { if (!zend_check_class_constant_type(c, constant)) { zend_verify_class_constant_type_error(c, name, constant); - return 0; + return false; } - return 1; + return true; } static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_use_object_as_array(const zend_object *object) @@ -3922,46 +3865,14 @@ ZEND_API ZEND_COLD void zend_throw_conflicting_coercion_error(const zend_propert zend_string_release(type2_str); } -/* 1: valid, 0: invalid, -1: may be valid after type coercion */ -static zend_always_inline int i_zend_verify_type_assignable_zval( - const zend_property_info *info, const zval *zv, bool strict) { - zend_type type = info->type; - uint32_t type_mask; - uint8_t zv_type = Z_TYPE_P(zv); - - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, zv_type))) { - return 1; - } - - if (ZEND_TYPE_IS_COMPLEX(type) && zv_type == IS_OBJECT - && zend_check_and_resolve_property_or_class_constant_class_type(info->ce, info->type, Z_OBJCE_P(zv))) { - return 1; - } - - type_mask = ZEND_TYPE_FULL_MASK(type); - ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC))); - - /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */ - if (strict) { - if ((type_mask & MAY_BE_DOUBLE) && zv_type == IS_LONG) { - return -1; - } - return 0; - } - - /* NULL may be accepted only by nullable hints (this is already checked) */ - if (zv_type == IS_NULL) { - return 0; - } - - /* 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; - } - - /* Coercion may be necessary, check separately */ - return -1; +static zend_always_inline zend_type_check_status i_zend_verify_type_assignable_zval( + const zend_property_info *info, + const zval *zv, + bool strict, + zval *coerced_value +) { + zend_type_check_status status = zend_check_type_ex(&info->type, zv, info->ce, strict, 0, coerced_value); + return status; } ZEND_API bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference *ref, zval *zv, bool strict) @@ -3976,40 +3887,34 @@ ZEND_API bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference *ref, ZEND_ASSERT(Z_TYPE_P(zv) != IS_REFERENCE); ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { - int result = i_zend_verify_type_assignable_zval(prop, zv, strict); - if (result == 0) { -type_error: + zval tmp; + ZVAL_UNDEF(&tmp); + zend_type_check_status result = i_zend_verify_type_assignable_zval(prop, zv, strict, &tmp); + if (result == ZEND_TYPE_CHECK_INVALID) { zend_throw_ref_type_error_zval(prop, zv); zval_ptr_dtor(&coerced_value); return 0; } - if (result < 0) { + if (result == ZEND_TYPE_CHECK_MAY_COERCE) { 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; - } + ZVAL_COPY(&coerced_value, &tmp); + zval_ptr_dtor(&tmp); } else if (Z_ISUNDEF(coerced_value)) { /* A previous property did not require coercion, but this one does, * so they are incompatible. */ + zval_ptr_dtor(&tmp); 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); + bool is_identical = zend_is_identical(&coerced_value, &tmp); + zval_ptr_dtor(&tmp); + if (!is_identical) { goto conflicting_coercion_error; } - zval_ptr_dtor(&tmp); } } else { + ZEND_ASSERT(Z_ISUNDEF(tmp)); if (!first_prop) { first_prop = prop; } else if (!Z_ISUNDEF(coerced_value)) { @@ -4087,31 +3992,27 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ui 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) { zval *val = orig_val; if (Z_ISREF_P(val) && ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(val))) { - int result; - val = Z_REFVAL_P(val); - result = i_zend_verify_type_assignable_zval(prop_info, val, strict); - if (result > 0) { - return 1; + zval tmp; + + ZVAL_UNDEF(&tmp); + zend_type_check_status result = i_zend_verify_type_assignable_zval(prop_info, val, strict, &tmp); + if (EXPECTED(result == ZEND_TYPE_CHECK_VALID)) { + ZEND_ASSERT(Z_ISUNDEF(tmp)); + return true; } - if (result < 0) { - /* 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)) { - const 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 (result == ZEND_TYPE_CHECK_MAY_COERCE) { + /* This is an error, because or a conflicting coercion. */ + const 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 false; } } else { ZVAL_DEREF(val); - if (i_zend_check_property_type(prop_info, val, strict)) { - return 1; + if (zend_check_type_and_coerce(&prop_info->type, val, prop_info->ce, strict, 0)) { + return true; } } @@ -4122,7 +4023,7 @@ ZEND_API bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref_ex(const zend_pro zend_magic_get_property_type_inconsistency_error(prop_info, val); } - return 0; + return false; } ZEND_API bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(const zend_property_info *prop_info, zval *orig_val, bool strict) { diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index ef385b3ac4dc3..3d41b72080044 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -100,7 +100,6 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_cannot_add_element(void); ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_property_info *prop_info); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_asymmetric_visibility_property_modification_error(const zend_property_info *prop_info, const char *operation); -ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg); ZEND_API ZEND_COLD void zend_verify_arg_error( const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, const zval *value); ZEND_API ZEND_COLD void zend_verify_return_error( @@ -114,7 +113,7 @@ ZEND_API bool zend_check_user_type_slow( #if ZEND_DEBUG ZEND_API bool zend_internal_call_should_throw(const zend_function *fbc, zend_execute_data *call); ZEND_API ZEND_COLD void zend_internal_call_arginfo_violation(const zend_function *fbc); -ZEND_API bool zend_verify_internal_return_type(const zend_function *zf, zval *ret); +ZEND_API bool zend_verify_internal_return_type(const zend_function *zf, const zval *ret); #endif #define ZEND_REF_TYPE_SOURCES(ref) \ @@ -584,8 +583,7 @@ ZEND_API zend_result ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *cal #define ZEND_CLASS_HAS_TYPE_HINTS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) #define ZEND_CLASS_HAS_READONLY_PROPS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_READONLY_PROPS)) - -ZEND_API bool zend_verify_class_constant_type(const zend_class_constant *c, const zend_string *name, zval *constant); +ZEND_API bool zend_verify_class_constant_type(const zend_class_constant *c, const zend_string *name, const zval *constant); ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant); ZEND_API bool zend_verify_property_type(const zend_property_info *info, zval *property, bool strict); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 86708f8c97a29..a8f232b6f1236 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -23,6 +23,8 @@ * php zend_vm_gen.php */ +#include "../ext/opcache/jit/ir/ir_private.h" + ZEND_VM_HELPER(zend_add_helper, ANY, ANY, zval *op_1, zval *op_2) { USE_OPLINE @@ -4512,7 +4514,10 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -8906,7 +8911,13 @@ ZEND_VM_HOT_HANDLER(211, ZEND_TYPE_ASSERT, CONST, ANY, NUM) uint16_t argno = opline->extended_value >> 16; zend_arg_info *arginfo = &fbc->common.arg_info[argno - 1]; - if (!zend_check_type(&arginfo->type, value, /* is_return_type */ false, /* is_internal */ true)) { + if (UNEXPECTED(!zend_check_type_and_coerce( + &arginfo->type, + value, + EX(func)->common.scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES), + IS_CALLABLE_SUPPRESS_DEPRECATIONS) + )) { const char *param_name = get_function_arg_name(fbc, argno); zend_string *expected = zend_type_to_string(arginfo->type); zend_type_error("%s(): Argument #%d%s%s%s must be of type %s, %s given", ZSTR_VAL(fbc->common.function_name), argno, param_name ? " ($" : "", param_name ? param_name : "", param_name ? ")" : "", ZSTR_VAL(expected), zend_zval_value_name(value)); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 7dfedca98d3b9..9477afb6d942c 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -6116,7 +6116,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_T uint16_t argno = opline->extended_value >> 16; zend_arg_info *arginfo = &fbc->common.arg_info[argno - 1]; - if (!zend_check_type(&arginfo->type, value, /* is_return_type */ false, /* is_internal */ true)) { + if (UNEXPECTED(!zend_check_type_and_coerce( + &arginfo->type, + value, + EX(func)->common.scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES), + IS_CALLABLE_SUPPRESS_DEPRECATIONS) + )) { const char *param_name = get_function_arg_name(fbc, argno); zend_string *expected = zend_type_to_string(arginfo->type); zend_type_error("%s(): Argument #%d%s%s%s must be of type %s, %s given", ZSTR_VAL(fbc->common.function_name), argno, param_name ? " ($" : "", param_name ? param_name : "", param_name ? ")" : "", ZSTR_VAL(expected), zend_zval_value_name(value)); @@ -11198,7 +11204,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -21505,7 +21514,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -29548,7 +29560,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -36935,7 +36950,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -49144,7 +49162,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -58724,7 +58745,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_TYPE_A uint16_t argno = opline->extended_value >> 16; zend_arg_info *arginfo = &fbc->common.arg_info[argno - 1]; - if (!zend_check_type(&arginfo->type, value, /* is_return_type */ false, /* is_internal */ true)) { + if (UNEXPECTED(!zend_check_type_and_coerce( + &arginfo->type, + value, + EX(func)->common.scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES), + IS_CALLABLE_SUPPRESS_DEPRECATIONS) + )) { const char *param_name = get_function_arg_name(fbc, argno); zend_string *expected = zend_type_to_string(arginfo->type); zend_type_error("%s(): Argument #%d%s%s%s must be of type %s, %s given", ZSTR_VAL(fbc->common.function_name), argno, param_name ? " ($" : "", param_name ? param_name : "", param_name ? ")" : "", ZSTR_VAL(expected), zend_zval_value_name(value)); @@ -63704,7 +63731,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIF } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -73911,7 +73941,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -81954,7 +81987,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -89341,7 +89377,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -101448,7 +101487,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, 1, 0))) { + zend_class_entry *scope = Z_TYPE(EX(This)) == IS_OBJECT ? Z_OBJCE(EX(This)) : Z_CE(EX(This)); + bool ref_has_type_sources = ref ? ZEND_REF_HAS_TYPE_SOURCES(ref) : false; + if (UNEXPECTED(!zend_check_type_and_coerce_slow(&ret_info->type, retval_ptr, scope, + (EX(func)->op_array.fn_flags & ZEND_ACC_STRICT_TYPES) || ref_has_type_sources, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } diff --git a/ext/standard/tests/filters/user_filter_seek_01.phpt b/ext/standard/tests/filters/user_filter_seek_01.phpt index cb4e9fe72267f..31ec95ca6aa64 100644 --- a/ext/standard/tests/filters/user_filter_seek_01.phpt +++ b/ext/standard/tests/filters/user_filter_seek_01.phpt @@ -1,5 +1,7 @@ --TEST-- php_user_filter with seek method - always seekable (stateless filter) +--EXTENSIONS-- +ctype --FILE--