Catch exceptions without capturing them to variables by MaxSem · Pull Request #5345 · php/php-src
A suggested fix for the test exiting without properly reporting the uncaught exception - @nikic might know more about the edge case I'm worried about.
I'm also not sure why the GC_ADDREF leads to a memory leak when the exception has no variable, but taking it out seems to work.
The test for catch_novar_2.phpt seems to be crashing. I think that EG(exception) should be set to null before calling the destructor on the caught exception.
I'm not 100% sure of what to do about public function __destruct() { throw $this; }. The below case is different in that the RuntimeException is the uncaught exception which still has a reference count, not the ThrowsOnDestruct. Maybe the object still exists but __destruct won't be called again?
php > class ThrowsOnDestruct extends Exception { public function __destruct() { echo "throwing self\n"; throw $this; }}
php > $e = new ThrowsOnDestruct();
php > try { throw new RuntimeException(); } catch (Exception $e) {}
throwing self
Warning: Uncaught RuntimeException in php shell code:1
Stack trace:
#0 {main}
Next ThrowsOnDestruct in php shell code:1
Stack trace:
#0 {main}
thrown in php shell code on line 1
diff --git a/Zend/tests/try/catch_novar_2.phpt b/Zend/tests/try/catch_novar_2.phpt index 60a7ebb283..846fb86484 100644 --- a/Zend/tests/try/catch_novar_2.phpt +++ b/Zend/tests/try/catch_novar_2.phpt @@ -15,5 +15,11 @@ try { } echo "Unreachable fallthrough\n"; ---EXPECT-- +--EXPECTF-- Throwing + +Fatal error: Uncaught RuntimeException: ThrowsOnDestruct::__destruct in %scatch_novar_2.php:5 +Stack trace: +#0 %scatch_novar_2.php(10): ThrowsOnDestruct->__destruct() +#1 {main} + thrown in %scatch_novar_2.php on line 5 diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index ab44b9ca7c..20058b5337 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4493,15 +4493,22 @@ ZEND_VM_HANDLER(107, ZEND_CATCH, CONST, JMP_ADDR, LAST_CATCH|CACHE_SLOT) } zval_ptr_dtor(ex); ZVAL_OBJ(ex, EG(exception)); - } else { - OBJ_RELEASE(EG(exception)); - } - if (UNEXPECTED(EG(exception) != exception)) { - GC_ADDREF(EG(exception)); - HANDLE_EXCEPTION(); + if (UNEXPECTED(EG(exception) != exception)) { + GC_ADDREF(EG(exception)); + HANDLE_EXCEPTION(); + } else { + EG(exception) = NULL; + ZEND_VM_NEXT_OPCODE(); + } } else { EG(exception) = NULL; - ZEND_VM_NEXT_OPCODE(); + OBJ_RELEASE(exception); + // TODO: What about function __destruct() { throw $this; } + if (UNEXPECTED(EG(exception) != NULL)) { + HANDLE_EXCEPTION(); + } else { + ZEND_VM_NEXT_OPCODE(); + } } } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 7ecc72a280..b8f511075a 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3709,15 +3709,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CATCH_SPEC_CONST_HANDLER(ZEND_ } zval_ptr_dtor(ex); ZVAL_OBJ(ex, EG(exception)); - } else { - OBJ_RELEASE(EG(exception)); - } - if (UNEXPECTED(EG(exception) != exception)) { - GC_ADDREF(EG(exception)); - HANDLE_EXCEPTION(); + if (UNEXPECTED(EG(exception) != exception)) { + GC_ADDREF(EG(exception)); + HANDLE_EXCEPTION(); + } else { + EG(exception) = NULL; + ZEND_VM_NEXT_OPCODE(); + } } else { EG(exception) = NULL; - ZEND_VM_NEXT_OPCODE(); + OBJ_RELEASE(exception); + if (UNEXPECTED(EG(exception) != NULL)) { + HANDLE_EXCEPTION(); + } else { + ZEND_VM_NEXT_OPCODE(); + } } }