Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

opcache_compile_file() on a file with a fatal compile error disables the cycle collector for the rest of the request

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

メンテナーはふだん 1 日以内に返信

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

評価

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

調査の方向性

Start with the zend_catch branch of ZEND_FUNCTION(opcache_compile_file) in ext/opcache/zend_accelerator_module.c, then look at _zend_bailout() in Zend/zend.c, which calls gc_protect(1). Reproduce with the script in the issue and check that gc_status()['protected'] returns to false after the failed compile. Done means the cycle collector runs again after the catch, or the behaviour is documented and the gc_protect state is restored.

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

説明

Description

When opcache_compile_file() is given a file that fails with a fatal compile error, the function catches the bailout, emits a warning, returns false and lets the script continue. But zend_bailout() has already called gc_protect(1), and nothing resets it until the next request. From that point on gc_possible_root() drops every new root candidate, so cycles created later in the request are never collected, not even by an explicit gc_collect_cycles().

The following code:

<?php
$bad = sys_get_temp_dir() . "/bad.php";
file_put_contents($bad, "<?php\nbreak 2;\n");

class Node { public $self; }

var_dump(gc_status()["protected"]);
var_dump(@opcache_compile_file($bad));
var_dump(gc_status()["protected"]);

for ($i = 0; $i < 100000; $i++) { $o = new Node; $o->self = $o; }
var_dump(gc_collect_cycles());
var_dump(gc_status()["collected"]);

Run with php -n -d opcache.enable_cli=1 (master, debug build).

Resulted in this output:

bool(false)

Fatal error: 'break' not in the 'loop' or 'switch' context in /tmp/bad.php on line 2
Stack trace:
#0 /path/to/test.php(8): opcache_compile_file('/tmp/bad.php')
#1 {main}
bool(false)
bool(true)
int(0)
int(0)

But I expected this output instead (the second protected stays false and the cycles get collected; the last two numbers are what the same script prints with the opcache_compile_file() line removed):

bool(false)

Fatal error: 'break' not in the 'loop' or 'switch' context in /tmp/bad.php on line 2
Stack trace:
#0 /path/to/test.php(8): opcache_compile_file('/tmp/bad.php')
#1 {main}
bool(false)
bool(false)
int(9999)
int(99999)

Measured with a slightly longer script (prints gc_status() and memory_get_usage() after creating 100 000 self-referencing objects):

build opcache_compile_file() called protected after GC runs collected memory at end
master 0b4a5a84492, debug, NTS no false 10 100 000 583 136
master 0b4a5a84492, debug, NTS yes true 0 0 11 141 648
master, debug, opcache.enable_cli=0 yes (returns false without compiling) false 10 100 000 635 656
PHP-8.4 81fd6105bc6, release + ASAN, NTS no false 10 100 000 414 792
PHP-8.4 81fd6105bc6, release + ASAN, NTS yes true 0 0 6 942 856
PHP-8.3 9c6ad6caeeb, release, NTS no false 10 100 000 471 080
PHP-8.3 9c6ad6caeeb, release, NTS yes true 0 0 7 023 624

Where it happens: ext/opcache/zend_accelerator_module.c, ZEND_FUNCTION(opcache_compile_file): the zend_try { persistent_compile_file(...) } zend_catch { ... zend_error(E_WARNING, "... could not compile file %s") } branch (non-preload path). The catch restores EG(current_execute_data) but not the GC protection that zend_bailout() set (Zend/zend.c, _zend_bailout() → gc_protect(1)). The catch dates back to the introduction of the function (69454d9f0f7, 2013), the gc_protect(1) in _zend_bailout() to f844d40fdc3 (2018, PHP 7.3.0), so presumably every version since 7.3 is affected.

The same class of problem is reported for ext/soap in #10026 (SoapClient converts fatal errors into SoapFault after a bailout). I found this second instance while auditing the zend_catch sites that continue execution after a bailout.

Possible directions (not a proposal of one in particular):

  • report compile failures from opcache_compile_file() without a bailout, if the compiler path allows it;
  • or document that after a fatal compile error the rest of the request runs without the cycle collector.
PHP Version
PHP 8.7.0-dev (cli) (NTS DEBUG), master 0b4a5a84492
PHP 8.4.28-dev (cli) (NTS), PHP-8.4 81fd6105bc6
PHP 8.3.36-dev (cli) (NTS), PHP-8.3 9c6ad6caeeb
Operating System

Ubuntu 24.04, x86_64

主要言語
C
スター
40.7k
フォーク
8.3k
平均マージ
2日 17時間
マージ済み PR(30日)
159

環境構築

はじめの一歩

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

php/php-src のほかの issue

php/php-src の issue をすべて見る

似ている issue

C の issue をもっと見る

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

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