Commit fb1ec9a5a7c for php.net

commit fb1ec9a5a7c35e0bdb4aa5b793d4aac7bb765a9c
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date:   Mon Dec 15 23:39:07 2025 +0100

    Fix uncatchable exception thrown in generator

    This procedure may be called during i_free_compiled_variables(), when
    EG(current_execute_data) is unfortunately already reset to the parent frame.
    EG(opline_before_exception) does not actually belong to this frame. Furthermore,
    setting opline to EG(exception_op) early will miss a later
    zend_rethrow_exception(), which will also miss installation of the correct
    EG(opline_before_exception).

    Fixes GH-20714
    Closes GH-20716

diff --git a/NEWS b/NEWS
index beff3f224ce..11ae976b323 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,7 @@ PHP                                                                        NEWS
     with dynamic class const lookup default argument). (ilutov)
   . Fixed bug GH-20695 (Assertion failure in normalize_value() when parsing
     malformed INI input via parse_ini_string()). (ndossche)
+  . Fixed bug GH-20714 (Uncatchable exception thrown in generator). (ilutov)

 - Bz2:
   . Fixed bug GH-20620 (bzcompress overflow on large source size).
diff --git a/Zend/tests/gh20714.phpt b/Zend/tests/gh20714.phpt
new file mode 100644
index 00000000000..10ffde555f8
--- /dev/null
+++ b/Zend/tests/gh20714.phpt
@@ -0,0 +1,29 @@
+--TEST--
+GH-20714: Uncatchable exception thrown in generator
+--CREDITS--
+Grégoire Paris (greg0ire)
+--FILE--
+<?php
+
+function gen(): Generator {
+    try {
+        yield 1;
+    } finally {}
+}
+
+function process(): void {
+    $g = gen();
+    foreach ($g as $_) {
+        throw new Exception('ERROR');
+    }
+}
+
+try {
+    process();
+} catch (Exception $e) {
+    echo "Caught\n";
+}
+
+?>
+--EXPECT--
+Caught
diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c
index 5ba215f788c..cfadf06b46f 100644
--- a/Zend/zend_generators.c
+++ b/Zend/zend_generators.c
@@ -321,7 +321,9 @@ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
 			zend_object *old_exception = NULL;
 			const zend_op *old_opline_before_exception = NULL;
 			if (EG(exception)) {
-				if (EG(current_execute_data)) {
+				if (EG(current_execute_data)
+				 && EG(current_execute_data)->opline
+				 && EG(current_execute_data)->opline->opcode == ZEND_HANDLE_EXCEPTION) {
 					EG(current_execute_data)->opline = EG(opline_before_exception);
 					old_opline_before_exception = EG(opline_before_exception);
 				}
@@ -337,7 +339,7 @@ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
 			zend_generator_resume(generator);

 			if (old_exception) {
-				if (EG(current_execute_data)) {
+				if (old_opline_before_exception) {
 					EG(current_execute_data)->opline = EG(exception_op);
 					EG(opline_before_exception) = old_opline_before_exception;
 				}