You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3f90b11
Browse filesBrowse the repository at this point in the historyBrowse files
Refuse the trap's open for a file the process has already run
The empty script FileReadTrapStreamWrapper::stream_read() serves only shadows
the real file while the compiler actually asks the wrapper for the contents.
With OPcache holding the script it does not ask: stream_open() still runs, so
the path is recorded, but stream_read() never does, and the real op_array
executes a second time. A file declaring a function then fatals with "Cannot
redeclare", which is the function-per-file layout of php-standard-library and
azjezz/psl: a files-autoload bootstrap loads every one of them at startup, and
the same paths stay reachable through a PSR-4 prefix for the autoloader to
include again.
Refuse the open when the target has already run in this process. That stops
the include before anything can be compiled or fetched from a cache, and the
path is recorded beforehand, which is all AutoloadSourceLocator wants from the
trap. Serving the empty script is unchanged for every file that has not run,
so the cold path keeps working as it did.
Deciding this on get_included_files() rather than on the OPcache API keeps the
answer independent of how, or whether, the script is cached. An earlier
revision of this fix dropped the shared memory entry with opcache_invalidate()
so that the include would read through the wrapper again; that held on Windows
but not on Linux, where the same three assumptions it rested on - that
opcache_is_script_cached() reports the entry, that opcache_invalidate() drops
it, and that OPcache consults its cache only after stream_open() has run - do
not all hold with this wrapper registered over file://. Refusing the open
depends on none of them, and also covers opcache.file_cache_only, which no API
can invalidate.
Refusing is not a new failure mode: an autoloader reaching this branch is
about to include a file that already ran, which fatally redeclares today. An
autoloader using include(), as Composer's does, now gets a warning that
AutoloadSourceLocator::silenceErrors() swallows.
The test drives both branches in a subprocess under the worker's own OPcache
flags: a file the bootstrap already loaded, which must be trapped without
running again, and one nothing has loaded, which must still be trapped and
left undeclared.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments