[php-src] master: Zend: handle non-string arguments for ZPP class-string specifier as expected (#23001)

Gina Peter Banyard via GitHub <[email protected]> Mon, 3 Aug 2026 16:30:46 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Gina Peter Banyard (Girgias)
Committer: GitHub (web-flow)
Pusher: Girgias
Date: 2026-08-03T17:30:43+01:00

Commit: https://github.com/php/php-src/commit/d6b0c761b2387498bf24874166e382cb0f5acffa
Raw diff: https://github.com/php/php-src/commit/d6b0c761b2387498bf24874166e382cb0f5acffa.diff

Zend: handle non-string arguments for ZPP class-string specifier as expected (#23001)

* Zend: add tests for ZPP class-string specifier

* Zend: handle non-string arguments for ZPP class-string specifier as expected

This doesn't behave like any of the other specifiers, as it doesn't respect strict_types, emit a deprecation for null, or reject arrays

Changed paths:
  A  ext/zend_test/tests/zpp/class-string_zpp_specifier_strict_mode.phpt
  A  ext/zend_test/tests/zpp/class-string_zpp_specifier_weak_mode.phpt
  M  Zend/zend_API.c


Diff:

diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index 758977e07d6a..527f4e232538 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -486,27 +486,29 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error(int type, const zend_clas
 
 ZEND_API bool ZEND_FASTCALL zend_parse_arg_class(zval *arg, zend_class_entry **pce, uint32_t num, bool check_null) /* {{{ */
 {
-	zend_class_entry *ce_base = *pce;
+	const zend_class_entry *ce_base = *pce;
 
 	if (check_null && Z_TYPE_P(arg) == IS_NULL) {
 		*pce = NULL;
 		return 1;
 	}
-	if (!try_convert_to_string(arg)) {
+	zend_string *class_name;
+	if (!zend_parse_arg_str(arg, &class_name, check_null, num)) {
 		*pce = NULL;
+		zend_wrong_parameter_error(ZPP_ERROR_WRONG_ARG, num, NULL, check_null ? Z_EXPECTED_STRING_OR_NULL : Z_EXPECTED_STRING, arg);
 		return 0;
 	}
 
-	*pce = zend_lookup_class(Z_STR_P(arg));
+	*pce = zend_lookup_class(class_name);
 	if (ce_base) {
 		if ((!*pce || !instanceof_function(*pce, ce_base))) {
-			zend_argument_type_error(num, "must be a class name derived from %s, %s given", ZSTR_VAL(ce_base->name), Z_STRVAL_P(arg));
+			zend_argument_type_error(num, "must be a class name derived from %s, %s given", ZSTR_VAL(ce_base->name), ZSTR_VAL(class_name));
 			*pce = NULL;
 			return 0;
 		}
 	}
 	if (!*pce) {
-		zend_argument_type_error(num, "must be a valid class name, %s given", Z_STRVAL_P(arg));
+		zend_argument_type_error(num, "must be a valid class name, %s given", ZSTR_VAL(class_name));
 		return 0;
 	}
 	return 1;
diff --git a/ext/zend_test/tests/zpp/class-string_zpp_specifier_strict_mode.phpt b/ext/zend_test/tests/zpp/class-string_zpp_specifier_strict_mode.phpt
new file mode 100644
index 000000000000..a72d504a2db3
--- /dev/null
+++ b/ext/zend_test/tests/zpp/class-string_zpp_specifier_strict_mode.phpt
@@ -0,0 +1,49 @@
+--TEST--
+Test class string ZPP specifier
+--EXTENSIONS--
+zend_test
+--FILE--
+<?php
+
+declare(strict_types=1);
+
+class S {
+	public function __toString(): string {
+		return 'S class';
+	}
+}
+
+$types = [
+	null,
+	false,
+	true,
+	42,
+	73.5,
+	'string',
+	[],
+	new stdClass(),
+	new S(),
+	STDOUT,
+];
+
+foreach ($types as $type) {
+	/* Use zend_object_init_with_constructor() function as it used Z_PARAM_CLASS */
+	try {
+		var_dump(zend_object_init_with_constructor($type));
+	} catch (Throwable $e) {
+		echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+	}
+}
+
+?>
+--EXPECT--
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, null given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, false given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, true given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, int given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, float given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be a valid class name, string given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, array given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, stdClass given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, S given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, resource given
diff --git a/ext/zend_test/tests/zpp/class-string_zpp_specifier_weak_mode.phpt b/ext/zend_test/tests/zpp/class-string_zpp_specifier_weak_mode.phpt
new file mode 100644
index 000000000000..2110ecc4a0e4
--- /dev/null
+++ b/ext/zend_test/tests/zpp/class-string_zpp_specifier_weak_mode.phpt
@@ -0,0 +1,48 @@
+--TEST--
+Test class string ZPP specifier
+--EXTENSIONS--
+zend_test
+--FILE--
+<?php
+
+class S {
+	public function __toString(): string {
+		return 'S class';
+	}
+}
+
+$types = [
+	null,
+	false,
+	true,
+	42,
+	73.5,
+	'string',
+	[],
+	new stdClass(),
+	new S(),
+	STDOUT,
+];
+
+foreach ($types as $type) {
+	/* Use zend_object_init_with_constructor() function as it used Z_PARAM_CLASS */
+	try {
+		var_dump(zend_object_init_with_constructor($type));
+	} catch (Throwable $e) {
+		echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+	}
+}
+
+?>
+--EXPECTF--
+Deprecated: zend_object_init_with_constructor(): Passing null to parameter #1 ($class) of type string is deprecated in %s on line %d
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be a valid class name,  given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be a valid class name,  given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be a valid class name, 1 given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be a valid class name, 42 given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be a valid class name, 73.5 given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be a valid class name, string given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, array given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, stdClass given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be a valid class name, S class given
+TypeError: zend_object_init_with_constructor(): Argument #1 ($class) must be of type string, resource given