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 a96af71aa900..b0adeeed3366 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,16 @@ 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)) { + 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/elide_self_verify_return_type_for_this.phpt b/ext/opcache/tests/opt/elide_self_verify_return_type_for_this.phpt new file mode 100644 index 000000000000..70971cd84ffe --- /dev/null +++ b/ext/opcache/tests/opt/elide_self_verify_return_type_for_this.phpt @@ -0,0 +1,100 @@ +--TEST-- +Return type check elision +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +opcache.preload= +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=5, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-39 +0000 DECLARE_CLASS string("c4") +0001 DECLARE_CLASS string("i2") +0002 DECLARE_CLASS string("c5") +0003 DECLARE_CLASS_DELAYED string("c6") string("c5") +0004 RETURN int(1) + +C1::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:4-6 +0000 T0 = FETCH_THIS +0001 RETURN T0 + +C3::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:11-13 +0000 T0 = FETCH_THIS +0001 RETURN T0 + +C4::foo: + ; (lines=2, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:19-21 +0000 T0 = FETCH_THIS +0001 RETURN T0 + +C5::foo: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:27-29 +0000 T0 = FETCH_THIS +0001 VERIFY_RETURN_TYPE T0 +0002 RETURN T0 +LIVE RANGES: + 0: 0001 - 0002 (tmp/var) + +C6::foo: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:33-35 +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/elide_static_verify_return_type_for_this.phpt b/ext/opcache/tests/opt/elide_static_verify_return_type_for_this.phpt new file mode 100644 index 000000000000..05c62646854a --- /dev/null +++ b/ext/opcache/tests/opt/elide_static_verify_return_type_for_this.phpt @@ -0,0 +1,33 @@ +--TEST-- +Return type check elision +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +opcache.preload= +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_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