[php-src] Issue #24745: OPcache: file cache loses attributes on constants on 32-bit builds
| From: | mho22 | Date: | Mon, 26 Oct 2026 14:48:07 +0000 |
| Subject: | [php-src] Issue #24745: OPcache: file cache loses attributes on constants on 32-bit builds | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253215@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24745
Author: mho22
### Description
I prepared this report with an LLM. I ran the reproduction and the controls myself, on the Docker
images named below.
The following code:
foo.php:
```php
<?php
#[Deprecated(message: 'bar')]
const FOO = 1;
```
test.php:
```php
<?php
require __DIR__ . '/foo.php';
var_dump((new ReflectionConstant('FOO'))->getAttributes()[0]->getArguments());
```
Run two times on a 32-bit build:
```
php -d opcache.enable_cli=1 -d opcache.file_cache=/tmp/opcache -d opcache.file_cache_only=1 -d
opcache.file_update_protection=0 test.php
```
Resulted in this output, on both runs:
```
Segmentation fault
```
But I expected this output instead:
```
array(1) {
["message"]=>
string(3) "bar"
}
```
I reproduced it with the i386/php:8.5-cli Docker image. The same image gives the
expected output without OPcache, and with the file cache for a constant without an attribute.
php:8.5-cli on amd64 gives the expected output with the file cache.
The cause is in zend_file_cache_serialize_op_array()
([ext/opcache/zend_file_cache.c#L560-L565](https://github.com/php/php-src/blob/php-8.5.12/ext/opcache/zend_file_cache.c#L560-L565)):
```c
if (opline->opcode == ZEND_OP_DATA
&& (opline-1)->opcode == ZEND_DECLARE_ATTRIBUTED_CONST
) {
zval *literal = RT_CONSTANT(opline, opline->op1);
SERIALIZE_ATTRIBUTES(Z_PTR_P(literal));
}
```
opline points into the serialization buffer. With ZEND_USE_ABS_CONST_ADDR
(32-bit builds), RT_CONSTANT() returns opline->op1.zv. That pointer is
not serialized yet, so it still points to the literal in script->mem, not to its
copy in buf. SERIALIZE_ATTRIBUTES() therefore rewrites the attributes
pointer of the live script and leaves a raw pointer in the buffer:
- the current request loses the attributes of the constant;
- the .bin file stores an invalid pointer, which every later request loads.
On 64-bit builds, RT_CONSTANT() is relative to opline, so it already
resolves into buf. zend_file_cache_unserialize_op_array() is correct,
because it reads the literal after it unserializes op1.zv.
A possible fix, with the same pattern as the GH-17733 code for ZEND_INIT_FCALL a few
lines below:
```c
zval *literal = RT_CONSTANT(opline, opline->op1);
#if ZEND_USE_ABS_CONST_ADDR
SERIALIZE_PTR(literal);
UNSERIALIZE_PTR(literal);
#endif
SERIALIZE_ATTRIBUTES(Z_PTR_P(literal));
```
### PHP Version
```plain
PHP 8.5.12 (cli) (built: Oct 21 2026 15:55:33) (NTS)
Copyright (c) The PHP Group
Built by https://github.com/docker-library/php
Zend Engine v4.5.12, Copyright (c) Zend Technologies
with Zend OPcache v8.5.12, Copyright (c), by Zend Technologies
```
### Operating System
Debian, i386 (i386/php:8.5-cli Docker image under qemu on macOS arm64)