[php-src] PR #25286: Fix array_map() optimization not resolving the function name of an FCC
| From: | ChrisAbner | Date: | Sat, 07 Nov 2026 16:23:34 +0000 |
| Subject: | [php-src] PR #25286: Fix array_map() optimization not resolving the function name of an FCC | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-40397@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/25286
Author: ChrisAbner
In a namespace, or with a use function import, array_map() with a first-class callable called the
wrong function. The foreach optimization (added in #20934, extended in #23254) compiles the function
name as an expression before the loop. That yields the name as written, with no namespace
resolution. The loop then calls it as a global function, ignoring the current namespace, the use
function imports and the fallback to the global function. In namespace App with App\strtoupper
defined, \array_map(strtoupper(...), ['a']) returned A from the internal function instead
of calling App\strtoupper. With use function Lib\fmt, array_map(fmt(...), ...) failed with
"undefined function fmt()".
For a literal function name in a ZEND_AST_CALL, this keeps the name AST and lets zend_compile_call()
resolve it in the loop body, the same way a normal call is resolved, with the namespace fallback.
Non-literal names keep the pre-loop evaluation, because they can have side effects and must run
once. This mirrors #24244, which did the same for the class name of a static FCC. Resolving the name
eagerly with zend_resolve_function_name() was the other option, but it does not express the runtime
App\f then \f fallback, so I kept the lazy resolution in the call.
Introduced by 40a8468559 (#23254) in 8.6.0RC3. Confirmed by bisect: the parent 31db39c returns the
correct value, 40a8468559 returns the wrong one. It affects 8.6 (RC3 to RC5) and master, not 8.5 or
8.4.
**Verification**
| Case (in namespace App) | Before | After |
|---|---|---|
|
\array_map(strtoupper(...), ['a']) with App\strtoupper | A |
APP:a |
| \array_map(label(...), [1]) with App\label | global label | App\label |
| use function Lib\fmt; array_map(fmt(...), [1]) | undefined function | Lib\fmt |
| \array_map(namespace\label(...), [4]), \label(...), PFA pad(?,
'!') | already correct | still correct |
The new test Zend/tests/array_map_foreach_optimization_function_name.phpt passes, and the 14
existing array_map_foreach_optimization_* tests still pass, without OPcache, with OPcache and with
the tracing JIT. On PHP-8.6, Zend/tests, ext/opcache/tests and ext/standard/tests/array run with 0
failures (7360 tests), without OPcache and with it. On master, Zend/tests and ext/opcache/tests run
with 0 failures (6572 tests).
Not covered: non-literal function names are unchanged. Static method FCCs already resolved correctly
(the class name since #24244, the method name is a plain literal). I found this with a deterministic
differential testing tool I am developing.