From c27e7fc31880e9eb913e0c51d7ed1b35c28b5e3e Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 5 May 2026 14:19:19 +0100 Subject: [PATCH 1/4] Add tests prior to optimization --- ...ace_verify_return_type_for_this_class.phpt | 62 ++++++++++++++++++ ...ace_verify_return_type_for_this_class.phpt | 59 +++++++++++++++++ ...ace_verify_return_type_for_this_class.phpt | 61 +++++++++++++++++ ...nce_verify_return_type_for_this_class.phpt | 65 +++++++++++++++++++ ...cit_verify_return_type_for_this_class.phpt | 54 +++++++++++++++ ...ent_verify_return_type_for_this_class.phpt | 54 +++++++++++++++ ...cit_verify_return_type_for_this_class.phpt | 52 +++++++++++++++ ...elf_verify_return_type_for_this_class.phpt | 52 +++++++++++++++ ...elf_verify_return_type_for_this_trait.phpt | 55 ++++++++++++++++ ...tic_verify_return_type_for_this_class.phpt | 55 ++++++++++++++++ ...tic_verify_return_type_for_this_trait.phpt | 55 ++++++++++++++++ 11 files changed, 624 insertions(+) create mode 100644 ext/opcache/tests/opt/verify_return_type/direct_extended_interface_verify_return_type_for_this_class.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/direct_interface_verify_return_type_for_this_class.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/inherited_interface_verify_return_type_for_this_class.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/inherited_interface_via_class_inheritance_verify_return_type_for_this_class.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/parent_explicit_verify_return_type_for_this_class.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/parent_verify_return_type_for_this_class.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/self_explicit_verify_return_type_for_this_class.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_class.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_trait.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_class.phpt create mode 100644 ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_trait.phpt 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..ae8ea84fea7f --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/direct_extended_interface_verify_return_type_for_this_class.phpt @@ -0,0 +1,62 @@ +--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=5, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:7-9 + ; 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-13 +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:7-9 +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/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..64f56b85b0b5 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/direct_interface_verify_return_type_for_this_class.phpt @@ -0,0 +1,59 @@ +--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=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=2, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-12 +0000 DECLARE_CLASS string("c") +0001 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_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..83df0bde2de5 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/parent_explicit_verify_return_type_for_this_class.phpt @@ -0,0 +1,54 @@ +--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=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=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..e232df9b5803 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/parent_verify_return_type_for_this_class.phpt @@ -0,0 +1,54 @@ +--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=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=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..289b8c66e0d1 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/self_explicit_verify_return_type_for_this_class.phpt @@ -0,0 +1,52 @@ +--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=5, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; 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=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..daddf47f3327 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_class.phpt @@ -0,0 +1,52 @@ +--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=5, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; 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=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..db6974f66fe3 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/self_verify_return_type_for_this_trait.phpt @@ -0,0 +1,55 @@ +--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=5, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; 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=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-10 +0000 RETURN int(1) + +T::foo: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:4-6 +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/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..7d6b959cfbb6 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_class.phpt @@ -0,0 +1,55 @@ +--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=5, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; 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=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-10 +0000 RETURN int(1) + +C::returnStatic: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:4-6 +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/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..f74d80349b03 --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type/static_verify_return_type_for_this_trait.phpt @@ -0,0 +1,55 @@ +--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=5, args=0, vars=0, tmps=1) + ; (before optimizer) + ; %s:4-6 + ; 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=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-10 +0000 RETURN int(1) + +T::returnStatic: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s:4-6 +0000 T0 = FETCH_THIS +0001 VERIFY_RETURN_TYPE T0 +0002 RETURN T0 +LIVE RANGES: + 0: 0001 - 0002 (tmp/var) From aaaafd893b49c2b43960dd1b96ef2bc9eb8d5e81 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 4 May 2026 21:14:46 +0100 Subject: [PATCH 2/4] Do not emit ZEND_VERIFY_RETURN_TYPE if we return $this and return value is static --- Zend/zend_compile.c | 11 +++++++++++ ...atic_verify_return_type_for_this_class.phpt | 18 ++++++------------ ...atic_verify_return_type_for_this_trait.phpt | 18 ++++++------------ 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ee57e1dafb87..36b9d4fd600b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2696,6 +2696,17 @@ static void zend_emit_return_type_check( /* we don't need run-time 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 */ + if (expr && ZEND_TYPE_CONTAINS_CODE(type, IS_STATIC)) { + const zend_op_array *op_array = CG(active_op_array); + zend_op previous = op_array->opcodes[op_array->last-1]; + if (previous.opcode == ZEND_FETCH_THIS) { + return; + } + } opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL); if (expr && expr->op_type == IS_CONST) { 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 index 7d6b959cfbb6..2b87b7cbc734 100644 --- 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 @@ -26,17 +26,14 @@ $_main: 0000 RETURN int(1) C::returnStatic: - ; (lines=5, args=0, vars=0, tmps=1) + ; (lines=4, args=0, vars=0, tmps=1) ; (before optimizer) ; %s:4-6 ; 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) +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null $_main: ; (lines=1, args=0, vars=0, tmps=0) @@ -45,11 +42,8 @@ $_main: 0000 RETURN int(1) C::returnStatic: - ; (lines=3, args=0, vars=0, tmps=1) + ; (lines=2, args=0, vars=0, tmps=1) ; (after optimizer) ; %s:4-6 0000 T0 = FETCH_THIS -0001 VERIFY_RETURN_TYPE T0 -0002 RETURN T0 -LIVE RANGES: - 0: 0001 - 0002 (tmp/var) +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 index f74d80349b03..fc8f852ba707 100644 --- 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 @@ -26,17 +26,14 @@ $_main: 0000 RETURN int(1) T::returnStatic: - ; (lines=5, args=0, vars=0, tmps=1) + ; (lines=4, args=0, vars=0, tmps=1) ; (before optimizer) ; %s:4-6 ; 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) +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null $_main: ; (lines=1, args=0, vars=0, tmps=0) @@ -45,11 +42,8 @@ $_main: 0000 RETURN int(1) T::returnStatic: - ; (lines=3, args=0, vars=0, tmps=1) + ; (lines=2, args=0, vars=0, tmps=1) ; (after optimizer) ; %s:4-6 0000 T0 = FETCH_THIS -0001 VERIFY_RETURN_TYPE T0 -0002 RETURN T0 -LIVE RANGES: - 0: 0001 - 0002 (tmp/var) +0001 RETURN T0 From dd69c23854ba7bb4b92994959c6496d771f444a4 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 5 May 2026 15:08:28 +0100 Subject: [PATCH 3/4] Optimizer: Implement basic unlinked interface traversal in safe_instanceof() --- Zend/Optimizer/dfa_pass.c | 46 ++++++++++++++++--- ...ace_verify_return_type_for_this_class.phpt | 7 +-- 2 files changed, 42 insertions(+), 11 deletions(-) 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/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 index 64f56b85b0b5..46737c9fc310 100644 --- 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 @@ -49,11 +49,8 @@ $_main: 0001 RETURN int(1) C::foo: - ; (lines=3, args=0, vars=0, tmps=1) + ; (lines=2, 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) +0001 RETURN T0 From 7a27ef4ec71f88d7f83f953910322e67c01d9fe3 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 5 May 2026 00:39:24 +0100 Subject: [PATCH 4/4] Elide return type check for directly provable instances of $this at compile time --- Zend/tests/return_types/025_2.phpt | 14 ++++ Zend/zend_compile.c | 66 +++++++++++++++++-- ...ace_verify_return_type_for_this_class.phpt | 18 ++--- ...ace_verify_return_type_for_this_class.phpt | 11 ++-- ...cit_verify_return_type_for_this_class.phpt | 11 ++-- ...ent_verify_return_type_for_this_class.phpt | 11 ++-- ...cit_verify_return_type_for_this_class.phpt | 11 ++-- ...elf_verify_return_type_for_this_class.phpt | 11 ++-- ...elf_verify_return_type_for_this_trait.phpt | 18 ++--- 9 files changed, 105 insertions(+), 66 deletions(-) create mode 100644 Zend/tests/return_types/025_2.phpt 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 36b9d4fd600b..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) /* {{{ */ { @@ -2696,16 +2748,16 @@ static void zend_emit_return_type_check( /* we don't need run-time 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 */ - if (expr && ZEND_TYPE_CONTAINS_CODE(type, IS_STATIC)) { - const zend_op_array *op_array = CG(active_op_array); - zend_op previous = op_array->opcodes[op_array->last-1]; - if (previous.opcode == ZEND_FETCH_THIS) { - return; - } + 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); 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 index ae8ea84fea7f..0ed9d39e6763 100644 --- 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 @@ -31,17 +31,14 @@ $_main: 0002 RETURN int(1) C::foo: - ; (lines=5, args=0, vars=0, tmps=1) + ; (lines=4, args=0, vars=0, tmps=1) ; (before optimizer) ; %s:7-9 ; 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) +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null $_main: ; (lines=3, args=0, vars=0, tmps=0) @@ -52,11 +49,8 @@ $_main: 0002 RETURN int(1) C::foo: - ; (lines=3, args=0, vars=0, tmps=1) + ; (lines=2, args=0, vars=0, tmps=1) ; (after optimizer) ; %s:7-9 0000 T0 = FETCH_THIS -0001 VERIFY_RETURN_TYPE T0 -0002 RETURN T0 -LIVE RANGES: - 0: 0001 - 0002 (tmp/var) +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 index 46737c9fc310..3f9b73f2feb6 100644 --- 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 @@ -29,17 +29,14 @@ $_main: 0001 RETURN int(1) C::foo: - ; (lines=5, args=0, vars=0, tmps=1) + ; (lines=4, 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) +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null $_main: ; (lines=2, args=0, vars=0, tmps=0) 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 index 83df0bde2de5..a1f5302f7bc2 100644 --- 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 @@ -28,17 +28,14 @@ $_main: 0000 RETURN int(1) C2::foo: - ; (lines=5, args=0, vars=0, tmps=1) + ; (lines=4, 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) +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null $_main: ; (lines=1, args=0, vars=0, tmps=0) 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 index e232df9b5803..2eafbfbb822d 100644 --- 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 @@ -28,17 +28,14 @@ $_main: 0000 RETURN int(1) C2::foo: - ; (lines=5, args=0, vars=0, tmps=1) + ; (lines=4, 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) +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null $_main: ; (lines=1, args=0, vars=0, tmps=0) 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 index 289b8c66e0d1..ef138191ba33 100644 --- 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 @@ -26,17 +26,14 @@ $_main: 0000 RETURN int(1) C::foo: - ; (lines=5, args=0, vars=0, tmps=1) + ; (lines=4, args=0, vars=0, tmps=1) ; (before optimizer) ; %s:4-6 ; 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) +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null $_main: ; (lines=1, args=0, vars=0, tmps=0) 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 index daddf47f3327..b59d919ea49f 100644 --- 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 @@ -26,17 +26,14 @@ $_main: 0000 RETURN int(1) C::foo: - ; (lines=5, args=0, vars=0, tmps=1) + ; (lines=4, args=0, vars=0, tmps=1) ; (before optimizer) ; %s:4-6 ; 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) +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null $_main: ; (lines=1, args=0, vars=0, tmps=0) 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 index db6974f66fe3..924d96cb0732 100644 --- 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 @@ -26,17 +26,14 @@ $_main: 0000 RETURN int(1) T::foo: - ; (lines=5, args=0, vars=0, tmps=1) + ; (lines=4, args=0, vars=0, tmps=1) ; (before optimizer) ; %s:4-6 ; 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) +0001 RETURN T0 +0002 VERIFY_RETURN_TYPE +0003 RETURN null $_main: ; (lines=1, args=0, vars=0, tmps=0) @@ -45,11 +42,8 @@ $_main: 0000 RETURN int(1) T::foo: - ; (lines=3, args=0, vars=0, tmps=1) + ; (lines=2, args=0, vars=0, tmps=1) ; (after optimizer) ; %s:4-6 0000 T0 = FETCH_THIS -0001 VERIFY_RETURN_TYPE T0 -0002 RETURN T0 -LIVE RANGES: - 0: 0001 - 0002 (tmp/var) +0001 RETURN T0