Skip to content

Fix GH-23232: lone namespace separator asks the autoloader for an empty class name - #23233

Merged
Girgias merged 1 commit into
php:PHP-8.4from
spawnia:fix/is-callable-empty-class-name
Sep 6, 2026
Merged

Girgias merged 1 commit into
php:PHP-8.4from
spawnia:fix/is-callable-empty-class-name

Conversation

@spawnia

@spawnia spawnia commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Fixes GH-23232.

zend_lookup_class_ex() rejects an empty class name, but not a name consisting solely of the namespace separator: "\" passes the length check, gets its leading \ stripped, and is then looked up — and autoloaded — as "".

That is reachable from userland through any entry point using that lookup:

  • is_callable('\::method') splits at ::, takes "\" as the class part and hands it to zend_lookup_class(). is_callable('::method') is rejected as an invalid function name earlier, which is where the asymmetry in the issue comes from.
  • class_exists('\') passes the name straight through, see https://3v4l.org/Qe3u4.

A lone \ names no class, so return NULL before consulting the class table or the autoloader.

This is observable, not just wasted work: Composer's ClassLoader::findFileWithExtension() does $first = $class[0]; and warns Uninitialized string offset 0 when handed '', which in applications that promote warnings to exceptions aborts the request. We hit it in CI through Laravel's Factory::expandAttributes(), which calls is_callable() on every string attribute — a randomly generated password starting with \:: was enough.

Targeting PHP-8.4 as the lowest branch still receiving bug fixes.

make test passes on Zend/tests and ext/standard/tests/general_functions (5085 passed, 0 failed) in a debug build; the new .phpt fails without the patch.

@spawnia
spawnia changed the base branch from master to PHP-8.4 August 12, 2026 15:16

@Girgias Girgias left a comment

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 seems okay as a fix, might be worse to see if this can be prevent on the call sites for the future.

@spawnia
spawnia marked this pull request as ready for review August 13, 2026 06:29
@spawnia
spawnia requested a review from dstogov as a code owner August 13, 2026 06:29
@spawnia

spawnia commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks! I put the guard in zend_lookup_class_ex() deliberately: every path that can produce a lone \is_callable(), class_exists(), string-callable invocation, callable type coercion — funnels through it, so one check covers them all and no future call site can regress.

Validating at the call sites would mean duplicating "is this even a syntactically possible class name?" in each of them. zend_is_callable_check_class() is the main offender today: it hands over whatever precedes :: without inspecting it. Happy to follow up with a stricter check there (and in the other string-callable parsers) as a separate PR if you'd prefer that on top — it would turn the autoloader call into a no-op earlier, but it is a behaviour question rather than a bug fix.

@spawnia
spawnia force-pushed the fix/is-callable-empty-class-name branch from b0d5d4d to 3916faf Compare August 27, 2026 09:22
@spawnia

spawnia commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

For the record, the two userland-side fixes were both declined, which leaves this the only remaining place to fix it — and arguably the right level anyway.

That reasoning points here: the caller handing Composer an empty class name is the engine itself, via zend_lookup_class_ex() stripping the leading \ from "\". No userland code asked for the class ''.

@Seldaek

Seldaek commented Aug 27, 2026

Copy link
Copy Markdown

Indeed, autoloading '\' or '' is the same silly thing nobody should be doing. But yeah if php core can stop calling autoloaders for it that's also good.

@Girgias

Girgias commented Aug 28, 2026

Copy link
Copy Markdown
Member

I'm realising that this is a long standing behavioural change (even if it is dumb) so I'd prefer to only target master for this.

@spawnia

spawnia commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

I am struggling to construct a valid use case that would work now and break with this fix applied, I would categorize this as a bugfix. Unless the policy of PHP is to remain bug-for-bug compatible once a minor version is released, it should be fine for a bugfix release? Open to retargeting to master if that is what it takes to get this merged though.

@Seldaek

Seldaek commented Aug 31, 2026

Copy link
Copy Markdown

Just to make sure, this also fixes class_exists()? https://3v4l.org/Qe3u4#v8.5.3 because that's the main one I've had reports about regarding the Composer ClassLoader

@spawnia

spawnia commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Yes. class_exists() goes through the same zend_lookup_class_ex() path, so class_exists('\') no longer calls the autoloader with ''.

The test in this PR asserts it: var_dump(class_exists('\\')) with no autoload: '' line in --EXPECT--, so CI would fail if the autoloader were still invoked.

…ty class name

A class name consisting solely of the namespace separator passed the
length check in zend_lookup_class_ex(), lost its leading backslash and
was then looked up and autoloaded as an empty string. This affected all
entry points using that lookup, e.g. is_callable('\::method') and
class_exists('\').
@spawnia
spawnia force-pushed the fix/is-callable-empty-class-name branch from 3916faf to 61874eb Compare August 31, 2026 10:00
@spawnia spawnia changed the title Fix GH-23232: is_callable('\::method') asks the autoloader for an empty class name Fix GH-23232: lone namespace separator asks the autoloader for an empty class name Aug 31, 2026
@Girgias
Girgias merged commit 3ab8be6 into php:PHP-8.4 Sep 6, 2026
18 checks passed
@spawnia
spawnia deleted the fix/is-callable-empty-class-name branch September 7, 2026 06:30
@spawnia

spawnia commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Thank you @Girgias for your consideration and merge!

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.

Lone namespace separator asks the autoloader for an empty class name

3 participants