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