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 {