Hi
On 8/5/24 13:04, Derick Rethans wrote:
As userland PHP developer, I always regarded exit
as a control flow
instruction (quite similar to break
), and as such I'm not really in
favor of converting it to a proper function (especially since it is
not, because the parantheses could be omitted).
Xdebug uses exit for exactly that too. For control flow analysis. And I
also always have considered it to be a control flow instruction.
I see no benefit in changing it to a function, especially because
there will never be a function "exit" from it, just only an "entry".
This breaks function execution symmetry (and causes issues with Xdebug
when I last tried to make it work with a development branch for this
RFC).
This is false. The observers are perfectly capable of detecting that the exit()
function returns / throws when observing zend_observer_fcall_end_handler
as demonstrated by the following script:
<?php
class MyClass {
public function __destruct() {
echo __METHOD__, PHP_EOL;
}
}
function a() {
$dummy = new MyClass();
exit();
}
a();
Running this script as:
sapi/cli/php -d zend_test.observer.enabled=1 -d zend_test.observer.observe_all=1 test.php
will return the following output:
<!-- init 'php-src/test.php' -->
<file 'php-src/test.php'>
<!-- init a() -->
<a>
<!-- init exit() -->
<exit>
<!-- Exception: UnwindExit -->
</exit>
<!-- Exception: UnwindExit -->
</a>
<!-- init MyClass::__destruct() -->
<MyClass::__destruct>
MyClass::__destruct
</MyClass::__destruct>
<!-- Exception: UnwindExit -->
</file 'php-src/test.php'>
Leaving the exit() function will be observed as indicated by </exit>
. It also shows that any destructors will be called and it also shows the internal UnwindExit exception.
Thus you are able to detect if exit()
has successfully been called by observing an fcall_end and checking for zend_is_unwind_exit(EG(exception))
to determine if the exception you're dealing with is the unwind exception.
That way you will also correctly handle that exit()
is not successful, e.g. when a non-stringable class is passed to it and a TypeError is thrown, because then the output will look something like this:
<!-- init 'php-src/test.php' -->
<file 'php-src/test.php'>
<!-- init a() -->
<a>
<!-- init exit() -->
<exit>
<!-- Exception: TypeError -->
</exit>
<!-- Exception: TypeError -->
</a>
<!-- Exception: TypeError -->
</file 'php-src/test.php'>
<!-- init Error::__toString() -->
<Error::__toString>
<!-- init Error::getTraceAsString() -->
<Error::getTraceAsString>
</Error::getTraceAsString>
</Error::__toString>
Fatal error: Uncaught TypeError: exit(): Argument #1 ($code) must be of type string|int, MyClass given in php-src/test.php:11
Stack trace:
#0 php-src/test.php(11): exit(Object(MyClass))
#1 php-src/test.php(14): a()
#2 {main}
thrown in php-src/test.php on line 11
<!-- init MyClass::__destruct() -->
<MyClass::__destruct>
MyClass::__destruct
</MyClass::__destruct>
Best regards
Tim Düsterhus