OPcache folds FETCH_CONSTANT across later DECLARE_CONST when zend_execute_internal is set
@arnaud-lb y travaille déjà.
Depuis le 13/1/2026.
- Langage dominant
- C
- Étoiles
- 40.4k
- Forks
- 8.2k
- Merge moyen
- 2 j 13 h
- PR mergées (30 j)
- 96
Description
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
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Évaluation
Cette issue n'a pas encore été évaluée.