[PHP-CVS] [php-src] master: Deprecate returning from a finally block. (#22509)

[email protected] (Osama Aldemeery via GitHub)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Osama Aldemeery (aldemeery)
Committer: GitHub (web-flow)
Pusher: Girgias
Date: 2026-08-18T20:09:21+01:00

Commit: https://github.com/php/php-src/commit/e721b3fdc35dc74121ed27ed43c2f9568f321bee
Raw diff: https://github.com/php/php-src/commit/e721b3fdc35dc74121ed27ed43c2f9568f321bee.diff

Deprecate returning from a finally block. (#22509)

RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_returning_from_a_finally_block

Changed paths:
  A  Zend/tests/try/finally_return_deprecation.phpt
  M  Zend/tests/exit_finally_3.phpt
  M  Zend/tests/gc/gc_050.phpt
  M  Zend/tests/generators/finally/return_return.phpt
  M  Zend/tests/generators/finally/yield_return.phpt
  M  Zend/tests/generators/get_return_and_finally.phpt
  M  Zend/tests/generators/gh11028_1.phpt
  M  Zend/tests/generators/gh11028_2.phpt
  M  Zend/tests/generators/gh11028_3.phpt
  M  Zend/tests/generators/yield_in_finally_cleanup.phpt
  M  Zend/tests/oss_fuzz_438780145.phpt
  M  Zend/tests/return_types/029.phpt
  M  Zend/tests/return_types/never_finally_return.phpt
  M  Zend/tests/try/bug70228.phpt
  M  Zend/tests/try/bug70228_2.phpt
  M  Zend/tests/try/bug70228_3.phpt
  M  Zend/tests/try/bug70228_4.phpt
  M  Zend/tests/try/bug70228_6.phpt
  M  Zend/tests/try/catch_finally_003.phpt
  M  Zend/tests/try/catch_finally_005.phpt
  M  Zend/tests/try/catch_finally_006.phpt
  M  Zend/tests/try/finally_goto_005.phpt
  M  Zend/tests/try/try_catch_finally_003.phpt
  M  Zend/tests/try/try_finally_013.phpt
  M  Zend/tests/try/try_finally_014.phpt
  M  Zend/tests/try/try_finally_021.phpt
  M  Zend/tests/try/try_finally_023.phpt
  M  Zend/tests/try/try_finally_027.phpt
  M  Zend/zend_compile.c
  M  Zend/zend_compile.h
  M  sapi/phpdbg/tests/exceptions_001.phpt
  M  sapi/phpdbg/tests/exceptions_002.phpt


Diff:

diff --git a/Zend/tests/exit_finally_3.phpt b/Zend/tests/exit_finally_3.phpt
index 12d7747adea9..a2ae2429b28e 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 0bedc7220fd4..cf30776d9de9 100644
--- a/Zend/tests/gc/gc_050.phpt
+++ b/Zend/tests/gc/gc_050.phpt
@@ -36,5 +36,6 @@ for ($i = 0; $i < 100000; $i++) {
 }
 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 194fc93de7c2..895adc37cce3 100644
--- a/Zend/tests/generators/finally/return_return.phpt
+++ b/Zend/tests/generators/finally/return_return.phpt
@@ -27,7 +27,8 @@ $gen = 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 6191f389e53e..dbfabea50889 100644
--- a/Zend/tests/generators/finally/yield_return.phpt
+++ b/Zend/tests/generators/finally/yield_return.phpt
@@ -15,5 +15,6 @@ foreach (foo(1, 5) as $x) {
     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 150e5b83c4db..8ced4d0008b5 100644
--- a/Zend/tests/generators/get_return_and_finally.phpt
+++ b/Zend/tests/generators/get_return_and_finally.phpt
@@ -41,7 +41,8 @@ try {
 }
 
 ?>
---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 e1e7aa5019e5..d1c07dc9de66 100644
--- a/Zend/tests/generators/gh11028_1.phpt
+++ b/Zend/tests/generators/gh11028_1.phpt
@@ -24,7 +24,8 @@ test("false", false);
 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 ddc9618e8ed8..bf364329849f 100644
--- a/Zend/tests/generators/gh11028_2.phpt
+++ b/Zend/tests/generators/gh11028_2.phpt
@@ -15,6 +15,8 @@ $c = (function () {
 })()[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 7ea1aac6f6cf..c58e00dc09b1 100644
--- a/Zend/tests/generators/gh11028_3.phpt
+++ b/Zend/tests/generators/gh11028_3.phpt
@@ -17,5 +17,6 @@ try {
     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 9a76261e0d5e..538c14bdb774 100644
--- a/Zend/tests/generators/yield_in_finally_cleanup.phpt
+++ b/Zend/tests/generators/yield_in_finally_cleanup.phpt
@@ -47,5 +47,6 @@ gen4()->rewind();
 
 ?>
 ===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 4c6936a69a01..11797fcce999 100644
--- a/Zend/tests/oss_fuzz_438780145.phpt
+++ b/Zend/tests/oss_fuzz_438780145.phpt
@@ -20,6 +20,8 @@ test();
 
 ?>
 --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 c6fb4432158c..8711df828fff 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 b03859710276..2a6af6923eef 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 8b812517a305..b69034da8a6c 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 c988e706ce47..bbb44bcd6a42 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 55dbe4f8914a..80a0f379765b 100644
--- a/Zend/tests/try/bug70228_3.phpt
+++ b/Zend/tests/try/bug70228_3.phpt
@@ -26,6 +26,7 @@ try {
     } 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 f0ab3b0c2c11..7e0fd78707d7 100644
--- a/Zend/tests/try/bug70228_4.phpt
+++ b/Zend/tests/try/bug70228_4.phpt
@@ -28,5 +28,6 @@ try {
     } 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 fc68657f4c15..5bc3a70bdcfe 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 610d701872b1..3cfb4fd6c5a1 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 7671d05df582..bc4a72155bf6 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 216219b6a5b3..463ce377f0a3 100644
--- a/Zend/tests/try/catch_finally_006.phpt
+++ b/Zend/tests/try/catch_finally_006.phpt
@@ -22,7 +22,8 @@ try {
     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 ffb0e68be103..0cb38a502993 100644
--- a/Zend/tests/try/finally_goto_005.phpt
+++ b/Zend/tests/try/finally_goto_005.phpt
@@ -11,5 +11,6 @@ label: try {
 }
 
 ?>
---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 000000000000..86cc07e59481
--- /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 4d285eedb8da..3e37256837b0 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 0e82dcbb4cf4..1f0e044a1285 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 a45f63f1f3d4..0cdc8ddffc8a 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 d25f393be1f0..f624eeb5c6fb 100644
--- a/Zend/tests/try/try_finally_021.phpt
+++ b/Zend/tests/try/try_finally_021.phpt
@@ -15,6 +15,7 @@ foreach ([0] as $_) {
     }
 }
 ?>
---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 e88eddb3b236..305684e3111b 100644
--- a/Zend/tests/try/try_finally_023.phpt
+++ b/Zend/tests/try/try_finally_023.phpt
@@ -31,6 +31,7 @@ try {
 
 ?>
 --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 1e66479eb09b..059a16a977f1 100644
--- a/Zend/tests/try/try_finally_027.phpt
+++ b/Zend/tests/try/try_finally_027.phpt
@@ -23,6 +23,7 @@ try {
 
 ?>
 --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 c7ac93f8d34e..f3527330a2c9 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 3d4e6f3c3f9f..34a91183b2a9 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 2ebdc0f664d1..799832ae6d07 100644
--- a/sapi/phpdbg/tests/exceptions_001.phpt
+++ b/sapi/phpdbg/tests/exceptions_001.phpt
@@ -7,6 +7,7 @@ ev 1 + 2
 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 @@ Stack trace:
 #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 47d4c7d11c7d..a2c3e9298452 100644
--- a/sapi/phpdbg/tests/exceptions_002.phpt
+++ b/sapi/phpdbg/tests/exceptions_002.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()]
@@ -26,6 +27,8 @@ Stack trace:
 #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--
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.