Skip to content

magic property guards are not reset in timeout  #14983

@ivantsepp

Description

@ivantsepp

Description

The overloaded methods are protected via property guards flags from circular calls. An example is at

*guard |= IN_GET; /* prevent circular getting */
zend_std_call_getter(zobj, name, rv);
*guard &= ~IN_GET;

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() will return null and raise an E_WARNING if there is no foo property defined, rather than calling __get() a second time. However, overload methods may invoke other overload methods implicitly (such as __set() triggering __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

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

  1. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions