[php-src] Issue #14983: magic property guards are not reset in timeout
| From: | ivantsepp | Date: | Tue, 16 Jul 2024 18:39:48 +0000 |
| Subject: | [php-src] Issue #14983: magic property guards are not reset in timeout | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-247305@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/14983
Author: ivantsepp
### Description
The overloaded methods are protected via property guards flags from circular calls. An example is at
https://github.com/php/php-src/blob/0f398a437e401fc1abf2a23196918ba1ccb5d8c5/Zend/zend_object_handlers.c#L724-L726
However, these guards are not reset when a timeout happens within the overloaded method. We will get
undefined property warnings in the shutdown handlers as a result. This is because PHP still thinks
we are inside the __get method
From https://www.php.net/manual/en/language.oop5.overloading.php#object.get:
> PHP will not call an overloaded method from within the same overloaded method. That means, for
> example, writing
return $this->foo inside
> of [__get()](https://www.php.net/manual/en/language.oop5.overloading.php#object.get) will
> return null and raise
> an **[E_WARNING](https://www.php.net/manual/en/errorfunc.constants.php#constant.e-warning)** if
> there is no foo property defined, rather than
> calling [__get()](https://www.php.net/manual/en/language.oop5.overloading.php#object.get) a second
> time. However, overload methods may invoke other overload methods implicitly (such
> as [__set()](https://www.php.net/manual/en/language.oop5.overloading.php#object.set) triggering [__get()](https://www.php.net/manual/en/language.oop5.overloading.php#object.get)).
However, I believe that we are no longer inside the \_\_get call when we are in the shutdown
handlers and we should allow further \_\_get calls to work.
Here's a repro script (https://3v4l.org/DhXZH) :
```php
<?php
ini_set('max_execution_time', 1);
set_error_handler(null, E_ALL);
class A {
function __get($name) {
return $name;
}
}
global $a;
$a = new A;
function shutdown() {
global $a;
// Warning: Undefined property: A::$foo ...
var_dump($a->foo);
}
register_shutdown_function('shutdown');
// Try to trigger "Fatal error: Maximum execution time of 1 second exceeded"
// within the A::__get() method.
for ($i = 0; $i < 100000000; $i++) {
$a->foo;
}
```
And the possible outputs are:
1. The timeout is triggered outside of the __get call (A
```
Fatal error: Maximum execution time of 1 second exceeded in
/home/itse/development/Etsyweb/ivantestcase.php on line 26
string(3) "foo"
```
or
2. The timeout is triggered within the __get call:
```
Fatal error: Maximum execution time of 1 second exceeded in
/home/itse/development/Etsyweb/ivantestcase.php on line 9
Warning: Undefined property: A::$foo in /home/itse/development/Etsyweb/ivantestcase.php on line 18
NULL
```
I expect output 1 no matter when the timeout occurs. Output 2 shouldn't occur.
I attempted to dig into the source code for a fix. Perhaps in the zend_interrupt_helper
that gets called from ZEND_VM_INTERRUPT, we can look at the call frames and reset the
property guards? I'm not too familiar with PHP Virtual Machine so I am not sure if this is the
correct approach.
https://github.com/php/php-src/compare/f58a3c392f4fc0ce2f82935399c5e6e64441e2e9...ivantsepp:php-src:61e0b6293829a508884de388f99ab093be9e4563?expand=1
Let me know if this is a bug that should be addressed and I would appreciate any pointers on what a
potential fix might look like! I am interested in working on a fix if so.
### PHP Version
PHP 8.4.0-dev
### Operating System
macOS 13.6