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--