Re: [RFC] [VOTE] Transform exit() from a language construct into a standard function
| From: | Tim Düsterhus | Date: | Mon, 05 Aug 2024 16:50:20 +0000 |
| Subject: | Re: [RFC] [VOTE] Transform exit() from a language construct into a standard function | ||
| References: | 1 2 3 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-124778@lists.php.net to get a copy of this message | ||
Hi
On 8/5/24 13:04, Derick Rethans wrote:
This is false. The observers are perfectly capable of detecting that theAs userland PHP developer, I always regardedXdebug 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).exitas a control flow instruction (quite similar tobreak), 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).
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.phpwill 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