Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Aperta
#24,361 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
22/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
c, php
Ambito
compilers

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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

Lingua principale
C
Stelle
41.7k
Fork
8.5k
Merge medio
2g 18h
PR unite (30g)
159

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di php/php-src

Tutte le issue di php/php-src

Issue simili

Altre issue su C

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.