[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] {
+ }
+}