[php-src] Issue #20913: OPcache folds FETCH_CONSTANT across later DECLARE_CONST when zend_execute_internal is set

[email protected] (KaseyJenkins)
Newsgroups php.bugs
Message-ID <[email protected]>
Issue: https://github.com/php/php-src/issues/20913
Author: 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](https://github.com/php/php-src/blob/PHP-8.5.1/Zend/zend_compile.c#L3917) 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](https://github.com/php/php-src/blob/PHP-8.5.1/Zend/Optimizer/pass1.c#L160) 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](https://github.com/php/php-src/blob/PHP-8.5.1/ext/opcache/tests/bug66251.phpt)).
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

```plain
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
```

### Operating System

_No response_
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.