Commit e721b3fdc35 for php.net
commit e721b3fdc35dc74121ed27ed43c2f9568f321bee
Author: Osama Aldemeery <aldemeery@gmail.com>
Date: Tue Aug 18 22:09:21 2026 +0300
Deprecate returning from a finally block. (#22509)
RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_returning_from_a_finally_block
diff --git a/Zend/tests/exit_finally_3.phpt b/Zend/tests/exit_finally_3.phpt
index 12d7747adea..a2ae2429b28 100644
--- a/Zend/tests/exit_finally_3.phpt
+++ b/Zend/tests/exit_finally_3.phpt
@@ -15,5 +15,6 @@ function test() {
var_dump(test());
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
Exit
diff --git a/Zend/tests/gc/gc_050.phpt b/Zend/tests/gc/gc_050.phpt
index 0bedc7220fd..cf30776d9de 100644
--- a/Zend/tests/gc/gc_050.phpt
+++ b/Zend/tests/gc/gc_050.phpt
@@ -36,5 +36,6 @@ function f(int $n): object {
}
echo "OK\n";
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
OK
diff --git a/Zend/tests/generators/finally/return_return.phpt b/Zend/tests/generators/finally/return_return.phpt
index 194fc93de7c..895adc37cce 100644
--- a/Zend/tests/generators/finally/return_return.phpt
+++ b/Zend/tests/generators/finally/return_return.phpt
@@ -27,7 +27,8 @@ function gen() {
$gen->rewind(); // force run
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
before return
before return in inner finally
outer finally run
diff --git a/Zend/tests/generators/finally/yield_return.phpt b/Zend/tests/generators/finally/yield_return.phpt
index 6191f389e53..dbfabea5088 100644
--- a/Zend/tests/generators/finally/yield_return.phpt
+++ b/Zend/tests/generators/finally/yield_return.phpt
@@ -15,5 +15,6 @@ function foo($f, $t) {
echo $x, "\n";
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
1
diff --git a/Zend/tests/generators/get_return_and_finally.phpt b/Zend/tests/generators/get_return_and_finally.phpt
index 150e5b83c4d..8ced4d0008b 100644
--- a/Zend/tests/generators/get_return_and_finally.phpt
+++ b/Zend/tests/generators/get_return_and_finally.phpt
@@ -41,7 +41,8 @@ function gen2() {
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
int(42)
gen2() throw
Cannot get return value of a generator that hasn't returned
diff --git a/Zend/tests/generators/gh11028_1.phpt b/Zend/tests/generators/gh11028_1.phpt
index e1e7aa5019e..d1c07dc9de6 100644
--- a/Zend/tests/generators/gh11028_1.phpt
+++ b/Zend/tests/generators/gh11028_1.phpt
@@ -24,7 +24,8 @@ function test($msg, $x) {
test("true", true);
test("object", new stdClass);
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
yield null
Keys must be of type int|string during array unpacking
yield false
diff --git a/Zend/tests/generators/gh11028_2.phpt b/Zend/tests/generators/gh11028_2.phpt
index ddc9618e8ed..bf364329849 100644
--- a/Zend/tests/generators/gh11028_2.phpt
+++ b/Zend/tests/generators/gh11028_2.phpt
@@ -15,6 +15,8 @@
})()[0];
?>
--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
+
Warning: Undefined variable $a in %s on line %d
Fatal error: Uncaught Error: Keys must be of type int|string during array unpacking in %s:%d
diff --git a/Zend/tests/generators/gh11028_3.phpt b/Zend/tests/generators/gh11028_3.phpt
index 7ea1aac6f6c..c58e00dc09b 100644
--- a/Zend/tests/generators/gh11028_3.phpt
+++ b/Zend/tests/generators/gh11028_3.phpt
@@ -17,5 +17,6 @@ function generator() {
echo $e->getMessage(), "\n";
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
exception
diff --git a/Zend/tests/generators/yield_in_finally_cleanup.phpt b/Zend/tests/generators/yield_in_finally_cleanup.phpt
index 9a76261e0d5..538c14bdb77 100644
--- a/Zend/tests/generators/yield_in_finally_cleanup.phpt
+++ b/Zend/tests/generators/yield_in_finally_cleanup.phpt
@@ -47,5 +47,6 @@ function gen4() {
?>
===DONE===
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
===DONE===
diff --git a/Zend/tests/oss_fuzz_438780145.phpt b/Zend/tests/oss_fuzz_438780145.phpt
index 4c6936a69a0..11797fcce99 100644
--- a/Zend/tests/oss_fuzz_438780145.phpt
+++ b/Zend/tests/oss_fuzz_438780145.phpt
@@ -20,6 +20,8 @@ function &test(): int {
?>
--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
+
Fatal error: Uncaught TypeError: test(): Return value must be of type int, string returned in %s:%d
Stack trace:
#0 %s(%d): test()
diff --git a/Zend/tests/return_types/029.phpt b/Zend/tests/return_types/029.phpt
index c6fb4432158..8711df828ff 100644
--- a/Zend/tests/return_types/029.phpt
+++ b/Zend/tests/return_types/029.phpt
@@ -14,6 +14,8 @@ function foo() : array {
foo();
?>
--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
+
Fatal error: Uncaught Exception: xxxx in %s:%d
Stack trace:
#0 %s(%d): foo()
diff --git a/Zend/tests/return_types/never_finally_return.phpt b/Zend/tests/return_types/never_finally_return.phpt
index b0385971027..2a6af6923ee 100644
--- a/Zend/tests/return_types/never_finally_return.phpt
+++ b/Zend/tests/return_types/never_finally_return.phpt
@@ -14,4 +14,6 @@ function foo() : never {
// Note the lack of function call: function validated at compile-time
?>
--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
+
Fatal error: A never-returning function must not return in %s on line %d
diff --git a/Zend/tests/try/bug70228.phpt b/Zend/tests/try/bug70228.phpt
index 8b812517a30..b69034da8a6 100644
--- a/Zend/tests/try/bug70228.phpt
+++ b/Zend/tests/try/bug70228.phpt
@@ -10,5 +10,6 @@ function foo() {
var_dump(foo());
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
string(2) "bb"
diff --git a/Zend/tests/try/bug70228_2.phpt b/Zend/tests/try/bug70228_2.phpt
index c988e706ce4..bbb44bcd6a4 100644
--- a/Zend/tests/try/bug70228_2.phpt
+++ b/Zend/tests/try/bug70228_2.phpt
@@ -16,5 +16,6 @@ function test() {
var_dump(test());
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
int(42)
diff --git a/Zend/tests/try/bug70228_3.phpt b/Zend/tests/try/bug70228_3.phpt
index 55dbe4f8914..80a0f379765 100644
--- a/Zend/tests/try/bug70228_3.phpt
+++ b/Zend/tests/try/bug70228_3.phpt
@@ -26,6 +26,7 @@ function test() {
} while ($e);
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
2
1
diff --git a/Zend/tests/try/bug70228_4.phpt b/Zend/tests/try/bug70228_4.phpt
index f0ab3b0c2c1..7e0fd78707d 100644
--- a/Zend/tests/try/bug70228_4.phpt
+++ b/Zend/tests/try/bug70228_4.phpt
@@ -28,5 +28,6 @@ function test() {
} while ($e);
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
1
diff --git a/Zend/tests/try/bug70228_6.phpt b/Zend/tests/try/bug70228_6.phpt
index fc68657f4c1..5bc3a70bdcf 100644
--- a/Zend/tests/try/bug70228_6.phpt
+++ b/Zend/tests/try/bug70228_6.phpt
@@ -14,5 +14,6 @@ function test($x) {
var_dump(test([1]));
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
int(42)
diff --git a/Zend/tests/try/catch_finally_003.phpt b/Zend/tests/try/catch_finally_003.phpt
index 610d701872b..3cfb4fd6c5a 100644
--- a/Zend/tests/try/catch_finally_003.phpt
+++ b/Zend/tests/try/catch_finally_003.phpt
@@ -32,7 +32,10 @@ function &bar($a) {
var_dump(foo("para"));
var_dump(bar("para"));
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
+
+Deprecated: Returning from a finally block is deprecated in %s on line %d
string(3) "try"
string(7) "finally"
string(7) "finally"
diff --git a/Zend/tests/try/catch_finally_005.phpt b/Zend/tests/try/catch_finally_005.phpt
index 7671d05df58..bc4a72155bf 100644
--- a/Zend/tests/try/catch_finally_005.phpt
+++ b/Zend/tests/try/catch_finally_005.phpt
@@ -17,5 +17,6 @@ function foo ($a) {
var_dump(foo("para"));
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
int(3)
diff --git a/Zend/tests/try/catch_finally_006.phpt b/Zend/tests/try/catch_finally_006.phpt
index 216219b6a5b..463ce377f0a 100644
--- a/Zend/tests/try/catch_finally_006.phpt
+++ b/Zend/tests/try/catch_finally_006.phpt
@@ -22,7 +22,8 @@ function foo ($a) {
var_dump($e->getMessage());
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
string(4) "para"
string(7) "finally"
string(6) "return"
diff --git a/Zend/tests/try/finally_goto_005.phpt b/Zend/tests/try/finally_goto_005.phpt
index ffb0e68be10..0cb38a50299 100644
--- a/Zend/tests/try/finally_goto_005.phpt
+++ b/Zend/tests/try/finally_goto_005.phpt
@@ -11,5 +11,6 @@
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
success
diff --git a/Zend/tests/try/finally_return_deprecation.phpt b/Zend/tests/try/finally_return_deprecation.phpt
new file mode 100644
index 00000000000..86cc07e5948
--- /dev/null
+++ b/Zend/tests/try/finally_return_deprecation.phpt
@@ -0,0 +1,86 @@
+--TEST--
+Returning from a finally block is deprecated
+--FILE--
+<?php
+
+function withValue() {
+ try {
+ } finally {
+ return 1;
+ }
+}
+
+function withoutValue() {
+ try {
+ } finally {
+ return;
+ }
+}
+
+// Not deprecated: the return belongs to the try block, not the finally.
+function inTry() {
+ try {
+ return 1;
+ } finally {
+ }
+}
+
+// Not deprecated: the return belongs to a catch block.
+function inCatch() {
+ try {
+ throw new Exception();
+ } catch (Exception $e) {
+ return 1;
+ } finally {
+ }
+}
+
+// Not deprecated: the return belongs to a nested closure, not the finally.
+function nestedClosure() {
+ try {
+ } finally {
+ $f = function () {
+ return 1;
+ };
+ }
+}
+
+// Deprecated twice: both returns are lexically inside the outer finally, even
+// though one of them sits in a nested try block.
+function nestedTry() {
+ try {
+ } finally {
+ try {
+ return 1;
+ } finally {
+ return 2;
+ }
+ }
+}
+
+// Deprecated once: only the closure's own finally return is flagged. The outer
+// finally must not leak into the closure, so the closure's try return is left alone.
+function closureWithTry() {
+ try {
+ } finally {
+ $f = function () {
+ try {
+ return 1;
+ } finally {
+ return 2;
+ }
+ };
+ }
+}
+
+?>
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
+
+Deprecated: Returning from a finally block is deprecated in %s on line %d
+
+Deprecated: Returning from a finally block is deprecated in %s on line %d
+
+Deprecated: Returning from a finally block is deprecated in %s on line %d
+
+Deprecated: Returning from a finally block is deprecated in %s on line %d
diff --git a/Zend/tests/try/try_catch_finally_003.phpt b/Zend/tests/try/try_catch_finally_003.phpt
index 4d285eedb8d..3e37256837b 100644
--- a/Zend/tests/try/try_catch_finally_003.phpt
+++ b/Zend/tests/try/try_catch_finally_003.phpt
@@ -32,5 +32,6 @@ function foo () {
var_dump(foo());
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
1234int(4)
diff --git a/Zend/tests/try/try_finally_013.phpt b/Zend/tests/try/try_finally_013.phpt
index 0e82dcbb4cf..1f0e044a128 100644
--- a/Zend/tests/try/try_finally_013.phpt
+++ b/Zend/tests/try/try_finally_013.phpt
@@ -18,6 +18,7 @@ function foo() {
foo();
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
try
finally
diff --git a/Zend/tests/try/try_finally_014.phpt b/Zend/tests/try/try_finally_014.phpt
index a45f63f1f3d..0cdc8ddffc8 100644
--- a/Zend/tests/try/try_finally_014.phpt
+++ b/Zend/tests/try/try_finally_014.phpt
@@ -20,6 +20,7 @@ function foo() {
foo();
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
try
finally
diff --git a/Zend/tests/try/try_finally_021.phpt b/Zend/tests/try/try_finally_021.phpt
index d25f393be1f..f624eeb5c6f 100644
--- a/Zend/tests/try/try_finally_021.phpt
+++ b/Zend/tests/try/try_finally_021.phpt
@@ -15,6 +15,7 @@
}
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
ok
ok
diff --git a/Zend/tests/try/try_finally_023.phpt b/Zend/tests/try/try_finally_023.phpt
index e88eddb3b23..305684e3111 100644
--- a/Zend/tests/try/try_finally_023.phpt
+++ b/Zend/tests/try/try_finally_023.phpt
@@ -31,6 +31,7 @@ function test() {
?>
--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
Exception: 1 in %s:%d
Stack trace:
#0 %s(%d): test()
diff --git a/Zend/tests/try/try_finally_027.phpt b/Zend/tests/try/try_finally_027.phpt
index 1e66479eb09..059a16a977f 100644
--- a/Zend/tests/try/try_finally_027.phpt
+++ b/Zend/tests/try/try_finally_027.phpt
@@ -23,6 +23,7 @@ function test() {
?>
--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
Exception: 1 in %s:%d
Stack trace:
#0 %s(%d): test()
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index c7ac93f8d34..f3527330a2c 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -348,6 +348,7 @@ void zend_oparray_context_begin(zend_oparray_context *prev_context, zend_op_arra
CG(context).brk_cont_array = NULL;
CG(context).labels = NULL;
CG(context).in_jmp_frameless_branch = false;
+ CG(context).in_finally = false;
CG(context).active_property_info_name = NULL;
CG(context).active_property_hook_kind = (zend_property_hook_kind)-1;
}
@@ -6164,6 +6165,10 @@ static void zend_compile_return(const zend_ast *ast) /* {{{ */
}
}
+ if (CG(context).in_finally) {
+ zend_error(E_DEPRECATED, "Returning from a finally block is deprecated");
+ }
+
if ((CG(active_op_array)->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)
&& (expr_node.op_type == IS_CV || (by_ref && expr_node.op_type == IS_VAR))
&& zend_has_finally()) {
@@ -7410,7 +7415,10 @@ static void zend_compile_try(const zend_ast *ast) /* {{{ */
zend_emit_op(NULL, ZEND_JMP, NULL, NULL);
+ bool orig_in_finally = CG(context).in_finally;
+ CG(context).in_finally = true;
zend_compile_stmt(finally_ast);
+ CG(context).in_finally = orig_in_finally;
CG(active_op_array)->try_catch_array[try_catch_offset].finally_op = opnum_jmp + 1;
CG(active_op_array)->try_catch_array[try_catch_offset].finally_end
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 3d4e6f3c3f9..34a91183b2a 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -207,6 +207,7 @@ typedef struct _zend_oparray_context {
zend_string *active_property_info_name;
zend_property_hook_kind active_property_hook_kind;
bool in_jmp_frameless_branch;
+ bool in_finally;
bool has_assigned_to_http_response_header;
} zend_oparray_context;
diff --git a/sapi/phpdbg/tests/exceptions_001.phpt b/sapi/phpdbg/tests/exceptions_001.phpt
index 2ebdc0f664d..799832ae6d0 100644
--- a/sapi/phpdbg/tests/exceptions_001.phpt
+++ b/sapi/phpdbg/tests/exceptions_001.phpt
@@ -7,6 +7,7 @@
c
q
--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
[Successful compilation of %s]
prompt> handle first
[Uncaught Error in %s on line 16: Call to undefined function foo()]
@@ -22,6 +23,8 @@
#0 %s(22): {closure:%s:%d}()
#1 {main}
[Script ended normally]
+
+Deprecated: Returning from a finally block is deprecated in %s on line %d
prompt>
--FILE--
<?php
diff --git a/sapi/phpdbg/tests/exceptions_002.phpt b/sapi/phpdbg/tests/exceptions_002.phpt
index 47d4c7d11c7..a2c3e929845 100644
--- a/sapi/phpdbg/tests/exceptions_002.phpt
+++ b/sapi/phpdbg/tests/exceptions_002.phpt
@@ -7,6 +7,7 @@
q
--EXPECTF--
+Deprecated: Returning from a finally block is deprecated in %s on line %d
[Successful compilation of %s]
prompt> handle first
[Uncaught Error in %s on line 16: Call to undefined function foo()]
@@ -26,6 +27,8 @@
#0 %s(20): {closure:%s:%d}()
#1 {main}
[Script ended normally]
+
+Deprecated: Returning from a finally block is deprecated in %s on line %d
prompt> [The stack contains nothing !]
prompt>
--FILE--