[php-src] PR #24486: Fix GH-24480: finally block running twice when a VM interrupt throws in FAST_RET

From: Date: Wed, 21 Oct 2026 16:17:43 +0000
Subject: [php-src] PR #24486: Fix GH-24480: finally block running twice when a VM interrupt throws in FAST_RET
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-39662@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/24486 Author: cscott Fixes GH-24480 and Wikimedia [T440568](https://phabricator.wikimedia.org/T440568). ZEND_FAST_RET jumps back to the RETURN (or JMP/FAST_CALL) that follows the FAST_CALL, with ZEND_VM_JMP_EX(), which checks for interrupts after setting opline. That op is inside the try block, so if the interrupt function throws, **the finally block runs a second time** and the pending return value leaks (hybrid VM). In the CALL VM the interrupt is handled at FAST_RET itself, after the user function called from the finally block returns. FAST_RET is finally_end, which ZEND_HANDLE_EXCEPTION and zend_dispatch_try_catch_finally_helper count as outside the try/finally, so the return value and any delayed exception leak. This PR: - checks for interrupts in FAST_RET *before* the jump, while opline is still FAST_RET; - treats an exception thrown at FAST_RET as thrown inside its finally block (< finally_end → <= finally_end). This frees the pending return value and chains the delayed exception, as for any other exception in the finally block. FAST_RET's own rethrow path reaches that branch with an empty fast_call, so it is not affected; - adds zend_test.observer.set_vm_interrupt_on_end, which makes an interrupt pending and throwing as an observed function returns, so the tests are deterministic. Tested on debug builds of the hybrid and CALL VMs, without opcache and with opcache with JIT off, function JIT and tracing JIT. The new tests fail without the fix on both VM kinds. On upmerge to 8.5+, observer_end() needs atomic_store(&EG(vm_interrupt), true).

« previous php.git-pulls (#39662) next »