From fbf4a9532a5e5380e9f12e77ab240ba89de6618c Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 7 Oct 2025 21:16:22 +0200 Subject: [PATCH] Fix GH-20085: Assertion failure when combining lazy object get_properties exception with foreach loop In this test, we will loop once, and then replace the object with an instance that'll throw on property construction in Z_OBJPROP_P() in the ZEND_FE_FETCH_RW VM handler. Since at that point `pos >= fe_ht->nNumUsed`, we exit via `fe_fetch_w_exit` without checking for an exception, causing incorrect continuation of the code and an eventual assertion failure. To solve this, we perform an exception check at the end of the iteration. This should be sufficient to guarantee the exception is checked in time as failure of get_properties() via Z_OBJPROP_P() will always result in an empty hash table. This should also be more efficient than the alternative fix that checks for an exception right after Z_OBJPROP_P() as that would be executed at each iteration. --- Zend/tests/lazy_objects/gh20085.phpt | 25 +++++++++++++++++++++++++ Zend/zend_vm_def.h | 3 ++- Zend/zend_vm_execute.h | 3 ++- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/lazy_objects/gh20085.phpt diff --git a/Zend/tests/lazy_objects/gh20085.phpt b/Zend/tests/lazy_objects/gh20085.phpt new file mode 100644 index 0000000000000..5624f71ea8b47 --- /dev/null +++ b/Zend/tests/lazy_objects/gh20085.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-20085 (Assertion failure when combining lazy object get_properties exception with foreach loop) +--FILE-- +a = 1; + } +} +$obj = new C; +$reflector = new ReflectionClass(C::class); +foreach ($obj as &$value) { + $obj = $reflector->newLazyGhost(function ($obj) { + throw new Error; + }); +} +echo !obj; +?> +--EXPECTF-- +Fatal error: Uncaught Error in %s:%d +Stack trace: +#0 %s(%d): {closure:%s:%d}(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index c40dd4cc8edf4..039d9679848ee 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -7275,7 +7275,7 @@ ZEND_VM_HANDLER(126, ZEND_FE_FETCH_RW, VAR, ANY, JMP_ADDR) while (1) { if (UNEXPECTED(pos >= fe_ht->nNumUsed)) { /* reached end of iteration */ - ZEND_VM_C_GOTO(fe_fetch_w_exit); + ZEND_VM_C_GOTO(fe_fetch_w_exit_exc); } pos++; value = &p->val; @@ -7371,6 +7371,7 @@ ZEND_VM_HANDLER(126, ZEND_FE_FETCH_RW, VAR, ANY, JMP_ADDR) } } else { zend_error(E_WARNING, "foreach() argument must be of type array|object, %s given", zend_zval_value_name(array)); +ZEND_VM_C_LABEL(fe_fetch_w_exit_exc): if (UNEXPECTED(EG(exception))) { UNDEF_RESULT(); HANDLE_EXCEPTION(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a9f33224d82a9..d6ee850839aec 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -23173,7 +23173,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_FETCH_RW_SPEC_VAR_HANDLER(Z while (1) { if (UNEXPECTED(pos >= fe_ht->nNumUsed)) { /* reached end of iteration */ - goto fe_fetch_w_exit; + goto fe_fetch_w_exit_exc; } pos++; value = &p->val; @@ -23269,6 +23269,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_FETCH_RW_SPEC_VAR_HANDLER(Z } } else { zend_error(E_WARNING, "foreach() argument must be of type array|object, %s given", zend_zval_value_name(array)); +fe_fetch_w_exit_exc: if (UNEXPECTED(EG(exception))) { UNDEF_RESULT(); HANDLE_EXCEPTION();