Commit 3ab8be661e8 for php.net
commit 3ab8be661e811fa5dbcadb9267f37fbc1666101f
Author: Benedikt Franke <benedikt@franke.tech>
Date: Sun Sep 6 14:56:46 2026 +0200
Fix GH-23232: lone namespace separator asks the autoloader for an empty class name (#23233)
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('\').
diff --git a/NEWS b/NEWS
index b2e8d22465b..41b26e5d094 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ PHP NEWS
- Core:
. Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or
next() call on the inner generator). (iliaal)
+ . Fixed bug GH-23232 (lone namespace separator asks the autoloader for an
+ empty class name). (spawnia)
. Fixed bug GH-23301 (Nested "yield from" yields a value twice when the
middle generator delegates again). (Lazizbek Ergashev)
diff --git a/Zend/tests/gh23232.phpt b/Zend/tests/gh23232.phpt
new file mode 100644
index 00000000000..57a2e0135e8
--- /dev/null
+++ b/Zend/tests/gh23232.phpt
@@ -0,0 +1,27 @@
+--TEST--
+GH-23232 (lone namespace separator asks the autoloader for an empty class name)
+--FILE--
+<?php
+spl_autoload_register(function (string $class): void {
+ echo "autoload: '$class'\n";
+});
+
+foreach (['::a', '\::a', '\Foo::a', 'Foo::a'] as $callable) {
+ echo "is_callable(\"$callable\")\n";
+ var_dump(is_callable($callable));
+}
+
+var_dump(class_exists('\\'));
+?>
+--EXPECT--
+is_callable("::a")
+bool(false)
+is_callable("\::a")
+bool(false)
+is_callable("\Foo::a")
+autoload: 'Foo'
+bool(false)
+is_callable("Foo::a")
+autoload: 'Foo'
+bool(false)
+bool(false)
diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c
index c81a8cfe5ff..6e42be5f888 100644
--- a/Zend/zend_execute_API.c
+++ b/Zend/zend_execute_API.c
@@ -1180,6 +1180,10 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *
}
if (ZSTR_VAL(name)[0] == '\\') {
+ if (ZSTR_LEN(name) == 1) {
+ /* A lone namespace separator names no class, e.g. is_callable('\::method'). */
+ return NULL;
+ }
lc_name = zend_string_alloc(ZSTR_LEN(name) - 1, 0);
zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1);
} else {