Commit 3a9d86a81bb for php.net

commit 3a9d86a81bbecf867eb8e5d8515e5219d75d14dc
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Fri Jul 31 07:53:05 2026 -0400

    Parse qualified reflection names with a length-aware search

    ReflectionMethod::__construct(), ReflectionMethod::createFromMethodName()
    and ReflectionClass::getProperty() split "Class::member" with strstr(),
    which stops at the first NUL. Anonymous class names embed one, so a name
    obtained from ReflectionClass::getName() was rejected. Use zend_memnstr()
    over the whole string instead.

    Closes GH-22970

diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 65462bb8652..1e2f5d08acf 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -3327,7 +3327,7 @@ static void instantiate_reflection_method(INTERNAL_FUNCTION_PARAMETERS, bool is_
 	zend_object *orig_obj = NULL;
 	zend_class_entry *ce = NULL;
 	zend_string *class_name = NULL;
-	char *method_name;
+	const char *method_name;
 	size_t method_name_len;
 	char *lcname;

@@ -3370,11 +3370,11 @@ static void instantiate_reflection_method(INTERNAL_FUNCTION_PARAMETERS, bool is_
 		method_name = ZSTR_VAL(arg2_str);
 		method_name_len = ZSTR_LEN(arg2_str);
 	} else {
-		char *tmp;
+		const char *tmp;
 		size_t tmp_len;
-		char *name = ZSTR_VAL(arg1_str);
+		const char *name = ZSTR_VAL(arg1_str);

-		if ((tmp = strstr(name, "::")) == NULL) {
+		if ((tmp = zend_memnstr(name, "::", 2, name + ZSTR_LEN(arg1_str))) == NULL) {
 			zend_argument_error(reflection_exception_ptr, 1, "must be a valid method name");
 			RETURN_THROWS();
 		}
@@ -4759,7 +4759,7 @@ ZEND_METHOD(ReflectionClass, getProperty)
 	zend_class_entry *ce, *ce2;
 	zend_property_info *property_info;
 	zend_string *name, *classname;
-	char *tmp, *str_name;
+	const char *tmp, *str_name;
 	size_t classname_len, str_name_len;

 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) {
@@ -4781,7 +4781,7 @@ ZEND_METHOD(ReflectionClass, getProperty)
 		}
 	}
 	str_name = ZSTR_VAL(name);
-	if ((tmp = strstr(ZSTR_VAL(name), "::")) != NULL) {
+	if ((tmp = zend_memnstr(ZSTR_VAL(name), "::", 2, ZSTR_VAL(name) + ZSTR_LEN(name))) != NULL) {
 		classname_len = tmp - ZSTR_VAL(name);
 		classname = zend_string_init(ZSTR_VAL(name), classname_len, 0);
 		str_name_len = ZSTR_LEN(name) - (classname_len + 2);
diff --git a/ext/reflection/tests/ReflectionClass_getProperty_anonymous_class.phpt b/ext/reflection/tests/ReflectionClass_getProperty_anonymous_class.phpt
new file mode 100644
index 00000000000..67169000b4e
--- /dev/null
+++ b/ext/reflection/tests/ReflectionClass_getProperty_anonymous_class.phpt
@@ -0,0 +1,21 @@
+--TEST--
+ReflectionClass::getProperty() with a qualified anonymous class name
+--FILE--
+<?php
+
+$obj = new class {
+    public $p = 42;
+};
+
+$reflector = new ReflectionClass($obj);
+$name = $reflector->getName();
+var_dump(str_contains($name, "\0"));
+
+$p = $reflector->getProperty($name . '::p');
+var_dump($p->getName(), $p->getValue($obj));
+
+?>
+--EXPECT--
+bool(true)
+string(1) "p"
+int(42)
diff --git a/ext/reflection/tests/ReflectionMethod_createFromMethodName_anonymous_class.phpt b/ext/reflection/tests/ReflectionMethod_createFromMethodName_anonymous_class.phpt
new file mode 100644
index 00000000000..a3ddf9bcd81
--- /dev/null
+++ b/ext/reflection/tests/ReflectionMethod_createFromMethodName_anonymous_class.phpt
@@ -0,0 +1,20 @@
+--TEST--
+ReflectionMethod::createFromMethodName() with an anonymous class name
+--FILE--
+<?php
+
+$obj = new class {
+    public function m() { return 42; }
+};
+
+$name = (new ReflectionClass($obj))->getName();
+var_dump(str_contains($name, "\0"));
+
+$m = ReflectionMethod::createFromMethodName($name . '::m');
+var_dump($m->getName(), $m->invoke($obj));
+
+?>
+--EXPECT--
+bool(true)
+string(1) "m"
+int(42)