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