From 0ac57d5d1d3566081703e2107fdae3f7949f5ef5 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 20 Sep 2019 17:01:19 +0200 Subject: [PATCH 01/48] Make zend_type a 2-field struct --- Zend/zend.c | 7 +- Zend/zend_API.c | 21 ++---- Zend/zend_API.h | 25 ++++--- Zend/zend_compile.c | 20 +++--- Zend/zend_execute.c | 25 ++++--- Zend/zend_inheritance.c | 6 +- Zend/zend_types.h | 88 ++++++++++++----------- ext/com_dotnet/com_handlers.c | 2 +- ext/opcache/Optimizer/dfa_pass.c | 3 +- ext/opcache/Optimizer/zend_inference.c | 56 +++++++-------- ext/opcache/Optimizer/zend_optimizer.c | 4 +- ext/opcache/ZendAccelerator.c | 6 +- ext/opcache/jit/zend_jit_helpers.c | 2 +- ext/opcache/jit/zend_jit_x86.dasc | 2 +- ext/opcache/zend_accelerator_util_funcs.c | 2 +- ext/opcache/zend_file_cache.c | 51 ++++++------- ext/opcache/zend_persist.c | 8 +-- ext/opcache/zend_persist_calc.c | 6 +- ext/reflection/php_reflection.c | 21 +++--- ext/zend_test/test.c | 7 +- 20 files changed, 169 insertions(+), 193 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index ce98f50025ffa..bd5a2a892fce3 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -594,10 +594,7 @@ static void function_copy_ctor(zval *zv) /* {{{ */ for (i = 0 ; i < num_args; i++) { if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { zend_string *name = zend_string_dup(ZEND_TYPE_NAME(arg_info[i].type), 1); - - new_arg_info[i].type = - ZEND_TYPE_ENCODE_CLASS( - name, ZEND_TYPE_ALLOW_NULL(arg_info[i].type)); + ZEND_TYPE_SET_PTR(new_arg_info[i].type, name); } } func->common.arg_info = new_arg_info + 1; @@ -968,7 +965,7 @@ static void zend_resolve_property_types(void) /* {{{ */ zend_class_entry *prop_ce = zend_hash_find_ptr(CG(class_table), lc_type_name); ZEND_ASSERT(prop_ce && prop_ce->type == ZEND_INTERNAL_CLASS); - prop_info->type = ZEND_TYPE_ENCODE_CE(prop_ce, ZEND_TYPE_ALLOW_NULL(prop_info->type)); + prop_info->type = (zend_type) ZEND_TYPE_INIT_CE(prop_ce, ZEND_TYPE_ALLOW_NULL(prop_info->type)); zend_string_release(lc_type_name); zend_string_release(type_name); } diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 387f6e1a9822e..42283c4670d3c 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2055,11 +2055,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio } if (ZEND_TYPE_IS_SET(info->type)) { if (ZEND_TYPE_IS_CLASS(info->type)) { - const char *type_name = (const char*)info->type; - - if (type_name[0] == '?') { - type_name++; - } + const char *type_name = ZEND_TYPE_LITERAL_NAME(info->type); if (!scope && (!strcasecmp(type_name, "self") || !strcasecmp(type_name, "parent"))) { zend_error_noreturn(E_CORE_ERROR, "Cannot declare a return type of %s outside of a class scope", type_name); } @@ -2140,16 +2136,9 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio reg_function->common.arg_info = new_arg_info + 1; for (i = 0; i < num_args; i++) { if (ZEND_TYPE_IS_CLASS(new_arg_info[i].type)) { - const char *class_name = (const char*)new_arg_info[i].type; - zend_bool allow_null = 0; - zend_string *str; - - if (class_name[0] == '?') { - class_name++; - allow_null = 1; - } - str = zend_string_init_interned(class_name, strlen(class_name), 1); - new_arg_info[i].type = ZEND_TYPE_ENCODE_CLASS(str, allow_null); + const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type); + ZEND_TYPE_SET_PTR(new_arg_info[i].type, + zend_string_init_interned(class_name, strlen(class_name), 1)); } } } @@ -3715,7 +3704,7 @@ ZEND_API int zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval *zv, ze ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment) /* {{{ */ { - return zend_declare_typed_property(ce, name, property, access_type, doc_comment, ZEND_TYPE_ENCODE_NONE()); + return zend_declare_typed_property(ce, name, property, access_type, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE()); } /* }}} */ diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 1389f0a6d0f1a..d18da89c840bc 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -96,34 +96,33 @@ typedef struct _zend_fcall_info_cache { #define ZEND_FE_END { NULL, NULL, NULL, 0, 0 } -#define ZEND_ARG_INFO(pass_by_ref, name) { #name, 0, pass_by_ref, 0}, -#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, pass_by_ref, 0}, -#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, ZEND_TYPE_ENCODE_CLASS_CONST(#classname, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_ENCODE_CODE(IS_ARRAY, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_ENCODE_CODE(IS_CALLABLE, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, ZEND_TYPE_ENCODE_CODE(type_hint, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, 0, pass_by_ref, 1 }, -#define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, ZEND_TYPE_ENCODE_CODE(type_hint, allow_null), pass_by_ref, 1 }, -#define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, ZEND_TYPE_ENCODE_CLASS_CONST(#classname, allow_null), pass_by_ref, 1 }, +#define ZEND_ARG_INFO(pass_by_ref, name) { #name, ZEND_TYPE_INIT_NONE(), pass_by_ref, 0}, +#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, ZEND_TYPE_INIT_CLASS_CONST(#classname, allow_null), pass_by_ref, 0 }, +#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null), pass_by_ref, 0 }, +#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null), pass_by_ref, 0 }, +#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null), pass_by_ref, 0 }, +#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, ZEND_TYPE_INIT_NONE(), pass_by_ref, 1 }, +#define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null), pass_by_ref, 1 }, +#define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, ZEND_TYPE_INIT_CLASS_CONST(#classname, allow_null), pass_by_ref, 1 }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, return_reference, required_num_args, class_name, allow_null) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_ENCODE_CLASS_CONST(#class_name, allow_null), return_reference, 0 }, + { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null), return_reference, 0 }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO(name, class_name, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, 0, -1, class_name, allow_null) #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_ENCODE_CODE(type, allow_null), return_reference, 0 }, + { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null), return_reference, 0 }, #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(name, type, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, 0, -1, type, allow_null) #define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(zend_uintptr_t)(required_num_args), 0, return_reference, 0 }, + { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(), return_reference, 0 }, #define ZEND_BEGIN_ARG_INFO(name, _unused) \ - ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1) + ZEND_BEGIN_ARG_INFO_EX(name, {}, ZEND_RETURN_VALUE, -1) #define ZEND_END_ARG_INFO() }; /* Name macros */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 3800c664e1728..e6f00aef94f3f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1135,7 +1135,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop } else if (ZEND_TYPE_IS_CE(type)) { str = zend_string_copy(ZEND_TYPE_CE(type)->name); } else { - uint32_t type_mask = ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(type)); + uint32_t type_mask = ZEND_TYPE_MASK_WITHOUT_NULL(type); switch (type_mask) { case MAY_BE_FALSE|MAY_BE_TRUE: str = ZSTR_KNOWN(ZEND_STR_BOOL); @@ -2175,7 +2175,7 @@ static void zend_emit_return_type_check( zend_op *opline; /* `return ...;` is illegal in a void function (but `return;` isn't) */ - if (ZEND_TYPE_IS_MASK(type) && ZEND_TYPE_CONTAINS_CODE(type, IS_VOID)) { + if (ZEND_TYPE_CONTAINS_CODE(type, IS_VOID)) { if (expr) { if (expr->op_type == IS_CONST && Z_TYPE(expr->u.constant) == IS_NULL) { zend_error_noreturn(E_COMPILE_ERROR, @@ -2201,8 +2201,7 @@ static void zend_emit_return_type_check( } if (expr && expr->op_type == IS_CONST) { - if (ZEND_TYPE_IS_MASK(type) - && ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE(expr->u.constant))) { + if (ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE(expr->u.constant))) { /* we don't need run-time check */ return; } @@ -5382,7 +5381,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null } if (ast->kind == ZEND_AST_TYPE) { - return ZEND_TYPE_ENCODE_CODE(ast->attr, allow_null); + return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, allow_null); } else { zend_string *class_name = zend_ast_get_str(ast); zend_uchar type = zend_lookup_builtin_type_by_name(class_name); @@ -5396,7 +5395,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null if (type == IS_VOID && allow_null) { zend_error_noreturn(E_COMPILE_ERROR, "Void type cannot be nullable"); } - return ZEND_TYPE_ENCODE_CODE(type, allow_null); + return (zend_type) ZEND_TYPE_INIT_CODE(type, allow_null); } else { const char *correct_name; zend_string *orig_name = zend_ast_get_str(ast); @@ -5428,7 +5427,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null } } - return ZEND_TYPE_ENCODE_CLASS(class_name, allow_null); + return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, allow_null); } } } @@ -5542,7 +5541,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ arg_info->name = zend_string_copy(name); arg_info->pass_by_reference = is_ref; arg_info->is_variadic = is_variadic; - arg_info->type = ZEND_TYPE_ENCODE_NONE(); + arg_info->type = (zend_type) ZEND_TYPE_INIT_NONE(); if (type_ast) { uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF; @@ -6079,13 +6078,12 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) / zend_string *name = zval_make_interned_string(zend_ast_get_zval(name_ast)); zend_string *doc_comment = NULL; zval value_zv; - zend_type type = ZEND_TYPE_ENCODE_NONE(); + zend_type type = ZEND_TYPE_INIT_NONE(); if (type_ast) { type = zend_compile_typename(type_ast, 0); - if (ZEND_TYPE_IS_MASK(type) - && (ZEND_TYPE_MASK(type) & (MAY_BE_VOID|MAY_BE_CALLABLE))) { + if (ZEND_TYPE_MASK(type) & (MAY_BE_VOID|MAY_BE_CALLABLE)) { zend_string *str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Property %s::$%s cannot have type %s", diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 8278f698cdda4..d6928a6cfdfd5 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -676,8 +676,8 @@ static ZEND_COLD void zend_verify_type_error_common( *need_kind = ZSTR_VAL(ZEND_TYPE_NAME(arg_info->type)); } } else { - zend_type type = ZEND_TYPE_WITHOUT_NULL(arg_info->type); - switch (ZEND_TYPE_MASK(type)) { + uint32_t type_mask = ZEND_TYPE_MASK_WITHOUT_NULL(arg_info->type); + switch (type_mask) { case MAY_BE_OBJECT: *need_msg = "be an "; *need_kind = "object"; @@ -691,11 +691,15 @@ static ZEND_COLD void zend_verify_type_error_common( *need_kind = ""; 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. */ + zend_type type = arg_info->type; + ZEND_TYPE_MASK(type) &= ~MAY_BE_NULL; *need_msg = "be of the type "; *need_kind = ZSTR_VAL(zend_type_to_string(type)); break; + } } } @@ -904,7 +908,7 @@ static zend_bool zend_resolve_class_type(zend_type *type, zend_class_entry *self } zend_string_release(name); - *type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(*type)); + *type = (zend_type) ZEND_TYPE_INIT_CE(ce, ZEND_TYPE_ALLOW_NULL(*type)); return 1; } @@ -1184,7 +1188,7 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret) zend_internal_arg_info *ret_info = zf->internal_function.arg_info - 1; void *dummy_cache_slot = NULL; - if (ZEND_TYPE_IS_MASK(ret_info->type) && (ZEND_TYPE_MASK(ret_info->type) & MAY_BE_VOID)) { + if (ZEND_TYPE_MASK(ret_info->type) & MAY_BE_VOID) { if (UNEXPECTED(Z_TYPE_P(ret) != IS_NULL)) { zend_verify_void_return_error(zf, zend_zval_type_name(ret), ""); return 0; @@ -1216,6 +1220,7 @@ static ZEND_COLD int zend_verify_missing_return_type(const zend_function *zf, vo 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)); @@ -1560,7 +1565,7 @@ static zend_property_info *zend_get_prop_not_accepting_double(zend_reference *re { zend_property_info *prop; ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { - if (!ZEND_TYPE_IS_MASK(prop->type) || !(ZEND_TYPE_MASK(prop->type) & MAY_BE_DOUBLE)) { + if (!(ZEND_TYPE_MASK(prop->type) & MAY_BE_DOUBLE)) { return prop; } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); @@ -2528,7 +2533,7 @@ static zend_always_inline zend_bool check_type_array_assignable(zend_type type) if (!ZEND_TYPE_IS_SET(type)) { return 1; } - return ZEND_TYPE_IS_MASK(type) && (ZEND_TYPE_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)); + return (ZEND_TYPE_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) != 0; } /* Checks whether an array can be assigned to the reference. Throws error if not assignable. */ @@ -3013,9 +3018,9 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference if (!seen_prop) { seen_prop = prop; seen_type_mask = ZEND_TYPE_IS_CLASS(prop->type) - ? MAY_BE_OBJECT : ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(prop->type)); + ? MAY_BE_OBJECT : ZEND_TYPE_MASK_WITHOUT_NULL(prop->type); } else if (needs_coercion - && seen_type_mask != ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(prop->type))) { + && seen_type_mask != ZEND_TYPE_MASK_WITHOUT_NULL(prop->type)) { zend_throw_conflicting_coercion_error(seen_prop, prop, zv); return 0; } @@ -3082,8 +3087,8 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_propert if (result < 0) { zend_property_info *ref_prop = ZEND_REF_FIRST_SOURCE(Z_REF_P(orig_val)); - if (ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(prop_info->type)) - != ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(ref_prop->type))) { + if (ZEND_TYPE_MASK_WITHOUT_NULL(prop_info->type) + != ZEND_TYPE_MASK_WITHOUT_NULL(ref_prop->type)) { /* Invalid due to conflicting coercion */ zend_throw_ref_type_error_type(ref_prop, prop_info, val); return 0; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index a1ecb9e2b70f3..40d4296e5d7d3 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -382,8 +382,7 @@ static inheritance_status zend_perform_covariant_type_check( return ZEND_TYPE_MASK(fe_type) & MAY_BE_OBJECT ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } else { - return ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(fe_type)) - == ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(proto_type)) + return ZEND_TYPE_MASK_WITHOUT_NULL(fe_type) == ZEND_TYPE_MASK_WITHOUT_NULL(proto_type) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } } @@ -847,7 +846,8 @@ inheritance_status property_types_compatible( const zend_property_info *parent_info, const zend_property_info *child_info) { zend_string *parent_name, *child_name; zend_class_entry *parent_type_ce, *child_type_ce; - if (parent_info->type == child_info->type) { + if (ZEND_TYPE_MASK(parent_info->type) == ZEND_TYPE_MASK(child_info->type) + && ZEND_TYPE_NAME(parent_info->type) == ZEND_TYPE_NAME(child_info->type)) { return INHERITANCE_SUCCESS; } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index b381eeff14674..c5cb8425e1dc8 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -120,73 +120,81 @@ typedef void (*copy_ctor_func_t)(zval *pElement); * ZEND_TYPE_ENCODE_*() should be used for construction. */ -typedef uintptr_t zend_type; - -#define _ZEND_TYPE_CODE_MAX ((Z_L(1)<<(IS_VOID+1))-1) -#define _ZEND_TYPE_FLAG_MASK Z_L(0x3) -#define _ZEND_TYPE_CE_BIT Z_L(0x1) +typedef struct { + /* Not using a union here, because there's no good way to initialize them + * in a way that is supported in both C and C++ (designated initializers + * are only supported since C++20). */ + void *ptr; + uint32_t type_mask; + /* TODO: We could use the extra 32-bit of padding on 64-bit systems. */ +} zend_type; + +#define _ZEND_TYPE_MASK ((1u<<(IS_VOID+1))-1) +#define _ZEND_TYPE_CE_BIT (1u << 30) +#define _ZEND_TYPE_NAME_BIT (1u << 31) /* Must have same value as MAY_BE_NULL */ -#define _ZEND_TYPE_NULLABLE_BIT Z_L(0x2) +#define _ZEND_TYPE_NULLABLE_BIT 0x2 #define ZEND_TYPE_IS_SET(t) \ - ((t) != 0) + ((t).type_mask != 0) #define ZEND_TYPE_IS_MASK(t) \ - ((t) != 0 && (t) <= _ZEND_TYPE_CODE_MAX) + ((t).type_mask != 0 && (t) <= _ZEND_TYPE_CODE_MAX) #define ZEND_TYPE_IS_CLASS(t) \ - ((t) > _ZEND_TYPE_CODE_MAX) + (((t.type_mask) & (_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_CE_BIT)) != 0) #define ZEND_TYPE_IS_CE(t) \ - (((t) & _ZEND_TYPE_CE_BIT) != 0) + (((t.type_mask) & _ZEND_TYPE_CE_BIT) != 0) #define ZEND_TYPE_IS_NAME(t) \ - (ZEND_TYPE_IS_CLASS(t) && !ZEND_TYPE_IS_CE(t)) + (((t.type_mask) & _ZEND_TYPE_NAME_BIT) != 0) + +#define ZEND_TYPE_IS_ONLY_MASK(t) \ + ((t).type_mask != 0 && (t).ptr == NULL) #define ZEND_TYPE_NAME(t) \ - ((zend_string*)((t) & ~_ZEND_TYPE_FLAG_MASK)) + ((zend_string *) (t).ptr) + +#define ZEND_TYPE_LITERAL_NAME(t) \ + ((const char *) (t).ptr) #define ZEND_TYPE_CE(t) \ - ((zend_class_entry*)((t) & ~_ZEND_TYPE_FLAG_MASK)) + ((zend_class_entry *) (t).ptr) + +#define ZEND_TYPE_SET_PTR(t, _ptr) \ + ((t).ptr = (_ptr)) #define ZEND_TYPE_MASK(t) \ - (t) + ((t).type_mask) + +#define ZEND_TYPE_MASK_WITHOUT_NULL(t) \ + ((t).type_mask & ~_ZEND_TYPE_NULLABLE_BIT) #define ZEND_TYPE_CONTAINS_CODE(t, code) \ - (((t) & (1 << (code))) != 0) + (((t).type_mask & (1u << (code))) != 0) #define ZEND_TYPE_ALLOW_NULL(t) \ - (((t) & _ZEND_TYPE_NULLABLE_BIT) != 0) - -#define ZEND_TYPE_WITHOUT_NULL(t) \ - ((t) & ~_ZEND_TYPE_NULLABLE_BIT) + (((t).type_mask & _ZEND_TYPE_NULLABLE_BIT) != 0) -#define ZEND_TYPE_ENCODE_NONE() \ - (0) +#define ZEND_TYPE_INIT_NONE() \ + { NULL, 0 } -#define ZEND_TYPE_ENCODE_MASK(maybe_code) \ - (maybe_code) +#define ZEND_TYPE_INIT_MASK(_type_mask) \ + { NULL, (_type_mask) } -#define ZEND_TYPE_ENCODE_CODE(code, allow_null) \ - (((code) == _IS_BOOL ? (MAY_BE_FALSE|MAY_BE_TRUE) : (1 << (code))) \ - | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : Z_L(0x0))) +#define ZEND_TYPE_INIT_CODE(code, allow_null) \ + ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? (MAY_BE_FALSE|MAY_BE_TRUE) : (1 << (code))) \ + | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0)) -#define ZEND_TYPE_ENCODE_CE(ce, allow_null) \ - (((uintptr_t)(ce)) | _ZEND_TYPE_CE_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : Z_L(0x0))) +#define ZEND_TYPE_INIT_CE(_ce, allow_null) \ + { (void *) (_ce), _ZEND_TYPE_CE_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) } -#define ZEND_TYPE_ENCODE_CLASS(class_name, allow_null) \ - (((uintptr_t)(class_name)) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : Z_L(0x0))) +#define ZEND_TYPE_INIT_CLASS(class_name, allow_null) \ + { (void *) (class_name), _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) } -#define ZEND_TYPE_ENCODE_CLASS_CONST_0(class_name) \ - ((zend_type) class_name) -#define ZEND_TYPE_ENCODE_CLASS_CONST_1(class_name) \ - ((zend_type) "?" class_name) -#define ZEND_TYPE_ENCODE_CLASS_CONST_Q2(macro, class_name) \ - macro(class_name) -#define ZEND_TYPE_ENCODE_CLASS_CONST_Q1(allow_null, class_name) \ - ZEND_TYPE_ENCODE_CLASS_CONST_Q2(ZEND_TYPE_ENCODE_CLASS_CONST_ ##allow_null, class_name) -#define ZEND_TYPE_ENCODE_CLASS_CONST(class_name, allow_null) \ - ZEND_TYPE_ENCODE_CLASS_CONST_Q1(allow_null, class_name) +#define ZEND_TYPE_INIT_CLASS_CONST(class_name, allow_null) \ + { (void *) (class_name), _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) } typedef union _zend_value { zend_long lval; /* long value */ diff --git a/ext/com_dotnet/com_handlers.c b/ext/com_dotnet/com_handlers.c index 1a5d9c30461b8..a481e5d303478 100644 --- a/ext/com_dotnet/com_handlers.c +++ b/ext/com_dotnet/com_handlers.c @@ -327,7 +327,7 @@ static zend_function *com_method_get(zend_object **object_ptr, zend_string *name f.arg_info = ecalloc(bindptr.lpfuncdesc->cParams, sizeof(zend_arg_info)); for (i = 0; i < bindptr.lpfuncdesc->cParams; i++) { - f.arg_info[i].type = ZEND_TYPE_ENCODE_NONE(); + f.arg_info[i].type = (zend_type) ZEND_TYPE_INIT_NONE(); if (bindptr.lpfuncdesc->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FOUT) { f.arg_info[i].pass_by_reference = ZEND_SEND_BY_REF; } diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c index f4c5dee916209..f6d057a0267e4 100644 --- a/ext/opcache/Optimizer/dfa_pass.c +++ b/ext/opcache/Optimizer/dfa_pass.c @@ -307,8 +307,7 @@ static inline zend_bool can_elide_return_type_check( } /* These types are not represented exactly */ - if (ZEND_TYPE_IS_MASK(info->type) - && (ZEND_TYPE_MASK(info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE))) { + if (ZEND_TYPE_MASK(info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) { return 0; } diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c index 51872dcc611b5..d095f6d0c8a59 100644 --- a/ext/opcache/Optimizer/zend_inference.c +++ b/ext/opcache/Optimizer/zend_inference.c @@ -1420,21 +1420,19 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int } else if (op_array->arg_info && opline->op1.num <= op_array->num_args) { zend_type type = op_array->arg_info[opline->op1.num-1].type; - if (ZEND_TYPE_IS_MASK(type)) { - uint32_t mask = ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(type)); - if (mask == MAY_BE_LONG) { - tmp->underflow = 0; - tmp->min = ZEND_LONG_MIN; - tmp->max = ZEND_LONG_MAX; - tmp->overflow = 0; - return 1; - } else if (mask == (MAY_BE_FALSE|MAY_BE_TRUE)) { - tmp->underflow = 0; - tmp->min = 0; - tmp->max = 1; - tmp->overflow = 0; - return 1; - } + uint32_t mask = ZEND_TYPE_MASK_WITHOUT_NULL(type); + if (mask == MAY_BE_LONG) { + tmp->underflow = 0; + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + tmp->overflow = 0; + return 1; + } else if (mask == (MAY_BE_FALSE|MAY_BE_TRUE)) { + tmp->underflow = 0; + tmp->min = 0; + tmp->max = 1; + tmp->overflow = 0; + return 1; } } } @@ -2231,42 +2229,36 @@ static inline zend_class_entry *get_class_entry(const zend_script *script, zend_ } static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) { + uint32_t result_mask = type_mask & MAY_BE_ANY; if (type_mask & MAY_BE_VOID) { - type_mask &= ~MAY_BE_VOID; - type_mask |= MAY_BE_NULL; + result_mask |= MAY_BE_NULL; } if (type_mask & MAY_BE_CALLABLE) { - type_mask &= ~MAY_BE_CALLABLE; - type_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + result_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; } if (type_mask & MAY_BE_ITERABLE) { - type_mask &= ~MAY_BE_ITERABLE; - type_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + result_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; } if (type_mask & MAY_BE_ARRAY) { - type_mask |= MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + result_mask |= MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; } - return type_mask; + return result_mask; } uint32_t zend_fetch_arg_info_type(const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce) { - uint32_t tmp = 0; + uint32_t tmp; + if (!ZEND_TYPE_IS_SET(arg_info->type)) { + return MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_RC1|MAY_BE_RCN; + } + tmp = zend_convert_type_declaration_mask(ZEND_TYPE_MASK(arg_info->type)); *pce = NULL; if (ZEND_TYPE_IS_CLASS(arg_info->type)) { - // class type hinting... zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type)); tmp |= MAY_BE_OBJECT; *pce = get_class_entry(script, lcname); zend_string_release_ex(lcname, 0); - } else if (ZEND_TYPE_IS_MASK(arg_info->type)) { - tmp |= zend_convert_type_declaration_mask(ZEND_TYPE_MASK(arg_info->type)); - } else { - tmp |= MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; - } - if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { - tmp |= MAY_BE_NULL; } if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { tmp |= MAY_BE_RC1 | MAY_BE_RCN; diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c index aa638032cae2a..08879734a871c 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -652,9 +652,7 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, } case ZEND_VERIFY_RETURN_TYPE: { zend_arg_info *ret_info = op_array->arg_info - 1; - if (ZEND_TYPE_IS_CLASS(ret_info->type) - || (ZEND_TYPE_IS_MASK(ret_info->type) - && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(val))) + if (!ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(val)) || (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) { return 0; } diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 12dd8aba7aee6..97b6dba789a78 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -602,8 +602,8 @@ static void accel_copy_permanent_strings(zend_new_interned_string_func_t new_int } for (i = 0 ; i < num_args; i++) { if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_bool allow_null = ZEND_TYPE_ALLOW_NULL(arg_info[i].type); - arg_info[i].type = ZEND_TYPE_ENCODE_CLASS(new_interned_string(ZEND_TYPE_NAME(arg_info[i].type)), allow_null); + ZEND_TYPE_SET_PTR(arg_info[i].type, + new_interned_string(ZEND_TYPE_NAME(arg_info[i].type))); } } } @@ -3580,7 +3580,7 @@ static zend_bool preload_try_resolve_property_types(zend_class_entry *ce) } zend_string_release(name); - prop->type = ZEND_TYPE_ENCODE_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type)); + prop->type = (zend_type) ZEND_TYPE_INIT_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type)); } ZEND_HASH_FOREACH_END(); } diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index f581841aab5ce..47b1cfb5f59c4 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1345,7 +1345,7 @@ static zend_property_info *zend_jit_get_prop_not_accepting_double(zend_reference { zend_property_info *prop; ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { - if (!ZEND_TYPE_IS_MASK(prop->type) || !(ZEND_TYPE_MASK(prop->type) & MAY_BE_DOUBLE)) { + if (!(ZEND_TYPE_MASK(prop->type) & MAY_BE_DOUBLE)) { return prop; } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index 02b6f606aebca..cff2b4a9d3569 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -7102,7 +7102,7 @@ static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ss zend_arg_info *arg_info = func->op_array.arg_info + num_args; if (ZEND_TYPE_IS_SET(arg_info->type)) { - if (ZEND_TYPE_IS_MASK(arg_info->type)) { + if (ZEND_TYPE_IS_ONLY_MASK(arg_info->type)) { uint32_t type_mask = ZEND_TYPE_MASK(arg_info->type); uint32_t info = _ssa_op1_info(op_array, ssa, call_info->arg_info[num_args].opline); if ((info & (MAY_BE_ANY|MAY_BE_UNDEF)) & ~type_mask) { diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index dc7a76b32610f..e74b7bb6689b7 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -237,7 +237,7 @@ static void zend_hash_clone_prop_info(HashTable *ht) zend_class_entry *ce = ZEND_TYPE_CE(prop_info->type); if (IN_ARENA(ce)) { ce = ARENA_REALLOC(ce); - prop_info->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop_info->type)); + ZEND_TYPE_SET_PTR(prop_info->type, ce); } } } diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 697bb10b0d749..6e4d52cee6ffc 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -499,14 +499,9 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra SERIALIZE_STR(p->name); } if (ZEND_TYPE_IS_CLASS(p->type)) { - zend_bool allow_null = ZEND_TYPE_ALLOW_NULL(p->type); zend_string *type_name = ZEND_TYPE_NAME(p->type); - SERIALIZE_STR(type_name); - p->type = - (Z_UL(1) << (sizeof(zend_type)*8-1)) | /* type is class */ - (allow_null ? (Z_UL(1) << (sizeof(zend_type)*8-2)) : Z_UL(0)) | /* type allow null */ - (zend_type)type_name; + ZEND_TYPE_SET_PTR(p->type, type_name); } p++; } @@ -577,16 +572,14 @@ static void zend_file_cache_serialize_prop_info(zval *zv, SERIALIZE_STR(prop->doc_comment); } } - if (prop->type) { - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *name = ZEND_TYPE_NAME(prop->type); - SERIALIZE_STR(name); - prop->type = ZEND_TYPE_ENCODE_CLASS(name, ZEND_TYPE_ALLOW_NULL(prop->type)); - } else if (ZEND_TYPE_IS_CE(prop->type)) { - zend_class_entry *ce = ZEND_TYPE_CE(prop->type); - SERIALIZE_PTR(ce); - prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type)); - } + if (ZEND_TYPE_IS_NAME(prop->type)) { + zend_string *name = ZEND_TYPE_NAME(prop->type); + SERIALIZE_STR(name); + ZEND_TYPE_SET_PTR(prop->type, name); + } else if (ZEND_TYPE_IS_CE(prop->type)) { + zend_class_entry *ce = ZEND_TYPE_CE(prop->type); + SERIALIZE_PTR(ce); + ZEND_TYPE_SET_PTR(prop->type, ce); } } } @@ -1202,12 +1195,10 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr if (!IS_UNSERIALIZED(p->name)) { UNSERIALIZE_STR(p->name); } - if (p->type & (Z_UL(1) << (sizeof(zend_type)*8-1))) { /* type is class */ - zend_bool allow_null = (p->type & (Z_UL(1) << (sizeof(zend_type)*8-2))) != 0; /* type allow null */ - zend_string *type_name = (zend_string*)(p->type & ~(((Z_UL(1) << (sizeof(zend_type)*8-1))) | ((Z_UL(1) << (sizeof(zend_type)*8-2))))); - + if (ZEND_TYPE_IS_CLASS(p->type)) { + zend_string *type_name = ZEND_TYPE_NAME(p->type); UNSERIALIZE_STR(type_name); - p->type = ZEND_TYPE_ENCODE_CLASS(type_name, allow_null); + ZEND_TYPE_SET_PTR(p->type, type_name); } p++; } @@ -1278,16 +1269,14 @@ static void zend_file_cache_unserialize_prop_info(zval *zv, UNSERIALIZE_STR(prop->doc_comment); } } - if (prop->type) { - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *name = ZEND_TYPE_NAME(prop->type); - UNSERIALIZE_STR(name); - prop->type = ZEND_TYPE_ENCODE_CLASS(name, ZEND_TYPE_ALLOW_NULL(prop->type)); - } else if (ZEND_TYPE_IS_CE(prop->type)) { - zend_class_entry *ce = ZEND_TYPE_CE(prop->type); - UNSERIALIZE_PTR(ce); - prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type)); - } + if (ZEND_TYPE_IS_NAME(prop->type)) { + zend_string *name = ZEND_TYPE_NAME(prop->type); + UNSERIALIZE_STR(name); + ZEND_TYPE_SET_PTR(prop->type, name); + } else if (ZEND_TYPE_IS_CE(prop->type)) { + zend_class_entry *ce = ZEND_TYPE_CE(prop->type); + UNSERIALIZE_PTR(ce); + ZEND_TYPE_SET_PTR(prop->type, ce); } } } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 60c7620ee9d99..56c64dc87d1a8 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -501,10 +501,8 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc } if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type); - zend_bool allow_null = ZEND_TYPE_ALLOW_NULL(arg_info[i].type); - zend_accel_store_interned_string(type_name); - arg_info[i].type = ZEND_TYPE_ENCODE_CLASS(type_name, allow_null); + ZEND_TYPE_SET_PTR(arg_info[i].type, type_name); } } if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { @@ -664,7 +662,7 @@ static void zend_persist_property_info(zval *zv) if (ZEND_TYPE_IS_NAME(prop->type)) { zend_string *class_name = ZEND_TYPE_NAME(prop->type); zend_accel_store_interned_string(class_name); - prop->type = ZEND_TYPE_ENCODE_CLASS(class_name, ZEND_TYPE_ALLOW_NULL(prop->type)); + ZEND_TYPE_SET_PTR(prop->type, class_name); } } @@ -944,7 +942,7 @@ static void zend_update_parent_ce(zend_class_entry *ce) if (ce->type == ZEND_USER_CLASS) { ce = zend_shared_alloc_get_xlat_entry(ce); if (ce) { - prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type)); + ZEND_TYPE_SET_PTR(prop->type, ce); } } } diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index cc798b27de050..62a3deaea0053 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -224,10 +224,8 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) } if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type); - zend_bool allow_null = ZEND_TYPE_ALLOW_NULL(arg_info[i].type); - ADD_INTERNED_STRING(type_name); - arg_info[i].type = ZEND_TYPE_ENCODE_CLASS(type_name, allow_null); + ZEND_TYPE_SET_PTR(arg_info[i].type, type_name); } } } @@ -307,7 +305,7 @@ static void zend_persist_property_info_calc(zval *zv) if (ZEND_TYPE_IS_NAME(prop->type)) { zend_string *class_name = ZEND_TYPE_NAME(prop->type); ADD_INTERNED_STRING(class_name); - prop->type = ZEND_TYPE_ENCODE_CLASS(class_name, ZEND_TYPE_ALLOW_NULL(prop->type)); + ZEND_TYPE_SET_PTR(prop->type, class_name); } if (ZCG(accel_directives).save_comments && prop->doc_comment) { ADD_STRING(prop->doc_comment); diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 5bfbb5f6f9a60..9dd17d5638846 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -2572,15 +2572,15 @@ ZEND_METHOD(reflection_parameter, isArray) { reflection_object *intern; parameter_reference *param; - zend_type type; + uint32_t type_mask; if (zend_parse_parameters_none() == FAILURE) { return; } GET_REFLECTION_OBJECT_PTR(param); - type = ZEND_TYPE_WITHOUT_NULL(param->arg_info->type); - RETVAL_BOOL(ZEND_TYPE_MASK(type) == MAY_BE_ARRAY); + type_mask = ZEND_TYPE_MASK_WITHOUT_NULL(param->arg_info->type); + RETVAL_BOOL(type_mask == MAY_BE_ARRAY); } /* }}} */ @@ -2590,15 +2590,15 @@ ZEND_METHOD(reflection_parameter, isCallable) { reflection_object *intern; parameter_reference *param; - zend_type type; + uint32_t type_mask; if (zend_parse_parameters_none() == FAILURE) { return; } GET_REFLECTION_OBJECT_PTR(param); - type = ZEND_TYPE_WITHOUT_NULL(param->arg_info->type); - RETVAL_BOOL(ZEND_TYPE_MASK(type) == MAY_BE_CALLABLE); + type_mask = ZEND_TYPE_MASK_WITHOUT_NULL(param->arg_info->type); + RETVAL_BOOL(type_mask == MAY_BE_CALLABLE); } /* }}} */ @@ -2829,6 +2829,11 @@ ZEND_METHOD(reflection_type, allowsNull) } /* }}} */ +static zend_string *zend_type_to_string_without_null(zend_type type) { + ZEND_TYPE_MASK(type) &= ~MAY_BE_NULL; + return zend_type_to_string(type); +} + /* {{{ proto public string ReflectionType::__toString() Return the text of the type hint */ ZEND_METHOD(reflection_type, __toString) @@ -2857,7 +2862,7 @@ ZEND_METHOD(reflection_named_type, getName) } GET_REFLECTION_OBJECT_PTR(param); - RETURN_STR(zend_type_to_string(ZEND_TYPE_WITHOUT_NULL(param->type))); + RETURN_STR(zend_type_to_string_without_null(param->type)); } /* }}} */ @@ -2873,7 +2878,7 @@ ZEND_METHOD(reflection_named_type, isBuiltin) } GET_REFLECTION_OBJECT_PTR(param); - RETVAL_BOOL(ZEND_TYPE_IS_MASK(param->type)); + RETVAL_BOOL(ZEND_TYPE_IS_ONLY_MASK(param->type)); } /* }}} */ diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index cdff844ea9eb8..a3f09f32ef615 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -237,7 +237,8 @@ PHP_MINIT_FUNCTION(zend_test) zval val; ZVAL_LONG(&val, 123); zend_declare_typed_property( - zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, ZEND_TYPE_ENCODE_CODE(IS_LONG, 0)); + zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, + (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0)); zend_string_release(name); } @@ -248,7 +249,7 @@ PHP_MINIT_FUNCTION(zend_test) ZVAL_NULL(&val); zend_declare_typed_property( zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, - ZEND_TYPE_ENCODE_CLASS(class_name, 1)); + (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 1)); zend_string_release(name); } @@ -258,7 +259,7 @@ PHP_MINIT_FUNCTION(zend_test) ZVAL_LONG(&val, 123); zend_declare_typed_property( zend_test_class, name, &val, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC, NULL, - ZEND_TYPE_ENCODE_CODE(IS_LONG, 0)); + (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0)); zend_string_release(name); } From 8b62d663b100d8f1543ccc63ad0ea159db89893b Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 24 Sep 2019 16:05:54 +0200 Subject: [PATCH 02/48] Store pass_by_reference+is_variadic in type mask --- Zend/zend.c | 2 +- Zend/zend_API.c | 6 +-- Zend/zend_API.h | 34 ++++++++---- Zend/zend_closures.c | 6 +-- Zend/zend_compile.c | 39 +++++++------- Zend/zend_compile.h | 16 +++--- Zend/zend_execute.c | 32 ++++++------ Zend/zend_inheritance.c | 21 ++++---- Zend/zend_types.h | 57 ++++++++++++--------- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 10 ++-- ext/com_dotnet/com_com.c | 6 +-- ext/com_dotnet/com_handlers.c | 6 +-- ext/opcache/Optimizer/dfa_pass.c | 2 +- ext/opcache/Optimizer/optimize_func_calls.c | 2 +- ext/opcache/Optimizer/zend_inference.c | 8 +-- ext/opcache/ZendAccelerator.c | 2 +- ext/opcache/jit/zend_jit_helpers.c | 6 +-- ext/opcache/jit/zend_jit_x86.dasc | 8 +-- ext/pdo/pdo_dbh.c | 4 +- ext/reflection/php_reflection.c | 16 +++--- ext/zend_test/test.c | 6 +-- sapi/phpdbg/phpdbg_frame.c | 2 +- 23 files changed, 157 insertions(+), 136 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index bd5a2a892fce3..8c9ae86d747ca 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -965,7 +965,7 @@ static void zend_resolve_property_types(void) /* {{{ */ zend_class_entry *prop_ce = zend_hash_find_ptr(CG(class_table), lc_type_name); ZEND_ASSERT(prop_ce && prop_ce->type == ZEND_INTERNAL_CLASS); - prop_info->type = (zend_type) ZEND_TYPE_INIT_CE(prop_ce, ZEND_TYPE_ALLOW_NULL(prop_info->type)); + prop_info->type = (zend_type) ZEND_TYPE_INIT_CE(prop_ce, ZEND_TYPE_ALLOW_NULL(prop_info->type), 0); zend_string_release(lc_type_name); zend_string_release(type_name); } diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 42283c4670d3c..3c1ca4c7188c4 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2045,10 +2045,10 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio } else { internal_function->required_num_args = info->required_num_args; } - if (info->return_reference) { + if (ZEND_ARG_SEND_MODE(info)) { internal_function->fn_flags |= ZEND_ACC_RETURN_REFERENCE; } - if (ptr->arg_info[ptr->num_args].is_variadic) { + if (ZEND_ARG_IS_VARIADIC(&ptr->arg_info[ptr->num_args])) { internal_function->fn_flags |= ZEND_ACC_VARIADIC; /* Don't count the variadic argument */ internal_function->num_args--; @@ -3704,7 +3704,7 @@ ZEND_API int zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval *zv, ze ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment) /* {{{ */ { - return zend_declare_typed_property(ce, name, property, access_type, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE()); + return zend_declare_typed_property(ce, name, property, access_type, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0)); } /* }}} */ diff --git a/Zend/zend_API.h b/Zend/zend_API.h index d18da89c840bc..2801b08266897 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -96,31 +96,43 @@ typedef struct _zend_fcall_info_cache { #define ZEND_FE_END { NULL, NULL, NULL, 0, 0 } -#define ZEND_ARG_INFO(pass_by_ref, name) { #name, ZEND_TYPE_INIT_NONE(), pass_by_ref, 0}, -#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, ZEND_TYPE_INIT_CLASS_CONST(#classname, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, ZEND_TYPE_INIT_NONE(), pass_by_ref, 1 }, -#define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null), pass_by_ref, 1 }, -#define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, ZEND_TYPE_INIT_CLASS_CONST(#classname, allow_null), pass_by_ref, 1 }, +#define _ZEND_ARG_INFO_FLAGS(pass_by_ref, is_variadic) \ + (((pass_by_ref) << _ZEND_SEND_MODE_SHIFT) | ((is_variadic) ? _ZEND_IS_VARIADIC_BIT : 0)) + +#define ZEND_ARG_INFO(pass_by_ref, name) \ + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0))}, +#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) \ + { #name, ZEND_TYPE_INIT_CLASS_CONST(#classname, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0)) }, +#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) \ + { #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0)) }, +#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) \ + { #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0)) }, +#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \ + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0)) }, +#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) \ + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 1)) }, +#define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \ + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1)) }, +#define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, classname, allow_null) \ + { #name, ZEND_TYPE_INIT_CLASS_CONST(#classname, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1)) }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, return_reference, required_num_args, class_name, allow_null) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null), return_reference, 0 }, + { (const char*)(zend_uintptr_t)(required_num_args), \ + ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0)) }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO(name, class_name, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, 0, -1, class_name, allow_null) #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null), return_reference, 0 }, + { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0)) }, #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(name, type, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, 0, -1, type, allow_null) #define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(), return_reference, 0 }, + { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(return_reference, 0)) }, #define ZEND_BEGIN_ARG_INFO(name, _unused) \ ZEND_BEGIN_ARG_INFO_EX(name, {}, ZEND_RETURN_VALUE, -1) #define ZEND_END_ARG_INFO() }; diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index bb469558a1aec..63b91aacbda2b 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -557,16 +557,16 @@ static HashTable *zend_closure_get_debug_info(zend_object *object, int *is_temp) if (arg_info->name) { if (zstr_args) { name = zend_strpprintf(0, "%s$%s", - arg_info->pass_by_reference ? "&" : "", + ZEND_ARG_SEND_MODE(arg_info) ? "&" : "", ZSTR_VAL(arg_info->name)); } else { name = zend_strpprintf(0, "%s$%s", - arg_info->pass_by_reference ? "&" : "", + ZEND_ARG_SEND_MODE(arg_info) ? "&" : "", ((zend_internal_arg_info*)arg_info)->name); } } else { name = zend_strpprintf(0, "%s$param%d", - arg_info->pass_by_reference ? "&" : "", + ZEND_ARG_SEND_MODE(arg_info) ? "&" : "", i + 1); } ZVAL_NEW_STR(&info, zend_strpprintf(0, "%s", i >= required ? "" : "")); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index e6f00aef94f3f..ea93e878d03a3 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1135,7 +1135,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop } else if (ZEND_TYPE_IS_CE(type)) { str = zend_string_copy(ZEND_TYPE_CE(type)->name); } else { - uint32_t type_mask = ZEND_TYPE_MASK_WITHOUT_NULL(type); + uint32_t type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); switch (type_mask) { case MAY_BE_FALSE|MAY_BE_TRUE: str = ZSTR_KNOWN(ZEND_STR_BOOL); @@ -1199,7 +1199,7 @@ static void zend_mark_function_as_generator() /* {{{ */ || zend_string_equals_literal_ci(name, "Iterator") || zend_string_equals_literal_ci(name, "Generator"); } else { - valid_type = (ZEND_TYPE_MASK(return_info.type) & MAY_BE_ITERABLE) != 0; + valid_type = (ZEND_TYPE_FULL_MASK(return_info.type) & MAY_BE_ITERABLE) != 0; } if (!valid_type) { @@ -5358,11 +5358,11 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */ n = MIN(func->common.num_args, MAX_ARG_FLAG_NUM); i = 0; while (i < n) { - ZEND_SET_ARG_FLAG(func, i + 1, func->common.arg_info[i].pass_by_reference); + ZEND_SET_ARG_FLAG(func, i + 1, ZEND_ARG_SEND_MODE(&func->common.arg_info[i])); i++; } - if (UNEXPECTED(func->common.fn_flags & ZEND_ACC_VARIADIC && func->common.arg_info[i].pass_by_reference)) { - uint32_t pass_by_reference = func->common.arg_info[i].pass_by_reference; + if (UNEXPECTED(func->common.fn_flags & ZEND_ACC_VARIADIC && ZEND_ARG_SEND_MODE(&func->common.arg_info[i]))) { + uint32_t pass_by_reference = ZEND_ARG_SEND_MODE(&func->common.arg_info[i]); while (i < MAX_ARG_FLAG_NUM) { ZEND_SET_ARG_FLAG(func, i + 1, pass_by_reference); i++; @@ -5381,7 +5381,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null } if (ast->kind == ZEND_AST_TYPE) { - return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, allow_null); + return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, allow_null, 0); } else { zend_string *class_name = zend_ast_get_str(ast); zend_uchar type = zend_lookup_builtin_type_by_name(class_name); @@ -5395,7 +5395,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null if (type == IS_VOID && allow_null) { zend_error_noreturn(E_COMPILE_ERROR, "Void type cannot be nullable"); } - return (zend_type) ZEND_TYPE_INIT_CODE(type, allow_null); + return (zend_type) ZEND_TYPE_INIT_CODE(type, allow_null, 0); } else { const char *correct_name; zend_string *orig_name = zend_ast_get_str(ast); @@ -5427,7 +5427,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null } } - return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, allow_null); + return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, allow_null, 0); } } } @@ -5447,12 +5447,12 @@ static zend_bool zend_is_valid_default_value(zend_type type, zval *value) if (ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(value))) { return 1; } - if ((ZEND_TYPE_MASK(type) & MAY_BE_DOUBLE) && Z_TYPE_P(value) == IS_LONG) { + if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_DOUBLE) && Z_TYPE_P(value) == IS_LONG) { /* Integers are allowed as initializers for floating-point values. */ convert_to_double(value); return 1; } - if ((ZEND_TYPE_MASK(type) & MAY_BE_ITERABLE) && Z_TYPE_P(value) == IS_ARRAY) { + if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_ITERABLE) && Z_TYPE_P(value) == IS_ARRAY) { return 1; } return 0; @@ -5469,9 +5469,9 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ /* Use op_array->arg_info[-1] for return type */ arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0); arg_infos->name = NULL; - arg_infos->pass_by_reference = (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0; - arg_infos->is_variadic = 0; arg_infos->type = zend_compile_typename(return_type_ast, 0); + ZEND_TYPE_FULL_MASK(arg_infos->type) |= _ZEND_ARG_INFO_FLAGS( + (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0, /* is_variadic */ 0); arg_infos++; op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE; } else { @@ -5539,23 +5539,18 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ arg_info = &arg_infos[i]; arg_info->name = zend_string_copy(name); - arg_info->pass_by_reference = is_ref; - arg_info->is_variadic = is_variadic; - arg_info->type = (zend_type) ZEND_TYPE_INIT_NONE(); + arg_info->type = (zend_type) ZEND_TYPE_INIT_NONE(0); if (type_ast) { uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF; - uint32_t arg_type; zend_bool is_class; op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS; arg_info->type = zend_compile_typename(type_ast, default_type == IS_NULL); - is_class = ZEND_TYPE_IS_CLASS(arg_info->type); - arg_type = !is_class ? ZEND_TYPE_MASK(arg_info->type) : 0; - if (arg_type & MAY_BE_VOID) { + if (!is_class && (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID)) { zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type"); } @@ -5579,6 +5574,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ opline->extended_value = zend_alloc_cache_slot(); } } + + ZEND_TYPE_FULL_MASK(arg_info->type) |= _ZEND_ARG_INFO_FLAGS(is_ref, is_variadic); } /* These are assigned at the end to avoid uninitialized memory in case of an error */ @@ -6078,12 +6075,12 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) / zend_string *name = zval_make_interned_string(zend_ast_get_zval(name_ast)); zend_string *doc_comment = NULL; zval value_zv; - zend_type type = ZEND_TYPE_INIT_NONE(); + zend_type type = ZEND_TYPE_INIT_NONE(0); if (type_ast) { type = zend_compile_typename(type_ast, 0); - if (ZEND_TYPE_MASK(type) & (MAY_BE_VOID|MAY_BE_CALLABLE)) { + if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_CALLABLE)) { zend_string *str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Property %s::$%s cannot have type %s", diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index e4096a05a5693..96db1dcb3a695 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -383,16 +383,12 @@ typedef struct _zend_class_constant { typedef struct _zend_internal_arg_info { const char *name; zend_type type; - zend_uchar pass_by_reference; - zend_bool is_variadic; } zend_internal_arg_info; /* arg_info for user functions */ typedef struct _zend_arg_info { zend_string *name; zend_type type; - zend_uchar pass_by_reference; - zend_bool is_variadic; } zend_arg_info; /* the following structure repeats the layout of zend_internal_arg_info, @@ -403,8 +399,6 @@ typedef struct _zend_arg_info { typedef struct _zend_internal_function_info { zend_uintptr_t required_num_args; zend_type type; - zend_bool return_reference; - zend_bool _is_variadic; } zend_internal_function_info; struct _zend_op_array { @@ -934,6 +928,14 @@ zend_string *zend_type_to_string(zend_type type); #define ZEND_SEND_BY_REF 1u #define ZEND_SEND_PREFER_REF 2u +/* The send mode and is_variadic flag are stored as part of zend_type */ +#define _ZEND_SEND_MODE_SHIFT _ZEND_TYPE_EXTRA_FLAGS_SHIFT +#define _ZEND_IS_VARIADIC_BIT (1 << (_ZEND_TYPE_EXTRA_FLAGS_SHIFT + 2)) +#define ZEND_ARG_SEND_MODE(arg_info) \ + ((ZEND_TYPE_FULL_MASK((arg_info)->type) >> _ZEND_SEND_MODE_SHIFT) & 3) +#define ZEND_ARG_IS_VARIADIC(arg_info) \ + ((ZEND_TYPE_FULL_MASK((arg_info)->type) & _ZEND_IS_VARIADIC_BIT) != 0) + #define ZEND_DIM_IS (1 << 0) /* isset fetch needed for null coalesce */ #define ZEND_DIM_ALTERNATIVE_SYNTAX (1 << 1) /* deprecated curly brace usage */ @@ -950,7 +952,7 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, } arg_num = zf->common.num_args; } - return UNEXPECTED((zf->common.arg_info[arg_num].pass_by_reference & mask) != 0); + return UNEXPECTED((ZEND_ARG_SEND_MODE(&zf->common.arg_info[arg_num]) & mask) != 0); } #define ARG_MUST_BE_SENT_BY_REF(zf, arg_num) \ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index d6928a6cfdfd5..097137aa5cff0 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -676,7 +676,7 @@ static ZEND_COLD void zend_verify_type_error_common( *need_kind = ZSTR_VAL(ZEND_TYPE_NAME(arg_info->type)); } } else { - uint32_t type_mask = ZEND_TYPE_MASK_WITHOUT_NULL(arg_info->type); + uint32_t type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(arg_info->type); switch (type_mask) { case MAY_BE_OBJECT: *need_msg = "be an "; @@ -695,7 +695,7 @@ static ZEND_COLD void zend_verify_type_error_common( /* 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. */ zend_type type = arg_info->type; - ZEND_TYPE_MASK(type) &= ~MAY_BE_NULL; + ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL; *need_msg = "be of the type "; *need_kind = ZSTR_VAL(zend_type_to_string(type)); break; @@ -908,7 +908,7 @@ static zend_bool zend_resolve_class_type(zend_type *type, zend_class_entry *self } zend_string_release(name); - *type = (zend_type) ZEND_TYPE_INIT_CE(ce, ZEND_TYPE_ALLOW_NULL(*type)); + *type = (zend_type) ZEND_TYPE_INIT_CE(ce, ZEND_TYPE_ALLOW_NULL(*type), 0); return 1; } @@ -928,13 +928,13 @@ static zend_always_inline zend_bool i_zend_check_property_type(zend_property_inf return instanceof_function(Z_OBJCE_P(property), ZEND_TYPE_CE(info->type)); } - ZEND_ASSERT(!(ZEND_TYPE_MASK(info->type) & MAY_BE_CALLABLE)); + ZEND_ASSERT(!(ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_CALLABLE)); if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) { return 1; - } else if (ZEND_TYPE_MASK(info->type) & MAY_BE_ITERABLE) { + } 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_MASK(info->type), property, strict, 0); + return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0); } } @@ -1000,7 +1000,7 @@ static zend_always_inline zend_bool zend_check_type( return 1; } - type_mask = ZEND_TYPE_MASK(type); + 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) { @@ -1188,7 +1188,7 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret) zend_internal_arg_info *ret_info = zf->internal_function.arg_info - 1; void *dummy_cache_slot = NULL; - if (ZEND_TYPE_MASK(ret_info->type) & MAY_BE_VOID) { + 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_type_name(ret), ""); return 0; @@ -1565,7 +1565,7 @@ static zend_property_info *zend_get_prop_not_accepting_double(zend_reference *re { zend_property_info *prop; ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { - if (!(ZEND_TYPE_MASK(prop->type) & MAY_BE_DOUBLE)) { + if (!(ZEND_TYPE_FULL_MASK(prop->type) & MAY_BE_DOUBLE)) { return prop; } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); @@ -2533,7 +2533,7 @@ static zend_always_inline zend_bool check_type_array_assignable(zend_type type) if (!ZEND_TYPE_IS_SET(type)) { return 1; } - return (ZEND_TYPE_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) != 0; + return (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) != 0; } /* Checks whether an array can be assigned to the reference. Throws error if not assignable. */ @@ -2964,7 +2964,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( return 1; } - type_mask = ZEND_TYPE_MASK(type); + type_mask = ZEND_TYPE_FULL_MASK(type); if (type_mask & MAY_BE_ITERABLE) { return zend_is_iterable(zv); } @@ -3018,9 +3018,9 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference if (!seen_prop) { seen_prop = prop; seen_type_mask = ZEND_TYPE_IS_CLASS(prop->type) - ? MAY_BE_OBJECT : ZEND_TYPE_MASK_WITHOUT_NULL(prop->type); + ? MAY_BE_OBJECT : ZEND_TYPE_PURE_MASK_WITHOUT_NULL(prop->type); } else if (needs_coercion - && seen_type_mask != ZEND_TYPE_MASK_WITHOUT_NULL(prop->type)) { + && seen_type_mask != ZEND_TYPE_PURE_MASK_WITHOUT_NULL(prop->type)) { zend_throw_conflicting_coercion_error(seen_prop, prop, zv); return 0; } @@ -3087,13 +3087,13 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_propert if (result < 0) { zend_property_info *ref_prop = ZEND_REF_FIRST_SOURCE(Z_REF_P(orig_val)); - if (ZEND_TYPE_MASK_WITHOUT_NULL(prop_info->type) - != ZEND_TYPE_MASK_WITHOUT_NULL(ref_prop->type)) { + if (ZEND_TYPE_PURE_MASK_WITHOUT_NULL(prop_info->type) + != ZEND_TYPE_PURE_MASK_WITHOUT_NULL(ref_prop->type)) { /* Invalid due to conflicting coercion */ zend_throw_ref_type_error_type(ref_prop, prop_info, val); return 0; } - if (zend_verify_weak_scalar_type_hint(ZEND_TYPE_MASK(prop_info->type), val)) { + if (zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop_info->type), val)) { return 1; } } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 40d4296e5d7d3..af81c327e1586 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -350,7 +350,7 @@ static inheritance_status zend_perform_covariant_type_check( } return unlinked_instanceof(fe_ce, proto_ce) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; - } else if (ZEND_TYPE_MASK(proto_type) & MAY_BE_ITERABLE) { + } else if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) { if (ZEND_TYPE_IS_CLASS(fe_type)) { zend_string *fe_class_name = resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); @@ -363,9 +363,9 @@ static inheritance_status zend_perform_covariant_type_check( ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } - return ZEND_TYPE_MASK(fe_type) & (MAY_BE_ARRAY|MAY_BE_ITERABLE) + return ZEND_TYPE_FULL_MASK(fe_type) & (MAY_BE_ARRAY|MAY_BE_ITERABLE) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; - } else if (ZEND_TYPE_MASK(proto_type) & MAY_BE_OBJECT) { + } else if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_OBJECT) { if (ZEND_TYPE_IS_CLASS(fe_type)) { /* Currently, any class name would be allowed here. We still perform a class lookup * for forward-compatibility reasons, as we may have named types in the future that @@ -380,9 +380,10 @@ static inheritance_status zend_perform_covariant_type_check( return INHERITANCE_SUCCESS; } - return ZEND_TYPE_MASK(fe_type) & MAY_BE_OBJECT ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + return ZEND_TYPE_FULL_MASK(fe_type) & MAY_BE_OBJECT + ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } else { - return ZEND_TYPE_MASK_WITHOUT_NULL(fe_type) == ZEND_TYPE_MASK_WITHOUT_NULL(proto_type) + return ZEND_TYPE_PURE_MASK_WITHOUT_NULL(fe_type) == ZEND_TYPE_PURE_MASK_WITHOUT_NULL(proto_type) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } } @@ -489,7 +490,7 @@ static inheritance_status zend_do_perform_implementation_check( } /* by-ref constraints on arguments are invariant */ - if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(fe_arg_info) != ZEND_ARG_SEND_MODE(proto_arg_info)) { return INHERITANCE_ERROR; } } @@ -560,11 +561,11 @@ static ZEND_COLD zend_string *zend_get_function_declaration(const zend_function for (i = 0; i < num_args;) { zend_append_type_hint(&str, fptr, arg_info, 0); - if (arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(arg_info)) { smart_str_appendc(&str, '&'); } - if (arg_info->is_variadic) { + if (ZEND_ARG_IS_VARIADIC(arg_info)) { smart_str_appends(&str, "..."); } @@ -581,7 +582,7 @@ static ZEND_COLD zend_string *zend_get_function_declaration(const zend_function smart_str_append_unsigned(&str, i); } - if (i >= required && !arg_info->is_variadic) { + if (i >= required && !ZEND_ARG_IS_VARIADIC(arg_info)) { smart_str_appends(&str, " = "); if (fptr->type == ZEND_USER_FUNCTION) { zend_op *precv = NULL; @@ -846,7 +847,7 @@ inheritance_status property_types_compatible( const zend_property_info *parent_info, const zend_property_info *child_info) { zend_string *parent_name, *child_name; zend_class_entry *parent_type_ce, *child_type_ce; - if (ZEND_TYPE_MASK(parent_info->type) == ZEND_TYPE_MASK(child_info->type) + if (ZEND_TYPE_PURE_MASK(parent_info->type) == ZEND_TYPE_PURE_MASK(child_info->type) && ZEND_TYPE_NAME(parent_info->type) == ZEND_TYPE_NAME(child_info->type)) { return INHERITANCE_SUCCESS; } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index c5cb8425e1dc8..d77fbc68a3718 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -106,18 +106,19 @@ typedef void (*copy_ctor_func_t)(zval *pElement); * It shouldn't be used directly. Only through ZEND_TYPE_* macros. * * ZEND_TYPE_IS_SET() - checks if type-hint exists - * ZEND_TYPE_IS_MASK() - checks if type-hint refer to standard type + * ZEND_TYPE_IS_ONLY_MASK() - checks if type-hint refer to standard type * ZEND_TYPE_IS_CLASS() - checks if type-hint refer to some class * ZEND_TYPE_IS_CE() - checks if type-hint refer to some class by zend_class_entry * * ZEND_TYPE_IS_NAME() - checks if type-hint refer to some class by zend_string * * * ZEND_TYPE_NAME() - returns referenced class name * ZEND_TYPE_CE() - returns referenced class entry - * ZEND_TYPE_MASK() - returns MAY_BE_* type mask + * ZEND_TYPE_PURE_MASK() - returns MAY_BE_* type mask + * ZEND_TYPE_FULL_MASK() - returns MAY_BE_* type mask together with other flags * * ZEND_TYPE_ALLOW_NULL() - checks if NULL is allowed * - * ZEND_TYPE_ENCODE_*() should be used for construction. + * ZEND_TYPE_INIT_*() should be used for construction. */ typedef struct { @@ -129,17 +130,16 @@ typedef struct { /* TODO: We could use the extra 32-bit of padding on 64-bit systems. */ } zend_type; -#define _ZEND_TYPE_MASK ((1u<<(IS_VOID+1))-1) -#define _ZEND_TYPE_CE_BIT (1u << 30) -#define _ZEND_TYPE_NAME_BIT (1u << 31) +#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 24 +#define _ZEND_TYPE_MASK ((1u << 24) - 1) +#define _ZEND_TYPE_MAY_BE_MASK ((1u << (IS_VOID+1)) - 1) +#define _ZEND_TYPE_CE_BIT (1u << 22) +#define _ZEND_TYPE_NAME_BIT (1u << 23) /* Must have same value as MAY_BE_NULL */ #define _ZEND_TYPE_NULLABLE_BIT 0x2 #define ZEND_TYPE_IS_SET(t) \ - ((t).type_mask != 0) - -#define ZEND_TYPE_IS_MASK(t) \ - ((t).type_mask != 0 && (t) <= _ZEND_TYPE_CODE_MAX) + (((t).type_mask & _ZEND_TYPE_MASK) != 0) #define ZEND_TYPE_IS_CLASS(t) \ (((t.type_mask) & (_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_CE_BIT)) != 0) @@ -151,7 +151,7 @@ typedef struct { (((t.type_mask) & _ZEND_TYPE_NAME_BIT) != 0) #define ZEND_TYPE_IS_ONLY_MASK(t) \ - ((t).type_mask != 0 && (t).ptr == NULL) + (ZEND_TYPE_IS_SET(t) && (t).ptr == NULL) #define ZEND_TYPE_NAME(t) \ ((zend_string *) (t).ptr) @@ -165,36 +165,47 @@ typedef struct { #define ZEND_TYPE_SET_PTR(t, _ptr) \ ((t).ptr = (_ptr)) -#define ZEND_TYPE_MASK(t) \ +/* FULL_MASK() includes the MAY_BE_* type mask, the CE/NAME bits, as well as extra reserved bits. + * The PURE_MASK() only includes the MAY_BE_* type mask. */ +#define ZEND_TYPE_FULL_MASK(t) \ ((t).type_mask) -#define ZEND_TYPE_MASK_WITHOUT_NULL(t) \ +#define ZEND_TYPE_PURE_MASK(t) \ + ((t).type_mask & _ZEND_TYPE_MAY_BE_MASK) + +#define ZEND_TYPE_FULL_MASK_WITHOUT_NULL(t) \ ((t).type_mask & ~_ZEND_TYPE_NULLABLE_BIT) +#define ZEND_TYPE_PURE_MASK_WITHOUT_NULL(t) \ + ((t).type_mask & _ZEND_TYPE_MAY_BE_MASK & ~_ZEND_TYPE_NULLABLE_BIT) + #define ZEND_TYPE_CONTAINS_CODE(t, code) \ (((t).type_mask & (1u << (code))) != 0) #define ZEND_TYPE_ALLOW_NULL(t) \ (((t).type_mask & _ZEND_TYPE_NULLABLE_BIT) != 0) -#define ZEND_TYPE_INIT_NONE() \ - { NULL, 0 } +#define ZEND_TYPE_INIT_NONE(extra_flags) \ + { NULL, (extra_flags) } #define ZEND_TYPE_INIT_MASK(_type_mask) \ { NULL, (_type_mask) } -#define ZEND_TYPE_INIT_CODE(code, allow_null) \ +#define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? (MAY_BE_FALSE|MAY_BE_TRUE) : (1 << (code))) \ - | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0)) + | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags)) -#define ZEND_TYPE_INIT_CE(_ce, allow_null) \ - { (void *) (_ce), _ZEND_TYPE_CE_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) } +#define ZEND_TYPE_INIT_CE(_ce, allow_null, extra_flags) \ + { (void *) (_ce), \ + _ZEND_TYPE_CE_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } -#define ZEND_TYPE_INIT_CLASS(class_name, allow_null) \ - { (void *) (class_name), _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) } +#define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \ + { (void *) (class_name), \ + _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } -#define ZEND_TYPE_INIT_CLASS_CONST(class_name, allow_null) \ - { (void *) (class_name), _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) } +#define ZEND_TYPE_INIT_CLASS_CONST(class_name, allow_null, extra_flags) \ + { (void *) (class_name), \ + _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } typedef union _zend_value { zend_long lval; /* long value */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3ba0c41d630d2..a36458b8995c3 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4109,7 +4109,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV } if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index f754348bac9dd..be26c30765806 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -8739,7 +8739,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP } if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -18669,7 +18669,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN } if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -26096,7 +26096,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN } if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -32718,7 +32718,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED } if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -44161,7 +44161,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU } if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) diff --git a/ext/com_dotnet/com_com.c b/ext/com_dotnet/com_com.c index 2d3f6c8e5cb36..526ccf79773c9 100644 --- a/ext/com_dotnet/com_com.c +++ b/ext/com_dotnet/com_com.c @@ -496,7 +496,7 @@ int php_com_do_invoke_byref(php_com_dotnet_object *obj, zend_internal_function * if (f->arg_info) { for (i = 0; i < nargs; i++) { - if (f->arg_info[nargs - i - 1].pass_by_reference) { + if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) { byref_count++; } } @@ -505,7 +505,7 @@ int php_com_do_invoke_byref(php_com_dotnet_object *obj, zend_internal_function * if (byref_count) { byref_vals = (VARIANT*)safe_emalloc(sizeof(VARIANT), byref_count, 0); for (j = 0, i = 0; i < nargs; i++) { - if (f->arg_info[nargs - i - 1].pass_by_reference) { + if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) { /* put the value into byref_vals instead */ php_com_variant_from_zval(&byref_vals[j], &args[nargs - i - 1], obj->code_page); @@ -552,7 +552,7 @@ int php_com_do_invoke_byref(php_com_dotnet_object *obj, zend_internal_function * if (f && f->arg_info) { for (i = 0, j = 0; i < nargs; i++) { /* if this was byref, update the zval */ - if (f->arg_info[nargs - i - 1].pass_by_reference) { + if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) { zval *arg = &args[nargs - i - 1]; ZVAL_DEREF(arg); diff --git a/ext/com_dotnet/com_handlers.c b/ext/com_dotnet/com_handlers.c index a481e5d303478..ed08494d85721 100644 --- a/ext/com_dotnet/com_handlers.c +++ b/ext/com_dotnet/com_handlers.c @@ -327,10 +327,8 @@ static zend_function *com_method_get(zend_object **object_ptr, zend_string *name f.arg_info = ecalloc(bindptr.lpfuncdesc->cParams, sizeof(zend_arg_info)); for (i = 0; i < bindptr.lpfuncdesc->cParams; i++) { - f.arg_info[i].type = (zend_type) ZEND_TYPE_INIT_NONE(); - if (bindptr.lpfuncdesc->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FOUT) { - f.arg_info[i].pass_by_reference = ZEND_SEND_BY_REF; - } + zend_bool by_ref = (bindptr.lpfuncdesc->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FOUT) != 0; + f.arg_info[i].type = (zend_type) ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(by_ref, 0)); } f.num_args = bindptr.lpfuncdesc->cParams; diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c index f6d057a0267e4..59c562425f13b 100644 --- a/ext/opcache/Optimizer/dfa_pass.c +++ b/ext/opcache/Optimizer/dfa_pass.c @@ -307,7 +307,7 @@ static inline zend_bool can_elide_return_type_check( } /* These types are not represented exactly */ - if (ZEND_TYPE_MASK(info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) { + if (ZEND_TYPE_FULL_MASK(info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) { return 0; } diff --git a/ext/opcache/Optimizer/optimize_func_calls.c b/ext/opcache/Optimizer/optimize_func_calls.c index ea2b904a0f4e6..2894ca89f4d54 100644 --- a/ext/opcache/Optimizer/optimize_func_calls.c +++ b/ext/opcache/Optimizer/optimize_func_calls.c @@ -116,7 +116,7 @@ static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_o for (i = 0; i < num_args; i++) { /* Don't inline functions with by-reference arguments. This would require * correct handling of INDIRECT arguments. */ - if (func->op_array.arg_info[i].pass_by_reference) { + if (ZEND_ARG_SEND_MODE(&func->op_array.arg_info[i])) { return; } } diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c index d095f6d0c8a59..8fc92f842c070 100644 --- a/ext/opcache/Optimizer/zend_inference.c +++ b/ext/opcache/Optimizer/zend_inference.c @@ -1420,7 +1420,7 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int } else if (op_array->arg_info && opline->op1.num <= op_array->num_args) { zend_type type = op_array->arg_info[opline->op1.num-1].type; - uint32_t mask = ZEND_TYPE_MASK_WITHOUT_NULL(type); + uint32_t mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); if (mask == MAY_BE_LONG) { tmp->underflow = 0; tmp->min = ZEND_LONG_MIN; @@ -2252,7 +2252,7 @@ uint32_t zend_fetch_arg_info_type(const zend_script *script, zend_arg_info *arg_ return MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_RC1|MAY_BE_RCN; } - tmp = zend_convert_type_declaration_mask(ZEND_TYPE_MASK(arg_info->type)); + tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(arg_info->type)); *pce = NULL; if (ZEND_TYPE_IS_CLASS(arg_info->type)) { zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type)); @@ -2357,7 +2357,7 @@ static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_in if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) { uint32_t type = ZEND_TYPE_IS_CLASS(prop_info->type) ? MAY_BE_OBJECT - : zend_convert_type_declaration_mask(ZEND_TYPE_MASK(prop_info->type)); + : zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type)); if (ZEND_TYPE_ALLOW_NULL(prop_info->type)) { type |= MAY_BE_NULL; @@ -3089,7 +3089,7 @@ static int zend_update_type_info(const zend_op_array *op_array, ce = NULL; if (arg_info) { tmp = zend_fetch_arg_info_type(script, arg_info, &ce); - if (arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(arg_info)) { tmp |= MAY_BE_REF; } } else { diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 97b6dba789a78..68be526892b3b 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3580,7 +3580,7 @@ static zend_bool preload_try_resolve_property_types(zend_class_entry *ce) } zend_string_release(name); - prop->type = (zend_type) ZEND_TYPE_INIT_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type)); + prop->type = (zend_type) ZEND_TYPE_INIT_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type), 0); } ZEND_HASH_FOREACH_END(); } diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 47b1cfb5f59c4..658350794ed11 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1177,7 +1177,7 @@ static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_arra goto err; } - type_mask = ZEND_TYPE_MASK(arg_info->type); + type_mask = ZEND_TYPE_FULL_MASK(arg_info->type); if (type_mask & MAY_BE_CALLABLE) { if (zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL) == 0) { goto err; @@ -1188,7 +1188,7 @@ static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_arra } } else { if (Z_ISUNDEF_P(arg) || - zend_verify_scalar_type_hint(ZEND_TYPE_MASK(arg_info->type), arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0) == 0) { + zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0) == 0) { goto err; } } @@ -1345,7 +1345,7 @@ static zend_property_info *zend_jit_get_prop_not_accepting_double(zend_reference { zend_property_info *prop; ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { - if (!(ZEND_TYPE_MASK(prop->type) & MAY_BE_DOUBLE)) { + if (!(ZEND_TYPE_FULL_MASK(prop->type) & MAY_BE_DOUBLE)) { return prop; } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index cff2b4a9d3569..0681a450641cc 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -7103,7 +7103,7 @@ static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ss if (ZEND_TYPE_IS_SET(arg_info->type)) { if (ZEND_TYPE_IS_ONLY_MASK(arg_info->type)) { - uint32_t type_mask = ZEND_TYPE_MASK(arg_info->type); + uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type); uint32_t info = _ssa_op1_info(op_array, ssa, call_info->arg_info[num_args].opline); if ((info & (MAY_BE_ANY|MAY_BE_UNDEF)) & ~type_mask) { break; @@ -9032,12 +9032,12 @@ static int zend_jit_recv(dasm_State **Dst, const zend_op *opline, const zend_op_ zend_jit_addr res_addr = zend_jit_decode_op(op_array, opline->result_type, opline->result, opline, NULL, -1); | LOAD_ZVAL_ADDR r0, res_addr - if (arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(arg_info)) { | GET_Z_PTR r0, r0 | add r0, offsetof(zend_reference, val) } if (!ZEND_TYPE_IS_CLASS(type)) { - uint32_t type_mask = ZEND_TYPE_MASK(type); + uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); if (is_power_of_two(type_mask)) { uint32_t type_code = concrete_type(type_mask); | cmp byte [r0 + 8], type_code @@ -9186,7 +9186,7 @@ static int zend_jit_recv_init(dasm_State **Dst, const zend_op *opline, const zen | LOAD_ZVAL_ADDR r0, res_addr | ZVAL_DEREF r0, MAY_BE_REF if (!ZEND_TYPE_IS_CLASS(arg_info->type)) { - uint32_t type_mask = ZEND_TYPE_MASK(arg_info->type); + uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type); if (is_power_of_two(type_mask)) { uint32_t type_code = concrete_type(type_mask); | cmp byte [r0 + 8], type_code diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index e5a740d9d7704..28cca1e11b1ff 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -1292,10 +1292,10 @@ int pdo_hash_methods(pdo_dbh_object_t *dbh_obj, int kind) } else { func.required_num_args = info->required_num_args; } - if (info->return_reference) { + if (ZEND_ARG_SEND_MODE(info)) { func.fn_flags |= ZEND_ACC_RETURN_REFERENCE; } - if (funcs->arg_info[funcs->num_args].is_variadic) { + if (ZEND_ARG_IS_VARIADIC(&funcs->arg_info[funcs->num_args])) { func.fn_flags |= ZEND_ACC_VARIADIC; /* Don't count the variadic argument */ func.num_args--; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 9dd17d5638846..522b9d211d32a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -595,10 +595,10 @@ static void _parameter_string(smart_str *str, zend_function *fptr, struct _zend_ smart_str_append_printf(str, "%s ", ZSTR_VAL(type_str)); zend_string_release(type_str); } - if (arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(arg_info)) { smart_str_appendc(str, '&'); } - if (arg_info->is_variadic) { + if (ZEND_ARG_IS_VARIADIC(arg_info)) { smart_str_appends(str, "..."); } if (arg_info->name) { @@ -2579,7 +2579,7 @@ ZEND_METHOD(reflection_parameter, isArray) } GET_REFLECTION_OBJECT_PTR(param); - type_mask = ZEND_TYPE_MASK_WITHOUT_NULL(param->arg_info->type); + type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(param->arg_info->type); RETVAL_BOOL(type_mask == MAY_BE_ARRAY); } /* }}} */ @@ -2597,7 +2597,7 @@ ZEND_METHOD(reflection_parameter, isCallable) } GET_REFLECTION_OBJECT_PTR(param); - type_mask = ZEND_TYPE_MASK_WITHOUT_NULL(param->arg_info->type); + type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(param->arg_info->type); RETVAL_BOOL(type_mask == MAY_BE_CALLABLE); } /* }}} */ @@ -2631,7 +2631,7 @@ ZEND_METHOD(reflection_parameter, isPassedByReference) } GET_REFLECTION_OBJECT_PTR(param); - RETVAL_BOOL(param->arg_info->pass_by_reference); + RETVAL_BOOL(ZEND_ARG_SEND_MODE(param->arg_info)); } /* }}} */ @@ -2648,7 +2648,7 @@ ZEND_METHOD(reflection_parameter, canBePassedByValue) GET_REFLECTION_OBJECT_PTR(param); /* true if it's ZEND_SEND_BY_VAL or ZEND_SEND_PREFER_REF */ - RETVAL_BOOL(param->arg_info->pass_by_reference != ZEND_SEND_BY_REF); + RETVAL_BOOL(ZEND_ARG_SEND_MODE(param->arg_info) != ZEND_SEND_BY_REF); } /* }}} */ @@ -2809,7 +2809,7 @@ ZEND_METHOD(reflection_parameter, isVariadic) } GET_REFLECTION_OBJECT_PTR(param); - RETVAL_BOOL(param->arg_info->is_variadic); + RETVAL_BOOL(ZEND_ARG_IS_VARIADIC(param->arg_info)); } /* }}} */ @@ -2830,7 +2830,7 @@ ZEND_METHOD(reflection_type, allowsNull) /* }}} */ static zend_string *zend_type_to_string_without_null(zend_type type) { - ZEND_TYPE_MASK(type) &= ~MAY_BE_NULL; + ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL; return zend_type_to_string(type); } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index a3f09f32ef615..3b2cb7376e039 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -238,7 +238,7 @@ PHP_MINIT_FUNCTION(zend_test) ZVAL_LONG(&val, 123); zend_declare_typed_property( zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, - (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0)); + (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0, 0)); zend_string_release(name); } @@ -249,7 +249,7 @@ PHP_MINIT_FUNCTION(zend_test) ZVAL_NULL(&val); zend_declare_typed_property( zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, - (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 1)); + (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 1, 0)); zend_string_release(name); } @@ -259,7 +259,7 @@ PHP_MINIT_FUNCTION(zend_test) ZVAL_LONG(&val, 123); zend_declare_typed_property( zend_test_class, name, &val, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC, NULL, - (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0)); + (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0, 0)); zend_string_release(name); } diff --git a/sapi/phpdbg/phpdbg_frame.c b/sapi/phpdbg/phpdbg_frame.c index d8c7d3941a8db..453a0d74ba374 100644 --- a/sapi/phpdbg/phpdbg_frame.c +++ b/sapi/phpdbg/phpdbg_frame.c @@ -229,7 +229,7 @@ static void phpdbg_dump_prototype(zval *tmp) /* {{{ */ } if (!is_variadic) { - is_variadic = arginfo ? arginfo[j].is_variadic : 0; + is_variadic = arginfo ? ZEND_ARG_IS_VARIADIC(&arginfo[j]) : 0; } phpdbg_xml(" variadic=\"%s\" name=\"%s\">", is_variadic ? "variadic" : "", arg_name ? arg_name : ""); From 4a9735f72dc377a66fb790e2acefcf11cdfd094d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 25 Sep 2019 13:21:13 +0200 Subject: [PATCH 03/48] WIP Union types --- Zend/tests/return_types/generators002.phpt | 2 +- .../type_declarations/nullable_void.phpt | 2 +- Zend/zend.c | 25 +- Zend/zend_API.c | 6 +- Zend/zend_ast.h | 1 + Zend/zend_compile.c | 315 ++++++++++++------ Zend/zend_execute.c | 309 ++++++++++------- Zend/zend_inheritance.c | 252 ++++++++++---- Zend/zend_language_parser.y | 8 +- Zend/zend_opcode.c | 25 +- Zend/zend_string.h | 1 + Zend/zend_types.h | 89 ++++- Zend/zend_vm_def.h | 3 +- Zend/zend_vm_execute.h | 15 +- ext/opcache/Optimizer/compact_literals.c | 44 ++- ext/opcache/Optimizer/dfa_pass.c | 2 +- ext/opcache/Optimizer/zend_inference.c | 43 ++- ext/opcache/ZendAccelerator.c | 109 +++--- ext/opcache/jit/zend_jit_disasm_x86.c | 1 - ext/opcache/jit/zend_jit_helpers.c | 86 +++-- ext/opcache/jit/zend_jit_x86.dasc | 104 ++---- ext/opcache/zend_accelerator_util_funcs.c | 13 +- ext/opcache/zend_file_cache.c | 95 ++++-- ext/opcache/zend_persist.c | 47 ++- ext/opcache/zend_persist_calc.c | 38 ++- ext/reflection/php_reflection.c | 3 +- 26 files changed, 1043 insertions(+), 595 deletions(-) diff --git a/Zend/tests/return_types/generators002.phpt b/Zend/tests/return_types/generators002.phpt index 90bada714b5c8..2e42f4b052880 100644 --- a/Zend/tests/return_types/generators002.phpt +++ b/Zend/tests/return_types/generators002.phpt @@ -6,4 +6,4 @@ function test1() : StdClass { yield 1; } --EXPECTF-- -Fatal error: Generators may only declare a return type of Generator, Iterator, Traversable, or iterable, StdClass is not permitted in %s on line %d +Fatal error: Generators may only declare a return type containing Generator, Iterator, Traversable, or iterable, StdClass is not permitted in %s on line %d diff --git a/Zend/tests/type_declarations/nullable_void.phpt b/Zend/tests/type_declarations/nullable_void.phpt index 4ff0edb0d8130..725c11bb594c2 100644 --- a/Zend/tests/type_declarations/nullable_void.phpt +++ b/Zend/tests/type_declarations/nullable_void.phpt @@ -8,4 +8,4 @@ function test() : ?void { ?> --EXPECTF-- -Fatal error: Void type cannot be nullable in %s on line %d +Fatal error: Void can only be used as a standalone type in %s on line %d diff --git a/Zend/zend.c b/Zend/zend.c index 8c9ae86d747ca..d52ce5260dfb1 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -947,6 +947,15 @@ void zend_register_standard_ini_entries(void) /* {{{ */ } /* }}} */ +static zend_class_entry *resolve_type_name(zend_string *type_name) { + zend_string *lc_type_name = zend_string_tolower(type_name); + zend_class_entry *ce = zend_hash_find_ptr(CG(class_table), lc_type_name); + + ZEND_ASSERT(ce && ce->type == ZEND_INTERNAL_CLASS); + zend_string_release(lc_type_name); + return ce; +} + static void zend_resolve_property_types(void) /* {{{ */ { zend_class_entry *ce; @@ -959,14 +968,16 @@ static void zend_resolve_property_types(void) /* {{{ */ if (UNEXPECTED(ZEND_CLASS_HAS_TYPE_HINTS(ce))) { ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop_info) { - if (ZEND_TYPE_IS_NAME(prop_info->type)) { + if (ZEND_TYPE_HAS_LIST(prop_info->type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) { + zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry); + *entry = ZEND_TYPE_LIST_ENCODE_CE(resolve_type_name(type_name)); + zend_string_release(type_name); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) { zend_string *type_name = ZEND_TYPE_NAME(prop_info->type); - zend_string *lc_type_name = zend_string_tolower(type_name); - zend_class_entry *prop_ce = zend_hash_find_ptr(CG(class_table), lc_type_name); - - ZEND_ASSERT(prop_ce && prop_ce->type == ZEND_INTERNAL_CLASS); - prop_info->type = (zend_type) ZEND_TYPE_INIT_CE(prop_ce, ZEND_TYPE_ALLOW_NULL(prop_info->type), 0); - zend_string_release(lc_type_name); + ZEND_TYPE_SET_CE(prop_info->type, resolve_type_name(type_name)); zend_string_release(type_name); } } ZEND_HASH_FOREACH_END(); diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 3c1ca4c7188c4..87cfd5f1394bc 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2054,7 +2054,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio internal_function->num_args--; } if (ZEND_TYPE_IS_SET(info->type)) { - if (ZEND_TYPE_IS_CLASS(info->type)) { + if (ZEND_TYPE_HAS_NAME(info->type)) { const char *type_name = ZEND_TYPE_LITERAL_NAME(info->type); if (!scope && (!strcasecmp(type_name, "self") || !strcasecmp(type_name, "parent"))) { zend_error_noreturn(E_CORE_ERROR, "Cannot declare a return type of %s outside of a class scope", type_name); @@ -2135,7 +2135,9 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio memcpy(new_arg_info, arg_info, sizeof(zend_arg_info) * num_args); reg_function->common.arg_info = new_arg_info + 1; for (i = 0; i < num_args; i++) { - if (ZEND_TYPE_IS_CLASS(new_arg_info[i].type)) { + if (ZEND_TYPE_HAS_CLASS(new_arg_info[i].type)) { + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type) + && "Only simple classes are currently supported"); const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type); ZEND_TYPE_SET_PTR(new_arg_info[i].type, zend_string_init_interned(class_name, strlen(class_name), 1)); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index fd6dd1677a450..5b8aae6f96c25 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -61,6 +61,7 @@ enum _zend_ast_kind { ZEND_AST_NAME_LIST, ZEND_AST_TRAIT_ADAPTATIONS, ZEND_AST_USE, + ZEND_AST_TYPE_UNION, /* 0 child nodes */ ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ea93e878d03a3..b3381fe1731ac 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -60,6 +60,10 @@ typedef struct _zend_loop_var { } zend_loop_var; static inline uint32_t zend_alloc_cache_slots(unsigned count) { + if (count == 0) { + return (uint32_t) -1; + } + zend_op_array *op_array = CG(active_op_array); uint32_t ret = op_array->cache_size; op_array->cache_size += count * sizeof(void*); @@ -211,6 +215,8 @@ typedef struct _builtin_type_info { } builtin_type_info; static const builtin_type_info builtin_types[] = { + {ZEND_STRL("null"), IS_NULL}, + {ZEND_STRL("false"), IS_FALSE}, {ZEND_STRL("int"), IS_LONG}, {ZEND_STRL("float"), IS_DOUBLE}, {ZEND_STRL("string"), IS_STRING}, @@ -1119,62 +1125,95 @@ ZEND_API int do_bind_class(zval *lcname, zend_string *lc_parent_name) /* {{{ */ } /* }}} */ +static zend_string *add_type_string(zend_string *type, zend_string *new_type) { + zend_string *result; + if (type == NULL) { + return zend_string_copy(new_type); + } + + // TODO: Switch to smart_str? + result = zend_string_alloc(ZSTR_LEN(type) + ZSTR_LEN(new_type) + 1, 0); + memcpy(ZSTR_VAL(result), ZSTR_VAL(type), ZSTR_LEN(type)); + ZSTR_VAL(result)[ZSTR_LEN(type)] = '|'; + memcpy(ZSTR_VAL(result) + ZSTR_LEN(type), ZSTR_VAL(new_type), ZSTR_LEN(new_type)); + ZSTR_VAL(result)[ZSTR_LEN(type) + ZSTR_LEN(new_type) + 1] = '\0'; + zend_string_release(type); + return result; +} + +static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scope) { + if (scope) { + if (zend_string_equals_literal_ci(name, "self")) { + name = scope->name; + } else if (zend_string_equals_literal_ci(name, "parent") && scope->parent) { + name = scope->parent->name; + } + } + return name; +} + zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) { - zend_bool nullable = ZEND_TYPE_ALLOW_NULL(type); - zend_string *str; - if (ZEND_TYPE_IS_NAME(type)) { - zend_string *name = ZEND_TYPE_NAME(type); - if (scope) { - if (zend_string_equals_literal_ci(name, "self")) { - name = scope->name; - } else if (zend_string_equals_literal_ci(name, "parent") && scope->parent) { - name = scope->parent->name; - } - } - str = zend_string_copy(name); - } else if (ZEND_TYPE_IS_CE(type)) { + zend_string *str = NULL; + if (ZEND_TYPE_HAS_LIST(type)) { + void *elem; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), elem) { + if (ZEND_TYPE_LIST_IS_CE(elem)) { + str = add_type_string(str, ZEND_TYPE_LIST_GET_CE(elem)->name); + } else { + str = add_type_string(str, + resolve_class_name(ZEND_TYPE_LIST_GET_NAME(elem), scope)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(type)) { + str = zend_string_copy(resolve_class_name(ZEND_TYPE_NAME(type), scope)); + } else if (ZEND_TYPE_HAS_CE(type)) { str = zend_string_copy(ZEND_TYPE_CE(type)->name); - } else { - uint32_t type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); - switch (type_mask) { - case MAY_BE_FALSE|MAY_BE_TRUE: - str = ZSTR_KNOWN(ZEND_STR_BOOL); - break; - case MAY_BE_LONG: - str = ZSTR_KNOWN(ZEND_STR_INT); - break; - case MAY_BE_DOUBLE: - str = ZSTR_KNOWN(ZEND_STR_FLOAT); - break; - case MAY_BE_STRING: - str = ZSTR_KNOWN(ZEND_STR_STRING); - break; - case MAY_BE_ARRAY: - str = ZSTR_KNOWN(ZEND_STR_ARRAY); - break; - case MAY_BE_OBJECT: - str = ZSTR_KNOWN(ZEND_STR_OBJECT); - break; - case MAY_BE_CALLABLE: - str = ZSTR_KNOWN(ZEND_STR_CALLABLE); - break; - case MAY_BE_ITERABLE: - str = ZSTR_KNOWN(ZEND_STR_ITERABLE); - break; - case MAY_BE_VOID: - str = ZSTR_KNOWN(ZEND_STR_VOID); - break; - EMPTY_SWITCH_DEFAULT_CASE() - } } - if (nullable) { - zend_string *nullable_str = zend_string_alloc(ZSTR_LEN(str) + 1, 0); - ZSTR_VAL(nullable_str)[0] = '?'; - memcpy(ZSTR_VAL(nullable_str) + 1, ZSTR_VAL(str), ZSTR_LEN(str)); - ZSTR_VAL(nullable_str)[ZSTR_LEN(nullable_str)] = '\0'; - zend_string_release(str); - return nullable_str; + uint32_t type_mask = ZEND_TYPE_FULL_MASK(type); + if (type_mask & MAY_BE_CALLABLE) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE)); + } + if (type_mask & MAY_BE_ITERABLE) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE)); + } + if (type_mask & MAY_BE_OBJECT) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT)); + } + if (type_mask & MAY_BE_ARRAY) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ARRAY)); + } + if (type_mask & MAY_BE_STRING) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STRING)); + } + if (type_mask & MAY_BE_LONG) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_INT)); + } + if (type_mask & MAY_BE_DOUBLE) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT)); + } + if ((type_mask & (MAY_BE_FALSE|MAY_BE_TRUE)) == (MAY_BE_FALSE|MAY_BE_TRUE)) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL)); + } else if (type_mask & MAY_BE_FALSE) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE)); + } + if (type_mask & MAY_BE_VOID) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID)); + } + + ZEND_ASSERT(str && "There should be at least one type!"); + if (type_mask & MAY_BE_NULL) { + zend_bool is_union = memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL; + if (!is_union) { + zend_string *nullable_str = zend_string_alloc(ZSTR_LEN(str) + 1, 0); + ZSTR_VAL(nullable_str)[0] = '?'; + memcpy(ZSTR_VAL(nullable_str) + 1, ZSTR_VAL(str), ZSTR_LEN(str)); + ZSTR_VAL(nullable_str)[ZSTR_LEN(nullable_str)] = '\0'; + zend_string_release(str); + return nullable_str; + } + + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL)); } return str; } @@ -1183,6 +1222,12 @@ zend_string *zend_type_to_string(zend_type type) { return zend_type_to_string_resolved(type, NULL); } +static zend_bool is_generator_compatible_class_type(zend_string *name) { + return zend_string_equals_literal_ci(name, "Traversable") + || zend_string_equals_literal_ci(name, "Iterator") + || zend_string_equals_literal_ci(name, "Generator"); +} + static void zend_mark_function_as_generator() /* {{{ */ { if (!CG(active_op_array)->function_name) { @@ -1191,21 +1236,30 @@ static void zend_mark_function_as_generator() /* {{{ */ } if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { - zend_arg_info return_info = CG(active_op_array)->arg_info[-1]; - zend_bool valid_type; - if (ZEND_TYPE_IS_CLASS(return_info.type)) { - zend_string *name = ZEND_TYPE_NAME(return_info.type); - valid_type = zend_string_equals_literal_ci(name, "Traversable") - || zend_string_equals_literal_ci(name, "Iterator") - || zend_string_equals_literal_ci(name, "Generator"); + zend_type return_type = CG(active_op_array)->arg_info[-1].type; + zend_bool valid_type = 0; + if (ZEND_TYPE_HAS_CLASS(return_type)) { + if (ZEND_TYPE_HAS_LIST(return_type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(return_type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + if (is_generator_compatible_class_type(ZEND_TYPE_LIST_GET_NAME(entry))) { + valid_type = 1; + break; + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else { + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(return_type)); + valid_type = is_generator_compatible_class_type(ZEND_TYPE_NAME(return_type)); + } } else { - valid_type = (ZEND_TYPE_FULL_MASK(return_info.type) & MAY_BE_ITERABLE) != 0; + valid_type = (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_ITERABLE) != 0; } if (!valid_type) { - zend_string *str = zend_type_to_string(return_info.type); + zend_string *str = zend_type_to_string(return_type); zend_error_noreturn(E_COMPILE_ERROR, - "Generators may only declare a return type of " \ + "Generators may only declare a return type containing " \ "Generator, Iterator, Traversable, or iterable, %s is not permitted", ZSTR_VAL(str)); } @@ -2167,6 +2221,16 @@ static void zend_compile_memoized_expr(znode *result, zend_ast *expr) /* {{{ */ } /* }}} */ +static size_t zend_type_get_num_classes(zend_type type) { + if (!ZEND_TYPE_HAS_CLASS(type)) { + return 0; + } + if (ZEND_TYPE_HAS_LIST(type)) { + return ZEND_TYPE_LIST(type)->num_types; + } + return 1; +} + static void zend_emit_return_type_check( znode *expr, zend_arg_info *return_info, zend_bool implicit) /* {{{ */ { @@ -2212,12 +2276,8 @@ static void zend_emit_return_type_check( opline->result_type = expr->op_type = IS_TMP_VAR; opline->result.var = expr->u.op.var = get_temporary_variable(); } - if (ZEND_TYPE_IS_CLASS(return_info->type)) { - opline->op2.num = CG(active_op_array)->cache_size; - CG(active_op_array)->cache_size += sizeof(void*); - } else { - opline->op2.num = -1; - } + + opline->op2.num = zend_alloc_cache_slots(zend_type_get_num_classes(return_info->type)); } } /* }}} */ @@ -5372,16 +5432,11 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */ } /* }}} */ -static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null) /* {{{ */ +static zend_type zend_compile_single_typename(zend_ast *ast) { - zend_bool allow_null = force_allow_null; - if (ast->attr & ZEND_TYPE_NULLABLE) { - allow_null = 1; - ast->attr &= ~ZEND_TYPE_NULLABLE; - } - + ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE)); if (ast->kind == ZEND_AST_TYPE) { - return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, allow_null, 0); + return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0); } else { zend_string *class_name = zend_ast_get_str(ast); zend_uchar type = zend_lookup_builtin_type_by_name(class_name); @@ -5392,10 +5447,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null "Type declaration '%s' must be unqualified", ZSTR_VAL(zend_string_tolower(class_name))); } - if (type == IS_VOID && allow_null) { - zend_error_noreturn(E_COMPILE_ERROR, "Void type cannot be nullable"); - } - return (zend_type) ZEND_TYPE_INIT_CODE(type, allow_null, 0); + return (zend_type) ZEND_TYPE_INIT_CODE(type, 0, 0); } else { const char *correct_name; zend_string *orig_name = zend_ast_get_str(ast); @@ -5427,23 +5479,99 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null } } - return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, allow_null, 0); + return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 0, 0); } } } + +// TODO: Ideally we'd canonicalize "iterable" into "array|Traversable" and essentially +// treat it as a built-in type alias. +static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null) /* {{{ */ +{ + zend_bool allow_null = force_allow_null; + zend_type type = ZEND_TYPE_INIT_NONE(0); + if (ast->attr & ZEND_TYPE_NULLABLE) { + allow_null = 1; + ast->attr &= ~ZEND_TYPE_NULLABLE; + } + + if (ast->kind == ZEND_AST_TYPE_UNION) { + zend_ast_list *list = zend_ast_get_list(ast); + for (uint32_t i = 0; i < list->children; i++) { + zend_ast *type_ast = list->child[i]; + zend_type single_type = zend_compile_single_typename(type_ast); + uint32_t type_mask_overlap = ZEND_TYPE_PURE_MASK(type) & ZEND_TYPE_PURE_MASK(type); + if (type_mask_overlap) { + // TODO: Iterable requires special handling + zend_type overlap_type = ZEND_TYPE_INIT_MASK(type_mask_overlap); + zend_string *overlap_type_str = zend_type_to_string(overlap_type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s is redundant", ZSTR_VAL(overlap_type_str)); + } + ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type); + + if (ZEND_TYPE_HAS_CLASS(single_type)) { + if (!ZEND_TYPE_HAS_CLASS(type)) { + /* The first class type can be stored directly as the type ptr payload. */ + ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type)); + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT; + } else { + zend_type_list *list; + if (ZEND_TYPE_HAS_LIST(type)) { + /* Add name to existing name list. */ + zend_type_list *old_list = ZEND_TYPE_LIST(type); + list = erealloc(old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types + 1)); + list->types[list->num_types++] = ZEND_TYPE_NAME(single_type); + } else { + /* Switch from single name to name list. */ + list = emalloc(ZEND_TYPE_LIST_SIZE(2)); + list->num_types = 2; + list->types[0] = ZEND_TYPE_NAME(type); + list->types[1] = ZEND_TYPE_NAME(single_type); + } + ZEND_TYPE_SET_LIST(type, list); + + /* Check for trivially redundant class types */ + for (size_t i = 0; i < list->num_types - 1; i++) { + if (zend_string_equals_ci( + ZEND_TYPE_LIST_GET_NAME(list->types[i]), + ZEND_TYPE_NAME(single_type))) { + zend_string *single_type_str = zend_type_to_string(single_type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s is redundant", ZSTR_VAL(single_type_str)); + } + } + } + } + } + } else { + type = zend_compile_single_typename(ast); + } + + if (allow_null) { + ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL; + } + + if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) { + zend_string *type_str = zend_type_to_string(type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s contains both object and a class type, which is redundant", + ZSTR_VAL(type_str)); + } + + if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_VOID) && + (ZEND_TYPE_HAS_CLASS(type) || ZEND_TYPE_PURE_MASK(type) != MAY_BE_VOID)) { + zend_error_noreturn(E_COMPILE_ERROR, "Void can only be used as a standalone type"); + } + + return type; +} /* }}} */ /* May convert value from int to float. */ static zend_bool zend_is_valid_default_value(zend_type type, zval *value) { ZEND_ASSERT(ZEND_TYPE_IS_SET(type)); - if (Z_TYPE_P(value) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type)) { - return 1; - } - - if (ZEND_TYPE_IS_CLASS(type)) { - return 0; - } if (ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(value))) { return 1; } @@ -5544,13 +5672,13 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ if (type_ast) { uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF; - zend_bool is_class; + zend_bool has_class; op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS; arg_info->type = zend_compile_typename(type_ast, default_type == IS_NULL); - is_class = ZEND_TYPE_IS_CLASS(arg_info->type); + has_class = ZEND_TYPE_HAS_CLASS(arg_info->type); - if (!is_class && (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID)) { + if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID) { zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type"); } @@ -5570,9 +5698,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ if (type_ast) { /* Allocate cache slot to speed-up run-time class resolution */ - if (ZEND_TYPE_IS_CLASS(arg_info->type)) { - opline->extended_value = zend_alloc_cache_slot(); - } + opline->extended_value = + zend_alloc_cache_slots(zend_type_get_num_classes(arg_info->type)); } ZEND_TYPE_FULL_MASK(arg_info->type) |= _ZEND_ARG_INFO_FLAGS(is_ref, is_variadic); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 097137aa5cff0..70d5b24b6daa2 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -659,7 +659,8 @@ static ZEND_COLD void zend_verify_type_error_common( *fclass = ""; } - if (ZEND_TYPE_IS_CLASS(arg_info->type)) { + // TODO: This code is broken for now. + if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { zend_class_entry *ce = *cache_slot; if (ce) { if (ce->ce_flags & ZEND_ACC_INTERFACE) { @@ -710,7 +711,7 @@ static ZEND_COLD void zend_verify_type_error_common( } 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 { @@ -760,18 +761,26 @@ 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; + /* Type preference order: int -> float -> string -> bool */ + if (type_mask & MAY_BE_LONG) { + zend_long dest; - if (!zend_parse_arg_bool_weak(arg, &dest)) { + /* For a int|float union type and string value, + * determine type to choose by is_numeric_string() semantics. */ + if ((type_mask & MAY_BE_DOUBLE) && Z_TYPE_P(arg) == IS_STRING) { + zend_long lval; + double dval; + zend_uchar type = is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), &lval, &dval, -1); + if (type == IS_LONG) { + ZVAL_LONG(arg, lval); + return 1; + } + if (type == IS_DOUBLE) { + ZVAL_DOUBLE(arg, dval); + return 1; + } return 0; } - zval_ptr_dtor(arg); - ZVAL_BOOL(arg, dest); - return 1; - } - if (type_mask & MAY_BE_LONG) { - zend_long dest; if (!zend_parse_arg_long_weak(arg, &dest)) { return 0; @@ -796,6 +805,16 @@ static zend_bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg /* on success "arg" is converted to IS_STRING */ return zend_parse_arg_str_weak(arg, &dest); } + if ((type_mask & (MAY_BE_TRUE|MAY_BE_FALSE)) == (MAY_BE_TRUE|MAY_BE_FALSE)) { + zend_bool dest; + + if (!zend_parse_arg_bool_weak(arg, &dest)) { + return 0; + } + zval_ptr_dtor(arg); + ZVAL_BOOL(arg, dest); + return 1; + } return 0; } @@ -803,10 +822,6 @@ 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); - } if (type_mask & MAY_BE_LONG) { zend_long dest; if (Z_TYPE_P(arg) == IS_STRING) { @@ -817,7 +832,8 @@ static zend_bool zend_verify_weak_scalar_type_hint_no_sideeffect(uint32_t type_m return 1; } if (type == IS_DOUBLE) { - return !zend_isnan(dval) && ZEND_DOUBLE_FITS_LONG(dval); + return (type_mask & MAY_BE_DOUBLE) + || (!zend_isnan(dval) && ZEND_DOUBLE_FITS_LONG(dval)); } return 0; @@ -838,6 +854,10 @@ static zend_bool zend_verify_weak_scalar_type_hint_no_sideeffect(uint32_t type_m * more than actually allowed here. */ return Z_TYPE_P(arg) < IS_STRING || Z_TYPE_P(arg) == IS_OBJECT; } + if ((type_mask & (MAY_BE_TRUE|MAY_BE_FALSE)) == (MAY_BE_TRUE|MAY_BE_FALSE)) { + zend_bool dest; + return zend_parse_arg_bool_weak(arg, &dest); + } return 0; } #endif @@ -850,12 +870,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 +901,84 @@ 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")) { + // TODO: Eliminate this exception! /* 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; + zend_throw_error(NULL, "Cannot write a value to a 'self' typed static property of a trait"); + return NULL; } - ce = self_ce; + return self_ce; } else if (zend_string_equals_literal_ci(name, "parent")) { + // TODO: Eliminate this exception! if (UNEXPECTED(!self_ce->parent)) { zend_throw_error(NULL, "Cannot access parent:: when current class scope has no parent"); - return 0; + return NULL; } - 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 +1024,69 @@ 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) { + 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. */ } @@ -1219,11 +1288,11 @@ static ZEND_COLD int zend_verify_missing_return_type(const zend_function *zf, vo /* 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)); + // TODO: Generalize this loading + zend_class_entry *ce; + if (ZEND_TYPE_HAS_NAME(ret_info->type)) { + if (!*cache_slot) { + 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; } @@ -1575,22 +1644,26 @@ static zend_property_info *zend_get_prop_not_accepting_double(zend_reference *re static ZEND_COLD zend_long zend_throw_incdec_ref_error(zend_reference *ref OPLINE_DC) { zend_property_info *error_prop = zend_get_prop_not_accepting_double(ref); + ZEND_ASSERT(error_prop); + zend_string *type_str = zend_type_to_string(error_prop->type); + // TODO!!! No longer true with union types /* 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); 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; } } @@ -2942,25 +3015,17 @@ 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; } @@ -2996,39 +3061,41 @@ 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. */ + * type. Remember the first coerced type and value we've seen for this purpose. */ zend_property_info *seen_prop = NULL; - uint32_t seen_type_mask; - zend_bool needs_coercion = 0; + zval seen_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) { zend_throw_ref_type_error_zval(prop, zv); 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; + zval tmp; + ZVAL_COPY(&tmp, zv); + if (!zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop->type), &tmp)) { + zend_throw_ref_type_error_zval(prop, zv); + zval_ptr_dtor(&tmp); + return 0; + } + if (seen_prop) { + if (!zend_is_identical(&tmp, &seen_value)) { + zend_throw_conflicting_coercion_error(seen_prop, prop, zv); + return 0; + } + } else { + seen_prop = prop; + ZVAL_COPY_VALUE(&seen_value, &tmp); + } } } 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 (seen_prop) { + zval_ptr_dtor(zv); + ZVAL_COPY_VALUE(zv, &seen_value); } return 1; @@ -3080,7 +3147,7 @@ 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; } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index af81c327e1586..9c4bbf84de8ae 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -40,14 +40,29 @@ static void overridden_ptr_dtor(zval *zv) /* {{{ */ } /* }}} */ +static void zend_type_copy_ctor(zend_type *type) { + if (ZEND_TYPE_HAS_LIST(*type)) { + zend_type_list *old_list = ZEND_TYPE_LIST(*type); + zend_type_list *new_list = emalloc(ZEND_TYPE_LIST_SIZE(old_list->num_types)); + memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); + ZEND_TYPE_SET_PTR(*type, new_list); + + void *entry; + ZEND_TYPE_LIST_FOREACH(new_list, entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + zend_string_addref(ZEND_TYPE_LIST_GET_NAME(entry)); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string_addref(ZEND_TYPE_NAME(*type)); + } +} + static zend_property_info *zend_duplicate_property_info_internal(zend_property_info *property_info) /* {{{ */ { zend_property_info* new_property_info = pemalloc(sizeof(zend_property_info), 1); memcpy(new_property_info, property_info, sizeof(zend_property_info)); zend_string_addref(new_property_info->name); - if (ZEND_TYPE_IS_NAME(new_property_info->type)) { - zend_string_addref(ZEND_TYPE_NAME(new_property_info->type)); - } + zend_type_copy_ctor(&new_property_info->type); return new_property_info; } @@ -219,7 +234,8 @@ static zend_bool class_visible(zend_class_entry *ce) { } } -static zend_class_entry *lookup_class(zend_class_entry *scope, zend_string *name) { +static zend_class_entry *lookup_class( + zend_class_entry *scope, zend_string *name, zend_bool register_unresolved) { zend_class_entry *ce; if (!CG(in_compilation)) { uint32_t flags = ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD; @@ -228,12 +244,14 @@ static zend_class_entry *lookup_class(zend_class_entry *scope, zend_string *name return ce; } - /* We'll autoload this class and process delayed variance obligations later. */ - if (!CG(delayed_autoloads)) { - ALLOC_HASHTABLE(CG(delayed_autoloads)); - zend_hash_init(CG(delayed_autoloads), 0, NULL, NULL, 0); + if (register_unresolved) { + /* We'll autoload this class and process delayed variance obligations later. */ + if (!CG(delayed_autoloads)) { + ALLOC_HASHTABLE(CG(delayed_autoloads)); + zend_hash_init(CG(delayed_autoloads), 0, NULL, NULL, 0); + } + zend_hash_add_empty_element(CG(delayed_autoloads), name); } - zend_hash_add_empty_element(CG(delayed_autoloads), name); } else { ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); if (ce && class_visible(ce)) { @@ -302,6 +320,23 @@ static zend_bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce return 0; } +static zend_bool zend_type_contains_traversable(zend_type type) { + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + if (zend_string_equals_literal_ci(ZEND_TYPE_LIST_GET_NAME(entry), "Traversable")) { + return 1; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return 0; + } + if (ZEND_TYPE_HAS_NAME(type)) { + return zend_string_equals_literal_ci(ZEND_TYPE_NAME(type), "Traversable"); + } + return 0; +} + /* Unresolved means that class declarations that are currently not available are needed to * determine whether the inheritance is valid or not. At runtime UNRESOLVED should be treated * as an ERROR. */ @@ -311,6 +346,72 @@ typedef enum { INHERITANCE_SUCCESS = 1, } inheritance_status; +static inheritance_status zend_perform_covariant_class_type_check( + const zend_function *fe, zend_string *fe_class_name, + const zend_function *proto, zend_type proto_type, + zend_bool register_unresolved) { + zend_bool have_unresolved = 0; + zend_class_entry *fe_ce = NULL; + if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_OBJECT) { + /* Currently, any class name would be allowed here. We still perform a class lookup + * for forward-compatibility reasons, as we may have named types in the future that + * are not classes (such as enums or typedefs). */ + if (!fe_ce) fe_ce = lookup_class(fe->common.scope, fe_class_name, register_unresolved); + if (!fe_ce) { + have_unresolved = 1; + } else { + return INHERITANCE_SUCCESS; + } + } + if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) { + if (!fe_ce) fe_ce = lookup_class(fe->common.scope, fe_class_name, register_unresolved); + if (!fe_ce) { + have_unresolved = 1; + } else if (unlinked_instanceof(fe_ce, zend_ce_traversable)) { + return INHERITANCE_SUCCESS; + } + } + if (ZEND_TYPE_HAS_NAME(proto_type)) { + zend_string *proto_class_name = + resolve_class_name(proto->common.scope, ZEND_TYPE_NAME(proto_type)); + if (zend_string_equals_ci(fe_class_name, proto_class_name)) { + return INHERITANCE_SUCCESS; + } + + /* Make sure to always load both classes, to avoid only registering one of them as + * a delayed autoload. */ + if (!fe_ce) fe_ce = lookup_class(fe->common.scope, fe_class_name, register_unresolved); + zend_class_entry *proto_ce = + lookup_class(proto->common.scope, proto_class_name, register_unresolved); + if (!fe_ce || !proto_ce) { + have_unresolved = 1; + } else if (unlinked_instanceof(fe_ce, proto_ce)) { + return INHERITANCE_SUCCESS; + } + } + if (ZEND_TYPE_HAS_LIST(proto_type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(proto_type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + zend_string *proto_class_name = + resolve_class_name(proto->common.scope, ZEND_TYPE_LIST_GET_NAME(entry)); + if (zend_string_equals_ci(fe_class_name, proto_class_name)) { + return INHERITANCE_SUCCESS; + } + + if (!fe_ce) fe_ce = lookup_class(fe->common.scope, fe_class_name, register_unresolved); + zend_class_entry *proto_ce = + lookup_class(proto->common.scope, proto_class_name, register_unresolved); + if (!fe_ce || !proto_ce) { + have_unresolved = 1; + } else if (unlinked_instanceof(fe_ce, proto_ce)) { + return INHERITANCE_SUCCESS; + } + } ZEND_TYPE_LIST_FOREACH_END(); + } + return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR; +} + static inheritance_status zend_perform_covariant_type_check( zend_string **unresolved_class, const zend_function *fe, zend_arg_info *fe_arg_info, @@ -319,73 +420,79 @@ static inheritance_status zend_perform_covariant_type_check( zend_type fe_type = fe_arg_info->type, proto_type = proto_arg_info->type; ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_type) && ZEND_TYPE_IS_SET(proto_type)); - if (ZEND_TYPE_ALLOW_NULL(fe_type) && !ZEND_TYPE_ALLOW_NULL(proto_type)) { - return INHERITANCE_ERROR; - } - - if (ZEND_TYPE_IS_CLASS(proto_type)) { - zend_string *fe_class_name, *proto_class_name; - zend_class_entry *fe_ce, *proto_ce; - if (!ZEND_TYPE_IS_CLASS(fe_type)) { + /* Builtin types may be removed, but not added */ + uint32_t fe_type_mask = ZEND_TYPE_PURE_MASK(fe_type); + uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type); + uint32_t added_types = fe_type_mask & ~proto_type_mask; + if (added_types) { + // TODO: Make "iterable" an alias of "array|Traversable" instead, + // so these special cases will be handled automatically. + if (added_types == MAY_BE_ITERABLE + && (proto_type_mask & MAY_BE_ARRAY) + && zend_type_contains_traversable(proto_type)) { + /* Replacing array|Traversable with iterable is okay */ + } else if (added_types == MAY_BE_ARRAY && (proto_type_mask & MAY_BE_ITERABLE)) { + /* Replacing iterable with array is okay */ + } else { + /* Otherwise adding new types is illegal */ return INHERITANCE_ERROR; } + } - fe_class_name = resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); - proto_class_name = resolve_class_name(proto->common.scope, ZEND_TYPE_NAME(proto_type)); - if (zend_string_equals_ci(fe_class_name, proto_class_name)) { - return INHERITANCE_SUCCESS; - } + if (ZEND_TYPE_HAS_NAME(fe_type)) { + *unresolved_class = NULL; // TODO - /* Make sure to always load both classes, to avoid only registering one of them as - * a delayed autoload. */ - fe_ce = lookup_class(fe->common.scope, fe_class_name); - proto_ce = lookup_class(proto->common.scope, proto_class_name); - if (!fe_ce) { - *unresolved_class = fe_class_name; - return INHERITANCE_UNRESOLVED; - } - if (!proto_ce) { - *unresolved_class = proto_class_name; - return INHERITANCE_UNRESOLVED; + zend_string *fe_class_name = resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); + inheritance_status status = zend_perform_covariant_class_type_check( + fe, fe_class_name, proto, proto_type, /* register_unresolved */ 0); + if (status != INHERITANCE_UNRESOLVED) { + return status; } - return unlinked_instanceof(fe_ce, proto_ce) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; - } else if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) { - if (ZEND_TYPE_IS_CLASS(fe_type)) { + zend_perform_covariant_class_type_check( + fe, fe_class_name, proto, proto_type, /* register_unresolved */ 1); + return INHERITANCE_UNRESOLVED; + } + + if (ZEND_TYPE_HAS_LIST(fe_type)) { + *unresolved_class = NULL; // TODO + + void *entry; + zend_bool all_success = 1; + + /* First try to check whether we can succeed without resolving anything */ + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(fe_type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); zend_string *fe_class_name = - resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); - zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name); - if (!fe_ce) { - *unresolved_class = fe_class_name; - return INHERITANCE_UNRESOLVED; + resolve_class_name(fe->common.scope, ZEND_TYPE_LIST_GET_NAME(entry)); + inheritance_status status = zend_perform_covariant_class_type_check( + fe, fe_class_name, proto, proto_type, /* register_unresolved */ 0); + if (status == INHERITANCE_ERROR) { + return INHERITANCE_ERROR; } - return unlinked_instanceof(fe_ce, zend_ce_traversable) - ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; - } - return ZEND_TYPE_FULL_MASK(fe_type) & (MAY_BE_ARRAY|MAY_BE_ITERABLE) - ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; - } else if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_OBJECT) { - if (ZEND_TYPE_IS_CLASS(fe_type)) { - /* Currently, any class name would be allowed here. We still perform a class lookup - * for forward-compatibility reasons, as we may have named types in the future that - * are not classes (such as enums or typedefs). */ - zend_string *fe_class_name = - resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); - zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name); - if (!fe_ce) { - *unresolved_class = fe_class_name; - return INHERITANCE_UNRESOLVED; + if (status != INHERITANCE_SUCCESS) { + all_success = 0; } + } ZEND_TYPE_LIST_FOREACH_END(); + + /* All individual checks suceeded, overall success */ + if (all_success) { return INHERITANCE_SUCCESS; } - return ZEND_TYPE_FULL_MASK(fe_type) & MAY_BE_OBJECT - ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; - } else { - return ZEND_TYPE_PURE_MASK_WITHOUT_NULL(fe_type) == ZEND_TYPE_PURE_MASK_WITHOUT_NULL(proto_type) - ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + /* Register all classes that may have to be resolved */ + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(fe_type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + zend_string *fe_class_name = + resolve_class_name(fe->common.scope, ZEND_TYPE_LIST_GET_NAME(entry)); + zend_perform_covariant_class_type_check( + fe, fe_class_name, proto, proto_type, /* register_unresolved */ 1); + } ZEND_TYPE_LIST_FOREACH_END(); + return INHERITANCE_UNRESOLVED; } + + return INHERITANCE_SUCCESS; } /* }}} */ @@ -852,15 +959,16 @@ inheritance_status property_types_compatible( return INHERITANCE_SUCCESS; } - if (!ZEND_TYPE_IS_CLASS(parent_info->type) || !ZEND_TYPE_IS_CLASS(child_info->type) || - ZEND_TYPE_ALLOW_NULL(parent_info->type) != ZEND_TYPE_ALLOW_NULL(child_info->type)) { + // TODO: Base all of this on the main covariance check to handle non-trivial cases. + if (!ZEND_TYPE_HAS_CLASS(parent_info->type) || !ZEND_TYPE_HAS_CLASS(child_info->type) || + ZEND_TYPE_PURE_MASK(parent_info->type) != ZEND_TYPE_PURE_MASK(child_info->type)) { return INHERITANCE_ERROR; } - parent_name = ZEND_TYPE_IS_CE(parent_info->type) + parent_name = ZEND_TYPE_HAS_CE(parent_info->type) ? ZEND_TYPE_CE(parent_info->type)->name : resolve_class_name(parent_info->ce, ZEND_TYPE_NAME(parent_info->type)); - child_name = ZEND_TYPE_IS_CE(child_info->type) + child_name = ZEND_TYPE_HAS_CE(child_info->type) ? ZEND_TYPE_CE(child_info->type)->name : resolve_class_name(child_info->ce, ZEND_TYPE_NAME(child_info->type)); if (zend_string_equals_ci(parent_name, child_name)) { @@ -868,12 +976,12 @@ inheritance_status property_types_compatible( } /* Check for class aliases */ - parent_type_ce = ZEND_TYPE_IS_CE(parent_info->type) + parent_type_ce = ZEND_TYPE_HAS_CE(parent_info->type) ? ZEND_TYPE_CE(parent_info->type) - : lookup_class(parent_info->ce, parent_name); - child_type_ce = ZEND_TYPE_IS_CE(child_info->type) + : lookup_class(parent_info->ce, parent_name, /* register_unresolved */ 1); + child_type_ce = ZEND_TYPE_HAS_CE(child_info->type) ? ZEND_TYPE_CE(child_info->type) - : lookup_class(child_info->ce, child_name); + : lookup_class(child_info->ce, child_name, /* register_unresolved */ 1); if (!parent_type_ce || !child_type_ce) { return INHERITANCE_UNRESOLVED; } @@ -1958,9 +2066,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent Z_TRY_ADDREF_P(prop_value); doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL; - if (ZEND_TYPE_IS_NAME(property_info->type)) { - zend_string_addref(ZEND_TYPE_NAME(property_info->type)); - } + zend_type_copy_ctor(&property_info->type); zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, property_info->type); zend_string_release_ex(prop_name, 0); } ZEND_HASH_FOREACH_END(); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 0f35b69adf93f..57e88e630dee6 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -255,7 +255,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type array_pair non_empty_array_pair_list array_pair_list possible_array_pair %type isset_variable type return_type type_expr %type identifier -%type inline_function +%type inline_function union_type %type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier @@ -660,6 +660,7 @@ optional_type: type_expr: type { $$ = $1; } | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } + | union_type { $$ = $1; } ; type: @@ -668,6 +669,11 @@ type: | name { $$ = $1; } ; +union_type: + type '|' type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } + | union_type '|' type { $$ = zend_ast_list_add($1, $3); } +; + return_type: /* empty */ { $$ = NULL; } | ':' type_expr { $$ = $2; } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 626eacd35d199..ae20d270d4653 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -102,6 +102,19 @@ ZEND_API void destroy_zend_function(zend_function *function) zend_function_dtor(&tmp); } +static void zend_always_inline zend_type_release(zend_type *type) { + if (ZEND_TYPE_HAS_LIST(*type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string_release(ZEND_TYPE_NAME(*type)); + } +} + void zend_free_internal_arg_info(zend_internal_function *function) { if ((function->fn_flags & (ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_HAS_TYPE_HINTS)) && function->arg_info) { @@ -114,9 +127,7 @@ void zend_free_internal_arg_info(zend_internal_function *function) { num_args++; } for (i = 0 ; i < num_args; i++) { - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_string_release_ex(ZEND_TYPE_NAME(arg_info[i].type), 1); - } + zend_type_release(&arg_info[i].type); } free(arg_info); } @@ -303,9 +314,7 @@ ZEND_API void destroy_zend_class(zval *zv) if (prop_info->doc_comment) { zend_string_release_ex(prop_info->doc_comment, 0); } - if (ZEND_TYPE_IS_NAME(prop_info->type)) { - zend_string_release(ZEND_TYPE_NAME(prop_info->type)); - } + zend_type_release(&prop_info->type); } } ZEND_HASH_FOREACH_END(); zend_hash_destroy(&ce->properties_info); @@ -496,9 +505,7 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) if (arg_info[i].name) { zend_string_release_ex(arg_info[i].name, 0); } - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_string_release_ex(ZEND_TYPE_NAME(arg_info[i].type), 0); - } + zend_type_release(&arg_info[i].type); } efree(arg_info); } diff --git a/Zend/zend_string.h b/Zend/zend_string.h index a38c1cae8c552..9dac54765286b 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -512,6 +512,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_CALLABLE, "callable") \ _(ZEND_STR_ITERABLE, "iterable") \ _(ZEND_STR_VOID, "void") \ + _(ZEND_STR_FALSE, "false") \ typedef enum _zend_known_string_id { diff --git a/Zend/zend_types.h b/Zend/zend_types.h index d77fbc68a3718..eae1238137038 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -105,11 +105,11 @@ typedef void (*copy_ctor_func_t)(zval *pElement); * zend_type - is an abstraction layer to represent information about type hint. * It shouldn't be used directly. Only through ZEND_TYPE_* macros. * - * ZEND_TYPE_IS_SET() - checks if type-hint exists - * ZEND_TYPE_IS_ONLY_MASK() - checks if type-hint refer to standard type - * ZEND_TYPE_IS_CLASS() - checks if type-hint refer to some class - * ZEND_TYPE_IS_CE() - checks if type-hint refer to some class by zend_class_entry * - * ZEND_TYPE_IS_NAME() - checks if type-hint refer to some class by zend_string * + * ZEND_TYPE_IS_SET() - checks if there is a type-hint + * ZEND_TYPE_HAS_ONLY_MASK() - checks if type-hint refer to standard type only + * ZEND_TYPE_HAS_CLASS() - checks if type-hint contains some class + * ZEND_TYPE_HAS_CE() - checks if type-hint contains some class as zend_class_entry * + * ZEND_TYPE_HAS_NAME() - checks if type-hint contains some class as zend_string * * * ZEND_TYPE_NAME() - returns referenced class name * ZEND_TYPE_CE() - returns referenced class entry @@ -130,25 +130,36 @@ typedef struct { /* TODO: We could use the extra 32-bit of padding on 64-bit systems. */ } zend_type; +typedef struct { + size_t num_types; + void *types[1]; +} zend_type_list; + #define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 24 #define _ZEND_TYPE_MASK ((1u << 24) - 1) #define _ZEND_TYPE_MAY_BE_MASK ((1u << (IS_VOID+1)) - 1) -#define _ZEND_TYPE_CE_BIT (1u << 22) +/* Only one of these bits may be set. */ +#define _ZEND_TYPE_LIST_BIT (1u << 21) +#define _ZEND_TYPE_CE_BIT (1u << 22) #define _ZEND_TYPE_NAME_BIT (1u << 23) +#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_CE_BIT|_ZEND_TYPE_NAME_BIT) /* Must have same value as MAY_BE_NULL */ #define _ZEND_TYPE_NULLABLE_BIT 0x2 #define ZEND_TYPE_IS_SET(t) \ (((t).type_mask & _ZEND_TYPE_MASK) != 0) -#define ZEND_TYPE_IS_CLASS(t) \ - (((t.type_mask) & (_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_CE_BIT)) != 0) +#define ZEND_TYPE_HAS_CLASS(t) \ + ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0) + +#define ZEND_TYPE_HAS_CE(t) \ + ((((t).type_mask) & _ZEND_TYPE_CE_BIT) != 0) -#define ZEND_TYPE_IS_CE(t) \ - (((t.type_mask) & _ZEND_TYPE_CE_BIT) != 0) +#define ZEND_TYPE_HAS_NAME(t) \ + ((((t).type_mask) & _ZEND_TYPE_NAME_BIT) != 0) -#define ZEND_TYPE_IS_NAME(t) \ - (((t.type_mask) & _ZEND_TYPE_NAME_BIT) != 0) +#define ZEND_TYPE_HAS_LIST(t) \ + ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) #define ZEND_TYPE_IS_ONLY_MASK(t) \ (ZEND_TYPE_IS_SET(t) && (t).ptr == NULL) @@ -162,9 +173,63 @@ typedef struct { #define ZEND_TYPE_CE(t) \ ((zend_class_entry *) (t).ptr) +#define ZEND_TYPE_LIST(t) \ + ((zend_type_list *) (t).ptr) + +/* Type lists use the low bit to distinguish NAME and CE entries, + * both of which may exist in the same list. */ +#define ZEND_TYPE_LIST_IS_CE(entry) \ + (((uintptr_t) (entry)) & 1) + +#define ZEND_TYPE_LIST_IS_NAME(entry) \ + !ZEND_TYPE_LIST_IS_CE(entry) + +#define ZEND_TYPE_LIST_GET_NAME(entry) \ + ((zend_string *) (entry)) + +#define ZEND_TYPE_LIST_GET_CE(entry) \ + ((zend_class_entry *) ((uintptr_t) (entry) & ~1)) + +#define ZEND_TYPE_LIST_ENCODE_NAME(name) \ + ((void *) (name)) + +#define ZEND_TYPE_LIST_ENCODE_CE(ce) \ + ((void *) (((uintptr_t) ce) | 1)) + +#define ZEND_TYPE_LIST_SIZE(num_types) \ + (sizeof(zend_type_list) + ((num_types) - 1) * sizeof(void *)) + +#define ZEND_TYPE_LIST_FOREACH_PTR(list, entry_ptr) do { \ + void **_list = (list)->types; \ + void **_end = _list + (list)->num_types; \ + for (; _list < _end; _list++) { \ + entry_ptr = _list; + +#define ZEND_TYPE_LIST_FOREACH(list, entry) do { \ + void **_list = (list)->types; \ + void **_end = _list + (list)->num_types; \ + for (; _list < _end; _list++) { \ + entry = *_list; + +#define ZEND_TYPE_LIST_FOREACH_END() \ + } \ +} while (0) + #define ZEND_TYPE_SET_PTR(t, _ptr) \ ((t).ptr = (_ptr)) +#define ZEND_TYPE_SET_PTR_AND_KIND(t, _ptr, kind_bit) do { \ + (t).ptr = (_ptr); \ + (t).type_mask &= ~_ZEND_TYPE_KIND_MASK; \ + (t).type_mask |= (kind_bit); \ +} while (0) + +#define ZEND_TYPE_SET_CE(t, ce) \ + ZEND_TYPE_SET_PTR_AND_KIND(t, ce, _ZEND_TYPE_CE_BIT) + +#define ZEND_TYPE_SET_LIST(t, list) \ + ZEND_TYPE_SET_PTR_AND_KIND(t, list, _ZEND_TYPE_LIST_BIT) + /* FULL_MASK() includes the MAY_BE_* type mask, the CE/NAME bits, as well as extra reserved bits. * The PURE_MASK() only includes the MAY_BE_* type mask. */ #define ZEND_TYPE_FULL_MASK(t) \ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index a36458b8995c3..01b3bcf9f2d72 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4108,8 +4108,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index be26c30765806..a5b1e2e75244e 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -8738,8 +8738,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -18668,8 +18667,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -26095,8 +26093,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -32717,8 +32714,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -44160,8 +44156,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) diff --git a/ext/opcache/Optimizer/compact_literals.c b/ext/opcache/Optimizer/compact_literals.c index 0a99ac4140f8e..4aabe04c6e120 100644 --- a/ext/opcache/Optimizer/compact_literals.c +++ b/ext/opcache/Optimizer/compact_literals.c @@ -56,25 +56,31 @@ typedef struct _literal_info { info[n].flags = ((kind) | (related)); \ } while (0) -static zend_bool class_name_type_hint(const zend_op_array *op_array, uint32_t arg_num) +static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num) { zend_arg_info *arg_info; - if (arg_num > 0) { - if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { - if (EXPECTED(arg_num <= op_array->num_args)) { - arg_info = &op_array->arg_info[arg_num-1]; - } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) { - arg_info = &op_array->arg_info[op_array->num_args]; - } else { - return 0; - } - return ZEND_TYPE_IS_CLASS(arg_info->type); + if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { + return 0; + } + if (EXPECTED(arg_num <= op_array->num_args)) { + arg_info = &op_array->arg_info[arg_num-1]; + } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) { + arg_info = &op_array->arg_info[op_array->num_args]; + } else { + return 0; } } else { arg_info = op_array->arg_info - 1; - return ZEND_TYPE_IS_CLASS(arg_info->type); } + + if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { + if (ZEND_TYPE_HAS_LIST(arg_info->type)) { + return ZEND_TYPE_LIST(arg_info->type)->num_types; + } + return 1; + } + return 0; } @@ -505,17 +511,23 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx case ZEND_RECV_INIT: case ZEND_RECV: case ZEND_RECV_VARIADIC: - if (class_name_type_hint(op_array, opline->op1.num)) { + { + size_t num_classes = type_num_classes(op_array, opline->op1.num); + if (num_classes) { opline->extended_value = cache_size; - cache_size += sizeof(void *); + cache_size += num_classes * sizeof(void *); } break; + } case ZEND_VERIFY_RETURN_TYPE: - if (class_name_type_hint(op_array, 0)) { + { + size_t num_classes = type_num_classes(op_array, 0); + if (num_classes) { opline->op2.num = cache_size; - cache_size += sizeof(void *); + cache_size += num_classes * sizeof(void *); } break; + } case ZEND_ASSIGN_STATIC_PROP_OP: if (opline->op1_type == IS_CONST) { // op1 static property diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c index 59c562425f13b..8802577154269 100644 --- a/ext/opcache/Optimizer/dfa_pass.c +++ b/ext/opcache/Optimizer/dfa_pass.c @@ -311,7 +311,7 @@ static inline zend_bool can_elide_return_type_check( return 0; } - if (ZEND_TYPE_IS_CLASS(info->type)) { + if (ZEND_TYPE_HAS_CLASS(info->type)) { if (!use_info->ce || !def_info->ce || !safe_instanceof(use_info->ce, def_info->ce)) { return 0; } diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c index 8fc92f842c070..0ff64b0a92f09 100644 --- a/ext/opcache/Optimizer/zend_inference.c +++ b/ext/opcache/Optimizer/zend_inference.c @@ -2254,11 +2254,14 @@ uint32_t zend_fetch_arg_info_type(const zend_script *script, zend_arg_info *arg_ tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(arg_info->type)); *pce = NULL; - if (ZEND_TYPE_IS_CLASS(arg_info->type)) { - zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type)); + if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { tmp |= MAY_BE_OBJECT; - *pce = get_class_entry(script, lcname); - zend_string_release_ex(lcname, 0); + /* As we only have space to store one CE, we use a plain object type for class unions. */ + if (ZEND_TYPE_HAS_NAME(arg_info->type)) { + zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type)); + *pce = get_class_entry(script, lcname); + zend_string_release_ex(lcname, 0); + } } if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { tmp |= MAY_BE_RC1 | MAY_BE_RCN; @@ -2354,33 +2357,29 @@ static zend_property_info *zend_fetch_static_prop_info(const zend_script *script static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_info *prop_info, zend_class_entry **pce) { + if (pce) { + *pce = NULL; + } if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) { - uint32_t type = ZEND_TYPE_IS_CLASS(prop_info->type) - ? MAY_BE_OBJECT - : zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type)); + uint32_t type = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type)); - if (ZEND_TYPE_ALLOW_NULL(prop_info->type)) { - type |= MAY_BE_NULL; - } if (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { type |= MAY_BE_RC1 | MAY_BE_RCN; } - if (pce) { - if (ZEND_TYPE_IS_CE(prop_info->type)) { - *pce = ZEND_TYPE_CE(prop_info->type); - } else if (ZEND_TYPE_IS_NAME(prop_info->type)) { - zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type)); - *pce = get_class_entry(script, lcname); - zend_string_release(lcname); - } else { - *pce = NULL; + if (ZEND_TYPE_HAS_CLASS(prop_info->type)) { + type |= MAY_BE_OBJECT; + if (pce) { + if (ZEND_TYPE_HAS_CE(prop_info->type)) { + *pce = ZEND_TYPE_CE(prop_info->type); + } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) { + zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type)); + *pce = get_class_entry(script, lcname); + zend_string_release(lcname); + } } } return type; } - if (pce) { - *pce = NULL; - } return MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN; } diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 68be526892b3b..08fca1e3fa90f 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -601,7 +601,13 @@ static void accel_copy_permanent_strings(zend_new_interned_string_func_t new_int num_args++; } for (i = 0 ; i < num_args; i++) { - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { + if (ZEND_TYPE_HAS_LIST(arg_info[i].type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(arg_info[i].type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(*entry)); + *entry = zend_new_interned_string(ZEND_TYPE_LIST_GET_NAME(*entry)); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) { ZEND_TYPE_SET_PTR(arg_info[i].type, new_interned_string(ZEND_TYPE_NAME(arg_info[i].type))); } @@ -3539,6 +3545,32 @@ static zend_bool preload_try_resolve_constants(zend_class_entry *ce) return ok; } +static zend_class_entry *preload_fetch_resolved_ce(zend_string *name, zend_class_entry *self_ce) { + zend_string *lcname = zend_string_tolower(name); + zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lcname); + zend_string_release(lcname); + if (!ce) { + return NULL; + } + if (ce == self_ce) { + /* Ignore the following requirements if this is the class referring to itself */ + return ce; + } +#ifdef ZEND_WIN32 + /* On Windows we can't link with internal class, because of ASLR */ + if (ce->type == ZEND_INTERNAL_CLASS) { + return NULL; + } +#endif + if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { + return NULL; + } + if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { + return NULL; + } + return ce; +} + static zend_bool preload_try_resolve_property_types(zend_class_entry *ce) { zend_bool ok = 1; @@ -3547,66 +3579,61 @@ static zend_bool preload_try_resolve_property_types(zend_class_entry *ce) if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { - zend_string *name, *lcname; - - if (!ZEND_TYPE_IS_NAME(prop->type)) { - continue; - } - - name = ZEND_TYPE_NAME(prop->type); - lcname = zend_string_tolower(name); - p = zend_hash_find_ptr(EG(class_table), lcname); - zend_string_release(lcname); - if (!p) { - ok = 0; - continue; - } - if (p != ce) { -#ifdef ZEND_WIN32 - /* On Windows we can't link with internal class, because of ASLR */ - if (p->type == ZEND_INTERNAL_CLASS) { - ok = 0; - continue; - } -#endif - if (!(p->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { - ok = 0; - continue; - } - if (!(p->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { + if (ZEND_TYPE_HAS_LIST(prop->type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + p = preload_fetch_resolved_ce(ZEND_TYPE_LIST_GET_NAME(*entry), ce); + if (!p) { + ok = 0; + continue; + } + *entry = ZEND_TYPE_LIST_ENCODE_CE(p); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(prop->type)) { + p = preload_fetch_resolved_ce(ZEND_TYPE_NAME(prop->type), ce); + if (!p) { ok = 0; continue; } + ZEND_TYPE_SET_CE(prop->type, p); } - - zend_string_release(name); - prop->type = (zend_type) ZEND_TYPE_INIT_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type), 0); } ZEND_HASH_FOREACH_END(); } return ok; } -static zend_bool preload_is_type_known(zend_class_entry *ce, zend_type type) { - zend_string *name, *lcname; - zend_bool known; - if (!ZEND_TYPE_IS_NAME(type)) { - return 1; - } - - name = ZEND_TYPE_NAME(type); +static zend_bool preload_is_class_type_known(zend_class_entry *ce, zend_string *name) { if (zend_string_equals_literal_ci(name, "self") || zend_string_equals_literal_ci(name, "parent") || zend_string_equals_ci(name, ce->name)) { return 1; } - lcname = zend_string_tolower(name); - known = zend_hash_exists(EG(class_table), lcname); + zend_string *lcname = zend_string_tolower(name); + zend_bool known = zend_hash_exists(EG(class_table), lcname); zend_string_release(lcname); return known; } +static zend_bool preload_is_type_known(zend_class_entry *ce, zend_type type) { + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry) + && !preload_is_class_type_known(ce, ZEND_TYPE_LIST_GET_NAME(entry))) { + return 0; + } + } ZEND_TYPE_LIST_FOREACH_END(); + } + if (ZEND_TYPE_HAS_NAME(type)) { + return preload_is_class_type_known(ce, ZEND_TYPE_NAME(type)); + } + return 1; +} + static zend_bool preload_is_method_maybe_override(zend_class_entry *ce, zend_string *lcname) { zend_class_entry *p; if (ce->trait_aliases || ce->trait_precedences) { diff --git a/ext/opcache/jit/zend_jit_disasm_x86.c b/ext/opcache/jit/zend_jit_disasm_x86.c index 70708729b49ed..df6c4ab1bab01 100644 --- a/ext/opcache/jit/zend_jit_disasm_x86.c +++ b/ext/opcache/jit/zend_jit_disasm_x86.c @@ -424,7 +424,6 @@ static int zend_jit_disasm_init(void) REGISTER_HELPER(zend_jit_zval_copy_deref_helper) REGISTER_HELPER(zend_jit_new_ref_helper); REGISTER_HELPER(zend_jit_fetch_global_helper); - REGISTER_HELPER(zend_jit_verify_arg_object); REGISTER_HELPER(zend_jit_verify_arg_slow); REGISTER_HELPER(zend_jit_fetch_obj_r_slow); REGISTER_HELPER(zend_jit_fetch_obj_r_dynamic); diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 658350794ed11..59014c7e8d4b4 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1136,64 +1136,58 @@ static zval* ZEND_FASTCALL zend_jit_fetch_global_helper(zend_execute_data *execu return value; } -static void ZEND_FASTCALL zend_jit_verify_arg_object(zval *arg, const zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot) -{ - zend_class_entry *ce; - if (EXPECTED(*cache_slot)) { - ce = (zend_class_entry *)*cache_slot; - } else { - ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); - if (UNEXPECTED(!ce)) { - zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg); - return; - } - *cache_slot = (void *)ce; - } - if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(arg), ce))) { - zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg); - } -} - static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot) { uint32_t type_mask; - if (UNEXPECTED(ZEND_TYPE_IS_CLASS(arg_info->type))) { + if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) { zend_class_entry *ce; - if (Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(arg_info->type)) { - /* Null passed to nullable type */ - return; - } - - /* This is always an error - we fetch the class name for the error message here */ - if (EXPECTED(*cache_slot)) { - ce = (zend_class_entry *) *cache_slot; + if (ZEND_TYPE_HAS_LIST(arg_info->type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->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) { + continue; + } + *cache_slot = ce; + } + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + return; + } + cache_slot++; + } ZEND_TYPE_LIST_FOREACH_END(); } else { - ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); - if (ce) { - *cache_slot = (void *)ce; + if (EXPECTED(*cache_slot)) { + ce = (zend_class_entry *) *cache_slot; + } else { + ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->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; } } - goto err; } +builtin_types: type_mask = ZEND_TYPE_FULL_MASK(arg_info->type); - if (type_mask & MAY_BE_CALLABLE) { - if (zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL) == 0) { - goto err; - } - } else if (type_mask & MAY_BE_ITERABLE) { - if (zend_is_iterable(arg) == 0) { - goto err; - } - } else { - if (Z_ISUNDEF_P(arg) || - zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0) == 0) { - goto err; - } + if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { + return; } - return; -err: + if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { + return; + } + if (zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0)) { + return; + } + zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg); } diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index 0681a450641cc..6f1913a1a1e0d 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -9036,54 +9036,23 @@ static int zend_jit_recv(dasm_State **Dst, const zend_op *opline, const zend_op_ | GET_Z_PTR r0, r0 | add r0, offsetof(zend_reference, val) } - if (!ZEND_TYPE_IS_CLASS(type)) { - uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); - if (is_power_of_two(type_mask)) { - uint32_t type_code = concrete_type(type_mask); - | cmp byte [r0 + 8], type_code - | jne >8 - } else { - | mov edx, 1 - | mov cl, byte [r0 + 8] - | shl edx, cl - | test edx, type_mask - | je >8 - } + + uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); + if (is_power_of_two(type_mask)) { + uint32_t type_code = concrete_type(type_mask); + | cmp byte [r0 + 8], type_code + | jne >8 } else { - | SAVE_VALID_OPLINE opline - | cmp byte [r0 + 8], IS_OBJECT - | jne >9 - | mov FCARG1a, r0 - | mov r0, EX->run_time_cache - | add r0, opline->extended_value - | LOAD_ADDR FCARG2a, (ptrdiff_t)op_array - |.if X64WIN - | mov CARG3, arg_num - | LOAD_ADDR CARG4, (ptrdiff_t)arg_info - | mov aword A5, r0 - | EXT_CALL zend_jit_verify_arg_object, r0 - |.elif X64 - | mov CARG3, arg_num - | LOAD_ADDR CARG4, (ptrdiff_t)arg_info - | mov CARG5, r0 - | EXT_CALL zend_jit_verify_arg_object, r0 - |.else - | sub r4, 4 - | push r0 - | push (ptrdiff_t)arg_info - | push arg_num - | EXT_CALL zend_jit_verify_arg_object, r0 - | add r4, 4 - |.endif - if (!zend_jit_check_exception(Dst)) { - return 0; - } + | mov edx, 1 + | mov cl, byte [r0 + 8] + | shl edx, cl + | test edx, type_mask + | je >8 } |.cold_code |8: | SAVE_VALID_OPLINE opline - |9: | mov FCARG1a, r0 | mov r0, EX->run_time_cache | add r0, opline->extended_value @@ -9185,47 +9154,18 @@ static int zend_jit_recv_init(dasm_State **Dst, const zend_op *opline, const zen has_slow += 2; | LOAD_ZVAL_ADDR r0, res_addr | ZVAL_DEREF r0, MAY_BE_REF - if (!ZEND_TYPE_IS_CLASS(arg_info->type)) { - uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type); - if (is_power_of_two(type_mask)) { - uint32_t type_code = concrete_type(type_mask); - | cmp byte [r0 + 8], type_code - | jne >8 - } else { - | mov edx, 1 - | mov cl, byte [r0 + 8] - | shl edx, cl - | test edx, type_mask - | je >8 - } + + uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type); + if (is_power_of_two(type_mask)) { + uint32_t type_code = concrete_type(type_mask); + | cmp byte [r0 + 8], type_code + | jne >8 } else { - | cmp byte [r0 + 8], IS_OBJECT - | jne >8 - | mov FCARG1a, r0 - | mov r0, EX->run_time_cache - | lea r0, [r0 + opline->extended_value] - | LOAD_ADDR FCARG2a, (ptrdiff_t)op_array - |.if X64WIN - | mov CARG3, arg_num - | LOAD_ADDR CARG4, (ptrdiff_t)arg_info - | mov aword A5, r0 - | SAVE_VALID_OPLINE opline - | EXT_CALL zend_jit_verify_arg_object, r0 - |.elif X64 - | mov CARG3, arg_num - | LOAD_ADDR CARG4, (ptrdiff_t)arg_info - | mov CARG5, r0 - | SAVE_VALID_OPLINE opline - | EXT_CALL zend_jit_verify_arg_object, r0 - |.else - | sub r4, 4 - | push r0 - | push (ptrdiff_t)arg_info - | push arg_num - | SAVE_VALID_OPLINE opline - | EXT_CALL zend_jit_verify_arg_object, r0 - | add r4, 4 - |.endif + | mov edx, 1 + | mov cl, byte [r0 + 8] + | shl edx, cl + | test edx, type_mask + | je >8 } } while (0); } diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index e74b7bb6689b7..0d2a25ed59563 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -233,7 +233,18 @@ static void zend_hash_clone_prop_info(HashTable *ht) prop_info->ce = ARENA_REALLOC(prop_info->ce); } - if (ZEND_TYPE_IS_CE(prop_info->type)) { + if (ZEND_TYPE_HAS_LIST(prop_info->type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) { + if (ZEND_TYPE_LIST_IS_CE(*entry)) { + zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); + if (IN_ARENA(ce)) { + ce = ARENA_REALLOC(ce); + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_CE(prop_info->type)) { zend_class_entry *ce = ZEND_TYPE_CE(prop_info->type); if (IN_ARENA(ce)) { ce = ARENA_REALLOC(ce); diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 6e4d52cee6ffc..96e08e8ab05be 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -371,6 +371,38 @@ static void zend_file_cache_serialize_zval(zval *zv, } } +static void zend_file_cache_serialize_type( + zend_type *type, zend_persistent_script *script, zend_file_cache_metainfo *info, void *buf) +{ + if (ZEND_TYPE_HAS_LIST(*type)) { + zend_type_list *list = ZEND_TYPE_LIST(*type); + SERIALIZE_PTR(list); + ZEND_TYPE_SET_PTR(*type, list); + UNSERIALIZE_PTR(list); + + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(list, entry) { + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry); + SERIALIZE_STR(name); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(name); + } else { + zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); + SERIALIZE_PTR(ce); + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string *type_name = ZEND_TYPE_NAME(*type); + SERIALIZE_STR(type_name); + ZEND_TYPE_SET_PTR(*type, type_name); + } else if (ZEND_TYPE_HAS_CE(*type)) { + zend_class_entry *ce = ZEND_TYPE_CE(*type); + SERIALIZE_PTR(ce); + ZEND_TYPE_SET_PTR(*type, ce); + } +} + static void zend_file_cache_serialize_op_array(zend_op_array *op_array, zend_persistent_script *script, zend_file_cache_metainfo *info, @@ -498,11 +530,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra if (!IS_SERIALIZED(p->name)) { SERIALIZE_STR(p->name); } - if (ZEND_TYPE_IS_CLASS(p->type)) { - zend_string *type_name = ZEND_TYPE_NAME(p->type); - SERIALIZE_STR(type_name); - ZEND_TYPE_SET_PTR(p->type, type_name); - } + zend_file_cache_serialize_type(&p->type, script, info, buf); p++; } } @@ -572,15 +600,7 @@ static void zend_file_cache_serialize_prop_info(zval *zv, SERIALIZE_STR(prop->doc_comment); } } - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *name = ZEND_TYPE_NAME(prop->type); - SERIALIZE_STR(name); - ZEND_TYPE_SET_PTR(prop->type, name); - } else if (ZEND_TYPE_IS_CE(prop->type)) { - zend_class_entry *ce = ZEND_TYPE_CE(prop->type); - SERIALIZE_PTR(ce); - ZEND_TYPE_SET_PTR(prop->type, ce); - } + zend_file_cache_serialize_type(&prop->type, script, info, buf); } } @@ -1080,6 +1100,37 @@ static void zend_file_cache_unserialize_zval(zval *zv, } } +static void zend_file_cache_unserialize_type( + zend_type *type, zend_persistent_script *script, void *buf) +{ + if (ZEND_TYPE_HAS_LIST(*type)) { + zend_type_list *list = ZEND_TYPE_LIST(*type); + UNSERIALIZE_PTR(list); + ZEND_TYPE_SET_PTR(*type, list); + + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(list, entry) { + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry); + UNSERIALIZE_STR(name); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(name); + } else { + zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); + UNSERIALIZE_PTR(ce); + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string *type_name = ZEND_TYPE_NAME(*type); + UNSERIALIZE_STR(type_name); + ZEND_TYPE_SET_PTR(*type, type_name); + } else if (ZEND_TYPE_HAS_CE(*type)) { + zend_class_entry *ce = ZEND_TYPE_CE(*type); + UNSERIALIZE_PTR(ce); + ZEND_TYPE_SET_PTR(*type, ce); + } +} + static void zend_file_cache_unserialize_op_array(zend_op_array *op_array, zend_persistent_script *script, void *buf) @@ -1195,11 +1246,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr if (!IS_UNSERIALIZED(p->name)) { UNSERIALIZE_STR(p->name); } - if (ZEND_TYPE_IS_CLASS(p->type)) { - zend_string *type_name = ZEND_TYPE_NAME(p->type); - UNSERIALIZE_STR(type_name); - ZEND_TYPE_SET_PTR(p->type, type_name); - } + zend_file_cache_unserialize_type(&p->type, script, buf); p++; } } @@ -1269,15 +1316,7 @@ static void zend_file_cache_unserialize_prop_info(zval *zv, UNSERIALIZE_STR(prop->doc_comment); } } - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *name = ZEND_TYPE_NAME(prop->type); - UNSERIALIZE_STR(name); - ZEND_TYPE_SET_PTR(prop->type, name); - } else if (ZEND_TYPE_IS_CE(prop->type)) { - zend_class_entry *ce = ZEND_TYPE_CE(prop->type); - UNSERIALIZE_PTR(ce); - ZEND_TYPE_SET_PTR(prop->type, ce); - } + zend_file_cache_unserialize_type(&prop->type, script, buf); } } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 56c64dc87d1a8..a4d8cbb07baab 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -258,6 +258,25 @@ static void zend_persist_zval(zval *z) } } +static void zend_persist_type(zend_type *type) { + if (ZEND_TYPE_HAS_LIST(*type)) { + void **entry; + zend_type_list *list = ZEND_TYPE_LIST(*type); + list = zend_shared_memdup_put_free(list, ZEND_TYPE_LIST_SIZE(list->num_types)); + ZEND_TYPE_SET_PTR(*type, list); + + ZEND_TYPE_LIST_FOREACH_PTR(list, entry) { + zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry); + zend_accel_store_interned_string(type_name); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(type_name); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string *type_name = ZEND_TYPE_NAME(*type); + zend_accel_store_interned_string(type_name); + ZEND_TYPE_SET_PTR(*type, type_name); + } +} + static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_script* main_persistent_script) { zend_op *persist_ptr; @@ -499,11 +518,7 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc if (arg_info[i].name) { zend_accel_store_interned_string(arg_info[i].name); } - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type); - zend_accel_store_interned_string(type_name); - ZEND_TYPE_SET_PTR(arg_info[i].type, type_name); - } + zend_persist_type(&arg_info[i].type); } if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { arg_info++; @@ -658,12 +673,7 @@ static void zend_persist_property_info(zval *zv) prop->doc_comment = NULL; } } - - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *class_name = ZEND_TYPE_NAME(prop->type); - zend_accel_store_interned_string(class_name); - ZEND_TYPE_SET_PTR(prop->type, class_name); - } + zend_persist_type(&prop->type); } static void zend_persist_class_constant(zval *zv) @@ -937,7 +947,20 @@ static void zend_update_parent_ce(zend_class_entry *ce) if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { zend_property_info *prop; ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { - if (ZEND_TYPE_IS_CE(prop->type)) { + if (ZEND_TYPE_HAS_LIST(prop->type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop->type), entry) { + if (ZEND_TYPE_LIST_IS_CE(*entry)) { + zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); + if (ce->type == ZEND_USER_CLASS) { + ce = zend_shared_alloc_get_xlat_entry(ce); + if (ce) { + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } + } + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_CE(prop->type)) { zend_class_entry *ce = ZEND_TYPE_CE(prop->type); if (ce->type == ZEND_USER_CLASS) { ce = zend_shared_alloc_get_xlat_entry(ce); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 62a3deaea0053..7cf1c3506eae3 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -148,6 +148,23 @@ static void zend_persist_zval_calc(zval *z) } } +static void zend_persist_type_calc(zend_type *type) +{ + if (ZEND_TYPE_HAS_LIST(*type)) { + void **entry; + ADD_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types)); + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(*type), entry) { + zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry); + ADD_INTERNED_STRING(type_name); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(type_name); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string *type_name = ZEND_TYPE_NAME(*type); + ADD_INTERNED_STRING(type_name); + ZEND_TYPE_SET_PTR(*type, type_name); + } +} + static void zend_persist_op_array_calc_ex(zend_op_array *op_array) { if (op_array->scope && zend_shared_alloc_get_xlat_entry(op_array->opcodes)) { @@ -222,11 +239,7 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) if (arg_info[i].name) { ADD_INTERNED_STRING(arg_info[i].name); } - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type); - ADD_INTERNED_STRING(type_name); - ZEND_TYPE_SET_PTR(arg_info[i].type, type_name); - } + zend_persist_type_calc(&arg_info[i].type); } } @@ -302,11 +315,7 @@ static void zend_persist_property_info_calc(zval *zv) zend_shared_alloc_register_xlat_entry(prop, prop); ADD_SIZE_EX(sizeof(zend_property_info)); ADD_INTERNED_STRING(prop->name); - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *class_name = ZEND_TYPE_NAME(prop->type); - ADD_INTERNED_STRING(class_name); - ZEND_TYPE_SET_PTR(prop->type, class_name); - } + zend_persist_type_calc(&prop->type); if (ZCG(accel_directives).save_comments && prop->doc_comment) { ADD_STRING(prop->doc_comment); } @@ -336,7 +345,14 @@ static void check_property_type_resolution(zend_class_entry *ce) { if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { - if (ZEND_TYPE_IS_NAME(prop->type)) { + if (ZEND_TYPE_HAS_LIST(prop->type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(prop->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + return; + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(prop->type)) { return; } } ZEND_HASH_FOREACH_END(); diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 522b9d211d32a..2cd75aecf50b9 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -2482,7 +2482,8 @@ ZEND_METHOD(reflection_parameter, getClass) } GET_REFLECTION_OBJECT_PTR(param); - if (ZEND_TYPE_IS_CLASS(param->arg_info->type)) { + // TODO: This is going to return null for union types, which is rather odd. + if (ZEND_TYPE_HAS_NAME(param->arg_info->type)) { /* Class name is stored as a string, we might also get "self" or "parent" * - For "self", simply use the function scope. If scope is NULL then * the function is global and thus self does not make any sense From 8f9c57fda0824f07b087fc2afb75ab3f82dc45e3 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 11:25:47 +0200 Subject: [PATCH 04/48] Fetch unresolved classes from autoload table --- Zend/zend_inheritance.c | 56 ++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 9c4bbf84de8ae..d25082bc24b2e 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -413,7 +413,6 @@ static inheritance_status zend_perform_covariant_class_type_check( } static inheritance_status zend_perform_covariant_type_check( - zend_string **unresolved_class, const zend_function *fe, zend_arg_info *fe_arg_info, const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ { @@ -440,8 +439,6 @@ static inheritance_status zend_perform_covariant_type_check( } if (ZEND_TYPE_HAS_NAME(fe_type)) { - *unresolved_class = NULL; // TODO - zend_string *fe_class_name = resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); inheritance_status status = zend_perform_covariant_class_type_check( fe, fe_class_name, proto, proto_type, /* register_unresolved */ 0); @@ -455,8 +452,6 @@ static inheritance_status zend_perform_covariant_type_check( } if (ZEND_TYPE_HAS_LIST(fe_type)) { - *unresolved_class = NULL; // TODO - void *entry; zend_bool all_success = 1; @@ -497,7 +492,6 @@ static inheritance_status zend_perform_covariant_type_check( /* }}} */ static inheritance_status zend_do_perform_arg_type_hint_check( - zend_string **unresolved_class, const zend_function *fe, zend_arg_info *fe_arg_info, const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ { @@ -513,13 +507,12 @@ static inheritance_status zend_do_perform_arg_type_hint_check( /* Contravariant type check is performed as a covariant type check with swapped * argument order. */ - return zend_perform_covariant_type_check( - unresolved_class, proto, proto_arg_info, fe, fe_arg_info); + return zend_perform_covariant_type_check(proto, proto_arg_info, fe, fe_arg_info); } /* }}} */ static inheritance_status zend_do_perform_implementation_check( - zend_string **unresolved_class, const zend_function *fe, const zend_function *proto) /* {{{ */ + const zend_function *fe, const zend_function *proto) /* {{{ */ { uint32_t i, num_args; inheritance_status status, local_status; @@ -585,8 +578,7 @@ static inheritance_status zend_do_perform_implementation_check( proto_arg_info = &proto->common.arg_info[proto->common.num_args]; } - local_status = zend_do_perform_arg_type_hint_check( - unresolved_class, fe, fe_arg_info, proto, proto_arg_info); + local_status = zend_do_perform_arg_type_hint_check(fe, fe_arg_info, proto, proto_arg_info); if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) { if (UNEXPECTED(local_status == INHERITANCE_ERROR)) { @@ -611,7 +603,7 @@ static inheritance_status zend_do_perform_implementation_check( } local_status = zend_perform_covariant_type_check( - unresolved_class, fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1); + fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1); if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) { if (UNEXPECTED(local_status == INHERITANCE_ERROR)) { @@ -770,10 +762,17 @@ static zend_always_inline uint32_t func_lineno(const zend_function *fn) { static void ZEND_COLD emit_incompatible_method_error( const zend_function *child, const zend_function *parent, - inheritance_status status, zend_string *unresolved_class) { + inheritance_status status) { zend_string *parent_prototype = zend_get_function_declaration(parent); zend_string *child_prototype = zend_get_function_declaration(child); if (status == INHERITANCE_UNRESOLVED) { + /* Fetch the first unresolved class from registered autoloads */ + zend_string *unresolved_class = NULL; + ZEND_HASH_FOREACH_STR_KEY(CG(delayed_autoloads), unresolved_class) { + break; + } ZEND_HASH_FOREACH_END(); + ZEND_ASSERT(unresolved_class); + zend_error_at(E_COMPILE_ERROR, NULL, func_lineno(child), "Could not check compatibility between %s and %s, because class %s is not available", ZSTR_VAL(child_prototype), ZSTR_VAL(parent_prototype), ZSTR_VAL(unresolved_class)); @@ -790,17 +789,13 @@ static void perform_delayable_implementation_check( zend_class_entry *ce, const zend_function *fe, const zend_function *proto) { - zend_string *unresolved_class; - inheritance_status status = zend_do_perform_implementation_check( - &unresolved_class, fe, proto); - + inheritance_status status = zend_do_perform_implementation_check(fe, proto); if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { if (EXPECTED(status == INHERITANCE_UNRESOLVED)) { add_compatibility_obligation(ce, fe, proto); } else { ZEND_ASSERT(status == INHERITANCE_ERROR); - emit_incompatible_method_error( - fe, proto, status, unresolved_class); + emit_incompatible_method_error(fe, proto, status); } } } @@ -899,10 +894,7 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(z if (!checked) { if (check_only) { - zend_string *unresolved_class; - - return zend_do_perform_implementation_check( - &unresolved_class, child, parent); + return zend_do_perform_implementation_check(child, parent); } perform_delayable_implementation_check(ce, child, parent); } @@ -2331,16 +2323,14 @@ static int check_variance_obligation(zval *zv) { return ZEND_HASH_APPLY_KEEP; } } else if (obligation->type == OBLIGATION_COMPATIBILITY) { - zend_string *unresolved_class; inheritance_status status = zend_do_perform_implementation_check( - &unresolved_class, obligation->child_fn, obligation->parent_fn); + obligation->child_fn, obligation->parent_fn); if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { if (EXPECTED(status == INHERITANCE_UNRESOLVED)) { return ZEND_HASH_APPLY_KEEP; } ZEND_ASSERT(status == INHERITANCE_ERROR); - emit_incompatible_method_error( - obligation->child_fn, obligation->parent_fn, status, unresolved_class); + emit_incompatible_method_error(obligation->child_fn, obligation->parent_fn, status); } /* Either the compatibility check was successful or only threw a warning. */ } else { @@ -2403,16 +2393,14 @@ static void report_variance_errors(zend_class_entry *ce) { ZEND_ASSERT(obligations != NULL); ZEND_HASH_FOREACH_PTR(obligations, obligation) { - inheritance_status status; - zend_string *unresolved_class; - if (obligation->type == OBLIGATION_COMPATIBILITY) { - /* Just used to fetch the unresolved_class in this case. */ - status = zend_do_perform_implementation_check( - &unresolved_class, obligation->child_fn, obligation->parent_fn); + /* Just used to populate the delayed_autoloads table, + * which will be used when printing the "unresolved" error. */ + inheritance_status status = zend_do_perform_implementation_check( + obligation->child_fn, obligation->parent_fn); ZEND_ASSERT(status == INHERITANCE_UNRESOLVED); emit_incompatible_method_error( - obligation->child_fn, obligation->parent_fn, status, unresolved_class); + obligation->child_fn, obligation->parent_fn, status); } else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) { emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop); } else { From 9c58da41745ab358cf5969a70d4d77b2797e1a1b Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 11:41:12 +0200 Subject: [PATCH 05/48] Fix coercion error --- Zend/zend_execute.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 70d5b24b6daa2..22ff0f0700901 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3030,6 +3030,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( } 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); } @@ -3042,13 +3043,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_TRUE|MAY_BE_FALSE)) != (MAY_BE_TRUE|MAY_BE_FALSE)) { return 0; } From 48e3379dc722e12d384150332ea22bead67cb161 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 11:43:44 +0200 Subject: [PATCH 06/48] Remove special exceptions when failing to resolve prop type class Instead throw the generic invalid type message. Otherwise we may run into issues if a union type contains an invalid type, but also a valid one. --- Zend/tests/type_declarations/typed_properties_043.phpt | 6 +++--- Zend/zend_execute.c | 7 ------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Zend/tests/type_declarations/typed_properties_043.phpt b/Zend/tests/type_declarations/typed_properties_043.phpt index eefe35879626c..79f01545e151d 100644 --- a/Zend/tests/type_declarations/typed_properties_043.phpt +++ b/Zend/tests/type_declarations/typed_properties_043.phpt @@ -41,9 +41,9 @@ var_dump(Bar::$selfProp, Bar::$selfNullProp, Bar::$parentProp); ?> --EXPECT-- -Cannot write a value to a 'self' typed static property of a trait -Cannot write a non-null value to a 'self' typed static property of a trait -Cannot access parent:: when current class scope has no parent +Cannot assign stdClass to property Test::$selfProp of type self +Cannot assign stdClass to property Test::$selfNullProp of type ?self +Cannot assign stdClass to property Test::$parentProp of type parent NULL object(Bar)#3 (0) { } diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 22ff0f0700901..715121773966e 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -903,20 +903,13 @@ ZEND_COLD zend_never_inline void zend_verify_property_type_error(zend_property_i static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class_entry *self_ce) { if (zend_string_equals_literal_ci(name, "self")) { - // TODO: Eliminate this exception! /* 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 value to a 'self' typed static property of a trait"); return NULL; } return self_ce; } else if (zend_string_equals_literal_ci(name, "parent")) { - // TODO: Eliminate this exception! - if (UNEXPECTED(!self_ce->parent)) { - zend_throw_error(NULL, "Cannot access parent:: when current class scope has no parent"); - return NULL; - } return self_ce->parent; } else { return zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); From 82cfa0c8a603eb7bccd83f520b4231f3fe84876d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 12:28:02 +0200 Subject: [PATCH 07/48] Fix the typed reference assignment logic This is tricky... --- Zend/zend_execute.c | 57 ++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 715121773966e..9344d70d0a65a 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3057,8 +3057,9 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference /* The value must satisfy each property type, and coerce to the same value for each property * type. Remember the first coerced type and value we've seen for this purpose. */ - zend_property_info *seen_prop = NULL; - zval seen_value; + 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) { @@ -3069,28 +3070,52 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference } if (result < 0) { - zval tmp; - ZVAL_COPY(&tmp, zv); - if (!zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop->type), &tmp)) { - zend_throw_ref_type_error_zval(prop, zv); - zval_ptr_dtor(&tmp); - return 0; - } - if (seen_prop) { - if (!zend_is_identical(&tmp, &seen_value)) { - zend_throw_conflicting_coercion_error(seen_prop, prop, zv); + 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)) { + zend_throw_ref_type_error_zval(prop, zv); + zval_ptr_dtor(&coerced_value); return 0; } + } else if (Z_ISUNDEF(coerced_value)) { + /* A previous property did not require coercion, but this one does, + * so they are incompatible. */ + zend_throw_conflicting_coercion_error(first_prop, prop, zv); + return 0; } else { - seen_prop = prop; - ZVAL_COPY_VALUE(&seen_value, &tmp); + zval tmp; + ZVAL_COPY(&tmp, zv); + if (!zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop->type), &tmp)) { + zend_throw_ref_type_error_zval(prop, zv); + zval_ptr_dtor(&tmp); + zval_ptr_dtor(&coerced_value); + return 0; + } + if (!zend_is_identical(&coerced_value, &tmp)) { + zend_throw_conflicting_coercion_error(first_prop, prop, zv); + zval_ptr_dtor(&tmp); + zval_ptr_dtor(&coerced_value); + return 0; + } + } + } 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. */ + zend_throw_conflicting_coercion_error(first_prop, prop, zv); + zval_ptr_dtor(&coerced_value); + return 0; } } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); - if (seen_prop) { + if (!Z_ISUNDEF(coerced_value)) { zval_ptr_dtor(zv); - ZVAL_COPY_VALUE(zv, &seen_value); + ZVAL_COPY_VALUE(zv, &coerced_value); } return 1; From c786f7825658889d45334d01e92fc84f4a6f5a0a Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 12:30:23 +0200 Subject: [PATCH 08/48] Make loading in type errors robust --- Zend/zend_execute.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 9344d70d0a65a..8dce42512a6ad 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -662,6 +662,10 @@ static ZEND_COLD void zend_verify_type_error_common( // TODO: This code is broken for now. if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { 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 "; @@ -1279,18 +1283,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: Generalize this loading - zend_class_entry *ce; - if (ZEND_TYPE_HAS_NAME(ret_info->type)) { - if (!*cache_slot) { - 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; } From 67c0d292448ad5eb4e4eefe99b8d4c6bac5dfadf Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 12:51:28 +0200 Subject: [PATCH 09/48] Add tests for redundant types --- .../type_declarations/union_types/bool_and_false.phpt | 11 +++++++++++ .../union_types/duplicate_class_type.phpt | 11 +++++++++++ .../type_declarations/union_types/duplicate_type.phpt | 11 +++++++++++ .../union_types/iterable_and_array.phpt | 11 +++++++++++ Zend/zend_compile.c | 3 ++- 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_declarations/union_types/bool_and_false.phpt create mode 100644 Zend/tests/type_declarations/union_types/duplicate_class_type.phpt create mode 100644 Zend/tests/type_declarations/union_types/duplicate_type.phpt create mode 100644 Zend/tests/type_declarations/union_types/iterable_and_array.phpt diff --git a/Zend/tests/type_declarations/union_types/bool_and_false.phpt b/Zend/tests/type_declarations/union_types/bool_and_false.phpt new file mode 100644 index 0000000000000..7397bf169fa32 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/bool_and_false.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both bool and false in a union +--FILE-- + +--EXPECTF-- +Fatal error: Type bool is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/duplicate_class_type.phpt b/Zend/tests/type_declarations/union_types/duplicate_class_type.phpt new file mode 100644 index 0000000000000..046048a4eae60 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/duplicate_class_type.phpt @@ -0,0 +1,11 @@ +--TEST-- +Duplicate class type +--FILE-- + +--EXPECTF-- +Fatal error: Type FOO is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/duplicate_type.phpt b/Zend/tests/type_declarations/union_types/duplicate_type.phpt new file mode 100644 index 0000000000000..1e7c74c134269 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/duplicate_type.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using a type twice in a union +--FILE-- + +--EXPECTF-- +Fatal error: Type int is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/iterable_and_array.phpt b/Zend/tests/type_declarations/union_types/iterable_and_array.phpt new file mode 100644 index 0000000000000..b32c0fd06ab3d --- /dev/null +++ b/Zend/tests/type_declarations/union_types/iterable_and_array.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both iterable and array +--FILE-- + +--EXPECT-- +TODO diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b3381fe1731ac..d4b11d3a48a73 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5500,7 +5500,8 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null for (uint32_t i = 0; i < list->children; i++) { zend_ast *type_ast = list->child[i]; zend_type single_type = zend_compile_single_typename(type_ast); - uint32_t type_mask_overlap = ZEND_TYPE_PURE_MASK(type) & ZEND_TYPE_PURE_MASK(type); + uint32_t type_mask_overlap = + ZEND_TYPE_PURE_MASK(type) & ZEND_TYPE_PURE_MASK(single_type); if (type_mask_overlap) { // TODO: Iterable requires special handling zend_type overlap_type = ZEND_TYPE_INIT_MASK(type_mask_overlap); From af73a41954b3a53c79ca64388f10660336291135 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 12:57:50 +0200 Subject: [PATCH 10/48] Tweak error messages --- .../union_types/bool_and_false.phpt | 2 +- .../union_types/duplicate_class_type.phpt | 2 +- .../union_types/duplicate_type.phpt | 2 +- .../union_types/iterable_and_array.phpt | 4 ++-- Zend/zend_compile.c | 14 ++++++++++---- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Zend/tests/type_declarations/union_types/bool_and_false.phpt b/Zend/tests/type_declarations/union_types/bool_and_false.phpt index 7397bf169fa32..91d2d940f63df 100644 --- a/Zend/tests/type_declarations/union_types/bool_and_false.phpt +++ b/Zend/tests/type_declarations/union_types/bool_and_false.phpt @@ -8,4 +8,4 @@ function test(): bool|false { ?> --EXPECTF-- -Fatal error: Type bool is redundant in %s on line %d +Fatal error: Duplicate type false is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/duplicate_class_type.phpt b/Zend/tests/type_declarations/union_types/duplicate_class_type.phpt index 046048a4eae60..5739f699e72d9 100644 --- a/Zend/tests/type_declarations/union_types/duplicate_class_type.phpt +++ b/Zend/tests/type_declarations/union_types/duplicate_class_type.phpt @@ -8,4 +8,4 @@ function test(): Foo|int|FOO { ?> --EXPECTF-- -Fatal error: Type FOO is redundant in %s on line %d +Fatal error: Duplicate type FOO is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/duplicate_type.phpt b/Zend/tests/type_declarations/union_types/duplicate_type.phpt index 1e7c74c134269..f9cd3e01fa13d 100644 --- a/Zend/tests/type_declarations/union_types/duplicate_type.phpt +++ b/Zend/tests/type_declarations/union_types/duplicate_type.phpt @@ -8,4 +8,4 @@ function test(): int|INT { ?> --EXPECTF-- -Fatal error: Type int is redundant in %s on line %d +Fatal error: Duplicate type int is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/iterable_and_array.phpt b/Zend/tests/type_declarations/union_types/iterable_and_array.phpt index b32c0fd06ab3d..c6b0949418890 100644 --- a/Zend/tests/type_declarations/union_types/iterable_and_array.phpt +++ b/Zend/tests/type_declarations/union_types/iterable_and_array.phpt @@ -7,5 +7,5 @@ function test(): iterable|array { } ?> ---EXPECT-- -TODO +--EXPECTF-- +Fatal error: Type iterable|array contains both iterable and array, which is redundant in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d4b11d3a48a73..7cc22b0d0a678 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1135,7 +1135,7 @@ static zend_string *add_type_string(zend_string *type, zend_string *new_type) { result = zend_string_alloc(ZSTR_LEN(type) + ZSTR_LEN(new_type) + 1, 0); memcpy(ZSTR_VAL(result), ZSTR_VAL(type), ZSTR_LEN(type)); ZSTR_VAL(result)[ZSTR_LEN(type)] = '|'; - memcpy(ZSTR_VAL(result) + ZSTR_LEN(type), ZSTR_VAL(new_type), ZSTR_LEN(new_type)); + memcpy(ZSTR_VAL(result) + ZSTR_LEN(type) + 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type)); ZSTR_VAL(result)[ZSTR_LEN(type) + ZSTR_LEN(new_type) + 1] = '\0'; zend_string_release(type); return result; @@ -5503,11 +5503,10 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null uint32_t type_mask_overlap = ZEND_TYPE_PURE_MASK(type) & ZEND_TYPE_PURE_MASK(single_type); if (type_mask_overlap) { - // TODO: Iterable requires special handling zend_type overlap_type = ZEND_TYPE_INIT_MASK(type_mask_overlap); zend_string *overlap_type_str = zend_type_to_string(overlap_type); zend_error_noreturn(E_COMPILE_ERROR, - "Type %s is redundant", ZSTR_VAL(overlap_type_str)); + "Duplicate type %s is redundant", ZSTR_VAL(overlap_type_str)); } ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type); @@ -5539,7 +5538,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null ZEND_TYPE_NAME(single_type))) { zend_string *single_type_str = zend_type_to_string(single_type); zend_error_noreturn(E_COMPILE_ERROR, - "Type %s is redundant", ZSTR_VAL(single_type_str)); + "Duplicate type %s is redundant", ZSTR_VAL(single_type_str)); } } } @@ -5553,6 +5552,13 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL; } + if ((ZEND_TYPE_FULL_MASK(type) & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) + == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) { + zend_string *type_str = zend_type_to_string(type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s contains both iterable and array, which is redundant", ZSTR_VAL(type_str)); + } + if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) { zend_string *type_str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, From 4a8d0afb169d97fe42a492f8adf93052e50c034d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 13:05:09 +0200 Subject: [PATCH 11/48] Handle iterable + Traversable Not sure if all of this is really worthwhile, but we can still drop it... --- .../union_types/iterable_and_Traversable.phpt | 11 +++++++++ .../union_types/object_and_class_type.phpt | 11 +++++++++ Zend/zend_compile.c | 23 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 Zend/tests/type_declarations/union_types/iterable_and_Traversable.phpt create mode 100644 Zend/tests/type_declarations/union_types/object_and_class_type.phpt diff --git a/Zend/tests/type_declarations/union_types/iterable_and_Traversable.phpt b/Zend/tests/type_declarations/union_types/iterable_and_Traversable.phpt new file mode 100644 index 0000000000000..5b65a33de1a59 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/iterable_and_Traversable.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both iterable and Traversable +--FILE-- + +--EXPECTF-- +Fatal error: Type Traversable|iterable contains both iterable and Traversable, which is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/object_and_class_type.phpt b/Zend/tests/type_declarations/union_types/object_and_class_type.phpt new file mode 100644 index 0000000000000..e9f785ed17173 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/object_and_class_type.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both object and a class type +--FILE-- + +--EXPECTF-- +Fatal error: Type Test|object contains both object and a class type, which is redundant in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 7cc22b0d0a678..ef501b06f76c6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5484,6 +5484,21 @@ static zend_type zend_compile_single_typename(zend_ast *ast) } } +static zend_bool zend_type_contains_traversable(zend_type type) { + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + if (zend_string_equals_literal_ci(ZEND_TYPE_LIST_GET_NAME(entry), "Traversable")) { + return 1; + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(type)) { + return zend_string_equals_literal_ci(ZEND_TYPE_NAME(type), "Traversable"); + } + return 0; +} + // TODO: Ideally we'd canonicalize "iterable" into "array|Traversable" and essentially // treat it as a built-in type alias. static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null) /* {{{ */ @@ -5559,6 +5574,14 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null "Type %s contains both iterable and array, which is redundant", ZSTR_VAL(type_str)); } + if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_ITERABLE) + && zend_type_contains_traversable(type)) { + zend_string *type_str = zend_type_to_string(type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s contains both iterable and Traversable, which is redundant", + ZSTR_VAL(type_str)); + } + if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) { zend_string *type_str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, From 471c27fe3cc029c7a0c520566f79a81ee089c78c Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 13:07:03 +0200 Subject: [PATCH 12/48] Move test files --- .../union_types/{ => redundant_types}/bool_and_false.phpt | 0 .../union_types/{ => redundant_types}/duplicate_class_type.phpt | 0 .../union_types/{ => redundant_types}/duplicate_type.phpt | 0 .../{ => redundant_types}/iterable_and_Traversable.phpt | 0 .../union_types/{ => redundant_types}/iterable_and_array.phpt | 0 .../union_types/{ => redundant_types}/object_and_class_type.phpt | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename Zend/tests/type_declarations/union_types/{ => redundant_types}/bool_and_false.phpt (100%) rename Zend/tests/type_declarations/union_types/{ => redundant_types}/duplicate_class_type.phpt (100%) rename Zend/tests/type_declarations/union_types/{ => redundant_types}/duplicate_type.phpt (100%) rename Zend/tests/type_declarations/union_types/{ => redundant_types}/iterable_and_Traversable.phpt (100%) rename Zend/tests/type_declarations/union_types/{ => redundant_types}/iterable_and_array.phpt (100%) rename Zend/tests/type_declarations/union_types/{ => redundant_types}/object_and_class_type.phpt (100%) diff --git a/Zend/tests/type_declarations/union_types/bool_and_false.phpt b/Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt similarity index 100% rename from Zend/tests/type_declarations/union_types/bool_and_false.phpt rename to Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt diff --git a/Zend/tests/type_declarations/union_types/duplicate_class_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt similarity index 100% rename from Zend/tests/type_declarations/union_types/duplicate_class_type.phpt rename to Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt diff --git a/Zend/tests/type_declarations/union_types/duplicate_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt similarity index 100% rename from Zend/tests/type_declarations/union_types/duplicate_type.phpt rename to Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt diff --git a/Zend/tests/type_declarations/union_types/iterable_and_Traversable.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt similarity index 100% rename from Zend/tests/type_declarations/union_types/iterable_and_Traversable.phpt rename to Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt diff --git a/Zend/tests/type_declarations/union_types/iterable_and_array.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt similarity index 100% rename from Zend/tests/type_declarations/union_types/iterable_and_array.phpt rename to Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt diff --git a/Zend/tests/type_declarations/union_types/object_and_class_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt similarity index 100% rename from Zend/tests/type_declarations/union_types/object_and_class_type.phpt rename to Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt From f681fb605b008f3b84b173db14ddb434e8daa202 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 13:16:13 +0200 Subject: [PATCH 13/48] More tests --- ...nerator_return_containing_extra_types.phpt | 21 ++++++++++++++ .../union_types/illegal_default_value.phpt | 12 ++++++++ .../union_types/legal_default_values.phpt | 29 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt create mode 100644 Zend/tests/type_declarations/union_types/illegal_default_value.phpt create mode 100644 Zend/tests/type_declarations/union_types/legal_default_values.phpt diff --git a/Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt b/Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt new file mode 100644 index 0000000000000..7c369090a546c --- /dev/null +++ b/Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt @@ -0,0 +1,21 @@ +--TEST-- +Generator return value has to have Traversable-ish, but may also have extra types +--FILE-- +test()); + +?> +--EXPECT-- +object(Generator)#2 (0) { +} diff --git a/Zend/tests/type_declarations/union_types/illegal_default_value.phpt b/Zend/tests/type_declarations/union_types/illegal_default_value.phpt new file mode 100644 index 0000000000000..f5941751dc22e --- /dev/null +++ b/Zend/tests/type_declarations/union_types/illegal_default_value.phpt @@ -0,0 +1,12 @@ +--TEST-- +Default value not legal for any type in the union +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use string as default value for property Test::$prop of type int|float in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/legal_default_values.phpt b/Zend/tests/type_declarations/union_types/legal_default_values.phpt new file mode 100644 index 0000000000000..52927223aa3e8 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/legal_default_values.phpt @@ -0,0 +1,29 @@ +--TEST-- +The default value must be legal for one of the types in the union +--FILE-- + +--EXPECT-- +object(Test)#1 (5) { + ["a"]=> + int(1) + ["b"]=> + float(2) + ["c"]=> + float(3) + ["d"]=> + float(4) + ["e"]=> + string(1) "5" +} From 20495e191dd439a3243364e232378d63be33bce2 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 14:03:06 +0200 Subject: [PATCH 14/48] Check for standalone null/false --- .../union_types/illegal_default_value.phpt | 2 +- .../illegal_default_value_argument.phpt | 11 ++++++++++ .../illegal_default_value_property.phpt | 12 +++++++++++ .../union_types/legal_default_values.phpt | 16 ++++++++++++++ .../redundant_types/nullable_null.phpt | 11 ++++++++++ .../union_types/standalone_false.phpt | 10 +++++++++ .../union_types/standalone_null.phpt | 10 +++++++++ .../standalone_nullable_false.phpt | 10 +++++++++ Zend/zend_compile.c | 21 ++++++++++++------- 9 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/type_declarations/union_types/illegal_default_value_argument.phpt create mode 100644 Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt create mode 100644 Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt create mode 100644 Zend/tests/type_declarations/union_types/standalone_false.phpt create mode 100644 Zend/tests/type_declarations/union_types/standalone_null.phpt create mode 100644 Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt diff --git a/Zend/tests/type_declarations/union_types/illegal_default_value.phpt b/Zend/tests/type_declarations/union_types/illegal_default_value.phpt index f5941751dc22e..3bf305aa94d9b 100644 --- a/Zend/tests/type_declarations/union_types/illegal_default_value.phpt +++ b/Zend/tests/type_declarations/union_types/illegal_default_value.phpt @@ -1,5 +1,5 @@ --TEST-- -Default value not legal for any type in the union +Property default value not legal for any type in the union --FILE-- +--EXPECTF-- +Fatal error: Cannot use string as default value for parameter $arg of type int|float in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt b/Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt new file mode 100644 index 0000000000000..f5941751dc22e --- /dev/null +++ b/Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt @@ -0,0 +1,12 @@ +--TEST-- +Default value not legal for any type in the union +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use string as default value for property Test::$prop of type int|float in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/legal_default_values.phpt b/Zend/tests/type_declarations/union_types/legal_default_values.phpt index 52927223aa3e8..2807397734625 100644 --- a/Zend/tests/type_declarations/union_types/legal_default_values.phpt +++ b/Zend/tests/type_declarations/union_types/legal_default_values.phpt @@ -11,7 +11,18 @@ class Test { public float|string $e = "5"; } +function test( + int|float $a = 1, + int|float $b = 2.0, + float|string $c = 3, // Strict typing exception + float|string $d = 4.0, + float|string $e = "5" +) { + var_dump($a, $b, $c, $d, $e); +} + var_dump(new Test); +test(); ?> --EXPECT-- @@ -27,3 +38,8 @@ object(Test)#1 (5) { ["e"]=> string(1) "5" } +int(1) +float(2) +float(3) +float(4) +string(1) "5" diff --git a/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt b/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt new file mode 100644 index 0000000000000..5597794c86719 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt @@ -0,0 +1,11 @@ +--TEST-- +Combining nullability with null +--FILE-- + +--EXPECTF-- +Fatal error: Null can not be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/standalone_false.phpt b/Zend/tests/type_declarations/union_types/standalone_false.phpt new file mode 100644 index 0000000000000..3932f929e511c --- /dev/null +++ b/Zend/tests/type_declarations/union_types/standalone_false.phpt @@ -0,0 +1,10 @@ +--TEST-- +False cannot be used as a standalone type +--FILE-- + +--EXPECTF-- +Fatal error: False can not be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/standalone_null.phpt b/Zend/tests/type_declarations/union_types/standalone_null.phpt new file mode 100644 index 0000000000000..7a25f9cd05f58 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/standalone_null.phpt @@ -0,0 +1,10 @@ +--TEST-- +Null cannot be used as a standalone type +--FILE-- + +--EXPECTF-- +Fatal error: Null can not be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt new file mode 100644 index 0000000000000..1e680249b0cd6 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt @@ -0,0 +1,10 @@ +--TEST-- +Nullable false cannot be used as a standalone type +--FILE-- + +--EXPECTF-- +Fatal error: False can not be used as a standalone type in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ef501b06f76c6..0eebce3d26d33 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5567,33 +5567,40 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL; } - if ((ZEND_TYPE_FULL_MASK(type) & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) - == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) { + uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); + if ((type_mask & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) { zend_string *type_str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Type %s contains both iterable and array, which is redundant", ZSTR_VAL(type_str)); } - if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_ITERABLE) - && zend_type_contains_traversable(type)) { + if ((type_mask & MAY_BE_ITERABLE) && zend_type_contains_traversable(type)) { zend_string *type_str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Type %s contains both iterable and Traversable, which is redundant", ZSTR_VAL(type_str)); } - if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) { + if ((type_mask & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) { zend_string *type_str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Type %s contains both object and a class type, which is redundant", ZSTR_VAL(type_str)); } - if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_VOID) && - (ZEND_TYPE_HAS_CLASS(type) || ZEND_TYPE_PURE_MASK(type) != MAY_BE_VOID)) { + if ((type_mask & MAY_BE_VOID) && (ZEND_TYPE_HAS_CLASS(type) || type_mask != MAY_BE_VOID)) { zend_error_noreturn(E_COMPILE_ERROR, "Void can only be used as a standalone type"); } + if ((type_mask & (MAY_BE_NULL|MAY_BE_FALSE)) + && !ZEND_TYPE_HAS_CLASS(type) && !(type_mask & ~(MAY_BE_NULL|MAY_BE_FALSE))) { + if (type_mask == MAY_BE_NULL) { + zend_error_noreturn(E_COMPILE_ERROR, "Null can not be used as a standalone type"); + } else { + zend_error_noreturn(E_COMPILE_ERROR, "False can not be used as a standalone type"); + } + } + return type; } /* }}} */ From 291f52c9df4b2ca8a7ef44cc99c2c4e7db551a78 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 14:46:16 +0200 Subject: [PATCH 15/48] Use smart_str for type error --- Zend/zend_execute.c | 74 ++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 8dce42512a6ad..e9833a292735d 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" @@ -646,9 +647,9 @@ 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) { + smart_str str = {0}; zend_bool is_interface = 0; *fname = ZSTR_VAL(zf->common.function_name); if (zf->common.scope) { @@ -668,52 +669,48 @@ static ZEND_COLD void zend_verify_type_error_common( } 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 */ - - *need_msg = "be an instance of "; - *need_kind = ZSTR_VAL(ZEND_TYPE_NAME(arg_info->type)); + smart_str_appends(&str, "be an instance of "); + smart_str_append(&str, ZEND_TYPE_NAME(arg_info->type)); } } 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. */ 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 = ""; + smart_str_appends(&str, is_interface ? " or be null" : " or null"); } + *need_msg = smart_str_extract(&str); + if (value) { if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) { *given_msg = "instance of "; @@ -734,7 +731,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 @@ -745,19 +743,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); } @@ -1205,14 +1205,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 @@ -1221,14 +1224,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) From b49438bd2bf6df953f21eddeca2a064e94e5582f Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 15:05:36 +0200 Subject: [PATCH 16/48] Proper type error for union types I'm going with a canonical type format here -- and I think we should move the rest towards this as well. --- Zend/zend_execute.c | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index e9833a292735d..49ea2cd0648ae 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -643,6 +643,23 @@ 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_TRUE|MAY_BE_FALSE)) { + 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, @@ -650,7 +667,6 @@ static ZEND_COLD void zend_verify_type_error_common( zend_string **need_msg, const char **given_msg, const char **given_kind) { smart_str str = {0}; - zend_bool is_interface = 0; *fname = ZSTR_VAL(zf->common.function_name); if (zf->common.scope) { *fsep = "::"; @@ -660,8 +676,13 @@ static ZEND_COLD void zend_verify_type_error_common( *fclass = ""; } - // TODO: This code is broken for now. - if (ZEND_TYPE_HAS_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), @@ -680,6 +701,10 @@ static ZEND_COLD void zend_verify_type_error_common( smart_str_appends(&str, "be an instance of "); smart_str_append(&str, 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) { @@ -694,6 +719,7 @@ static ZEND_COLD void zend_verify_type_error_common( break; default: { + /* Hack to print the type without null */ zend_type type = arg_info->type; ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL; zend_string *type_str = zend_type_to_string(type); @@ -703,10 +729,10 @@ static ZEND_COLD void zend_verify_type_error_common( break; } } - } - if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { - smart_str_appends(&str, is_interface ? " or be null" : " or null"); + if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { + smart_str_appends(&str, " or null"); + } } *need_msg = smart_str_extract(&str); From 887656dadbe7d97fd9f4453b74271afc73138ae1 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 16:03:58 +0200 Subject: [PATCH 17/48] Fix weak typing logic --- Zend/zend_execute.c | 90 ++++++++++++++++++++----------------------- Zend/zend_type_info.h | 1 + 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 49ea2cd0648ae..a5b2b7948a234 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -791,58 +791,45 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( static zend_bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg) { + zend_long lval; + double dval; + zend_string *str; + zend_bool bval; + /* Type preference order: int -> float -> string -> bool */ if (type_mask & MAY_BE_LONG) { - zend_long dest; - /* For a int|float union type and string value, - * determine type to choose by is_numeric_string() semantics. */ + * determine chosen type by is_numeric_string() semantics. */ if ((type_mask & MAY_BE_DOUBLE) && Z_TYPE_P(arg) == IS_STRING) { - zend_long lval; - double dval; 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; } - return 0; - } - - if (!zend_parse_arg_long_weak(arg, &dest)) { - return 0; + } else if (zend_parse_arg_long_weak(arg, &lval)) { + zval_ptr_dtor(arg); + ZVAL_LONG(arg, lval); + return 1; } - zval_ptr_dtor(arg); - ZVAL_LONG(arg, dest); - 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_DOUBLE) && zend_parse_arg_double_weak(arg, &dval)) { zval_ptr_dtor(arg); - ZVAL_DOUBLE(arg, dest); + ZVAL_DOUBLE(arg, dval); return 1; } - if (type_mask & MAY_BE_STRING) { - zend_string *dest; - + if ((type_mask & MAY_BE_STRING) && zend_parse_arg_str_weak(arg, &str)) { /* on success "arg" is converted to IS_STRING */ - return zend_parse_arg_str_weak(arg, &dest); + return 1; } - if ((type_mask & (MAY_BE_TRUE|MAY_BE_FALSE)) == (MAY_BE_TRUE|MAY_BE_FALSE)) { - zend_bool dest; - - if (!zend_parse_arg_bool_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_BOOL(arg, dest); + ZVAL_BOOL(arg, bval); return 1; } return 0; @@ -852,41 +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) { + 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 (type_mask & MAY_BE_DOUBLE) - || (!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_TRUE|MAY_BE_FALSE)) == (MAY_BE_TRUE|MAY_BE_FALSE)) { - zend_bool dest; - return zend_parse_arg_bool_weak(arg, &dest); + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval)) { + return 1; } return 0; } diff --git a/Zend/zend_type_info.h b/Zend/zend_type_info.h index 9479d5aad2c03..ef2c6a19bc8e5 100644 --- a/Zend/zend_type_info.h +++ b/Zend/zend_type_info.h @@ -25,6 +25,7 @@ #define MAY_BE_NULL (1 << IS_NULL) #define MAY_BE_FALSE (1 << IS_FALSE) #define MAY_BE_TRUE (1 << IS_TRUE) +#define MAY_BE_BOOL (MAY_BE_FALSE|MAY_BE_TRUE) #define MAY_BE_LONG (1 << IS_LONG) #define MAY_BE_DOUBLE (1 << IS_DOUBLE) #define MAY_BE_STRING (1 << IS_STRING) From 24f863a817243b7f71549398f2e89bea61b948a8 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 16:05:41 +0200 Subject: [PATCH 18/48] Cleanup MAY_BE_BOOL --- Zend/zend_compile.c | 2 +- Zend/zend_execute.c | 4 ++-- Zend/zend_types.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0eebce3d26d33..2468655ee891c 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1192,7 +1192,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop if (type_mask & MAY_BE_DOUBLE) { str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT)); } - if ((type_mask & (MAY_BE_FALSE|MAY_BE_TRUE)) == (MAY_BE_FALSE|MAY_BE_TRUE)) { + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL)); } else if (type_mask & MAY_BE_FALSE) { str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE)); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index a5b2b7948a234..823e78c22800e 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -653,7 +653,7 @@ static zend_bool is_union_type(zend_type type) { if (ZEND_TYPE_HAS_CLASS(type)) { return type_mask_without_null != 0; } - if (type_mask_without_null == (MAY_BE_TRUE|MAY_BE_FALSE)) { + if (type_mask_without_null == MAY_BE_BOOL) { return 0; } /* Check that only one bit is set. */ @@ -3059,7 +3059,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( /* 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_TRUE|MAY_BE_FALSE)) != (MAY_BE_TRUE|MAY_BE_FALSE)) { + && (type_mask & MAY_BE_BOOL) != MAY_BE_BOOL) { return 0; } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index eae1238137038..fdb1cd9ae426a 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -257,7 +257,7 @@ typedef struct { { NULL, (_type_mask) } #define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ - ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? (MAY_BE_FALSE|MAY_BE_TRUE) : (1 << (code))) \ + ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : (1 << (code))) \ | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags)) #define ZEND_TYPE_INIT_CE(_ce, allow_null, extra_flags) \ From 37b399723538c51080b3a419ab9d9118aa36c73d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 16:32:59 +0200 Subject: [PATCH 19/48] Add weak typing tests --- .../type_declarations/union_types/basic.phpt | 205 ++++++++++++++++++ Zend/zend_compile.c | 2 +- Zend/zend_string.h | 1 + 3 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_declarations/union_types/basic.phpt diff --git a/Zend/tests/type_declarations/union_types/basic.phpt b/Zend/tests/type_declarations/union_types/basic.phpt new file mode 100644 index 0000000000000..cdacc1e671746 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/basic.phpt @@ -0,0 +1,205 @@ +--TEST-- +Basic checks for union type behavior +--FILE-- + '; + + try { + error_clear_last(); + $value = @$fn($value); + echo dump($value); + if ($e = error_get_last()) { + echo ' (', $e['message'], ')'; + } + } catch (TypeError $e) { + $msg = $e->getMessage(); + $msg = strstr($msg, ', called in', true); + $msg = str_replace('1 passed to {closure}()', '...', $msg); + echo $msg; + } + echo "\n"; + } +} + +class WithToString { + public function __toString() { + return "__toString()"; + } +} + +$values = [ + 42, 42.0, INF, "42", "42.0", "42x", "x", "", + true, false, null, [], new stdClass, new WithToString, +]; +test('int|float', $values); +test('int|float|false', $values); +test('int|float|bool', $values); +test('int|bool', $values); +test('int|string|null', $values); +test('string|bool', $values); +test('float|array', $values); +test('string|array', $values); +test('bool|array', $values); + +?> +--EXPECT-- +Type int|float: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => 42 +"42.0" => 42.0 +"42x" => 42 (A non well formed numeric value encountered) +"x" => Argument ... must be of type int|float, string given +"" => Argument ... must be of type int|float, string given +true => 1 +false => 0 +null => Argument ... must be of type int|float, null given +[] => Argument ... must be of type int|float, array given +new stdClass => Argument ... must be of type int|float, object given +new WithToString => Argument ... must be of type int|float, object given + +Type int|float|false: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => 42 +"42.0" => 42.0 +"42x" => 42 (A non well formed numeric value encountered) +"x" => Argument ... must be of type int|float|false, string given +"" => Argument ... must be of type int|float|false, string given +true => 1 +false => false +null => Argument ... must be of type int|float|false, null given +[] => Argument ... must be of type int|float|false, array given +new stdClass => Argument ... must be of type int|float|false, object given +new WithToString => Argument ... must be of type int|float|false, object given + +Type int|float|bool: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => 42 +"42.0" => 42.0 +"42x" => 42 (A non well formed numeric value encountered) +"x" => true +"" => false +true => true +false => false +null => Argument ... must be of type int|float|bool, null given +[] => Argument ... must be of type int|float|bool, array given +new stdClass => Argument ... must be of type int|float|bool, object given +new WithToString => Argument ... must be of type int|float|bool, object given + +Type int|bool: +42 => 42 +42.0 => 42 +INF => true +"42" => 42 +"42.0" => 42 +"42x" => 42 (A non well formed numeric value encountered) +"x" => true +"" => false +true => true +false => false +null => Argument ... must be of type int|bool, null given +[] => Argument ... must be of type int|bool, array given +new stdClass => Argument ... must be of type int|bool, object given +new WithToString => Argument ... must be of type int|bool, object given + +Type int|string|null: +42 => 42 +42.0 => 42 +INF => "INF" +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => 1 +false => 0 +null => null +[] => Argument ... must be of type string|int|null, array given +new stdClass => Argument ... must be of type string|int|null, object given +new WithToString => "__toString()" + +Type string|bool: +42 => "42" +42.0 => "42" +INF => "INF" +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => true +false => false +null => Argument ... must be of type string|bool, null given +[] => Argument ... must be of type string|bool, array given +new stdClass => Argument ... must be of type string|bool, object given +new WithToString => "__toString()" + +Type float|array: +42 => 42.0 +42.0 => 42.0 +INF => INF +"42" => 42.0 +"42.0" => 42.0 +"42x" => 42.0 (A non well formed numeric value encountered) +"x" => Argument ... must be of type array|float, string given +"" => Argument ... must be of type array|float, string given +true => 1.0 +false => 0.0 +null => Argument ... must be of type array|float, null given +[] => [] +new stdClass => Argument ... must be of type array|float, object given +new WithToString => Argument ... must be of type array|float, object given + +Type string|array: +42 => "42" +42.0 => "42" +INF => "INF" +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => "1" +false => "" +null => Argument ... must be of type array|string, null given +[] => [] +new stdClass => Argument ... must be of type array|string, object given +new WithToString => "__toString()" + +Type bool|array: +42 => true +42.0 => true +INF => true +"42" => true +"42.0" => true +"42x" => true +"x" => true +"" => false +true => true +false => false +null => Argument ... must be of type array|bool, null given +[] => [] +new stdClass => Argument ... must be of type array|bool, object given +new WithToString => Argument ... must be of type array|bool, object given diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2468655ee891c..70a1cf5d79280 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1213,7 +1213,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop return nullable_str; } - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE)); } return str; } diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 9dac54765286b..f2076beee10c8 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -513,6 +513,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_ITERABLE, "iterable") \ _(ZEND_STR_VOID, "void") \ _(ZEND_STR_FALSE, "false") \ + _(ZEND_STR_NULL_LOWERCASE, "null") \ typedef enum _zend_known_string_id { From ccf57e919340546221242b7af860c1155f4ee8a9 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 18 Oct 2019 16:48:04 +0200 Subject: [PATCH 20/48] Support union types in AST printer --- Zend/tests/assert/expect_015.phpt | 4 ++-- Zend/zend_ast.c | 33 +++++++++++++++++++------------ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Zend/tests/assert/expect_015.phpt b/Zend/tests/assert/expect_015.phpt index 72f13ff83f15f..769eed8270aa3 100644 --- a/Zend/tests/assert/expect_015.phpt +++ b/Zend/tests/assert/expect_015.phpt @@ -62,7 +62,7 @@ assert(0 && ($a = function &(array &$a, ?X $b = null) use ($c,&$d) : ?X { } })); -assert(0 && ($a = function &(array &$a, X $b = null) use ($c,&$d) : X { +assert(0 && ($a = function &(array &$a, X $b = null, int|float $c) use ($c,&$d) : X { final class A { final protected function f2() { if (!$x) { @@ -204,7 +204,7 @@ Warning: assert(): assert(0 && ($a = function &(array &$a, ?X $b = null) use($c, })) failed in %sexpect_015.php on line %d -Warning: assert(): assert(0 && ($a = function &(array &$a, X $b = null) use($c, &$d): X { +Warning: assert(): assert(0 && ($a = function &(array &$a, X $b = null, int|float $c) use($c, &$d): X { final class A { protected final function f2() { if (!$x) { diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index f0b524b30e829..980e23d055537 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1314,6 +1314,23 @@ static ZEND_COLD void zend_ast_export_class_no_header(smart_str *str, zend_ast_d smart_str_appends(str, "}"); } +static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int indent) { + if (ast->kind == ZEND_AST_TYPE_UNION) { + zend_ast_list *list = zend_ast_get_list(ast); + for (uint32_t i = 0; i < list->children; i++) { + if (i != 0) { + smart_str_appendc(str, '|'); + } + zend_ast_export_type(str, list->child[i], indent); + } + return; + } + if (ast->attr & ZEND_TYPE_NULLABLE) { + smart_str_appendc(str, '?'); + } + zend_ast_export_ns_name(str, ast, 0, indent); +} + #define BINARY_OP(_op, _p, _pl, _pr) do { \ op = _op; \ p = _p; \ @@ -1423,10 +1440,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio zend_ast_export_ex(str, decl->child[1], 0, indent); if (decl->child[3]) { smart_str_appends(str, ": "); - if (decl->child[3]->attr & ZEND_TYPE_NULLABLE) { - smart_str_appendc(str, '?'); - } - zend_ast_export_ns_name(str, decl->child[3], 0, indent); + zend_ast_export_type(str, decl->child[3], indent); } if (decl->child[2]) { if (decl->kind == ZEND_AST_ARROW_FUNC) { @@ -1516,11 +1530,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio } if (type_ast) { - if (type_ast->attr & ZEND_TYPE_NULLABLE) { - smart_str_appendc(str, '?'); - } - zend_ast_export_ns_name( - str, type_ast, 0, indent); + zend_ast_export_type(str, type_ast, indent); smart_str_appendc(str, ' '); } @@ -1990,10 +2000,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio break; case ZEND_AST_PARAM: if (ast->child[0]) { - if (ast->child[0]->attr & ZEND_TYPE_NULLABLE) { - smart_str_appendc(str, '?'); - } - zend_ast_export_ns_name(str, ast->child[0], 0, indent); + zend_ast_export_type(str, ast->child[0], indent); smart_str_appendc(str, ' '); } if (ast->attr & ZEND_PARAM_REF) { From c1152c9b68533a98f1518639e14e17d4989aa828 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 21 Oct 2019 10:39:18 +0200 Subject: [PATCH 21/48] Add strict type variant of test --- .../union_types/type_checking_strict.phpt | 207 ++++++++++++++++++ .../{basic.phpt => type_checking_weak.phpt} | 2 +- 2 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_declarations/union_types/type_checking_strict.phpt rename Zend/tests/type_declarations/union_types/{basic.phpt => type_checking_weak.phpt} (99%) diff --git a/Zend/tests/type_declarations/union_types/type_checking_strict.phpt b/Zend/tests/type_declarations/union_types/type_checking_strict.phpt new file mode 100644 index 0000000000000..81d164819d96f --- /dev/null +++ b/Zend/tests/type_declarations/union_types/type_checking_strict.phpt @@ -0,0 +1,207 @@ +--TEST-- +Behavior of union type checks (strict) +--FILE-- + '; + + try { + error_clear_last(); + $value = @$fn($value); + echo dump($value); + if ($e = error_get_last()) { + echo ' (', $e['message'], ')'; + } + } catch (TypeError $e) { + $msg = $e->getMessage(); + $msg = strstr($msg, ', called in', true); + $msg = str_replace('1 passed to {closure}()', '...', $msg); + echo $msg; + } + echo "\n"; + } +} + +class WithToString { + public function __toString() { + return "__toString()"; + } +} + +$values = [ + 42, 42.0, INF, "42", "42.0", "42x", "x", "", + true, false, null, [], new stdClass, new WithToString, +]; +test('int|float', $values); +test('int|float|false', $values); +test('int|float|bool', $values); +test('int|bool', $values); +test('int|string|null', $values); +test('string|bool', $values); +test('float|array', $values); +test('string|array', $values); +test('bool|array', $values); + +?> +--EXPECT-- +Type int|float: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => Argument ... must be of type int|float, string given +"42.0" => Argument ... must be of type int|float, string given +"42x" => Argument ... must be of type int|float, string given +"x" => Argument ... must be of type int|float, string given +"" => Argument ... must be of type int|float, string given +true => Argument ... must be of type int|float, bool given +false => Argument ... must be of type int|float, bool given +null => Argument ... must be of type int|float, null given +[] => Argument ... must be of type int|float, array given +new stdClass => Argument ... must be of type int|float, object given +new WithToString => Argument ... must be of type int|float, object given + +Type int|float|false: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => Argument ... must be of type int|float|false, string given +"42.0" => Argument ... must be of type int|float|false, string given +"42x" => Argument ... must be of type int|float|false, string given +"x" => Argument ... must be of type int|float|false, string given +"" => Argument ... must be of type int|float|false, string given +true => Argument ... must be of type int|float|false, bool given +false => false +null => Argument ... must be of type int|float|false, null given +[] => Argument ... must be of type int|float|false, array given +new stdClass => Argument ... must be of type int|float|false, object given +new WithToString => Argument ... must be of type int|float|false, object given + +Type int|float|bool: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => Argument ... must be of type int|float|bool, string given +"42.0" => Argument ... must be of type int|float|bool, string given +"42x" => Argument ... must be of type int|float|bool, string given +"x" => Argument ... must be of type int|float|bool, string given +"" => Argument ... must be of type int|float|bool, string given +true => true +false => false +null => Argument ... must be of type int|float|bool, null given +[] => Argument ... must be of type int|float|bool, array given +new stdClass => Argument ... must be of type int|float|bool, object given +new WithToString => Argument ... must be of type int|float|bool, object given + +Type int|bool: +42 => 42 +42.0 => Argument ... must be of type int|bool, float given +INF => Argument ... must be of type int|bool, float given +"42" => Argument ... must be of type int|bool, string given +"42.0" => Argument ... must be of type int|bool, string given +"42x" => Argument ... must be of type int|bool, string given +"x" => Argument ... must be of type int|bool, string given +"" => Argument ... must be of type int|bool, string given +true => true +false => false +null => Argument ... must be of type int|bool, null given +[] => Argument ... must be of type int|bool, array given +new stdClass => Argument ... must be of type int|bool, object given +new WithToString => Argument ... must be of type int|bool, object given + +Type int|string|null: +42 => 42 +42.0 => Argument ... must be of type string|int|null, float given +INF => Argument ... must be of type string|int|null, float given +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => Argument ... must be of type string|int|null, bool given +false => Argument ... must be of type string|int|null, bool given +null => null +[] => Argument ... must be of type string|int|null, array given +new stdClass => Argument ... must be of type string|int|null, object given +new WithToString => Argument ... must be of type string|int|null, object given + +Type string|bool: +42 => Argument ... must be of type string|bool, int given +42.0 => Argument ... must be of type string|bool, float given +INF => Argument ... must be of type string|bool, float given +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => true +false => false +null => Argument ... must be of type string|bool, null given +[] => Argument ... must be of type string|bool, array given +new stdClass => Argument ... must be of type string|bool, object given +new WithToString => Argument ... must be of type string|bool, object given + +Type float|array: +42 => 42.0 +42.0 => 42.0 +INF => INF +"42" => Argument ... must be of type array|float, string given +"42.0" => Argument ... must be of type array|float, string given +"42x" => Argument ... must be of type array|float, string given +"x" => Argument ... must be of type array|float, string given +"" => Argument ... must be of type array|float, string given +true => Argument ... must be of type array|float, bool given +false => Argument ... must be of type array|float, bool given +null => Argument ... must be of type array|float, null given +[] => [] +new stdClass => Argument ... must be of type array|float, object given +new WithToString => Argument ... must be of type array|float, object given + +Type string|array: +42 => Argument ... must be of type array|string, int given +42.0 => Argument ... must be of type array|string, float given +INF => Argument ... must be of type array|string, float given +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => Argument ... must be of type array|string, bool given +false => Argument ... must be of type array|string, bool given +null => Argument ... must be of type array|string, null given +[] => [] +new stdClass => Argument ... must be of type array|string, object given +new WithToString => Argument ... must be of type array|string, object given + +Type bool|array: +42 => Argument ... must be of type array|bool, int given +42.0 => Argument ... must be of type array|bool, float given +INF => Argument ... must be of type array|bool, float given +"42" => Argument ... must be of type array|bool, string given +"42.0" => Argument ... must be of type array|bool, string given +"42x" => Argument ... must be of type array|bool, string given +"x" => Argument ... must be of type array|bool, string given +"" => Argument ... must be of type array|bool, string given +true => true +false => false +null => Argument ... must be of type array|bool, null given +[] => [] +new stdClass => Argument ... must be of type array|bool, object given +new WithToString => Argument ... must be of type array|bool, object given diff --git a/Zend/tests/type_declarations/union_types/basic.phpt b/Zend/tests/type_declarations/union_types/type_checking_weak.phpt similarity index 99% rename from Zend/tests/type_declarations/union_types/basic.phpt rename to Zend/tests/type_declarations/union_types/type_checking_weak.phpt index cdacc1e671746..c27c9fe105894 100644 --- a/Zend/tests/type_declarations/union_types/basic.phpt +++ b/Zend/tests/type_declarations/union_types/type_checking_weak.phpt @@ -1,5 +1,5 @@ --TEST-- -Basic checks for union type behavior +Behavior of union type checks (weak) --FILE-- Date: Mon, 21 Oct 2019 10:51:26 +0200 Subject: [PATCH 22/48] Implement correct incdec handling --- .../union_types/incdec_prop.phpt | 132 ++++++++++++++++++ Zend/zend_execute.c | 27 ++-- 2 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 Zend/tests/type_declarations/union_types/incdec_prop.phpt diff --git a/Zend/tests/type_declarations/union_types/incdec_prop.phpt b/Zend/tests/type_declarations/union_types/incdec_prop.phpt new file mode 100644 index 0000000000000..dde6f595264a3 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/incdec_prop.phpt @@ -0,0 +1,132 @@ +--TEST-- +Increment/decrement a typed property with int|float type +--FILE-- +prop = PHP_INT_MAX; +$x = $test->prop++; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MAX; +$x = ++$test->prop; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MIN; +$x = $test->prop--; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MIN; +$x = --$test->prop; +var_dump(is_double($test->prop)); + +$test = new Test; +$test->prop = PHP_INT_MAX; +$r =& $test->prop; +$x = $test->prop++; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MAX; +$x = ++$test->prop; +$r =& $test->prop; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MIN; +$x = $test->prop--; +$r =& $test->prop; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MIN; +$x = --$test->prop; +$r =& $test->prop; +var_dump(is_double($test->prop)); + +/* Incrementing a non-int|float property past int min/max is an error, + * even if the result of the overflow (a float) would technically be allowed + * under a type coercion. */ + +try { + $test->prop2 = PHP_INT_MAX; + $x = $test->prop2++; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MAX; + $x = ++$test->prop2; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MIN; + $x = $test->prop2--; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MIN; + $x = --$test->prop2; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MAX; + $r =& $test->prop2; + $x = $test->prop2++; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MAX; + $r =& $test->prop2; + $x = ++$test->prop2; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MIN; + $r =& $test->prop2; + $x = $test->prop2--; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MIN; + $r =& $test->prop2; + $x = --$test->prop2; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +Cannot increment property Test::$prop2 of type int|bool past its maximal value +Cannot increment property Test::$prop2 of type int|bool past its maximal value +Cannot decrement property Test::$prop2 of type int|bool past its minimal value +Cannot decrement property Test::$prop2 of type int|bool past its minimal value +Cannot increment a reference held by property Test::$prop2 of type int|bool past its maximal value +Cannot increment a reference held by property Test::$prop2 of type int|bool past its maximal value +Cannot decrement a reference held by property Test::$prop2 of type int|bool past its minimal value +Cannot decrement a reference held by property Test::$prop2 of type int|bool past its minimal value diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 823e78c22800e..20e872c87bcac 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1650,14 +1650,10 @@ 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); - ZEND_ASSERT(error_prop); zend_string *type_str = zend_type_to_string(error_prop->type); - // TODO!!! No longer true with union types - /* 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. */ if (ZEND_IS_INCREMENT(opline->opcode)) { zend_type_error( "Cannot increment a reference held by property %s::$%s of type %s past its maximal value", @@ -1714,8 +1710,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); @@ -1742,8 +1741,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); @@ -1761,7 +1762,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); } @@ -1799,7 +1801,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); } From 209f7f1c5e51efc9a64b500671cb6d409615fe05 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 21 Oct 2019 12:53:37 +0200 Subject: [PATCH 23/48] Test property references --- .../union_types/prop_ref_assign.phpt | 59 +++++++++++++++++++ Zend/zend_execute.c | 15 ++--- 2 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/type_declarations/union_types/prop_ref_assign.phpt diff --git a/Zend/tests/type_declarations/union_types/prop_ref_assign.phpt b/Zend/tests/type_declarations/union_types/prop_ref_assign.phpt new file mode 100644 index 0000000000000..a8db8fca9cd51 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/prop_ref_assign.phpt @@ -0,0 +1,59 @@ +--TEST-- +Assignments to references that are held by properties with union types +--FILE-- +x =& $r; +$test->y =& $r; + +$v = 42; +try { + $r = $v; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +var_dump($r, $v); + +$v = 42.0; +try { + $r = $v; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +var_dump($r, $v); + +unset($r, $test->x, $test->y); + +$test->x = 42; +try { + $test->y =& $test->x; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +unset($test->x, $test->y); + +$test->y = 42.0; +try { + $test->x =& $test->y; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot assign int to reference held by property Test::$x of type string|int and property Test::$y of type string|float, as this would result in an inconsistent type conversion +string(6) "foobar" +int(42) +Cannot assign float to reference held by property Test::$x of type string|int and property Test::$y of type string|float, as this would result in an inconsistent type conversion +string(6) "foobar" +float(42) +Reference with value of type int held by property Test::$x of type string|int is not compatible with property Test::$y of type string|float +Reference with value of type float held by property Test::$y of type string|float is not compatible with property Test::$x of type string|int diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 20e872c87bcac..0db9f277ec263 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3192,16 +3192,17 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_propert } 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); From 5213df3c5c50b13d423874236d8c650c01c40070 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 21 Oct 2019 13:23:19 +0200 Subject: [PATCH 24/48] Implement full property invariance check --- .../union_types/variance/invalid_001.phpt | 15 ++++ .../union_types/variance/invalid_002.phpt | 15 ++++ .../union_types/variance/invalid_003.phpt | 18 +++++ .../union_types/variance/valid.phpt | 25 ++++++ Zend/zend_inheritance.c | 80 ++++++++----------- Zend/zend_opcode.c | 1 + 6 files changed, 108 insertions(+), 46 deletions(-) create mode 100644 Zend/tests/type_declarations/union_types/variance/invalid_001.phpt create mode 100644 Zend/tests/type_declarations/union_types/variance/invalid_002.phpt create mode 100644 Zend/tests/type_declarations/union_types/variance/invalid_003.phpt create mode 100644 Zend/tests/type_declarations/union_types/variance/valid.phpt diff --git a/Zend/tests/type_declarations/union_types/variance/invalid_001.phpt b/Zend/tests/type_declarations/union_types/variance/invalid_001.phpt new file mode 100644 index 0000000000000..9e1dcaefa16c4 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/variance/invalid_001.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid union type variance: Adding extra return type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::method(): int|float must be compatible with A::method(): int in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/variance/invalid_002.phpt b/Zend/tests/type_declarations/union_types/variance/invalid_002.phpt new file mode 100644 index 0000000000000..4448114fceb6c --- /dev/null +++ b/Zend/tests/type_declarations/union_types/variance/invalid_002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid union type variance: Removing argument union type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::method(int $a) must be compatible with A::method(int|float $a) in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/variance/invalid_003.phpt b/Zend/tests/type_declarations/union_types/variance/invalid_003.phpt new file mode 100644 index 0000000000000..26d9ae3eb430e --- /dev/null +++ b/Zend/tests/type_declarations/union_types/variance/invalid_003.phpt @@ -0,0 +1,18 @@ +--TEST-- +Invalid union type variance: Using parent of class in return type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::method(): X|string must be compatible with A::method(): Y|string in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/variance/valid.phpt b/Zend/tests/type_declarations/union_types/variance/valid.phpt new file mode 100644 index 0000000000000..4d4780b7cf4c0 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/variance/valid.phpt @@ -0,0 +1,25 @@ +--TEST-- +Valid union type variance +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index d25082bc24b2e..5478ef1ed51d9 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -347,8 +347,8 @@ typedef enum { } inheritance_status; static inheritance_status zend_perform_covariant_class_type_check( - const zend_function *fe, zend_string *fe_class_name, - const zend_function *proto, zend_type proto_type, + zend_class_entry *fe_scope, zend_string *fe_class_name, + zend_class_entry *proto_scope, zend_type proto_type, zend_bool register_unresolved) { zend_bool have_unresolved = 0; zend_class_entry *fe_ce = NULL; @@ -356,7 +356,7 @@ static inheritance_status zend_perform_covariant_class_type_check( /* Currently, any class name would be allowed here. We still perform a class lookup * for forward-compatibility reasons, as we may have named types in the future that * are not classes (such as enums or typedefs). */ - if (!fe_ce) fe_ce = lookup_class(fe->common.scope, fe_class_name, register_unresolved); + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); if (!fe_ce) { have_unresolved = 1; } else { @@ -364,7 +364,7 @@ static inheritance_status zend_perform_covariant_class_type_check( } } if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) { - if (!fe_ce) fe_ce = lookup_class(fe->common.scope, fe_class_name, register_unresolved); + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); if (!fe_ce) { have_unresolved = 1; } else if (unlinked_instanceof(fe_ce, zend_ce_traversable)) { @@ -372,17 +372,16 @@ static inheritance_status zend_perform_covariant_class_type_check( } } if (ZEND_TYPE_HAS_NAME(proto_type)) { - zend_string *proto_class_name = - resolve_class_name(proto->common.scope, ZEND_TYPE_NAME(proto_type)); + zend_string *proto_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(proto_type)); if (zend_string_equals_ci(fe_class_name, proto_class_name)) { return INHERITANCE_SUCCESS; } /* Make sure to always load both classes, to avoid only registering one of them as * a delayed autoload. */ - if (!fe_ce) fe_ce = lookup_class(fe->common.scope, fe_class_name, register_unresolved); + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); zend_class_entry *proto_ce = - lookup_class(proto->common.scope, proto_class_name, register_unresolved); + lookup_class(proto_scope, proto_class_name, register_unresolved); if (!fe_ce || !proto_ce) { have_unresolved = 1; } else if (unlinked_instanceof(fe_ce, proto_ce)) { @@ -394,14 +393,14 @@ static inheritance_status zend_perform_covariant_class_type_check( ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(proto_type), entry) { ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); zend_string *proto_class_name = - resolve_class_name(proto->common.scope, ZEND_TYPE_LIST_GET_NAME(entry)); + resolve_class_name(proto_scope, ZEND_TYPE_LIST_GET_NAME(entry)); if (zend_string_equals_ci(fe_class_name, proto_class_name)) { return INHERITANCE_SUCCESS; } - if (!fe_ce) fe_ce = lookup_class(fe->common.scope, fe_class_name, register_unresolved); + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); zend_class_entry *proto_ce = - lookup_class(proto->common.scope, proto_class_name, register_unresolved); + lookup_class(proto_scope, proto_class_name, register_unresolved); if (!fe_ce || !proto_ce) { have_unresolved = 1; } else if (unlinked_instanceof(fe_ce, proto_ce)) { @@ -413,10 +412,9 @@ static inheritance_status zend_perform_covariant_class_type_check( } static inheritance_status zend_perform_covariant_type_check( - const zend_function *fe, zend_arg_info *fe_arg_info, - const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ + zend_class_entry *fe_scope, zend_type fe_type, + zend_class_entry *proto_scope, zend_type proto_type) /* {{{ */ { - zend_type fe_type = fe_arg_info->type, proto_type = proto_arg_info->type; ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_type) && ZEND_TYPE_IS_SET(proto_type)); /* Builtin types may be removed, but not added */ @@ -439,15 +437,15 @@ static inheritance_status zend_perform_covariant_type_check( } if (ZEND_TYPE_HAS_NAME(fe_type)) { - zend_string *fe_class_name = resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); + zend_string *fe_class_name = resolve_class_name(fe_scope, ZEND_TYPE_NAME(fe_type)); inheritance_status status = zend_perform_covariant_class_type_check( - fe, fe_class_name, proto, proto_type, /* register_unresolved */ 0); + fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 0); if (status != INHERITANCE_UNRESOLVED) { return status; } zend_perform_covariant_class_type_check( - fe, fe_class_name, proto, proto_type, /* register_unresolved */ 1); + fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 1); return INHERITANCE_UNRESOLVED; } @@ -459,9 +457,9 @@ static inheritance_status zend_perform_covariant_type_check( ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(fe_type), entry) { ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); zend_string *fe_class_name = - resolve_class_name(fe->common.scope, ZEND_TYPE_LIST_GET_NAME(entry)); + resolve_class_name(fe_scope, ZEND_TYPE_LIST_GET_NAME(entry)); inheritance_status status = zend_perform_covariant_class_type_check( - fe, fe_class_name, proto, proto_type, /* register_unresolved */ 0); + fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 0); if (status == INHERITANCE_ERROR) { return INHERITANCE_ERROR; } @@ -480,9 +478,9 @@ static inheritance_status zend_perform_covariant_type_check( ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(fe_type), entry) { ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); zend_string *fe_class_name = - resolve_class_name(fe->common.scope, ZEND_TYPE_LIST_GET_NAME(entry)); + resolve_class_name(fe_scope, ZEND_TYPE_LIST_GET_NAME(entry)); zend_perform_covariant_class_type_check( - fe, fe_class_name, proto, proto_type, /* register_unresolved */ 1); + fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 1); } ZEND_TYPE_LIST_FOREACH_END(); return INHERITANCE_UNRESOLVED; } @@ -507,7 +505,8 @@ static inheritance_status zend_do_perform_arg_type_hint_check( /* Contravariant type check is performed as a covariant type check with swapped * argument order. */ - return zend_perform_covariant_type_check(proto, proto_arg_info, fe, fe_arg_info); + return zend_perform_covariant_type_check( + proto->common.scope, proto_arg_info->type, fe->common.scope, fe_arg_info->type); } /* }}} */ @@ -603,7 +602,8 @@ static inheritance_status zend_do_perform_implementation_check( } local_status = zend_perform_covariant_type_check( - fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1); + fe->common.scope, fe->common.arg_info[-1].type, + proto->common.scope, proto->common.arg_info[-1].type); if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) { if (UNEXPECTED(local_status == INHERITANCE_ERROR)) { @@ -944,40 +944,28 @@ static zend_always_inline void do_inherit_method(zend_string *key, zend_function inheritance_status property_types_compatible( const zend_property_info *parent_info, const zend_property_info *child_info) { - zend_string *parent_name, *child_name; - zend_class_entry *parent_type_ce, *child_type_ce; if (ZEND_TYPE_PURE_MASK(parent_info->type) == ZEND_TYPE_PURE_MASK(child_info->type) && ZEND_TYPE_NAME(parent_info->type) == ZEND_TYPE_NAME(child_info->type)) { return INHERITANCE_SUCCESS; } - // TODO: Base all of this on the main covariance check to handle non-trivial cases. - if (!ZEND_TYPE_HAS_CLASS(parent_info->type) || !ZEND_TYPE_HAS_CLASS(child_info->type) || - ZEND_TYPE_PURE_MASK(parent_info->type) != ZEND_TYPE_PURE_MASK(child_info->type)) { + if (ZEND_TYPE_IS_SET(parent_info->type) != ZEND_TYPE_IS_SET(child_info->type)) { return INHERITANCE_ERROR; } - parent_name = ZEND_TYPE_HAS_CE(parent_info->type) - ? ZEND_TYPE_CE(parent_info->type)->name - : resolve_class_name(parent_info->ce, ZEND_TYPE_NAME(parent_info->type)); - child_name = ZEND_TYPE_HAS_CE(child_info->type) - ? ZEND_TYPE_CE(child_info->type)->name - : resolve_class_name(child_info->ce, ZEND_TYPE_NAME(child_info->type)); - if (zend_string_equals_ci(parent_name, child_name)) { + /* Perform a covariant type check in both directions to determined invariance. */ + inheritance_status status1 = zend_perform_covariant_type_check( + child_info->ce, child_info->type, parent_info->ce, parent_info->type); + inheritance_status status2 = zend_perform_covariant_type_check( + parent_info->ce, parent_info->type, child_info->ce, child_info->type); + if (status1 == INHERITANCE_SUCCESS && status2 == INHERITANCE_SUCCESS) { return INHERITANCE_SUCCESS; } - - /* Check for class aliases */ - parent_type_ce = ZEND_TYPE_HAS_CE(parent_info->type) - ? ZEND_TYPE_CE(parent_info->type) - : lookup_class(parent_info->ce, parent_name, /* register_unresolved */ 1); - child_type_ce = ZEND_TYPE_HAS_CE(child_info->type) - ? ZEND_TYPE_CE(child_info->type) - : lookup_class(child_info->ce, child_name, /* register_unresolved */ 1); - if (!parent_type_ce || !child_type_ce) { - return INHERITANCE_UNRESOLVED; + if (status1 == INHERITANCE_ERROR || status2 == INHERITANCE_ERROR) { + return INHERITANCE_ERROR; } - return parent_type_ce == child_type_ce ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + ZEND_ASSERT(status1 == INHERITANCE_UNRESOLVED && status2 == INHERITANCE_UNRESOLVED); + return INHERITANCE_UNRESOLVED; } static void emit_incompatible_property_error( diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index ae20d270d4653..9fdec6f073aa7 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -110,6 +110,7 @@ static void zend_always_inline zend_type_release(zend_type *type) { zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry)); } } ZEND_TYPE_LIST_FOREACH_END(); + efree(ZEND_TYPE_LIST(*type)); } else if (ZEND_TYPE_HAS_NAME(*type)) { zend_string_release(ZEND_TYPE_NAME(*type)); } From 9e50dfe6223370e9df231385f7dce20911609d17 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 21 Oct 2019 15:13:48 +0200 Subject: [PATCH 25/48] Add ReflectionUnionType --- Zend/zend_compile.c | 3 +- ext/reflection/php_reflection.c | 125 +++++++++++++++++- .../ReflectionExtension_getClasses_basic.phpt | 23 ++-- ext/reflection/tests/union_types.phpt | 114 ++++++++++++++++ 4 files changed, 248 insertions(+), 17 deletions(-) create mode 100644 ext/reflection/tests/union_types.phpt diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 70a1cf5d79280..8cbc1ee344173 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1201,9 +1201,8 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID)); } - ZEND_ASSERT(str && "There should be at least one type!"); if (type_mask & MAY_BE_NULL) { - zend_bool is_union = memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL; + zend_bool is_union = !str || memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL; if (!is_union) { zend_string *nullable_str = zend_string_alloc(ZSTR_LEN(str) + 1, 0); ZSTR_VAL(nullable_str)[0] = '?'; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 2cd75aecf50b9..de2bfa96f9118 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -75,6 +75,7 @@ PHPAPI zend_class_entry *reflection_generator_ptr; PHPAPI zend_class_entry *reflection_parameter_ptr; PHPAPI zend_class_entry *reflection_type_ptr; PHPAPI zend_class_entry *reflection_named_type_ptr; +PHPAPI zend_class_entry *reflection_union_type_ptr; PHPAPI zend_class_entry *reflection_class_ptr; PHPAPI zend_class_entry *reflection_object_ptr; PHPAPI zend_class_entry *reflection_method_ptr; @@ -127,6 +128,8 @@ typedef struct _parameter_reference { /* Struct for type hints */ typedef struct _type_reference { zend_type type; + /* Whether to use backwards compatible null representation */ + zend_bool legacy_behavior; } type_reference; typedef enum { @@ -1130,16 +1133,37 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje } /* }}} */ +/* For backwards compatibility reasons, we need to return T|null style unions + * as a ReflectionNamedType. Here we determine what counts as a union type and + * what doesn't. */ +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; +} + /* {{{ reflection_type_factory */ -static void reflection_type_factory(zend_type type, zval *object) +static void reflection_type_factory(zend_type type, zval *object, zend_bool legacy_behavior) { reflection_object *intern; type_reference *reference; + zend_bool is_union = is_union_type(type); - reflection_instantiate(reflection_named_type_ptr, object); + reflection_instantiate( + is_union ? reflection_union_type_ptr : reflection_named_type_ptr, object); intern = Z_REFLECTION_P(object); reference = (type_reference*) emalloc(sizeof(type_reference)); reference->type = type; + reference->legacy_behavior = legacy_behavior && !is_union; intern->ptr = reference; intern->ref_type = REF_TYPE_TYPE; @@ -2563,7 +2587,7 @@ ZEND_METHOD(reflection_parameter, getType) if (!ZEND_TYPE_IS_SET(param->arg_info->type)) { RETURN_NULL(); } - reflection_type_factory(param->arg_info->type, return_value); + reflection_type_factory(param->arg_info->type, return_value, 1); } /* }}} */ @@ -2863,7 +2887,10 @@ ZEND_METHOD(reflection_named_type, getName) } GET_REFLECTION_OBJECT_PTR(param); - RETURN_STR(zend_type_to_string_without_null(param->type)); + if (param->legacy_behavior) { + RETURN_STR(zend_type_to_string_without_null(param->type)); + } + RETURN_STR(zend_type_to_string(param->type)); } /* }}} */ @@ -2883,6 +2910,83 @@ ZEND_METHOD(reflection_named_type, isBuiltin) } /* }}} */ +static void append_type(zval *return_value, zend_type type) { + zval reflection_type; + reflection_type_factory(type, &reflection_type, 0); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &reflection_type); +} + +static void append_type_mask(zval *return_value, uint32_t type_mask) { + append_type(return_value, (zend_type) ZEND_TYPE_INIT_MASK(type_mask)); +} + +/* {{{ proto public string ReflectionUnionType::getTypes() + Returns the types that are part of this union type */ +ZEND_METHOD(reflection_union_type, getTypes) +{ + reflection_object *intern; + type_reference *param; + uint32_t type_mask; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + GET_REFLECTION_OBJECT_PTR(param); + + array_init(return_value); + if (ZEND_TYPE_HAS_LIST(param->type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + append_type(return_value, + (zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_LIST_GET_NAME(entry), 0, 0)); + } else { + append_type(return_value, + (zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_LIST_GET_CE(entry), 0, 0)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(param->type)) { + append_type(return_value, + (zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_NAME(param->type), 0, 0)); + } else if (ZEND_TYPE_HAS_CE(param->type)) { + append_type(return_value, + (zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_CE(param->type), 0, 0)); + } + + type_mask = ZEND_TYPE_PURE_MASK(param->type); + ZEND_ASSERT(!(type_mask & MAY_BE_VOID)); + if (type_mask & MAY_BE_CALLABLE) { + append_type_mask(return_value, MAY_BE_CALLABLE); + } + if (type_mask & MAY_BE_ITERABLE) { + append_type_mask(return_value, MAY_BE_ITERABLE); + } + if (type_mask & MAY_BE_OBJECT) { + append_type_mask(return_value, MAY_BE_OBJECT); + } + if (type_mask & MAY_BE_ARRAY) { + append_type_mask(return_value, MAY_BE_ARRAY); + } + if (type_mask & MAY_BE_STRING) { + append_type_mask(return_value, MAY_BE_STRING); + } + if (type_mask & MAY_BE_LONG) { + append_type_mask(return_value, MAY_BE_LONG); + } + if (type_mask & MAY_BE_DOUBLE) { + append_type_mask(return_value, MAY_BE_DOUBLE); + } + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { + append_type_mask(return_value, MAY_BE_BOOL); + } else if (type_mask & MAY_BE_FALSE) { + append_type_mask(return_value, MAY_BE_FALSE); + } + if (type_mask & MAY_BE_NULL) { + append_type_mask(return_value, MAY_BE_NULL); + } +} +/* }}} */ + /* {{{ proto public static mixed ReflectionMethod::export(mixed class, string name [, bool return]) throws ReflectionException Exports a reflection object. Returns the output if TRUE is specified for return, printing it otherwise. */ ZEND_METHOD(reflection_method, export) @@ -3348,7 +3452,7 @@ ZEND_METHOD(reflection_function, getReturnType) RETURN_NULL(); } - reflection_type_factory(fptr->common.arg_info[-1].type, return_value); + reflection_type_factory(fptr->common.arg_info[-1].type, return_value, 1); } /* }}} */ @@ -5576,7 +5680,7 @@ ZEND_METHOD(reflection_property, getType) RETURN_NULL(); } - reflection_type_factory(ref->prop->type, return_value); + reflection_type_factory(ref->prop->type, return_value, 1); } /* }}} */ @@ -6430,6 +6534,11 @@ static const zend_function_entry reflection_named_type_functions[] = { PHP_FE_END }; +static const zend_function_entry reflection_union_type_functions[] = { + ZEND_ME(reflection_union_type, getTypes, arginfo_reflection__void, 0) + PHP_FE_END +}; + static const zend_function_entry reflection_extension_functions[] = { ZEND_ME(reflection, __clone, arginfo_class_ReflectionExtension___clone, ZEND_ACC_PRIVATE|ZEND_ACC_FINAL) ZEND_DEP_ME(reflection_extension, export, arginfo_class_ReflectionExtension_export, ZEND_ACC_STATIC|ZEND_ACC_PUBLIC) @@ -6553,6 +6662,10 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_init_class_handlers(&_reflection_entry); reflection_named_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr); + INIT_CLASS_ENTRY(_reflection_entry, "ReflectionUnionType", reflection_union_type_functions); + reflection_init_class_handlers(&_reflection_entry); + reflection_union_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr); + INIT_CLASS_ENTRY(_reflection_entry, "ReflectionMethod", reflection_method_functions); reflection_init_class_handlers(&_reflection_entry); reflection_method_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_function_abstract_ptr); diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index 5877f88e27076..5f3b6166b5af9 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -9,7 +9,7 @@ var_dump($ext->getClasses()); ?> ==DONE== --EXPECT-- -array(17) { +array(18) { ["ReflectionException"]=> object(ReflectionClass)#2 (1) { ["name"]=> @@ -55,43 +55,48 @@ array(17) { ["name"]=> string(19) "ReflectionNamedType" } - ["ReflectionMethod"]=> + ["ReflectionUnionType"]=> object(ReflectionClass)#11 (1) { + ["name"]=> + string(19) "ReflectionUnionType" + } + ["ReflectionMethod"]=> + object(ReflectionClass)#12 (1) { ["name"]=> string(16) "ReflectionMethod" } ["ReflectionClass"]=> - object(ReflectionClass)#12 (1) { + object(ReflectionClass)#13 (1) { ["name"]=> string(15) "ReflectionClass" } ["ReflectionObject"]=> - object(ReflectionClass)#13 (1) { + object(ReflectionClass)#14 (1) { ["name"]=> string(16) "ReflectionObject" } ["ReflectionProperty"]=> - object(ReflectionClass)#14 (1) { + object(ReflectionClass)#15 (1) { ["name"]=> string(18) "ReflectionProperty" } ["ReflectionClassConstant"]=> - object(ReflectionClass)#15 (1) { + object(ReflectionClass)#16 (1) { ["name"]=> string(23) "ReflectionClassConstant" } ["ReflectionExtension"]=> - object(ReflectionClass)#16 (1) { + object(ReflectionClass)#17 (1) { ["name"]=> string(19) "ReflectionExtension" } ["ReflectionZendExtension"]=> - object(ReflectionClass)#17 (1) { + object(ReflectionClass)#18 (1) { ["name"]=> string(23) "ReflectionZendExtension" } ["ReflectionReference"]=> - object(ReflectionClass)#18 (1) { + object(ReflectionClass)#19 (1) { ["name"]=> string(19) "ReflectionReference" } diff --git a/ext/reflection/tests/union_types.phpt b/ext/reflection/tests/union_types.phpt new file mode 100644 index 0000000000000..36773e55cadcf --- /dev/null +++ b/ext/reflection/tests/union_types.phpt @@ -0,0 +1,114 @@ +--TEST-- +Union types in reflection +--INI-- +error_reporting=E_ALL&~E_DEPRECATED +--FILE-- +allowsNull() ? "true" : "false") . "\n"; + foreach ($rt->getTypes() as $type) { + echo " Name: " . $type->getName() . "\n"; + echo " String: " . (string) $type . "\n"; + echo " Allows Null: " . ($type->allowsNull() ? "true" : "false") . "\n"; + } +} + +function test1(): X|Y|int|float|false|null { } +function test2(): X|iterable|bool { } + +class Test { + public X|Y|int $prop; +} + +dumpType((new ReflectionFunction('test1'))->getReturnType()); +dumpType((new ReflectionFunction('test2'))->getReturnType()); + +$rc = new ReflectionClass(Test::class); +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +/* Force CE resolution of the property type */ + +class x {} +$test = new Test; +$test->prop = new x; + +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +class y {} +$test->prop = new y; + +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +?> +--EXPECT-- +Type X|Y|int|float|false|null: +Allows null: true + Name: X + String: X + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: int + String: int + Allows Null: false + Name: float + String: float + Allows Null: false + Name: false + String: false + Allows Null: false + Name: null + String: null + Allows Null: true +Type X|iterable|bool: +Allows null: false + Name: X + String: X + Allows Null: false + Name: iterable + String: iterable + Allows Null: false + Name: bool + String: bool + Allows Null: false +Type X|Y|int: +Allows null: false + Name: X + String: X + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: int + String: int + Allows Null: false +Type x|Y|int: +Allows null: false + Name: x + String: x + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: int + String: int + Allows Null: false +Type x|y|int: +Allows null: false + Name: x + String: x + Allows Null: false + Name: y + String: y + Allows Null: false + Name: int + String: int + Allows Null: false From b0f545c77b26373273b2b18b37cb6674884cb553 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 21 Oct 2019 15:31:22 +0200 Subject: [PATCH 26/48] Add a class loading test --- .../union_types/class_loading.phpt | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Zend/tests/type_declarations/union_types/class_loading.phpt diff --git a/Zend/tests/type_declarations/union_types/class_loading.phpt b/Zend/tests/type_declarations/union_types/class_loading.phpt new file mode 100644 index 0000000000000..29282f16fbe50 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/class_loading.phpt @@ -0,0 +1,62 @@ +--TEST-- +Class loading issues related to multiple class types in a union +--FILE-- +prop = 42; +var_dump($test->prop); + +// Should try to load both classes +try { + $test->prop = new stdClass; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +if (true) { + class X {} +} + +// Should not cause class loading, as X is already loaded +try { + $test->prop = new X; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->prop); + +if (true) { + class Z {} +} + +// TODO: Should this load class Y or not? +try { + $test->prop = new Z; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +var_dump($test->prop); + +?> +--EXPECT-- +int(42) +Loading X +Loading Y +Loading Z +Cannot assign stdClass to property Test::$prop of type X|Y|Z|int +object(X)#3 (0) { +} +Loading Y +object(Z)#5 (0) { +} From 824ed44deb44c5c53001f134e1483f9eef146c50 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 21 Oct 2019 15:57:59 +0200 Subject: [PATCH 27/48] Remove unused variable --- Zend/zend_compile.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8cbc1ee344173..9b9a930d61548 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5708,11 +5708,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ if (type_ast) { uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF; - zend_bool has_class; - op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS; arg_info->type = zend_compile_typename(type_ast, default_type == IS_NULL); - has_class = ZEND_TYPE_HAS_CLASS(arg_info->type); if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID) { zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type"); From f1d05a45a3001530bf4c25eebf2041dfda4d623c Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 21 Oct 2019 16:26:46 +0200 Subject: [PATCH 28/48] Try to fix ZTS build (untested) --- Zend/zend.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Zend/zend.c b/Zend/zend.c index d52ce5260dfb1..eea4a12ea7d19 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -592,7 +592,18 @@ static void function_copy_ctor(zval *zv) /* {{{ */ new_arg_info = pemalloc(sizeof(zend_arg_info) * num_args, 1); memcpy(new_arg_info, arg_info, sizeof(zend_arg_info) * num_args); for (i = 0 ; i < num_args; i++) { - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { + if (ZEND_TYPE_HAS_LIST(arg_info[i].type)) { + zend_type_list *old_list = ZEND_TYPE_LIST(arg_info[i].type); + zend_type_list *new_list = pemalloc(ZEND_TYPE_LIST_SIZE(old_list->num_types), 1); + memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); + ZEND_TYPE_SET_PTR(new_arg_info[i].type, new_list); + + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(new_list, entry) { + zend_string *name = zend_string_dup(ZEND_TYPE_LIST_GET_NAME(*entry), 1); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(name); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) { zend_string *name = zend_string_dup(ZEND_TYPE_NAME(arg_info[i].type), 1); ZEND_TYPE_SET_PTR(new_arg_info[i].type, name); } From fad94190e5c93acb96f4ce1c7e1f4685c2cfe65b Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 22 Oct 2019 10:42:56 +0200 Subject: [PATCH 29/48] Allocate property type lists on arena Arg/return types are immutable, while property types are not, so allocate them on arena, otherwise we run into opcache issues. Might need adjustments in some more places to handle arena vs non-arena. --- Zend/zend_compile.c | 26 +++++++++++++++++------ Zend/zend_opcode.c | 8 ++++--- Zend/zend_types.h | 9 ++++++-- ext/opcache/zend_accelerator_util_funcs.c | 5 +++++ ext/opcache/zend_persist.c | 12 ++++++++++- ext/opcache/zend_persist_calc.c | 6 +++++- 6 files changed, 53 insertions(+), 13 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9b9a930d61548..c811ee42ed8cf 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5500,7 +5500,8 @@ static zend_bool zend_type_contains_traversable(zend_type type) { // TODO: Ideally we'd canonicalize "iterable" into "array|Traversable" and essentially // treat it as a built-in type alias. -static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null) /* {{{ */ +static zend_type zend_compile_typename( + zend_ast *ast, zend_bool force_allow_null, zend_bool use_arena) /* {{{ */ { zend_bool allow_null = force_allow_null; zend_type type = ZEND_TYPE_INIT_NONE(0); @@ -5534,16 +5535,27 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null if (ZEND_TYPE_HAS_LIST(type)) { /* Add name to existing name list. */ zend_type_list *old_list = ZEND_TYPE_LIST(type); - list = erealloc(old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types + 1)); + if (use_arena) { + // TODO: Add a zend_arena_realloc API? + list = zend_arena_alloc( + &CG(arena), ZEND_TYPE_LIST_SIZE(old_list->num_types + 1)); + memcpy(list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); + } else { + list = erealloc(old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types + 1)); + } list->types[list->num_types++] = ZEND_TYPE_NAME(single_type); } else { /* Switch from single name to name list. */ - list = emalloc(ZEND_TYPE_LIST_SIZE(2)); + size_t size = ZEND_TYPE_LIST_SIZE(2); + list = use_arena ? zend_arena_alloc(&CG(arena), size) : emalloc(size); list->num_types = 2; list->types[0] = ZEND_TYPE_NAME(type); list->types[1] = ZEND_TYPE_NAME(single_type); } ZEND_TYPE_SET_LIST(type, list); + if (use_arena) { + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT; + } /* Check for trivially redundant class types */ for (size_t i = 0; i < list->num_types - 1; i++) { @@ -5633,7 +5645,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ /* Use op_array->arg_info[-1] for return type */ arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0); arg_infos->name = NULL; - arg_infos->type = zend_compile_typename(return_type_ast, 0); + arg_infos->type = zend_compile_typename( + return_type_ast, /* force_allow_null */ 0, /* use_arena */ 0); ZEND_TYPE_FULL_MASK(arg_infos->type) |= _ZEND_ARG_INFO_FLAGS( (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0, /* is_variadic */ 0); arg_infos++; @@ -5709,7 +5722,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF; op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS; - arg_info->type = zend_compile_typename(type_ast, default_type == IS_NULL); + arg_info->type = zend_compile_typename( + type_ast, default_type == IS_NULL, /* use_arena */ 0); if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID) { zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type"); @@ -6238,7 +6252,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) / zend_type type = ZEND_TYPE_INIT_NONE(0); if (type_ast) { - type = zend_compile_typename(type_ast, 0); + type = zend_compile_typename(type_ast, /* force_allow_null */ 0, /* use_arena */ 1); if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_CALLABLE)) { zend_string *str = zend_type_to_string(type); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 9fdec6f073aa7..58d89e5faff68 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -110,7 +110,9 @@ static void zend_always_inline zend_type_release(zend_type *type) { zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry)); } } ZEND_TYPE_LIST_FOREACH_END(); - efree(ZEND_TYPE_LIST(*type)); + if (!ZEND_TYPE_USES_ARENA(*type)) { + efree(ZEND_TYPE_LIST(*type)); + } } else if (ZEND_TYPE_HAS_NAME(*type)) { zend_string_release(ZEND_TYPE_NAME(*type)); } @@ -140,8 +142,8 @@ ZEND_API void zend_function_dtor(zval *zv) if (function->type == ZEND_USER_FUNCTION) { ZEND_ASSERT(function->common.function_name); - destroy_op_array(&function->op_array); - /* op_arrays are allocated on arena, so we don't have to free them */ + destroy_op_array(&function->op_array); + /* op_arrays are allocated on arena, so we don't have to free them */ } else { ZEND_ASSERT(function->type == ZEND_INTERNAL_FUNCTION); ZEND_ASSERT(function->common.function_name); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index fdb1cd9ae426a..3d66aed6181a0 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -139,10 +139,12 @@ typedef struct { #define _ZEND_TYPE_MASK ((1u << 24) - 1) #define _ZEND_TYPE_MAY_BE_MASK ((1u << (IS_VOID+1)) - 1) /* Only one of these bits may be set. */ -#define _ZEND_TYPE_LIST_BIT (1u << 21) -#define _ZEND_TYPE_CE_BIT (1u << 22) #define _ZEND_TYPE_NAME_BIT (1u << 23) +#define _ZEND_TYPE_CE_BIT (1u << 22) +#define _ZEND_TYPE_LIST_BIT (1u << 21) #define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_CE_BIT|_ZEND_TYPE_NAME_BIT) +/* Whether the type list is arena allocated */ +#define _ZEND_TYPE_ARENA_BIT (1u << 20) /* Must have same value as MAY_BE_NULL */ #define _ZEND_TYPE_NULLABLE_BIT 0x2 @@ -161,6 +163,9 @@ typedef struct { #define ZEND_TYPE_HAS_LIST(t) \ ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) +#define ZEND_TYPE_USES_ARENA(t) \ + ((((t).type_mask) & _ZEND_TYPE_ARENA_BIT) != 0) + #define ZEND_TYPE_IS_ONLY_MASK(t) \ (ZEND_TYPE_IS_SET(t) && (t).ptr == NULL) diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 0d2a25ed59563..d751b0bebc69f 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -234,6 +234,11 @@ static void zend_hash_clone_prop_info(HashTable *ht) } if (ZEND_TYPE_HAS_LIST(prop_info->type)) { + zend_type_list *list = ZEND_TYPE_LIST(prop_info->type); + ZEND_ASSERT(IN_ARENA(list)); + list = ARENA_REALLOC(list); + ZEND_TYPE_SET_PTR(prop_info->type, list); + void **entry; ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) { if (ZEND_TYPE_LIST_IS_CE(*entry)) { diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index a4d8cbb07baab..ec5b9d417a1a0 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -262,7 +262,17 @@ static void zend_persist_type(zend_type *type) { if (ZEND_TYPE_HAS_LIST(*type)) { void **entry; zend_type_list *list = ZEND_TYPE_LIST(*type); - list = zend_shared_memdup_put_free(list, ZEND_TYPE_LIST_SIZE(list->num_types)); + if (ZEND_TYPE_USES_ARENA(*type)) { + if (!ZCG(is_immutable_class)) { + list = zend_shared_memdup_arena_put(list, ZEND_TYPE_LIST_SIZE(list->num_types)); + } else { + /* Moved from arena to SHM because type list was fully resolved. */ + list = zend_shared_memdup_put(list, ZEND_TYPE_LIST_SIZE(list->num_types)); + ZEND_TYPE_FULL_MASK(*type) &= ~_ZEND_TYPE_ARENA_BIT; + } + } else { + list = zend_shared_memdup_put_free(list, ZEND_TYPE_LIST_SIZE(list->num_types)); + } ZEND_TYPE_SET_PTR(*type, list); ZEND_TYPE_LIST_FOREACH_PTR(list, entry) { diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 7cf1c3506eae3..c4aa4adf5eaca 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -152,7 +152,11 @@ static void zend_persist_type_calc(zend_type *type) { if (ZEND_TYPE_HAS_LIST(*type)) { void **entry; - ADD_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types)); + if (ZEND_TYPE_USES_ARENA(*type) && !ZCG(is_immutable_class)) { + ADD_ARENA_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types)); + } else { + ADD_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types)); + } ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(*type), entry) { zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry); ADD_INTERNED_STRING(type_name); From 686e61ff3b35c7909cce851e052d8dcee3f93ab6 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 23 Oct 2019 11:30:52 +0200 Subject: [PATCH 30/48] Remove duplicate test --- .../union_types/illegal_default_value.phpt | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 Zend/tests/type_declarations/union_types/illegal_default_value.phpt diff --git a/Zend/tests/type_declarations/union_types/illegal_default_value.phpt b/Zend/tests/type_declarations/union_types/illegal_default_value.phpt deleted file mode 100644 index 3bf305aa94d9b..0000000000000 --- a/Zend/tests/type_declarations/union_types/illegal_default_value.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Property default value not legal for any type in the union ---FILE-- - ---EXPECTF-- -Fatal error: Cannot use string as default value for property Test::$prop of type int|float in %s on line %d From e0e204d0e4562df344a0d9437217cba78ccdb532 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 23 Oct 2019 11:32:04 +0200 Subject: [PATCH 31/48] Add test for void + class --- .../type_declarations/union_types/void_with_class.phpt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Zend/tests/type_declarations/union_types/void_with_class.phpt diff --git a/Zend/tests/type_declarations/union_types/void_with_class.phpt b/Zend/tests/type_declarations/union_types/void_with_class.phpt new file mode 100644 index 0000000000000..6e1f439e3193e --- /dev/null +++ b/Zend/tests/type_declarations/union_types/void_with_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +Combining void with class type +--FILE-- + +--EXPECTF-- +Fatal error: Void can only be used as a standalone type in %s on line %d From 476adfa35dbfee451e34b81874ef8f39464f3944 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 23 Oct 2019 09:51:08 +0200 Subject: [PATCH 32/48] Run coverage job --- azure-pipelines.yml | 76 ++------------------------------------------- 1 file changed, 3 insertions(+), 73 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b59550766d4cc..df4667d5191f6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,78 +12,8 @@ trigger: - UPGRADING.INTERNALS jobs: - - template: azure/job.yml + - template: azure/coverage_job.yml parameters: - configurationName: DEBUG_NTS + configurationName: COVERAGE_DEBUG_ZTS configurationParameters: '--enable-debug --disable-zts' - - template: azure/job.yml - parameters: - configurationName: RELEASE_ZTS - configurationParameters: '--disable-debug --enable-zts' - - template: azure/i386/job.yml - parameters: - configurationName: I386_DEBUG_ZTS - configurationParameters: '--enable-debug --enable-zts' - - template: azure/macos/job.yml - parameters: - configurationName: MACOS_DEBUG_NTS - configurationParameters: '--enable-debug --disable-zts' - - ${{ if eq(variables['Build.Reason'], 'Schedule') }}: - - template: azure/job.yml - parameters: - configurationName: DEBUG_ZTS - configurationParameters: '--enable-debug --enable-zts' - - template: azure/job.yml - parameters: - configurationName: RELEASE_NTS - configurationParameters: '--disable-debug --disable-zts' - - template: azure/i386/job.yml - parameters: - configurationName: I386_DEBUG_NTS - configurationParameters: '--enable-debug --disable-zts' - - template: azure/i386/job.yml - parameters: - configurationName: I386_RELEASE_NTS - configurationParameters: '--disable-debug --disable-zts' - - template: azure/i386/job.yml - parameters: - configurationName: I386_RELEASE_ZTS - configurationParameters: '--disable-debug --enable-zts' - - template: azure/macos/job.yml - parameters: - configurationName: MACOS_DEBUG_ZTS - configurationParameters: '--enable-debug --enable-zts' - - template: azure/macos/job.yml - parameters: - configurationName: MACOS_RELEASE_NTS - configurationParameters: '--disable-debug --disable-zts' - - template: azure/macos/job.yml - parameters: - configurationName: MACOS_RELEASE_ZTS - configurationParameters: '--disable-debug --enable-zts' - - template: azure/job.yml - parameters: - configurationName: DEBUG_ZTS_ASAN_UBSAN - configurationParameters: >- - --enable-debug --enable-zts - CFLAGS='-fsanitize=undefined,address -DZEND_TRACK_ARENA_ALLOC' - LDFLAGS='-fsanitize=undefined,address' - runTestsParameters: --asan - timeoutInMinutes: 150 - - template: azure/msan_job.yml - parameters: - configurationName: DEBUG_ZTS_MSAN - configurationParameters: '--enable-debug --enable-zts' - runTestsParameters: --asan - - template: azure/community_job.yml - parameters: - configurationName: COMMUNITY - configurationParameters: >- - --enable-debug --enable-zts - CFLAGS='-fsanitize=undefined,address -fno-sanitize-recover -DZEND_TRACK_ARENA_ALLOC' - LDFLAGS='-fsanitize=undefined,address' - - template: azure/coverage_job.yml - parameters: - configurationName: COVERAGE_DEBUG_ZTS - configurationParameters: '--enable-debug --disable-zts' - timeoutInMinutes: 90 + timeoutInMinutes: 90 From e7aecad83d326f4d78e6fff1aadcd5760776ea14 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 23 Oct 2019 12:33:17 +0200 Subject: [PATCH 33/48] Adjust test after property type loading changes Class loading is no longer relevant here, so just test general behavior. --- .../union_types/class_loading.phpt | 62 ---------------- .../union_types/multiple_classes.phpt | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 62 deletions(-) delete mode 100644 Zend/tests/type_declarations/union_types/class_loading.phpt create mode 100644 Zend/tests/type_declarations/union_types/multiple_classes.phpt diff --git a/Zend/tests/type_declarations/union_types/class_loading.phpt b/Zend/tests/type_declarations/union_types/class_loading.phpt deleted file mode 100644 index 29282f16fbe50..0000000000000 --- a/Zend/tests/type_declarations/union_types/class_loading.phpt +++ /dev/null @@ -1,62 +0,0 @@ ---TEST-- -Class loading issues related to multiple class types in a union ---FILE-- -prop = 42; -var_dump($test->prop); - -// Should try to load both classes -try { - $test->prop = new stdClass; -} catch (TypeError $e) { - echo $e->getMessage(), "\n"; -} - -if (true) { - class X {} -} - -// Should not cause class loading, as X is already loaded -try { - $test->prop = new X; -} catch (TypeError $e) { - echo $e->getMessage(), "\n"; -} -var_dump($test->prop); - -if (true) { - class Z {} -} - -// TODO: Should this load class Y or not? -try { - $test->prop = new Z; -} catch (TypeError $e) { - echo $e->getMessage(), "\n"; -} -var_dump($test->prop); - -?> ---EXPECT-- -int(42) -Loading X -Loading Y -Loading Z -Cannot assign stdClass to property Test::$prop of type X|Y|Z|int -object(X)#3 (0) { -} -Loading Y -object(Z)#5 (0) { -} diff --git a/Zend/tests/type_declarations/union_types/multiple_classes.phpt b/Zend/tests/type_declarations/union_types/multiple_classes.phpt new file mode 100644 index 0000000000000..116a54630b5a7 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/multiple_classes.phpt @@ -0,0 +1,71 @@ +--TEST-- +Union types with multiple classes +--FILE-- +prop = 42; +var_dump($test->prop); +var_dump($test->method(42)); + +$test->prop = "42"; +var_dump($test->prop); +var_dump($test->method("42")); + +try { + $test->prop = new stdClass; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->method(new stdClass); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +if (true) { + class X {} +} + +$test->prop = new X; +var_dump($test->prop); +var_dump($test->method(new X)); + +if (true) { + class Z {} +} + +$test->prop = new Z; +var_dump($test->prop); +var_dump($test->method(new Z)); + +?> +--EXPECTF-- +int(42) +int(42) +int(42) +int(42) +Cannot assign stdClass to property Test::$prop of type X|Y|Z|int +Argument 1 passed to Test::method() must be of type X|Y|Z|int, instance of stdClass given, called in %s on line %d +object(X)#4 (0) { +} +object(X)#6 (0) { +} +object(Z)#6 (0) { +} +object(Z)#4 (0) { +} From 716cd389d28bfb57e781f3ec08ab1f0087297afc Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 23 Oct 2019 12:36:43 +0200 Subject: [PATCH 34/48] Add test for generator with multiple class return types --- .../generator_return_multiple_classes.phpt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt diff --git a/Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt b/Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt new file mode 100644 index 0000000000000..8526c65537bff --- /dev/null +++ b/Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt @@ -0,0 +1,18 @@ +--TEST-- +Generator return type with multiple classes +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== From 7e7d240d0d1fdcd00435eb876fc7deaaf14a15a3 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 23 Oct 2019 12:38:05 +0200 Subject: [PATCH 35/48] Add test for iterable+Traversable redundancy with extra class --- .../redundant_types/iterable_and_Traversable_2.phpt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt new file mode 100644 index 0000000000000..e3f7c5858b400 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both iterable and Traversable, with extra classes +--FILE-- + +--EXPECTF-- +Fatal error: Type Traversable|ArrayAccess|iterable contains both iterable and Traversable, which is redundant in %s on line %d From 6abe54018da803946fcce56fcb1dbea4f438fe20 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 23 Oct 2019 12:48:52 +0200 Subject: [PATCH 36/48] Add inheritance test and fix some related issues --- .../union_types/inheritance.phpt | 46 +++++++++++++++++++ Zend/zend_inheritance.c | 4 +- ext/opcache/zend_accelerator_util_funcs.c | 27 +++++------ 3 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 Zend/tests/type_declarations/union_types/inheritance.phpt diff --git a/Zend/tests/type_declarations/union_types/inheritance.phpt b/Zend/tests/type_declarations/union_types/inheritance.phpt new file mode 100644 index 0000000000000..a0a1b65912323 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/inheritance.phpt @@ -0,0 +1,46 @@ +--TEST-- +Various inheritance scenarios for properties/methods with union types +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 5478ef1ed51d9..9ec12c61aa06c 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -43,7 +43,9 @@ static void overridden_ptr_dtor(zval *zv) /* {{{ */ static void zend_type_copy_ctor(zend_type *type) { if (ZEND_TYPE_HAS_LIST(*type)) { zend_type_list *old_list = ZEND_TYPE_LIST(*type); - zend_type_list *new_list = emalloc(ZEND_TYPE_LIST_SIZE(old_list->num_types)); + size_t size = ZEND_TYPE_LIST_SIZE(old_list->num_types); + zend_type_list *new_list = ZEND_TYPE_USES_ARENA(*type) + ? zend_arena_alloc(&CG(arena), size) : emalloc(size); memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); ZEND_TYPE_SET_PTR(*type, new_list); diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index d751b0bebc69f..9eb6745478f65 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -235,20 +235,21 @@ static void zend_hash_clone_prop_info(HashTable *ht) if (ZEND_TYPE_HAS_LIST(prop_info->type)) { zend_type_list *list = ZEND_TYPE_LIST(prop_info->type); - ZEND_ASSERT(IN_ARENA(list)); - list = ARENA_REALLOC(list); - ZEND_TYPE_SET_PTR(prop_info->type, list); - - void **entry; - ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) { - if (ZEND_TYPE_LIST_IS_CE(*entry)) { - zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); - if (IN_ARENA(ce)) { - ce = ARENA_REALLOC(ce); - *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + if (IN_ARENA(list)) { + list = ARENA_REALLOC(list); + ZEND_TYPE_SET_PTR(prop_info->type, list); + + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) { + if (ZEND_TYPE_LIST_IS_CE(*entry)) { + zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); + if (IN_ARENA(ce)) { + ce = ARENA_REALLOC(ce); + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } } - } - } ZEND_TYPE_LIST_FOREACH_END(); + } ZEND_TYPE_LIST_FOREACH_END(); + } } else if (ZEND_TYPE_HAS_CE(prop_info->type)) { zend_class_entry *ce = ZEND_TYPE_CE(prop_info->type); if (IN_ARENA(ce)) { From 59802bbd1377d0a52d0e47ae2b35aa10c6fb593c Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 7 Nov 2019 15:14:54 +0100 Subject: [PATCH 37/48] Fixup reflection after rebase --- ext/reflection/php_reflection.c | 20 +++++++++++++++++--- ext/reflection/reflection.stub.php | 5 +++++ ext/reflection/reflection_arginfo.h | 3 +++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index de2bfa96f9118..4b3851e9e0361 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -231,7 +231,14 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */ case REF_TYPE_TYPE: { type_reference *type_ref = intern->ptr; - if (ZEND_TYPE_IS_NAME(type_ref->type)) { + if (ZEND_TYPE_HAS_LIST(type_ref->type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type_ref->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(type_ref->type)) { zend_string_release(ZEND_TYPE_NAME(type_ref->type)); } efree(type_ref); @@ -1169,7 +1176,14 @@ static void reflection_type_factory(zend_type type, zval *object, zend_bool lega /* Property types may be resolved during the lifetime of the ReflectionType, * so we need to make sure that the strings we reference are not released. */ - if (ZEND_TYPE_IS_NAME(type)) { + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + zend_string_addref(ZEND_TYPE_LIST_GET_NAME(entry)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(type)) { zend_string_addref(ZEND_TYPE_NAME(type)); } } @@ -6535,7 +6549,7 @@ static const zend_function_entry reflection_named_type_functions[] = { }; static const zend_function_entry reflection_union_type_functions[] = { - ZEND_ME(reflection_union_type, getTypes, arginfo_reflection__void, 0) + ZEND_ME(reflection_union_type, getTypes, arginfo_class_ReflectionUnionType_getTypes, 0) PHP_FE_END }; diff --git a/ext/reflection/reflection.stub.php b/ext/reflection/reflection.stub.php index bfd5f0caf952b..b9cb156e7da56 100644 --- a/ext/reflection/reflection.stub.php +++ b/ext/reflection/reflection.stub.php @@ -552,6 +552,11 @@ public function getName() {} public function isBuiltin() {} } +class ReflectionUnionType extends ReflectionType +{ + public function getTypes(): array {} +} + class ReflectionExtension implements Reflector { final private function __clone() {} diff --git a/ext/reflection/reflection_arginfo.h b/ext/reflection/reflection_arginfo.h index 517668bd629d2..d5404d9b3157a 100644 --- a/ext/reflection/reflection_arginfo.h +++ b/ext/reflection/reflection_arginfo.h @@ -428,6 +428,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionNamedType_isBuiltin arginfo_class_Reflector___toString +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionUnionType_getTypes, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionExtension___clone arginfo_class_Reflector___toString #define arginfo_class_ReflectionExtension_export arginfo_class_ReflectionFunction_export From f85a71781728bdf93f371bb2fb6abf39a53f2c59 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 11:27:09 +0100 Subject: [PATCH 38/48] Revert "Run coverage job" This reverts commit 476adfa35dbfee451e34b81874ef8f39464f3944. --- azure-pipelines.yml | 76 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index df4667d5191f6..b59550766d4cc 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,8 +12,78 @@ trigger: - UPGRADING.INTERNALS jobs: - - template: azure/coverage_job.yml + - template: azure/job.yml parameters: - configurationName: COVERAGE_DEBUG_ZTS + configurationName: DEBUG_NTS configurationParameters: '--enable-debug --disable-zts' - timeoutInMinutes: 90 + - template: azure/job.yml + parameters: + configurationName: RELEASE_ZTS + configurationParameters: '--disable-debug --enable-zts' + - template: azure/i386/job.yml + parameters: + configurationName: I386_DEBUG_ZTS + configurationParameters: '--enable-debug --enable-zts' + - template: azure/macos/job.yml + parameters: + configurationName: MACOS_DEBUG_NTS + configurationParameters: '--enable-debug --disable-zts' + - ${{ if eq(variables['Build.Reason'], 'Schedule') }}: + - template: azure/job.yml + parameters: + configurationName: DEBUG_ZTS + configurationParameters: '--enable-debug --enable-zts' + - template: azure/job.yml + parameters: + configurationName: RELEASE_NTS + configurationParameters: '--disable-debug --disable-zts' + - template: azure/i386/job.yml + parameters: + configurationName: I386_DEBUG_NTS + configurationParameters: '--enable-debug --disable-zts' + - template: azure/i386/job.yml + parameters: + configurationName: I386_RELEASE_NTS + configurationParameters: '--disable-debug --disable-zts' + - template: azure/i386/job.yml + parameters: + configurationName: I386_RELEASE_ZTS + configurationParameters: '--disable-debug --enable-zts' + - template: azure/macos/job.yml + parameters: + configurationName: MACOS_DEBUG_ZTS + configurationParameters: '--enable-debug --enable-zts' + - template: azure/macos/job.yml + parameters: + configurationName: MACOS_RELEASE_NTS + configurationParameters: '--disable-debug --disable-zts' + - template: azure/macos/job.yml + parameters: + configurationName: MACOS_RELEASE_ZTS + configurationParameters: '--disable-debug --enable-zts' + - template: azure/job.yml + parameters: + configurationName: DEBUG_ZTS_ASAN_UBSAN + configurationParameters: >- + --enable-debug --enable-zts + CFLAGS='-fsanitize=undefined,address -DZEND_TRACK_ARENA_ALLOC' + LDFLAGS='-fsanitize=undefined,address' + runTestsParameters: --asan + timeoutInMinutes: 150 + - template: azure/msan_job.yml + parameters: + configurationName: DEBUG_ZTS_MSAN + configurationParameters: '--enable-debug --enable-zts' + runTestsParameters: --asan + - template: azure/community_job.yml + parameters: + configurationName: COMMUNITY + configurationParameters: >- + --enable-debug --enable-zts + CFLAGS='-fsanitize=undefined,address -fno-sanitize-recover -DZEND_TRACK_ARENA_ALLOC' + LDFLAGS='-fsanitize=undefined,address' + - template: azure/coverage_job.yml + parameters: + configurationName: COVERAGE_DEBUG_ZTS + configurationParameters: '--enable-debug --disable-zts' + timeoutInMinutes: 90 From c06e6c982cbc414f3120ac5d226fc1fc926e0469 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 11:34:17 +0100 Subject: [PATCH 39/48] Add json skipifs --- .../type_declarations/union_types/type_checking_strict.phpt | 4 ++++ .../type_declarations/union_types/type_checking_weak.phpt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Zend/tests/type_declarations/union_types/type_checking_strict.phpt b/Zend/tests/type_declarations/union_types/type_checking_strict.phpt index 81d164819d96f..f098b638dcfe3 100644 --- a/Zend/tests/type_declarations/union_types/type_checking_strict.phpt +++ b/Zend/tests/type_declarations/union_types/type_checking_strict.phpt @@ -1,5 +1,9 @@ --TEST-- Behavior of union type checks (strict) +--SKIPIF-- + --FILE-- --FILE-- Date: Fri, 8 Nov 2019 11:49:55 +0100 Subject: [PATCH 40/48] Extra variance test --- Zend/tests/type_declarations/union_types/variance/valid.phpt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Zend/tests/type_declarations/union_types/variance/valid.phpt b/Zend/tests/type_declarations/union_types/variance/valid.phpt index 4d4780b7cf4c0..47b54d11a3aad 100644 --- a/Zend/tests/type_declarations/union_types/variance/valid.phpt +++ b/Zend/tests/type_declarations/union_types/variance/valid.phpt @@ -11,12 +11,14 @@ class A { public function method(int $a): int|float {} public function method2(B|string $a): A|string {} + public function method3(Y|B $a): X|A {} } class B extends A { public X $prop; public function method(int|float $a): int {} public function method2(A|string $a): B|string {} + public function method3(A|X $a): B|Y {} } ?> From 8bb01a8032918c6966b1d3ce1ced23b80b49ae6d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 12:16:36 +0100 Subject: [PATCH 41/48] Fix issues related to inheritance of internal union types --- .../typed_properties_095.phpt | 8 +++-- .../union_types/inheritance_internal.phpt | 33 +++++++++++++++++++ Zend/zend.c | 8 +++-- Zend/zend_inheritance.c | 8 ++--- Zend/zend_types.h | 13 ++++---- ext/zend_test/test.c | 15 +++++++++ 6 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 Zend/tests/type_declarations/union_types/inheritance_internal.phpt diff --git a/Zend/tests/type_declarations/typed_properties_095.phpt b/Zend/tests/type_declarations/typed_properties_095.phpt index 3f1027f08f8b3..8470d4f437b8e 100644 --- a/Zend/tests/type_declarations/typed_properties_095.phpt +++ b/Zend/tests/type_declarations/typed_properties_095.phpt @@ -62,22 +62,26 @@ var_dump(_ZendTestClass::$staticIntProp); int(123) Cannot assign string to property _ZendTestClass::$intProp of type int Cannot assign _ZendTestClass to property _ZendTestClass::$classProp of type ?stdClass -object(_ZendTestClass)#1 (2) { +object(_ZendTestClass)#1 (3) { ["intProp"]=> int(456) ["classProp"]=> object(stdClass)#2 (0) { } + ["classUnionProp"]=> + NULL } int(123) Cannot assign string to property _ZendTestClass::$intProp of type int Cannot assign Test to property _ZendTestClass::$classProp of type ?stdClass -object(Test)#4 (2) { +object(Test)#4 (3) { ["intProp"]=> int(456) ["classProp"]=> object(stdClass)#1 (0) { } + ["classUnionProp"]=> + NULL } int(123) Cannot assign string to property _ZendTestClass::$staticIntProp of type int diff --git a/Zend/tests/type_declarations/union_types/inheritance_internal.phpt b/Zend/tests/type_declarations/union_types/inheritance_internal.phpt new file mode 100644 index 0000000000000..bb53411cad618 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/inheritance_internal.phpt @@ -0,0 +1,33 @@ +--TEST-- +Inheritance of union type from internal class +--SKIPIF-- + +--FILE-- +classUnionProp = new stdClass; +$obj->classUnionProp = new ArrayIterator; +try { + $obj->classUnionProp = new DateTime; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +$obj = new C; +$obj->classUnionProp = new stdClass; +$obj->classUnionProp = new ArrayIterator; +try { + $obj->classUnionProp = new DateTime; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot assign DateTime to property _ZendTestClass::$classUnionProp of type stdClass|Iterator|null +Cannot assign DateTime to property _ZendTestClass::$classUnionProp of type stdClass|Iterator|null diff --git a/Zend/zend.c b/Zend/zend.c index eea4a12ea7d19..d85135d28713f 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -982,9 +982,11 @@ static void zend_resolve_property_types(void) /* {{{ */ if (ZEND_TYPE_HAS_LIST(prop_info->type)) { void **entry; ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) { - zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry); - *entry = ZEND_TYPE_LIST_ENCODE_CE(resolve_type_name(type_name)); - zend_string_release(type_name); + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry); + *entry = ZEND_TYPE_LIST_ENCODE_CE(resolve_type_name(type_name)); + zend_string_release(type_name); + } } ZEND_TYPE_LIST_FOREACH_END(); } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) { zend_string *type_name = ZEND_TYPE_NAME(prop_info->type); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 9ec12c61aa06c..9b8a47f365ba5 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -40,12 +40,12 @@ static void overridden_ptr_dtor(zval *zv) /* {{{ */ } /* }}} */ -static void zend_type_copy_ctor(zend_type *type) { +static void zend_type_copy_ctor(zend_type *type, zend_bool persistent) { if (ZEND_TYPE_HAS_LIST(*type)) { zend_type_list *old_list = ZEND_TYPE_LIST(*type); size_t size = ZEND_TYPE_LIST_SIZE(old_list->num_types); zend_type_list *new_list = ZEND_TYPE_USES_ARENA(*type) - ? zend_arena_alloc(&CG(arena), size) : emalloc(size); + ? zend_arena_alloc(&CG(arena), size) : pemalloc(size, persistent); memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); ZEND_TYPE_SET_PTR(*type, new_list); @@ -64,7 +64,7 @@ static zend_property_info *zend_duplicate_property_info_internal(zend_property_i zend_property_info* new_property_info = pemalloc(sizeof(zend_property_info), 1); memcpy(new_property_info, property_info, sizeof(zend_property_info)); zend_string_addref(new_property_info->name); - zend_type_copy_ctor(&new_property_info->type); + zend_type_copy_ctor(&new_property_info->type, /* persistent */ 1); return new_property_info; } @@ -2048,7 +2048,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent Z_TRY_ADDREF_P(prop_value); doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL; - zend_type_copy_ctor(&property_info->type); + zend_type_copy_ctor(&property_info->type, /* persistent */ 0); zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, property_info->type); zend_string_release_ex(prop_name, 0); } ZEND_HASH_FOREACH_END(); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 3d66aed6181a0..4bfe335e0a667 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -265,17 +265,18 @@ typedef struct { ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : (1 << (code))) \ | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags)) +#define ZEND_TYPE_INIT_PTR(ptr, type_kind, allow_null, extra_flags) \ + { (void *) (ptr), \ + (type_kind) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } + #define ZEND_TYPE_INIT_CE(_ce, allow_null, extra_flags) \ - { (void *) (_ce), \ - _ZEND_TYPE_CE_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } + ZEND_TYPE_INIT_PTR(_ce, _ZEND_TYPE_CE_BIT, allow_null, extra_flags) #define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \ - { (void *) (class_name), \ - _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } + ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags) #define ZEND_TYPE_INIT_CLASS_CONST(class_name, allow_null, extra_flags) \ - { (void *) (class_name), \ - _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } + ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags) typedef union _zend_value { zend_long lval; /* long value */ diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 3b2cb7376e039..318419ba3e37b 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -253,6 +253,21 @@ PHP_MINIT_FUNCTION(zend_test) zend_string_release(name); } + { + zend_string *name = zend_string_init("classUnionProp", sizeof("classUnionProp") - 1, 1); + zend_string *class_name1 = zend_string_init("stdClass", sizeof("stdClass") - 1, 1); + zend_string *class_name2 = zend_string_init("Iterator", sizeof("Iterator") - 1, 1); + zend_type_list *type_list = malloc(ZEND_TYPE_LIST_SIZE(2)); + type_list->num_types = 2; + type_list->types[0] = ZEND_TYPE_LIST_ENCODE_NAME(class_name1); + type_list->types[1] = ZEND_TYPE_LIST_ENCODE_NAME(class_name2); + zend_type type = ZEND_TYPE_INIT_PTR(type_list, _ZEND_TYPE_LIST_BIT, 1, 0); + zval val; + ZVAL_NULL(&val); + zend_declare_typed_property(zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, type); + zend_string_release(name); + } + { zend_string *name = zend_string_init("staticIntProp", sizeof("staticIntProp") - 1, 1); zval val; From 3a9ff5de5c7a9a8b60c2f6a1cd7cb9e73121471a Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 12:24:40 +0100 Subject: [PATCH 42/48] Properly destroy internal property types --- Zend/zend_compile.c | 1 + Zend/zend_compile.h | 2 ++ Zend/zend_opcode.c | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c811ee42ed8cf..53fe9afde8c77 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -127,6 +127,7 @@ static void zend_destroy_property_info_internal(zval *zv) /* {{{ */ zend_property_info *property_info = Z_PTR_P(zv); zend_string_release_ex(property_info->name, 1); + zend_type_release(property_info->type, /* persistent */ 1); free(property_info); } /* }}} */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 96db1dcb3a695..a87204d26753b 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -783,6 +783,8 @@ ZEND_API void destroy_op_array(zend_op_array *op_array); ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle); ZEND_API void zend_cleanup_internal_class_data(zend_class_entry *ce); ZEND_API void zend_cleanup_internal_classes(void); +ZEND_API void zend_type_release(zend_type type, zend_bool persistent); + ZEND_API ZEND_COLD void zend_user_exception_handler(void); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 58d89e5faff68..8055d5bb09671 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -102,19 +102,19 @@ ZEND_API void destroy_zend_function(zend_function *function) zend_function_dtor(&tmp); } -static void zend_always_inline zend_type_release(zend_type *type) { - if (ZEND_TYPE_HAS_LIST(*type)) { +ZEND_API void zend_type_release(zend_type type, zend_bool persistent) { + if (ZEND_TYPE_HAS_LIST(type)) { void *entry; - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), entry) { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { if (ZEND_TYPE_LIST_IS_NAME(entry)) { zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry)); } } ZEND_TYPE_LIST_FOREACH_END(); - if (!ZEND_TYPE_USES_ARENA(*type)) { - efree(ZEND_TYPE_LIST(*type)); + if (!ZEND_TYPE_USES_ARENA(type)) { + pefree(ZEND_TYPE_LIST(type), persistent); } - } else if (ZEND_TYPE_HAS_NAME(*type)) { - zend_string_release(ZEND_TYPE_NAME(*type)); + } else if (ZEND_TYPE_HAS_NAME(type)) { + zend_string_release(ZEND_TYPE_NAME(type)); } } @@ -130,7 +130,7 @@ void zend_free_internal_arg_info(zend_internal_function *function) { num_args++; } for (i = 0 ; i < num_args; i++) { - zend_type_release(&arg_info[i].type); + zend_type_release(arg_info[i].type, /* persistent */ 1); } free(arg_info); } @@ -317,7 +317,7 @@ ZEND_API void destroy_zend_class(zval *zv) if (prop_info->doc_comment) { zend_string_release_ex(prop_info->doc_comment, 0); } - zend_type_release(&prop_info->type); + zend_type_release(prop_info->type, /* persistent */ 0); } } ZEND_HASH_FOREACH_END(); zend_hash_destroy(&ce->properties_info); @@ -508,7 +508,7 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) if (arg_info[i].name) { zend_string_release_ex(arg_info[i].name, 0); } - zend_type_release(&arg_info[i].type); + zend_type_release(arg_info[i].type, /* persistent */ 0); } efree(arg_info); } From edcd6d959f5d786980df4bffed734f74f2ae2964 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 12:50:30 +0100 Subject: [PATCH 43/48] Fix missing cache slot increment --- .../union_types/multiple_classes.phpt | 12 ++++++++++++ Zend/zend_execute.c | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Zend/tests/type_declarations/union_types/multiple_classes.phpt b/Zend/tests/type_declarations/union_types/multiple_classes.phpt index 116a54630b5a7..aac56c6603f66 100644 --- a/Zend/tests/type_declarations/union_types/multiple_classes.phpt +++ b/Zend/tests/type_declarations/union_types/multiple_classes.phpt @@ -53,6 +53,14 @@ $test->prop = new Z; var_dump($test->prop); var_dump($test->method(new Z)); +if (true) { + class Y {} +} + +$test->prop = new Y; +var_dump($test->prop); +var_dump($test->method(new Y)); + ?> --EXPECTF-- int(42) @@ -69,3 +77,7 @@ object(Z)#6 (0) { } object(Z)#4 (0) { } +object(Y)#4 (0) { +} +object(Y)#6 (0) { +} diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 0db9f277ec263..c02648e44aec4 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -798,7 +798,7 @@ static zend_bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg /* Type preference order: int -> float -> string -> bool */ if (type_mask & MAY_BE_LONG) { - /* For a int|float union type and string value, + /* 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); @@ -1056,6 +1056,7 @@ static zend_always_inline zend_bool zend_check_type( 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; From b036ffa50af7a40131dd0724820a681a4d0f1f35 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 13:03:27 +0100 Subject: [PATCH 44/48] Use goto for common code-paths --- Zend/zend_execute.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index c02648e44aec4..153d8e3bc4ea9 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3085,7 +3085,9 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { 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; } @@ -3095,29 +3097,22 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference ZVAL_COPY(&coerced_value, zv); if (!zend_verify_weak_scalar_type_hint( ZEND_TYPE_FULL_MASK(prop->type), &coerced_value)) { - zend_throw_ref_type_error_zval(prop, zv); - zval_ptr_dtor(&coerced_value); - return 0; + goto type_error; } } else if (Z_ISUNDEF(coerced_value)) { /* A previous property did not require coercion, but this one does, * so they are incompatible. */ - zend_throw_conflicting_coercion_error(first_prop, prop, zv); - return 0; + 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)) { - zend_throw_ref_type_error_zval(prop, zv); zval_ptr_dtor(&tmp); - zval_ptr_dtor(&coerced_value); - return 0; + goto type_error; } if (!zend_is_identical(&coerced_value, &tmp)) { - zend_throw_conflicting_coercion_error(first_prop, prop, zv); zval_ptr_dtor(&tmp); - zval_ptr_dtor(&coerced_value); - return 0; + goto conflicting_coercion_error; } } } else { @@ -3126,6 +3121,7 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference } 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; From ccbd868c63b99f8c883d837ebc448d0c44679616 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 13:08:58 +0100 Subject: [PATCH 45/48] Add test for iterable variance --- Zend/tests/type_declarations/union_types/variance/valid.phpt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Zend/tests/type_declarations/union_types/variance/valid.phpt b/Zend/tests/type_declarations/union_types/variance/valid.phpt index 47b54d11a3aad..f9e5cc498056c 100644 --- a/Zend/tests/type_declarations/union_types/variance/valid.phpt +++ b/Zend/tests/type_declarations/union_types/variance/valid.phpt @@ -8,17 +8,21 @@ class Y extends X {} class A { public X|Y $prop; + public iterable $prop2; public function method(int $a): int|float {} public function method2(B|string $a): A|string {} public function method3(Y|B $a): X|A {} + public function method4(Traversable|X $a): iterable|X {} } class B extends A { public X $prop; + public array|Traversable $prop2; public function method(int|float $a): int {} public function method2(A|string $a): B|string {} public function method3(A|X $a): B|Y {} + public function method4(iterable|X $a): Traversable|X {} } ?> From 54f9bd4495816460e84ca283f49997bebaa848f4 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 13:13:41 +0100 Subject: [PATCH 46/48] Fix accidental whitespace change --- Zend/zend_opcode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 8055d5bb09671..e197e9bf16ab5 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -142,8 +142,8 @@ ZEND_API void zend_function_dtor(zval *zv) if (function->type == ZEND_USER_FUNCTION) { ZEND_ASSERT(function->common.function_name); - destroy_op_array(&function->op_array); - /* op_arrays are allocated on arena, so we don't have to free them */ + destroy_op_array(&function->op_array); + /* op_arrays are allocated on arena, so we don't have to free them */ } else { ZEND_ASSERT(function->type == ZEND_INTERNAL_FUNCTION); ZEND_ASSERT(function->common.function_name); From c443f3b647ea5d39b4aeb4814c72e7e9d1206794 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 14:11:30 +0100 Subject: [PATCH 47/48] Apply cache slot fix to jit as well --- ext/opcache/jit/zend_jit_helpers.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 59014c7e8d4b4..0efd07c3dce6c 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1151,6 +1151,7 @@ static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_arra 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; From b1615d1335e52813881f5446a1634c4f17430ccf Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 14:24:47 +0100 Subject: [PATCH 48/48] Fix file cache --- ext/opcache/zend_file_cache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 96e08e8ab05be..049b0e9b48780 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -599,8 +599,8 @@ static void zend_file_cache_serialize_prop_info(zval *zv, if (prop->doc_comment) { SERIALIZE_STR(prop->doc_comment); } + zend_file_cache_serialize_type(&prop->type, script, info, buf); } - zend_file_cache_serialize_type(&prop->type, script, info, buf); } } @@ -1315,8 +1315,8 @@ static void zend_file_cache_unserialize_prop_info(zval *zv, if (prop->doc_comment) { UNSERIALIZE_STR(prop->doc_comment); } + zend_file_cache_unserialize_type(&prop->type, script, buf); } - zend_file_cache_unserialize_type(&prop->type, script, buf); } }