[wp-trac] [WordPress Trac] #64538: memoize wp_normalize_path
WordPress Trac
noreply at wordpress.org
Thu Apr 2 21:22:17 UTC 2026
#64538: memoize wp_normalize_path
--------------------------------------+--------------------------
Reporter: josephscott | Owner: dmsnell
Type: defect (bug) | Status: reopened
Priority: normal | Milestone: 7.0
Component: General | Version: 3.9
Severity: normal | Resolution:
Keywords: has-patch has-unit-tests | Focuses: performance
--------------------------------------+--------------------------
Comment (by josephscott):
I had Claude code go through the PHP source changes to see if something
could be found to explain why this only failed on those two versions of
PHP ( 8.1 and 8.2 ). It looks like the way this test works was
particularly unlucky.
{{{
PHP 8.1/8.2 Bug: ReflectionFunction::getStaticVariables() Returns Stale
Values
When OPcache Is Enabled
ReflectionFunction::getStaticVariables() returns compile-time default
values
instead of current runtime values on PHP 8.1 and 8.2 when OPcache is
enabled.
Affected versions: PHP 8.1.0–8.1.x, 8.2.0–8.2.x (with OPcache enabled)
Not affected: PHP 7.4, 8.0, 8.3+
Cause
Two changes combined to create this bug:
1. PHP 8.1 changed how static variable pointers are stored. In PHP 8.0
and
earlier, the internal static_variables_ptr pointed directly at the
function's
own static_variables field, so reflection always read the live data. PHP
8.1
switched to a separate storage slot initialized to NULL, relying on the
ZEND_BIND_STATIC opcode to populate it at runtime.
2. OPcache's Dead Code Elimination (DCE) incorrectly treats
ZEND_BIND_STATIC as
side-effect-free for static variables with simple initializers (like
static
$cache = array()). This allows the optimizer to eliminate or weaken the
opcode,
so the storage slot is never populated. getStaticVariables() then falls
back to
returning the compile-time defaults.
PHP 8.0 was unaffected because the self-referencing pointer masked the
optimizer's behavior. PHP 8.3 fixed the issue via the
https://wiki.php.net/rfc/arbitrary_static_variable_initializers
(https://github.com/php/php-src/pull/9301), which introduced a new
ZEND_BIND_INIT_STATIC_OR_JMP opcode that DCE always treats as side-
effectful,
and updated DCE to recognize that reference bindings with initializers
are
observable through reflection.
Reproduction
<?php
// Run with: php -d opcache.enable_cli=1 repro.php
function demo() {
static $cache = array();
if ( isset( $cache['key'] ) ) {
return $cache['key'];
}
$cache['key'] = 'value';
return $cache['key'];
}
demo();
$statics = ( new ReflectionFunction( 'demo' ) )->getStaticVariables();
// PHP 8.1/8.2 with OPcache: array() (empty — bug)
// All other versions: array( 'key' => 'value' )
var_dump( $statics['cache'] );
References
- https://wiki.php.net/rfc/arbitrary_static_variable_initializers — the
PHP 8.3
RFC whose implementation fixed this as a side effect
- https://github.com/php/php-src/pull/9301 — the implementation,
including the
DCE fix and the regression test optimize_static_002.phpt ("Keep
BIND_STATIC when
static variable has an initializer")
- https://github.com/php/php-src/issues/9177 — related issue about
static
variables and reflection
- https://www.npopov.com/2021/10/13/How-opcache-works.html — background
on
ZEND_MAP_PTR and immutable functions
}}}
I don't have a lot of experience with PHP internals, and given that this
is already fixed I think it is more helpful to focus addressing the
testing code. What if we split this test into two -
https://gist.github.com/josephscott/e1265dc63d878a53947ec6c4cc555390 - one
for each group of PHP versions ( with and without this bug ).
For `wp_normlize_path()` itself, I haven't found anything that needs to be
addressed.
--
Ticket URL: <https://core.trac.wordpress.org/ticket/64538#comment:29>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list