php / php/php-src

OPcache folds FETCH_CONSTANT across later DECLARE_CONST when zend_execute_internal is set

Offen
#20,913 1 Kommentar 0 Reaktionen 1 zugewiesene Person Auf GitHub ansehen

@arnaud-lb arbeitet bereits daran.

Seit 13.1.2026.

Bug Extension: opcache Status: Needs Triage
Vorherrschende Sprache
C
Sterne
40.4k
Forks
8.2k
Ø Merge
2 T. 13 Std.
Gemergte PRs (30 T.)
96

Beschreibung

Description

OPcache folds FETCH_CONSTANT across later DECLARE_CONST when zend_execute_internal is set, changing program semantics.

When an extension sets zend_execute_internal (even to a no-op), zend_get_call_op chooses the generic ZEND_DO_FCALL for INIT_FCALL_BY_NAME. This appears to affect OPcache pass 1: in a script that calls a function before a constant is declared, OPcache replaces ZEND_FETCH_CONSTANT inside that function with the later-declared literal, changing behavior from a fatal error to printing the constant value.

Reproducer

INI:

opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.opt_debug_level=0x10000

Code:

<?php
printf("A=%s\n", getA());
const A = "hello";
function getA() { return A; }

Without any module: the script fatals (Undefined constant "A"), and getA keeps FETCH_CONSTANT (see bug66251).
With a module that only does:

orig_execute_internal = zend_execute_internal;
zend_execute_internal = our_own_execute_internal;

the optimizer turns getA into RETURN "hello" and the script prints A=hello.

Question
Is this behavior expected when an extension hooks zend_execute_internal?

Output (for reference).

No zend_execute_internal (before and after optimizer):

$_main:
     ; (lines=8, args=0, vars=0, tmps=2)
     ; (before optimizer)
     ; /bin/bug66251.php:1-6
     ; return  [] RANGE[0..0]
0000 INIT_FCALL 2 112 string("printf")
0001 SEND_VAL string("A=%s\n") 1
0002 INIT_FCALL_BY_NAME 0 string("getA")
0003 V0 = DO_FCALL_BY_NAME
0004 SEND_VAR V0 2
0005 DO_ICALL
0006 DECLARE_CONST string("A") string("hello")
0007 RETURN int(1)

getA:
     ; (lines=3, args=0, vars=0, tmps=1)
     ; (before optimizer)
     ; /bin/bug66251.php:4-4
     ; return  [] RANGE[0..0]
0000 T0 = FETCH_CONSTANT string("A")
0001 RETURN T0
0002 RETURN null

$_main:
     ; (lines=8, args=0, vars=0, tmps=1)
     ; (after optimizer)
     ; /bin/bug66251.php:1-6
0000 INIT_FCALL 2 112 string("printf")
0001 SEND_VAL string("A=%s\n") 1
0002 INIT_FCALL 0 96 string("geta")
0003 V0 = DO_UCALL
0004 SEND_VAR V0 2
0005 DO_ICALL
0006 DECLARE_CONST string("A") string("hello")
0007 RETURN int(1)

getA:
     ; (lines=2, args=0, vars=0, tmps=1)
     ; (after optimizer)
     ; /bin/bug66251.php:4-4
0000 T0 = FETCH_CONSTANT string("A")
0001 RETURN T0

zend_execute_internal is set (before and after optimizer):

$_main:
     ; (lines=8, args=0, vars=0, tmps=2)
     ; (before optimizer)
     ; /bin/bug66251.php:1-6
     ; return  [] RANGE[0..0]
0000 INIT_FCALL 2 112 string("printf")
0001 SEND_VAL string("A=%s\n") 1
0002 INIT_FCALL_BY_NAME 0 string("getA")
0003 V0 = DO_FCALL
0004 SEND_VAR V0 2
0005 DO_FCALL
0006 DECLARE_CONST string("A") string("hello")
0007 RETURN int(1)

getA:
     ; (lines=3, args=0, vars=0, tmps=1)
     ; (before optimizer)
     ; /bin/bug66251.php:4-4
     ; return  [] RANGE[0..0]
0000 T0 = FETCH_CONSTANT string("A")
0001 RETURN T0
0002 RETURN null

$_main:
     ; (lines=8, args=0, vars=0, tmps=1)
     ; (after optimizer)
     ; /bin/bug66251.php:1-6
0000 INIT_FCALL 2 112 string("printf")
0001 SEND_VAL string("A=%s\n") 1
0002 INIT_FCALL 0 80 string("geta")
0003 V0 = DO_UCALL
0004 SEND_VAR V0 2
0005 DO_FCALL
0006 DECLARE_CONST string("A") string("hello")
0007 RETURN int(1)

getA:
     ; (lines=1, args=0, vars=0, tmps=0)
     ; (after optimizer)
     ; /bin/bug66251.php:4-4
0000 RETURN string("hello")
PHP Version
PHP 8.5.1 (cli) (built: Dec 17 2025 08:47:15) (ZTS)
Copyright (c) The PHP Group
Zend Engine v4.5.1, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.1, Copyright (c), by Zend Technologies

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.