php / php/php-src

magic property guards are not reset in timeout

オープン
#14,983 コメント 13 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Bug Category: Engine Status: Needs Triage
主要言語
C
スター
40.4k
フォーク
8.2k
平均マージ
2日 13時間
マージ済み PR(30日)
96

説明

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() 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

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

参照されている行の Zend/zend_object_handlers.c にあるプロパティガードのロジックから始め、タイムアウト処理について zend_interrupt_helper と ZEND_VM_INTERRUPT を追ってください。提供された PHP の再現コードを使ってシャットダウン時の動作を観察します。__get() 内のタイムアウトによってガードがアクティブなままにならず、シャットダウンハンドラーが未定義プロパティの警告なしに期待されるプロパティ検索結果を生成すれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
c
領域
backend
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
静か
明瞭さ
おおむね明確
初心者へのやさしさ
45/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。