⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

OPcache folds FETCH_CONSTANT across later DECLARE_CONST when zend_execute_internal is set #20913

@KaseyJenkins

Description

@KaseyJenkins

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions