diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index 77dc322fbdec..3b003f725068 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -252,15 +252,49 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa, zend_op free_alloca(shiftlist, use_heap); } -static bool safe_instanceof(const zend_class_entry *ce1, const zend_class_entry *ce2) { +static bool safe_instanceof( + const zend_class_entry *ce1, + const zend_class_entry *ce2, + const zend_script *script, + const zend_op_array *op_array +) { if (ce1 == ce2) { return true; } - if (!(ce1->ce_flags & ZEND_ACC_LINKED)) { - /* This case could be generalized, similarly to unlinked_instanceof */ - return false; + if (ce1->ce_flags & ZEND_ACC_LINKED) { + return instanceof_function(ce1, ce2); + } + + /* TODO Handle unlinked parents ike in unlinked_instanceof()? */ + + if (ce1->num_interfaces) { + uint32_t i; + if (ce1->ce_flags & ZEND_ACC_RESOLVED_INTERFACES) { + /* Unlike the normal instanceof_function(), we have to perform a recursive + * check here, as the parent interfaces might not have been fully copied yet. */ + for (i = 0; i < ce1->num_interfaces; i++) { + if (safe_instanceof(ce1->interfaces[i], ce2, script, op_array)) { + return true; + } + } + } else { + for (i = 0; i < ce1->num_interfaces; i++) { + const zend_class_entry *ce = zend_optimizer_get_class_entry(script, op_array, ce1->interface_names[i].lc_name); + if (!ce) { + continue; + } + /* Avoid recursing if class implements itself. */ + if (ce == ce1) { + continue; + } + if (safe_instanceof(ce, ce2, script, op_array)) { + return true; + } + } + } } - return instanceof_function(ce1, ce2); + + return false; } static inline bool can_elide_list_type( @@ -280,7 +314,7 @@ static inline bool can_elide_list_type( zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(*single_type)); const zend_class_entry *ce = zend_optimizer_get_class_entry(script, op_array, lcname); zend_string_release(lcname); - bool result = ce && safe_instanceof(use_info->ce, ce); + bool result = ce && safe_instanceof(use_info->ce, ce, script, op_array); if (result == !is_intersection) { return result; } diff --git a/Zend/tests/return_types/025_2.phpt b/Zend/tests/return_types/025_2.phpt new file mode 100644 index 000000000000..50683c5c1267 --- /dev/null +++ b/Zend/tests/return_types/025_2.phpt @@ -0,0 +1,14 @@ +--TEST-- +Return type of self is allowed in closure but $this return value must be checked as closure might not be bound to a class +--FILE-- +getMessage(), PHP_EOL; +} +?> +--EXPECT-- +Error: Using $this when not in object context diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ee57e1dafb87..81db7532de19 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2642,6 +2642,58 @@ static void zend_compile_memoized_expr(znode *result, zend_ast *expr, uint32_t t } /* }}} */ +static bool zend_is_this_instance_of_name(const zend_string *type_name) +{ + if (zend_string_equals_ci(CG(active_class_entry)->name, type_name)) { + return true; + } + if (zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_SELF))) { + return true; + } + if (zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_PARENT))) { + return true; + } + + ZEND_ASSERT((CG(active_class_entry)->ce_flags & ZEND_ACC_LINKED) == 0); + if (CG(active_class_entry)->num_interfaces) { + for (uint32_t i = 0; i < CG(active_class_entry)->num_interfaces; i++) { + if (zend_string_equals_ci(CG(active_class_entry)->interface_names[i].lc_name, type_name)) { + return true; + } + } + } + const zend_string *parent_name = CG(active_class_entry)->parent_name; + if (parent_name && zend_string_equals_ci(parent_name, type_name)) { + return true; + } + + return false; +} + +static bool zend_is_this_valid_for_return_type(zend_type type) +{ + /* Closures can be bound to a class scope, however it might not and this must type error */ + if (CG(active_op_array)->fn_flags & ZEND_ACC_CLOSURE) { + return false; + } + + if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_OBJECT|MAY_BE_STATIC)) { + return true; + } + + const zend_type *single_type; + ZEND_TYPE_FOREACH(type, single_type) { + if (ZEND_TYPE_HAS_NAME(*single_type)) { + const zend_string *name = ZEND_TYPE_NAME(*single_type); + if (zend_is_this_instance_of_name(name)) { + return true; + } + } + } ZEND_TYPE_FOREACH_END(); + + return false; +} + static void zend_emit_return_type_check( znode *expr, const zend_arg_info *return_info, bool implicit) /* {{{ */ { @@ -2697,6 +2749,17 @@ static void zend_emit_return_type_check( return; } + /* If return type contains static and we are returning $this + * (determined by checking if the previous opcode is ZEND_FETCH_THIS) + * then we don't need to check the return type */ + const zend_op_array *op_array = CG(active_op_array); + if (expr && op_array->last >= 1 + && op_array->opcodes[op_array->last-1].opcode == ZEND_FETCH_THIS + && zend_is_this_valid_for_return_type(type)) { + ZEND_ASSERT((expr->op_type & (IS_VAR|IS_TMP_VAR)) && expr->u.op.var == op_array->opcodes[op_array->last-1].result.var); + return; + } + opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL); if (expr && expr->op_type == IS_CONST) { opline->result_type = expr->op_type = IS_TMP_VAR; diff --git a/ext/opcache/tests/opt/verify_return_type/direct_extended_interface_verify_return_type_for_this_class.phpt b/ext/opcache/tests/opt/verify_return_type/direct_extended_interface_verify_return_type_for_this_class.phpt new file mode 100644 index 000000000000..0ed9d39e6763 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/direct_extended_interface_verify_return_type_for_this_class.phpt @@ -0,0 +1,56 @@ +--TEST-- +Return type check elision for direct interface return type and $this in class method when interface extends another one +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=3, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-13 + ; return [] RANGE[0..0] +0000 DECLARE_CLASS string("i2") +0001 DECLARE_CLASS string("c") +0002 RETURN int(1) + +C::foo: + ; (lines=4, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:7-9 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null + +$_main: + ; (lines=3, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-13 +0000 DECLARE_CLASS string("i2") +0001 DECLARE_CLASS string("c") +0002 RETURN int(1) + +C::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:7-9 +0000 T0 = FETCH_THIS +0001 RETURN T0 diff --git a/ext/opcache/tests/opt/verify_return_type/direct_interface_verify_return_type_for_this_class.phpt b/ext/opcache/tests/opt/verify_return_type/direct_interface_verify_return_type_for_this_class.phpt new file mode 100644 index 000000000000..3f9b73f2feb6 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/direct_interface_verify_return_type_for_this_class.phpt @@ -0,0 +1,53 @@ +--TEST-- +Return type check elision for direct interface return type and $this in class method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=2, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-12 + ; return [] RANGE[0..0] +0000 DECLARE_CLASS string("c") +0001 RETURN int(1) + +C::foo: + ; (lines=4, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:6-8 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null + +$_main: + ; (lines=2, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-12 +0000 DECLARE_CLASS string("c") +0001 RETURN int(1) + +C::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:6-8 +0000 T0 = FETCH_THIS +0001 RETURN T0 diff --git a/ext/opcache/tests/opt/verify_return_type/inherited_interface_verify_return_type_for_this_class.phpt b/ext/opcache/tests/opt/verify_return_type/inherited_interface_verify_return_type_for_this_class.phpt new file mode 100644 index 000000000000..f585af5919cc --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/inherited_interface_verify_return_type_for_this_class.phpt @@ -0,0 +1,61 @@ +--TEST-- +Return type check elision for inherited interface return type and $this in class method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=3, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-12 + ; return [] RANGE[0..0] +0000 DECLARE_CLASS string("i2") +0001 DECLARE_CLASS string("c") +0002 RETURN int(1) + +C::foo: + ; (lines=5, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:6-8 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 VERIFY_RETURN_TYPE T0 +0002 RETURN T0 +0003 VERIFY_RETURN_TYPE +0004 RETURN null +LIVE RANGES: + 0: 0001 - 0002 (tmp/var) + +$_main: + ; (lines=3, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-12 +0000 DECLARE_CLASS string("i2") +0001 DECLARE_CLASS string("c") +0002 RETURN int(1) + +C::foo: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:6-8 +0000 T0 = FETCH_THIS +0001 VERIFY_RETURN_TYPE T0 +0002 RETURN T0 +LIVE RANGES: + 0: 0001 - 0002 (tmp/var) diff --git a/ext/opcache/tests/opt/verify_return_type/inherited_interface_via_class_inheritance_verify_return_type_for_this_class.phpt b/ext/opcache/tests/opt/verify_return_type/inherited_interface_via_class_inheritance_verify_return_type_for_this_class.phpt new file mode 100644 index 000000000000..49c0b922f47b --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/inherited_interface_via_class_inheritance_verify_return_type_for_this_class.phpt @@ -0,0 +1,65 @@ +--TEST-- +Return type check elision for inherited interface via class extension return type and $this in class method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=4, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-14 + ; return [] RANGE[0..0] +0000 DECLARE_CLASS string("i2") +0001 DECLARE_CLASS string("c1") +0002 DECLARE_CLASS_DELAYED string("c2") string("c1") +0003 RETURN int(1) + +C2::foo: + ; (lines=5, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:8-10 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 VERIFY_RETURN_TYPE T0 +0002 RETURN T0 +0003 VERIFY_RETURN_TYPE +0004 RETURN null +LIVE RANGES: + 0: 0001 - 0002 (tmp/var) + +$_main: + ; (lines=4, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-14 +0000 DECLARE_CLASS string("i2") +0001 DECLARE_CLASS string("c1") +0002 DECLARE_CLASS_DELAYED string("c2") string("c1") +0003 RETURN int(1) + +C2::foo: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:8-10 +0000 T0 = FETCH_THIS +0001 VERIFY_RETURN_TYPE T0 +0002 RETURN T0 +LIVE RANGES: + 0: 0001 - 0002 (tmp/var) diff --git a/ext/opcache/tests/opt/verify_return_type/parent_explicit_verify_return_type_for_this_class.phpt b/ext/opcache/tests/opt/verify_return_type/parent_explicit_verify_return_type_for_this_class.phpt new file mode 100644 index 000000000000..a1f5302f7bc2 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/parent_explicit_verify_return_type_for_this_class.phpt @@ -0,0 +1,51 @@ +--TEST-- +Return type check elision for explicit parent return type and $this in class method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-12 + ; return [] RANGE[0..0] +0000 RETURN int(1) + +C2::foo: + ; (lines=4, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:6-8 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null + +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-12 +0000 RETURN int(1) + +C2::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:6-8 +0000 T0 = FETCH_THIS +0001 RETURN T0 diff --git a/ext/opcache/tests/opt/verify_return_type/parent_verify_return_type_for_this_class.phpt b/ext/opcache/tests/opt/verify_return_type/parent_verify_return_type_for_this_class.phpt new file mode 100644 index 000000000000..2eafbfbb822d --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/parent_verify_return_type_for_this_class.phpt @@ -0,0 +1,51 @@ +--TEST-- +Return type check elision for parent return type and $this in class method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-12 + ; return [] RANGE[0..0] +0000 RETURN int(1) + +C2::foo: + ; (lines=4, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:6-8 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null + +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-12 +0000 RETURN int(1) + +C2::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:6-8 +0000 T0 = FETCH_THIS +0001 RETURN T0 diff --git a/ext/opcache/tests/opt/verify_return_type/self_explicit_verify_return_type_for_this_class.phpt b/ext/opcache/tests/opt/verify_return_type/self_explicit_verify_return_type_for_this_class.phpt new file mode 100644 index 000000000000..ef138191ba33 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/self_explicit_verify_return_type_for_this_class.phpt @@ -0,0 +1,49 @@ +--TEST-- +Return type check elision for explicit self return type and $this in class method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-10 + ; return [] RANGE[0..0] +0000 RETURN int(1) + +C::foo: + ; (lines=4, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null + +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-10 +0000 RETURN int(1) + +C::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:4-6 +0000 T0 = FETCH_THIS +0001 RETURN T0 diff --git a/ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_class.phpt b/ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_class.phpt new file mode 100644 index 000000000000..b59d919ea49f --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_class.phpt @@ -0,0 +1,49 @@ +--TEST-- +Return type check elision for self return type and $this in class method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-10 + ; return [] RANGE[0..0] +0000 RETURN int(1) + +C::foo: + ; (lines=4, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null + +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-10 +0000 RETURN int(1) + +C::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:4-6 +0000 T0 = FETCH_THIS +0001 RETURN T0 diff --git a/ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_trait.phpt b/ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_trait.phpt new file mode 100644 index 000000000000..924d96cb0732 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_trait.phpt @@ -0,0 +1,49 @@ +--TEST-- +Return type check elision for self return type and $this in trait method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-10 + ; return [] RANGE[0..0] +0000 RETURN int(1) + +T::foo: + ; (lines=4, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null + +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-10 +0000 RETURN int(1) + +T::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:4-6 +0000 T0 = FETCH_THIS +0001 RETURN T0 diff --git a/ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_class.phpt b/ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_class.phpt new file mode 100644 index 000000000000..2b87b7cbc734 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_class.phpt @@ -0,0 +1,49 @@ +--TEST-- +Return type check elision for static return type and $this in class method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-10 + ; return [] RANGE[0..0] +0000 RETURN int(1) + +C::returnStatic: + ; (lines=4, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null + +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-10 +0000 RETURN int(1) + +C::returnStatic: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:4-6 +0000 T0 = FETCH_THIS +0001 RETURN T0 diff --git a/ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_trait.phpt b/ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_trait.phpt new file mode 100644 index 000000000000..fc8f852ba707 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_trait.phpt @@ -0,0 +1,49 @@ +--TEST-- +Return type check elision for static return type and $this in trait method +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x30000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (before optimizer) + ; %s:1-10 + ; return [] RANGE[0..0] +0000 RETURN int(1) + +T::returnStatic: + ; (lines=4, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; return [] RANGE[0..0] +0000 T0 = FETCH_THIS +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null + +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-10 +0000 RETURN int(1) + +T::returnStatic: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:4-6 +0000 T0 = FETCH_THIS +0001 RETURN T0