[php-src] Issue #22214: opcache.dups_fix is honored for duplicate classes but not duplicate functions
[email protected] (sibidharan) Tue, 2 Jun 2026 22:39:30 +0000
| Newsgroups | php.bugs |
|---|---|
| Message-ID | <[email protected]> |
Issue: https://github.com/php/php-src/issues/22214
Author: sibidharan
### Description
`opcache.dups_fix` is documented as a fix for "Cannot redeclare" errors, but it only covers classes, not functions.
Inside opcache the directive is read in the table-copy that installs a cached script's symbols (`ext/opcache/zend_accelerator_util_funcs.c`). The class-table copy honors it — when `ignore_dups` is set it keeps the existing class and skips the duplicate:
```c
/* _zend_accel_class_hash_copy */
} else if (UNEXPECTED(!ZCG(accel_directives).ignore_dups)) {
...
zend_class_redeclaration_error(E_ERROR, Z_PTR_P(t));
return;
}
continue; /* ignore_dups: keep the first definition */
```
The function-table copy right next to it doesn't check the directive at all — it goes straight to the fatal:
```c
/* _zend_accel_function_hash_copy */
t = zend_hash_find_known_hash(target, p->key);
if (UNEXPECTED(t != NULL)) {
goto failure; /* -> "Cannot redeclare function ..." regardless of opcache.dups_fix */
}
```
So with `opcache.dups_fix=1` a duplicate **class** is tolerated (first wins) but a duplicate **function** still fatals. The directive name and docs don't distinguish between the two, so this reads like an oversight rather than something intentional.
### Where it bites
Long-running application servers that re-execute `require_once`'d files per request (we ran into this building [ZealPHP](https://github.com/sibidharan/zealphp), an OpenSwoole-based runtime). opcache re-installs a cached script's symbols into a table that already has them; `dups_fix` covers the class collision, but the function collision still kills the request with "Cannot redeclare function". So `dups_fix` only half-solves it for these setups — WordPress for example gets past its class redeclares with `dups_fix=1` but then dies on the first function (`_wp_can_use_pcre_u` in `wp-includes/compat.php`).
### Suggested fix
Make the function copy consistent with the class copy:
```diff
t = zend_hash_find_known_hash(target, p->key);
if (UNEXPECTED(t != NULL)) {
- goto failure;
+ /* Honor opcache.dups_fix for functions too — the class-table
+ * copy above already does. Keep the first-declared function. */
+ if (!ZCG(accel_directives).ignore_dups) {
+ goto failure;
+ }
+ continue;
}
```
I've tested this against 8.4 and it does the job (WordPress runs clean under opcache + a per-request re-execution model with it). Happy to open a PR with a `.phpt` if the asymmetry is agreed to be unintended — mostly wanted to check whether it's deliberate before sending one.
### PHP Version
PHP 8.4 (the code is the same on master)
### Operating System
Linux