diff --git a/NEWS b/NEWS index 7324e5c591aa..60a471aecbae 100644 --- a/NEWS +++ b/NEWS @@ -67,6 +67,8 @@ PHP NEWS dynamic properties shadowing a private parent property). (iliaal) . Fixed bug GH-22658 (ReflectionConstant::__toString() with a string value with null bytes truncates output). (DanielEScherzer) + . 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 661b1eb1f3a4..d73a8100f4bc 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -308,9 +308,9 @@ static zval *reflection_instantiate(zend_class_entry *pce, zval *object) /* {{{ } /* }}} */ -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, zend_function *fptr, zend_class_entry *scope, const char* indent); -static void _property_string(smart_str *str, zend_property_info *prop, const char *prop_name, const char* indent); +static void _property_string(smart_str *str, 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 _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const char *indent); static void _extension_string(smart_str *str, const zend_module_entry *module, const char *indent); @@ -324,7 +324,10 @@ 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)); + // %S is used for zend_string pointers by smart str printing, but normally + // is for wide character strings and so compilers complain if this is inline + const char *format = "%s%S"; + smart_str_append_printf(str, format, indent, ce->doc_comment); smart_str_appendc(str, '\n'); } @@ -494,7 +497,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(); @@ -549,7 +552,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); @@ -579,7 +582,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) { @@ -610,7 +613,10 @@ 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)); + // %S is used for zend_string pointers by smart str printing, but normally + // is for wide character strings and so compilers complain if this is inline + const char *format = "%s%S\n"; + smart_str_append_printf(str, format, indent, c->doc_comment); } smart_str_append_printf(str, "%sConstant [ %s%s %s %s ] { ", indent, final, visibility, type, ZSTR_VAL(name)); @@ -826,10 +832,13 @@ static void _function_string(smart_str *str, zend_function *fptr, zend_class_ent * What's "wrong" is that any whitespace before the doc comment start is * swallowed, leading to an unaligned comment. */ + // %S is used for zend_string pointers by smart str printing, but normally + // is for wide character strings and so compilers complain if this is inline + const char *format = "%s%S\n"; 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_append_printf(str, format, indent, fptr->op_array.doc_comment); } 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_append_printf(str, format, indent, fptr->internal_function.doc_comment); } smart_str_appendl(str, indent, strlen(indent)); @@ -939,14 +948,19 @@ static zval *property_get_default(zend_property_info *prop_info) { } /* {{{ _property_string */ -static void _property_string(smart_str *str, zend_property_info *prop, const char *prop_name, const char* indent) +static void _property_string(smart_str *str, 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)); + // %S is used for zend_string pointers by smart str printing, but normally + // is for wide character strings and so compilers complain if this is inline + const char *format = "%s%S\n"; + smart_str_append_printf(str, format, indent, prop->doc_comment); } smart_str_append_printf(str, "%sProperty [ ", indent); if (!prop) { - smart_str_append_printf(str, " public $%s", prop_name); + ZEND_ASSERT(prop_name && "Properties without info must have a name provided"); + smart_str_appends(str, " public $"); + smart_str_append(str, prop_name); } else { if (prop->flags & ZEND_ACC_ABSTRACT) { smart_str_appends(str, "abstract "); @@ -989,11 +1003,15 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha 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); zval *default_value = property_get_default(prop); if (default_value && !Z_ISUNDEF_P(default_value)) { @@ -1031,9 +1049,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, " %s Current = '%s'\n", indent, ini_entry->value ? ZSTR_VAL(ini_entry->value) : ""); + if (ini_entry->value) { + // %S is used for zend_string pointers by smart str printing, but normally + // is for wide character strings and so compilers complain if this is inline + const char *format = " %s Current = '%S'\n"; + smart_str_append_printf(str, format, indent, ini_entry->value); + } else { + smart_str_append_printf(str, " %s Current = ''\n", indent); + } if (ini_entry->modified) { - smart_str_append_printf(str, " %s Default = '%s'\n", indent, ini_entry->orig_value ? ZSTR_VAL(ini_entry->orig_value) : ""); + if (ini_entry->orig_value) { + const char *format = " %s Default = '%S'\n"; + smart_str_append_printf(str, format, indent, ini_entry->orig_value); + } else { + smart_str_append_printf(str, " %s Default = ''\n", indent); + } } smart_str_append_printf(str, " %s}\n", indent); } @@ -1122,7 +1152,7 @@ static void _extension_string(smart_str *str, const zend_module_entry *module, c 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(); @@ -5894,7 +5924,7 @@ ZEND_METHOD(ReflectionProperty, __toString) RETURN_THROWS(); } 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)); } /* }}} */ @@ -7955,7 +7985,7 @@ ZEND_METHOD(ReflectionConstant, __toString) } 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-- +getDocComment() ); + +echo new ReflectionClass(Demo::class); + +?> +--EXPECTF-- +/** F%0oo */ +Constant [ public bool DEMO ] { 1 } +string(11) "/** F%0oo */" +Class [ 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-- +getDocComment() ); + +?> +--EXPECTF-- +/** F%0oo */ +Class [ 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-- +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..10afacade8f7 --- /dev/null +++ b/ext/reflection/tests/gh22681/ReflectionEnum_case_doc_comment.phpt @@ -0,0 +1,46 @@ +--TEST-- +GH-22681: null bytes in doc comment truncate ReflectionEnum::__toString() +--FILE-- +getDocComment() ); +?> +--EXPECTF-- +Class [ final class Demo implements UnitEnum ] { + @@ %s(%d) : eval()'d code %d-%d + + - Constants [1] { + /** F%0oo */ + Constant [ public Demo C ] { Object } + } + + - Static properties [0] { + } + + - Static methods [1] { + Method [ 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-- +getINIEntries()['arg_separator.output'] ); + +?> +--EXPECTF-- +Entry [ arg_separator.output ] + 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-- +getDocComment() ); + +$r = new ReflectionMethod(Demo::class, 'demo'); +echo $r; +var_dump( $r->getDocComment() ); + +?> +--EXPECTF-- +/** F%0oo */ +Function [ function demo ] { + @@ %s(%d) : eval()'d code %d - %d +} +string(11) "/** F%0oo */" +/** B%0ar */ +Method [ 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-- +getDocComment() ); + +echo new ReflectionClass(Demo::class); + +?> +--EXPECTF-- +/** F%0oo */ +Property [ public $prop = NULL ] +string(11) "/** F%0oo */" +Class [ 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-- + true]; +$r = new ReflectionProperty($obj, "F\0oo"); +echo $r; +var_dump( $r->getDocComment() ); + +echo new ReflectionObject($obj); + +?> +--EXPECTF-- +Property [ public $F%0oo ] +bool(false) +Object of class [ class stdClass ] { + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [0] { + } + + - Dynamic properties [1] { + Property [ public $F%0oo ] + } + + - Methods [0] { + } +}