diff options
author | Nikita Popov <nikita.ppv@gmail.com> | 2019-05-08 12:30:16 +0200 |
---|---|---|
committer | Nikita Popov <nikita.ppv@gmail.com> | 2019-05-24 09:30:37 +0200 |
commit | 49a3b03e9fa3b6a7ef5302a48203b03e9d870ce9 (patch) | |
tree | e50a48fcc488996a3f86361fb22609e273c155e0 /Zend/zend_inheritance.c | |
parent | afec3a9208c2a34762a87cb797f28e7e61c88cca (diff) | |
download | php-git-49a3b03e9fa3b6a7ef5302a48203b03e9d870ce9.tar.gz |
Implement basic variance support
This is a minimal variance implementation: It does not support any
cyclic type dependencies. Additionally the preloading requirements
are much more restrictive than necessary. Hopefully we can relax
these in the future.
Diffstat (limited to 'Zend/zend_inheritance.c')
-rw-r--r-- | Zend/zend_inheritance.c | 205 |
1 files changed, 131 insertions, 74 deletions
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index eb3886268f..36d765386d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -22,6 +22,7 @@ #include "zend_compile.h" #include "zend_execute.h" #include "zend_inheritance.h" +#include "zend_interfaces.h" #include "zend_smart_str.h" #include "zend_operators.h" @@ -173,7 +174,7 @@ static zend_string *resolve_class_name(const zend_function *fe, zend_string *nam zend_class_entry *ce = fe->common.scope; ZEND_ASSERT(ce); if (zend_string_equals_literal_ci(name, "parent") && ce->parent) { - if (ce->ce_flags & ZEND_ACC_LINKED) { + if (ce->ce_flags & (ZEND_ACC_LINKED|ZEND_ACC_LINKING_IN_PROGRESS)) { return ce->parent->name; } else { return ce->parent_name; @@ -198,16 +199,18 @@ static zend_bool class_visible(zend_class_entry *ce) { static zend_class_entry *lookup_class(const zend_function *fe, zend_string *name) { zend_class_entry *ce; if (!CG(in_compilation)) { - return zend_lookup_class(name); - } - - ce = zend_lookup_class_ex(name, NULL, /* autoload */ 0); - if (ce && class_visible(ce)) { - return ce; + ce = zend_lookup_class(name); + if (ce) { + return ce; + } + } else { + ce = zend_lookup_class_ex(name, NULL, /* autoload */ 0); + if (ce && class_visible(ce)) { + return ce; + } } - /* When checking whether early binding is possible, the current class will not be registered - * yet, so check for it explicitly. */ + /* The current class may not be registered yet, so check for it explicitly. */ if (zend_string_equals_ci(fe->common.scope->name, name)) { return fe->common.scope; } @@ -215,6 +218,15 @@ static zend_class_entry *lookup_class(const zend_function *fe, zend_string *name return NULL; } +/* Instanceof that's safe to use on unlinked classes. For the unlinked case, we only handle + * class identity here. */ +static zend_bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) { + if ((ce1->ce_flags & (ZEND_ACC_LINKED|ZEND_ACC_LINKING_IN_PROGRESS))) { + return instanceof_function(ce1, ce2); + } + return ce1 == ce2; +} + /* 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. */ @@ -225,6 +237,7 @@ typedef enum { } inheritance_status; 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) /* {{{ */ { @@ -235,52 +248,72 @@ static inheritance_status zend_perform_covariant_type_check( return INHERITANCE_ERROR; } - if (ZEND_TYPE_IS_CLASS(fe_type) && ZEND_TYPE_IS_CLASS(proto_type)) { - zend_string *fe_class_name = resolve_class_name(fe, ZEND_TYPE_NAME(fe_type)); - zend_string *proto_class_name = resolve_class_name(proto, ZEND_TYPE_NAME(proto_type)); - - if (fe_class_name != proto_class_name && strcasecmp(ZSTR_VAL(fe_class_name), ZSTR_VAL(proto_class_name)) != 0) { - if (fe->common.type != ZEND_USER_FUNCTION) { - return INHERITANCE_ERROR; - } else { - /* Check for class alias */ - zend_class_entry *fe_ce, *proto_ce; + 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)) { + return INHERITANCE_ERROR; + } - fe_ce = lookup_class(fe, fe_class_name); - proto_ce = lookup_class(proto, proto_class_name); + fe_class_name = resolve_class_name(fe, ZEND_TYPE_NAME(fe_type)); + proto_class_name = resolve_class_name(proto, ZEND_TYPE_NAME(proto_type)); + if (zend_string_equals_ci(fe_class_name, proto_class_name)) { + return INHERITANCE_SUCCESS; + } - if (!fe_ce || !proto_ce) { - return INHERITANCE_UNRESOLVED; - } + fe_ce = lookup_class(fe, fe_class_name); + if (!fe_ce) { + *unresolved_class = fe_class_name; + return INHERITANCE_UNRESOLVED; + } - if (fe_ce->type == ZEND_INTERNAL_CLASS || - proto_ce->type == ZEND_INTERNAL_CLASS || - fe_ce != proto_ce) { - return INHERITANCE_ERROR; - } - } + proto_ce = lookup_class(proto, proto_class_name); + if (!proto_ce) { + *unresolved_class = proto_class_name; + return INHERITANCE_UNRESOLVED; } - } else if (ZEND_TYPE_CODE(fe_type) != ZEND_TYPE_CODE(proto_type)) { - if (ZEND_TYPE_CODE(proto_type) == IS_ITERABLE) { - if (ZEND_TYPE_CODE(fe_type) == IS_ARRAY) { - return INHERITANCE_SUCCESS; - } - if (ZEND_TYPE_IS_CLASS(fe_type) && - zend_string_equals_literal_ci(ZEND_TYPE_NAME(fe_type), "Traversable")) { - return INHERITANCE_SUCCESS; + return unlinked_instanceof(fe_ce, proto_ce) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } else if (ZEND_TYPE_CODE(proto_type) == IS_ITERABLE) { + if (ZEND_TYPE_IS_CLASS(fe_type)) { + zend_string *fe_class_name = resolve_class_name(fe, ZEND_TYPE_NAME(fe_type)); + zend_class_entry *fe_ce = lookup_class(fe, fe_class_name); + if (!fe_ce) { + *unresolved_class = fe_class_name; + return INHERITANCE_UNRESOLVED; + } + return unlinked_instanceof(fe_ce, zend_ce_traversable) + ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } + + return ZEND_TYPE_CODE(fe_type) == IS_ITERABLE || ZEND_TYPE_CODE(fe_type) == IS_ARRAY + ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } else if (ZEND_TYPE_CODE(proto_type) == IS_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, ZEND_TYPE_NAME(fe_type)); + zend_class_entry *fe_ce = lookup_class(fe, fe_class_name); + if (!fe_ce) { + *unresolved_class = fe_class_name; + return INHERITANCE_UNRESOLVED; } + return INHERITANCE_SUCCESS; } - /* Incompatible built-in types */ - return INHERITANCE_ERROR; + return ZEND_TYPE_CODE(fe_type) == IS_OBJECT ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } else { + return ZEND_TYPE_CODE(fe_type) == ZEND_TYPE_CODE(proto_type) + ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } - - return INHERITANCE_SUCCESS; } /* }}} */ -static inheritance_status zend_do_perform_arg_type_hint_check(const zend_function *fe, zend_arg_info *fe_arg_info, const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ +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) /* {{{ */ { if (!ZEND_TYPE_IS_SET(fe_arg_info->type)) { /* Child with no type is always compatible */ @@ -294,11 +327,13 @@ static inheritance_status zend_do_perform_arg_type_hint_check(const zend_functio /* 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( + unresolved_class, proto, proto_arg_info, fe, fe_arg_info); } /* }}} */ -static inheritance_status zend_do_perform_implementation_check(const zend_function *fe, const zend_function *proto) /* {{{ */ +static inheritance_status zend_do_perform_implementation_check( + zend_string **unresolved_class, const zend_function *fe, const zend_function *proto) /* {{{ */ { uint32_t i, num_args; inheritance_status status, local_status; @@ -368,7 +403,8 @@ static inheritance_status zend_do_perform_implementation_check(const zend_functi proto_arg_info = &proto->common.arg_info[proto->common.num_args]; } - local_status = zend_do_perform_arg_type_hint_check(fe, fe_arg_info, proto, proto_arg_info); + local_status = zend_do_perform_arg_type_hint_check( + unresolved_class, fe, fe_arg_info, proto, proto_arg_info); if (local_status == INHERITANCE_ERROR) { return INHERITANCE_ERROR; } @@ -390,7 +426,8 @@ static inheritance_status zend_do_perform_implementation_check(const zend_functi return INHERITANCE_ERROR; } - local_status = zend_perform_covariant_type_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1); + local_status = zend_perform_covariant_type_check( + unresolved_class, fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1); if (local_status == INHERITANCE_ERROR) { return INHERITANCE_ERROR; } @@ -568,10 +605,30 @@ static zend_always_inline uint32_t func_lineno(zend_function *fn) { return fn->common.type == ZEND_USER_FUNCTION ? fn->op_array.line_start : 0; } +static void ZEND_COLD emit_incompatible_method_error( + int error_level, const char *error_verb, zend_function *child, zend_function *parent, + inheritance_status status, zend_string *unresolved_class) { + zend_string *parent_prototype = zend_get_function_declaration(parent); + zend_string *child_prototype = zend_get_function_declaration(child); + if (status == INHERITANCE_UNRESOLVED) { + zend_error_at(error_level, 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)); + } else { + zend_error_at(error_level, NULL, func_lineno(child), + "Declaration of %s %s be compatible with %s", + ZSTR_VAL(child_prototype), error_verb, ZSTR_VAL(parent_prototype)); + } + zend_string_efree(child_prototype); + zend_string_efree(parent_prototype); +} + static void do_inheritance_check_on_method(zend_function *child, zend_function *parent, zend_class_entry *ce, zval *child_zv) /* {{{ */ { uint32_t child_flags; uint32_t parent_flags = parent->common.fn_flags; + inheritance_status status; + zend_string *unresolved_class; if (UNEXPECTED(parent_flags & ZEND_ACC_FINAL)) { zend_error_at_noreturn(E_COMPILE_ERROR, NULL, func_lineno(child), @@ -650,12 +707,10 @@ static void do_inheritance_check_on_method(zend_function *child, zend_function * ZEND_FN_SCOPE_NAME(child), ZSTR_VAL(child->common.function_name), zend_visibility_string(parent_flags), ZEND_FN_SCOPE_NAME(parent), (parent_flags&ZEND_ACC_PUBLIC) ? "" : " or weaker"); } - if (UNEXPECTED(zend_do_perform_implementation_check(child, parent) != INHERITANCE_SUCCESS)) { + status = zend_do_perform_implementation_check(&unresolved_class, child, parent); + if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { int error_level; const char *error_verb; - zend_string *method_prototype = zend_get_function_declaration(parent); - zend_string *child_prototype = zend_get_function_declaration(child); - if (child->common.prototype && ( child->common.prototype->common.fn_flags & ZEND_ACC_ABSTRACT )) { @@ -663,18 +718,15 @@ static void do_inheritance_check_on_method(zend_function *child, zend_function * error_verb = "must"; } else if ((parent->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) && (!(child->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) || - zend_perform_covariant_type_check(child, child->common.arg_info - 1, parent, parent->common.arg_info - 1) != INHERITANCE_SUCCESS)) { + zend_perform_covariant_type_check(&unresolved_class, child, child->common.arg_info - 1, parent, parent->common.arg_info - 1) != INHERITANCE_SUCCESS)) { error_level = E_COMPILE_ERROR; error_verb = "must"; } else { error_level = E_WARNING; error_verb = "should"; } - zend_error_at(error_level, NULL, func_lineno(child), - "Declaration of %s %s be compatible with %s", - ZSTR_VAL(child_prototype), error_verb, ZSTR_VAL(method_prototype)); - zend_string_efree(child_prototype); - zend_string_efree(method_prototype); + emit_incompatible_method_error( + error_level, error_verb, child, parent, status, unresolved_class); } } } while (0); @@ -1383,6 +1435,8 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s { zend_function *existing_fn = NULL; zend_function *new_fn; + zend_string *unresolved_class; + inheritance_status status; if ((existing_fn = zend_hash_find_ptr(&ce->function_table, key)) != NULL) { /* if it is the same function with the same visibility and has not been assigned a class scope yet, regardless @@ -1400,18 +1454,20 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s if ((existing_fn = zend_hash_find_ptr(*overridden, key)) != NULL) { if (existing_fn->common.fn_flags & ZEND_ACC_ABSTRACT) { /* Make sure the trait method is compatible with previosly declared abstract method */ - if (UNEXPECTED(zend_do_perform_implementation_check(fn, existing_fn) != INHERITANCE_SUCCESS)) { - zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", - ZSTR_VAL(zend_get_function_declaration(fn)), - ZSTR_VAL(zend_get_function_declaration(existing_fn))); + status = zend_do_perform_implementation_check( + &unresolved_class, fn, existing_fn); + if (status != INHERITANCE_SUCCESS) { + emit_incompatible_method_error( + E_COMPILE_ERROR, "must", fn, existing_fn, status, unresolved_class); } } if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) { /* Make sure the abstract declaration is compatible with previous declaration */ - if (UNEXPECTED(zend_do_perform_implementation_check(existing_fn, fn) != INHERITANCE_SUCCESS)) { - zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", - ZSTR_VAL(zend_get_function_declaration(existing_fn)), - ZSTR_VAL(zend_get_function_declaration(fn))); + status = zend_do_perform_implementation_check( + &unresolved_class, existing_fn, fn); + if (status != INHERITANCE_SUCCESS) { + emit_incompatible_method_error( + E_COMPILE_ERROR, "must", existing_fn, fn, status, unresolved_class); } return; } @@ -1425,17 +1481,17 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s } else if (existing_fn->common.fn_flags & ZEND_ACC_ABSTRACT && (existing_fn->common.scope->ce_flags & ZEND_ACC_INTERFACE) == 0) { /* Make sure the trait method is compatible with previosly declared abstract method */ - if (UNEXPECTED(zend_do_perform_implementation_check(fn, existing_fn) != INHERITANCE_SUCCESS)) { - zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", - ZSTR_VAL(zend_get_function_declaration(fn)), - ZSTR_VAL(zend_get_function_declaration(existing_fn))); + status = zend_do_perform_implementation_check(&unresolved_class, fn, existing_fn); + if (status != INHERITANCE_SUCCESS) { + emit_incompatible_method_error( + E_COMPILE_ERROR, "must", fn, existing_fn, status, unresolved_class); } } else if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) { /* Make sure the abstract declaration is compatible with previous declaration */ - if (UNEXPECTED(zend_do_perform_implementation_check(existing_fn, fn) != INHERITANCE_SUCCESS)) { - zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", - ZSTR_VAL(zend_get_function_declaration(existing_fn)), - ZSTR_VAL(zend_get_function_declaration(fn))); + status = zend_do_perform_implementation_check(&unresolved_class, existing_fn, fn); + if (status != INHERITANCE_SUCCESS) { + emit_incompatible_method_error( + E_COMPILE_ERROR, "must", existing_fn, fn, status, unresolved_class); } return; } else if (UNEXPECTED(existing_fn->common.scope->ce_flags & ZEND_ACC_TRAIT)) { @@ -2102,12 +2158,13 @@ zend_bool zend_can_early_bind(zend_class_entry *ce, zend_class_entry *parent_ce) ZEND_HASH_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, parent_func) { uint32_t parent_flags = parent_func->common.fn_flags; zend_function *func = zend_hash_find_ptr(&ce->function_table, key); + zend_string *unresolved_class; if (!func || (parent_flags & ZEND_ACC_PRIVATE) || ((parent_flags & ZEND_ACC_CTOR) && !(parent_flags & ZEND_ACC_ABSTRACT)) ) { continue; } - if (zend_do_perform_implementation_check(func, parent_func) == INHERITANCE_UNRESOLVED) { + if (zend_do_perform_implementation_check(&unresolved_class, func, parent_func) == INHERITANCE_UNRESOLVED) { return 0; } } ZEND_HASH_FOREACH_END(); |