[php-src] master: Merge branch 'PHP-8.5'

Daniel Scherzer <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Daniel Scherzer (DanielEScherzer)
Date: 2026-07-13T14:24:08-07:00

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

Merge branch 'PHP-8.5'

* PHP-8.5:
  NEWS
  GH-22681: avoid truncation on null bytes in `Reflection*::__toString()`
  Add regression tests for `Reflection*::__toString()` with null bytes

Changed paths:
  A  ext/reflection/tests/gh22681/ReflectionClassConstant_doc_comment.phpt
  A  ext/reflection/tests/gh22681/ReflectionClass_doc_comment.phpt
  A  ext/reflection/tests/gh22681/ReflectionConstant_name.phpt
  A  ext/reflection/tests/gh22681/ReflectionEnum_case_doc_comment.phpt
  A  ext/reflection/tests/gh22681/ReflectionExtension_ini_value.phpt
  A  ext/reflection/tests/gh22681/ReflectionFunctionAbstract_doc_comment.phpt
  A  ext/reflection/tests/gh22681/ReflectionProperty_doc_comment.phpt
  A  ext/reflection/tests/gh22681/ReflectionProperty_dynamic_name.phpt
  M  NEWS
  M  ext/reflection/php_reflection.c


Diff:

diff --git a/NEWS b/NEWS
index 702d9d04fe3d..6f2e69aa8294 100644
--- a/NEWS
+++ b/NEWS
@@ -93,6 +93,8 @@ PHP                                                                        NEWS
 - Reflection:
   . Fixed bug GH-22683 (Reflection(Class)Constant::__toString() should not warn
     on NAN conversions). (Khaled Alam)
+  . Fixed bug GH-22681 (Reflection*::__toString() truncates on null bytes).
+    (DanielEScherzer)
 
 - Session:
   . Fixed bug GH-21314 (Different session garbage collector behavior between
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 13df0b2bbb4d..ef620369bb46 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -294,9 +294,9 @@ static zend_object *reflection_objects_new(zend_class_entry *class_type) /* {{{
 }
 /* }}} */
 
-static void _const_string(smart_str *str, const char *name, zval *value, const char *indent);
+static void _const_string(smart_str *str, const zend_string *name, zval *value, const char *indent);
 static void _function_string(smart_str *str, const zend_function *fptr, const zend_class_entry *scope, const char* indent);
-static void _property_string(smart_str *str, const zend_property_info *prop, const char *prop_name, const char* indent);
+static void _property_string(smart_str *str, const zend_property_info *prop, const zend_string *prop_name, const char* indent);
 static void _class_const_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char* indent);
 static void _enum_case_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char* indent);
 static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const char *indent);
@@ -311,7 +311,8 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
 
 	/* TBD: Repair indenting of doc comment (or is this to be done in the parser?) */
 	if (ce->doc_comment) {
-		smart_str_append_printf(str, "%s%s", indent, ZSTR_VAL(ce->doc_comment));
+		smart_str_appends(str, indent);
+		smart_str_append(str, ce->doc_comment);
 		smart_str_appendc(str, '\n');
 	}
 
@@ -517,7 +518,7 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
 				if (prop_name && ZSTR_LEN(prop_name) && ZSTR_VAL(prop_name)[0]) { /* skip all private and protected properties */
 					if (!zend_hash_exists(&ce->properties_info, prop_name)) {
 						count++;
-						_property_string(&prop_str, NULL, ZSTR_VAL(prop_name), ZSTR_VAL(sub_indent));
+						_property_string(&prop_str, NULL, prop_name, ZSTR_VAL(sub_indent));
 					}
 				}
 			} ZEND_HASH_FOREACH_END();
@@ -572,7 +573,7 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
 /* }}} */
 
 /* {{{ _const_string */
-static void _const_string(smart_str *str, const char *name, zval *value, const char *indent)
+static void _const_string(smart_str *str, const zend_string *name, zval *value, const char *indent)
 {
 	const char *type = zend_zval_type_name(value);
 	uint32_t flags = Z_CONSTANT_FLAGS_P(value);
@@ -602,7 +603,7 @@ static void _const_string(smart_str *str, const char *name, zval *value, const c
 
 	smart_str_appends(str, type);
 	smart_str_appendc(str, ' ');
-	smart_str_appends(str, name);
+	smart_str_append(str, name);
 	smart_str_appends(str, " ] { ");
 
 	if (Z_TYPE_P(value) == IS_ARRAY) {
@@ -635,7 +636,9 @@ static void _class_const_string(smart_str *str, const zend_string *name, zend_cl
 	const char *type = type_str ? ZSTR_VAL(type_str) : zend_zval_type_name(&c->value);
 
 	if (c->doc_comment) {
-		smart_str_append_printf(str, "%s%s\n", indent, ZSTR_VAL(c->doc_comment));
+		smart_str_appends(str, indent);
+		smart_str_append(str, c->doc_comment);
+		smart_str_appendc(str, '\n');
 	}
 	smart_str_append_printf(str, "%sConstant [ %s%s %s %s ] { ",
 		indent, final, visibility, type, ZSTR_VAL(name));
@@ -869,9 +872,13 @@ static void _function_string(smart_str *str, const zend_function *fptr, const ze
 	 * swallowed, leading to an unaligned comment.
 	 */
 	if (fptr->type == ZEND_USER_FUNCTION && fptr->op_array.doc_comment) {
-		smart_str_append_printf(str, "%s%s\n", indent, ZSTR_VAL(fptr->op_array.doc_comment));
+		smart_str_appends(str, indent);
+		smart_str_append(str, fptr->op_array.doc_comment);
+		smart_str_appendc(str, '\n');
 	} else if (fptr->type == ZEND_INTERNAL_FUNCTION && fptr->internal_function.doc_comment) {
-		smart_str_append_printf(str, "%s%s\n", indent, ZSTR_VAL(fptr->internal_function.doc_comment));
+		smart_str_appends(str, indent);
+		smart_str_append(str, fptr->internal_function.doc_comment);
+		smart_str_appendc(str, '\n');
 	}
 
 	smart_str_appends(str, indent);
@@ -979,14 +986,18 @@ static zval *property_get_default(const zend_property_info *prop_info) {
 }
 
 /* {{{ _property_string */
-static void _property_string(smart_str *str, const zend_property_info *prop, const char *prop_name, const char* indent)
+static void _property_string(smart_str *str, const zend_property_info *prop, const zend_string *prop_name, const char* indent)
 {
 	if (prop && prop->doc_comment) {
-		smart_str_append_printf(str, "%s%s\n", indent, ZSTR_VAL(prop->doc_comment));
+		smart_str_appends(str, indent);
+		smart_str_append(str, prop->doc_comment);
+		smart_str_appendc(str, '\n');
 	}
 	smart_str_append_printf(str, "%sProperty [ ", indent);
 	if (!prop) {
-		smart_str_append_printf(str, "<dynamic> public $%s", prop_name);
+		ZEND_ASSERT(prop_name && "Properties without info must have a name provided");
+		smart_str_appends(str, "<dynamic> public $");
+		smart_str_append(str, prop_name);
 	} else {
 		if (prop->flags & ZEND_ACC_ABSTRACT) {
 			smart_str_appends(str, "abstract ");
@@ -1032,11 +1043,15 @@ static void _property_string(smart_str *str, const zend_property_info *prop, con
 			smart_str_appendc(str, ' ');
 			zend_string_release(type_str);
 		}
+		smart_str_appendc(str, '$');
 		if (!prop_name) {
 			const char *class_name;
-			zend_unmangle_property_name(prop->name, &class_name, &prop_name);
+			const char *prop_name_cstr;
+			zend_unmangle_property_name(prop->name, &class_name, &prop_name_cstr);
+			smart_str_appends(str, prop_name_cstr);
+		} else {
+			smart_str_append(str, prop_name);
 		}
-		smart_str_append_printf(str, "$%s", prop_name);
 
 		const zval *default_value = property_get_default(prop);
 		if (default_value && !Z_ISUNDEF_P(default_value)) {
@@ -1092,9 +1107,21 @@ static void _extension_ini_string(const zend_ini_entry *ini_entry, smart_str *st
 		}
 
 		smart_str_appends(str, "> ]\n");
-		smart_str_append_printf(str, "      Current = '%s'\n", ini_entry->value ? ZSTR_VAL(ini_entry->value) : "");
+		if (ini_entry->value) {
+			smart_str_appends(str, "      Current = '");
+			smart_str_append(str, ini_entry->value);
+			smart_str_appends(str, "'\n");
+		} else {
+			smart_str_appends(str, "      Current = ''\n");
+		}
 		if (ini_entry->modified) {
-			smart_str_append_printf(str, "      Default = '%s'\n", ini_entry->orig_value ? ZSTR_VAL(ini_entry->orig_value) : "");
+			if (ini_entry->orig_value) {
+				smart_str_appends(str, "      Default = '");
+				smart_str_append(str, ini_entry->orig_value);
+				smart_str_appends(str, "'\n");
+			} else {
+				smart_str_appends(str, "      Default = ''\n");
+			}
 		}
 		smart_str_appends(str, "    }\n");
 	}
@@ -1183,7 +1210,7 @@ static void _extension_string(smart_str *str, const zend_module_entry *module) /
 
 		ZEND_HASH_MAP_FOREACH_PTR(EG(zend_constants), constant) {
 			if (ZEND_CONSTANT_MODULE_NUMBER(constant) == module->module_number) {
-				_const_string(&str_constants, ZSTR_VAL(constant->name), &constant->value, "    ");
+				_const_string(&str_constants, constant->name, &constant->value, "    ");
 				num_constants++;
 			}
 		} ZEND_HASH_FOREACH_END();
@@ -5744,7 +5771,7 @@ ZEND_METHOD(ReflectionProperty, __toString)
 
 	ZEND_PARSE_PARAMETERS_NONE();
 	GET_REFLECTION_OBJECT_PTR(ref);
-	_property_string(&str, ref->prop, ZSTR_VAL(ref->unmangled_name), "");
+	_property_string(&str, ref->prop, ref->unmangled_name, "");
 	RETURN_STR(smart_str_extract(&str));
 }
 /* }}} */
@@ -8167,7 +8194,7 @@ ZEND_METHOD(ReflectionConstant, __toString)
 	ZEND_PARSE_PARAMETERS_NONE();
 
 	GET_REFLECTION_OBJECT_PTR(const_);
-	_const_string(&str, ZSTR_VAL(const_->name), &const_->value, "");
+	_const_string(&str, const_->name, &const_->value, "");
 	RETURN_STR(smart_str_extract(&str));
 }
 
diff --git a/ext/reflection/tests/gh22681/ReflectionClassConstant_doc_comment.phpt b/ext/reflection/tests/gh22681/ReflectionClassConstant_doc_comment.phpt
new file mode 100644
index 000000000000..5002f066ff69
--- /dev/null
+++ b/ext/reflection/tests/gh22681/ReflectionClassConstant_doc_comment.phpt
@@ -0,0 +1,44 @@
+--TEST--
+GH-22681: null bytes in doc comment truncate ReflectionClassConstant::__toString()
+--FILE--
+<?php
+
+eval(<<<END
+class Demo {
+    /** F\0oo */
+    public const DEMO = true;
+}
+END
+);
+
+$r = new ReflectionClassConstant(Demo::class, 'DEMO');
+echo $r;
+var_dump( $r->getDocComment() );
+
+echo new ReflectionClass(Demo::class);
+
+?>
+--EXPECTF--
+/** F%0oo */
+Constant [ public bool DEMO ] { 1 }
+string(11) "/** F%0oo */"
+Class [ <user> class Demo ] {
+  @@ %s(%d) : eval()'d code %d-%d
+
+  - Constants [1] {
+    /** F%0oo */
+    Constant [ public bool DEMO ] { 1 }
+  }
+
+  - Static properties [0] {
+  }
+
+  - Static methods [0] {
+  }
+
+  - Properties [0] {
+  }
+
+  - Methods [0] {
+  }
+}
diff --git a/ext/reflection/tests/gh22681/ReflectionClass_doc_comment.phpt b/ext/reflection/tests/gh22681/ReflectionClass_doc_comment.phpt
new file mode 100644
index 000000000000..fc34cd95b1e3
--- /dev/null
+++ b/ext/reflection/tests/gh22681/ReflectionClass_doc_comment.phpt
@@ -0,0 +1,37 @@
+--TEST--
+GH-22681: null bytes in doc comment truncate ReflectionClass::__toString()
+--FILE--
+<?php
+
+eval(<<<END
+/** F\0oo */
+class Demo {}
+END
+);
+
+$r = new ReflectionClass(Demo::class);
+echo $r;
+var_dump( $r->getDocComment() );
+
+?>
+--EXPECTF--
+/** F%0oo */
+Class [ <user> class Demo ] {
+  @@ %s(%d) : eval()'d code %d-%d
+
+  - Constants [0] {
+  }
+
+  - Static properties [0] {
+  }
+
+  - Static methods [0] {
+  }
+
+  - Properties [0] {
+  }
+
+  - Methods [0] {
+  }
+}
+string(11) "/** F%0oo */"
diff --git a/ext/reflection/tests/gh22681/ReflectionConstant_name.phpt b/ext/reflection/tests/gh22681/ReflectionConstant_name.phpt
new file mode 100644
index 000000000000..c43397200c82
--- /dev/null
+++ b/ext/reflection/tests/gh22681/ReflectionConstant_name.phpt
@@ -0,0 +1,15 @@
+--TEST--
+GH-22681: null bytes in name truncate ReflectionConstant::__toString()
+--FILE--
+<?php
+
+define("F\0oo", true);
+
+$r = new ReflectionConstant("F\0oo");
+echo $r;
+var_dump( $r->getName() );
+
+?>
+--EXPECTF--
+Constant [ bool F%0oo ] { 1 }
+string(4) "F%0oo"
diff --git a/ext/reflection/tests/gh22681/ReflectionEnum_case_doc_comment.phpt b/ext/reflection/tests/gh22681/ReflectionEnum_case_doc_comment.phpt
new file mode 100644
index 000000000000..68bde0bed637
--- /dev/null
+++ b/ext/reflection/tests/gh22681/ReflectionEnum_case_doc_comment.phpt
@@ -0,0 +1,49 @@
+--TEST--
+GH-22681: null bytes in doc comment truncate ReflectionEnum::__toString()
+--FILE--
+<?php
+
+eval(<<<END
+enum Demo {
+    /** F\0oo */
+    case C;
+}
+END
+);
+
+$r = new ReflectionEnum(Demo::class);
+echo $r;
+var_dump( new ReflectionEnumUnitCase(Demo::class, 'C')->getDocComment() );
+?>
+--EXPECTF--
+Enum [ <user> enum Demo implements UnitEnum ] {
+  @@ %s(%d) : eval()'d code %d-%d
+
+  - Enum cases [1] {
+    /** F
+    Case C
+  }
+
+  - Constants [0] {
+  }
+
+  - Static properties [0] {
+  }
+
+  - Static methods [1] {
+    Method [ <internal, prototype UnitEnum> static public method cases ] {
+
+      - Parameters [0] {
+      }
+      - Return [ array ]
+    }
+  }
+
+  - Properties [1] {
+    Property [ public protected(set) readonly string $name ]
+  }
+
+  - Methods [0] {
+  }
+}
+string(11) "/** F%0oo */"
diff --git a/ext/reflection/tests/gh22681/ReflectionExtension_ini_value.phpt b/ext/reflection/tests/gh22681/ReflectionExtension_ini_value.phpt
new file mode 100644
index 000000000000..f12088b3a9e0
--- /dev/null
+++ b/ext/reflection/tests/gh22681/ReflectionExtension_ini_value.phpt
@@ -0,0 +1,23 @@
+--TEST--
+GH-22681: null bytes in INI values truncate ReflectionExtension::__toString()
+--FILE--
+<?php
+
+ini_set('arg_separator.output', "f\0oo");
+$r = new ReflectionExtension('core');
+$str = (string)$r;
+$index = strpos($str, 'Entry [ arg_separator.output');
+$str = substr($str, $index);
+$index = strpos($str, 'Entry', 1);
+$str = substr($str, 0, $index);
+echo $str . "\n";
+var_dump( $r->getINIEntries()['arg_separator.output'] );
+
+?>
+--EXPECTF--
+Entry [ arg_separator.output <ALL> ]
+      Current = 'f%0oo'
+      Default = '&'
+    }
+    
+string(4) "f%0oo"
diff --git a/ext/reflection/tests/gh22681/ReflectionFunctionAbstract_doc_comment.phpt b/ext/reflection/tests/gh22681/ReflectionFunctionAbstract_doc_comment.phpt
new file mode 100644
index 000000000000..fdd42903c3be
--- /dev/null
+++ b/ext/reflection/tests/gh22681/ReflectionFunctionAbstract_doc_comment.phpt
@@ -0,0 +1,36 @@
+--TEST--
+GH-22681: null bytes in doc comment truncate ReflectionFunctionAbstract::__toString()
+--FILE--
+<?php
+
+eval(<<<END
+/** F\0oo */
+function demo() {}
+
+class Demo {
+    /** B\0ar */
+    public function demo() {}
+}
+END
+);
+
+$r = new ReflectionFunction('demo');
+echo $r;
+var_dump( $r->getDocComment() );
+
+$r = new ReflectionMethod(Demo::class, 'demo');
+echo $r;
+var_dump( $r->getDocComment() );
+
+?>
+--EXPECTF--
+/** F%0oo */
+Function [ <user> function demo ] {
+  @@ %s(%d) : eval()'d code %d - %d
+}
+string(11) "/** F%0oo */"
+/** B%0ar */
+Method [ <user> public method demo ] {
+  @@ %s(%d) : eval()'d code %d - %d
+}
+string(11) "/** B%0ar */"
diff --git a/ext/reflection/tests/gh22681/ReflectionProperty_doc_comment.phpt b/ext/reflection/tests/gh22681/ReflectionProperty_doc_comment.phpt
new file mode 100644
index 000000000000..ca218216b2c3
--- /dev/null
+++ b/ext/reflection/tests/gh22681/ReflectionProperty_doc_comment.phpt
@@ -0,0 +1,44 @@
+--TEST--
+GH-22681: null bytes in doc comment truncate ReflectionProperty::__toString()
+--FILE--
+<?php
+
+eval(<<<END
+class Demo {
+    /** F\0oo */
+    public \$prop;
+}
+END
+);
+
+$r = new ReflectionProperty(Demo::class, 'prop');
+echo $r;
+var_dump( $r->getDocComment() );
+
+echo new ReflectionClass(Demo::class);
+
+?>
+--EXPECTF--
+/** F%0oo */
+Property [ public $prop = NULL ]
+string(11) "/** F%0oo */"
+Class [ <user> class Demo ] {
+  @@ %s(%d) : eval()'d code %d-%d
+
+  - Constants [0] {
+  }
+
+  - Static properties [0] {
+  }
+
+  - Static methods [0] {
+  }
+
+  - Properties [1] {
+    /** F%0oo */
+    Property [ public $prop = NULL ]
+  }
+
+  - Methods [0] {
+  }
+}
diff --git a/ext/reflection/tests/gh22681/ReflectionProperty_dynamic_name.phpt b/ext/reflection/tests/gh22681/ReflectionProperty_dynamic_name.phpt
new file mode 100644
index 000000000000..d8cecb0002aa
--- /dev/null
+++ b/ext/reflection/tests/gh22681/ReflectionProperty_dynamic_name.phpt
@@ -0,0 +1,37 @@
+--TEST--
+GH-22681: null bytes in name truncate ReflectionProperty::__toString()
+--FILE--
+<?php
+
+$obj = (object)["F\0oo" => true];
+$r = new ReflectionProperty($obj, "F\0oo");
+echo $r;
+var_dump( $r->getDocComment() );
+
+echo new ReflectionObject($obj);
+
+?>
+--EXPECTF--
+Property [ <dynamic> public $F%0oo ]
+bool(false)
+Object of class [ <internal:Core> class stdClass ] {
+
+  - Constants [0] {
+  }
+
+  - Static properties [0] {
+  }
+
+  - Static methods [0] {
+  }
+
+  - Properties [0] {
+  }
+
+  - Dynamic properties [1] {
+    Property [ <dynamic> public $F%0oo ]
+  }
+
+  - Methods [0] {
+  }
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.