Skip to content

Fix GH-20112: Fatal error during autoloading of complex inheritance chain#21573

Open
iliaal wants to merge 1 commit intophp:PHP-8.4from
iliaal:fix/gh-20112-nested-autoload-crash-8.4
Open

Fix GH-20112: Fatal error during autoloading of complex inheritance chain#21573
iliaal wants to merge 1 commit intophp:PHP-8.4from
iliaal:fix/gh-20112-nested-autoload-crash-8.4

Conversation

@iliaal
Copy link
Copy Markdown
Contributor

@iliaal iliaal commented Mar 29, 2026

Fixes #20112

load_delayed_classes() iterates a shared CG(delayed_autoloads) hash table. When autoloading triggers linking of another class with unresolved variance, the recursive call walks the same table and can pull entries that belong to an outer caller. If that entry's parent is still mid-linking up the call stack, the load hits a fatal error.

The fix adds a nesting depth counter. At depth > 1, if a class doesn't load and isn't in the class table, we catch the exception and re-append the entry for the outer caller to pick up once its dependency chain completes. A consecutive-failure count breaks out when no progress is possible. At depth 1, failures propagate as before.

Targeting 8.4 since the bug has existed since PHP 8.1 (bisected to a38f4f9). No performance impact; the new code only runs on the exception path.

Copy link
Copy Markdown
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't know the variance-related code of PHP well. I can't review this.


HashTable *delayed_variance_obligations;
HashTable *delayed_autoloads;
uint32_t delayed_autoloads_depth;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an ABI break. It's not allowed in minor stable releases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the counter to a file-local ZEND_TLS uint32_t in zend_inheritance.c, so it stays per-thread without touching zend_compiler_globals. Same scope as CG(delayed_autoloads) itself, matching the pattern at zend_fibers.c:187.

…e chain

When load_delayed_classes() is called recursively (e.g., autoloading a
class whose linking triggers another class with unresolved variance), the
shared delayed_autoloads table may contain entries from an outer caller
whose dependencies are not yet available. The inner call would attempt to
load these unrelated entries, causing a fatal error when their parent
class is still mid-linking higher up the call stack.

In nested calls, catch such loading failures and re-append the entry for
the outer caller to process later, when the dependency chain is complete.
A consecutive-failure counter ensures no infinite retry loops.

The nesting depth counter is kept as a file-local ZEND_TLS variable
rather than added to zend_compiler_globals, since PHP-8.4 forbids ABI
changes.

Closes phpGH-20112
@iliaal iliaal force-pushed the fix/gh-20112-nested-autoload-crash-8.4 branch from af2a409 to d73b46e Compare April 14, 2026 00:00
@iliaal iliaal requested a review from dstogov April 14, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants