Commit 642899fc18c for php
commit 642899fc18c5b32fb8a84c59435abe0d17cf9d20
Author: Jorg Sowa <jorg.sowa@gmail.com>
Date: Wed Nov 11 10:16:55 2026 +0100
Fix truncated anonymous class names in error messages and logs (GH-25352)
Anonymous class names contain a NUL byte. Since the %pS migration some
error messages print the full name, but the error log, HTML fatal error
and XML-RPC output still formatted the message as a C string, cutting it
at the NUL (e.g. "PHP Fatal error: Class class@anonymous in ..."). Log
NUL bytes as \0 and print display output by length.
Also use %pS for the remaining Zend, dom and xmlreader messages that
still truncated anonymous class names via ZSTR_VAL().
Closes GH-25352
diff --git a/NEWS b/NEWS
index cd0db3878df..1b23e8ec982 100644
--- a/NEWS
+++ b/NEWS
@@ -26,6 +26,8 @@ PHP NEWS
. Fixed use-after-free when the error handler releases a string while an
"Illegal string offset" warning is raised for its offset in a read, ?? or
nested isset()/empty(). (Jorg Sowa)
+ . Fixed anonymous class names being truncated at the NUL byte in error
+ messages, the error log, and HTML and XML-RPC error output. (Jorg Sowa)
- Curl:
. Fixed curl_reset() retaining CURLOPT_PRIVATE after reset. (lacatoire)
diff --git a/Zend/tests/anon/anon_class_name_in_abstract_info.phpt b/Zend/tests/anon/anon_class_name_in_abstract_info.phpt
new file mode 100644
index 00000000000..9c633ded065
--- /dev/null
+++ b/Zend/tests/anon/anon_class_name_in_abstract_info.phpt
@@ -0,0 +1,8 @@
+--TEST--
+Anonymous class name is not truncated in the abstract method list
+--FILE--
+<?php
+new class { abstract public $p { get; } };
+?>
+--EXPECTF--
+Fatal error: Class class@anonymous%0%s must implement 1 abstract method (class@anonymous%0%s::$p::get) in %s on line %d
diff --git a/Zend/tests/anon/anon_class_name_in_errors.phpt b/Zend/tests/anon/anon_class_name_in_errors.phpt
new file mode 100644
index 00000000000..be7f271283c
--- /dev/null
+++ b/Zend/tests/anon/anon_class_name_in_errors.phpt
@@ -0,0 +1,60 @@
+--TEST--
+Anonymous class names are not truncated in runtime error messages
+--FILE--
+<?php
+
+class A {
+ public private(set) int $p = 0;
+ private function f() {}
+}
+
+class B {
+ private function __destruct() {}
+}
+
+$o = new class {
+ public function __construct(public readonly int $ro = 1) {}
+ private function priv() {}
+ public function ret(): int { return "x"; }
+ public function args($a) {}
+ public function byRef(&$a) {}
+ public function callPriv() { (new A)->f(); }
+ public function setPrivateSet() { $a = new A; $a->p = 1; }
+ public function destroy() { new B; }
+};
+
+$v = new class extends ArrayObject {
+ public int $virtual { get => 1; }
+};
+
+foreach ([
+ fn() => $o->priv(),
+ fn() => $o->callPriv(),
+ fn() => $o->setPrivateSet(),
+ fn() => $o->destroy(),
+ fn() => $o->ro = 2,
+ fn() => $o->ret(),
+ fn() => $o->args(),
+ fn() => $v->__unserialize([0, [], ['virtual' => 2]]),
+] as $fn) {
+ try {
+ $fn();
+ } catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ }
+}
+
+$o->byRef(...new ArrayIterator([1]));
+
+?>
+--EXPECTF--
+Error: Call to private method class@anonymous%0%s::priv() from global scope
+Error: Call to private method A::f() from scope class@anonymous%0%s
+Error: Cannot modify private(set) property A::$p from scope class@anonymous%0%s
+Error: Call to private B::__destruct() from scope class@anonymous%0%s
+Error: Cannot modify readonly property class@anonymous%0%s::$ro
+TypeError: class@anonymous%0%s::ret(): Return value must be of type int, string returned
+ArgumentCountError: Too few arguments to function class@anonymous%0%s::args(), 0 passed in %s on line %d and exactly 1 expected
+Error: Cannot unserialize value for virtual property ArrayObject@anonymous%0%s::$virtual
+
+Warning: Cannot pass by-reference argument 1 of class@anonymous%0%s::byRef() by unpacking a Traversable, passing by-value instead in %s on line %d
diff --git a/Zend/tests/anon/anon_class_name_in_inheritance_error.phpt b/Zend/tests/anon/anon_class_name_in_inheritance_error.phpt
new file mode 100644
index 00000000000..cf5b8ffd9e1
--- /dev/null
+++ b/Zend/tests/anon/anon_class_name_in_inheritance_error.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Anonymous class name is not truncated in method inheritance errors
+--FILE--
+<?php
+class A { public function f() {} }
+new class extends A { private function f() {} };
+?>
+--EXPECTF--
+Fatal error: Access level to A@anonymous%0%s::f() must be public (as in class A) in %s on line %d
diff --git a/Zend/tests/attributes/override/020.phpt b/Zend/tests/attributes/override/020.phpt
index 0b6161b8feb..b96cdabf717 100644
--- a/Zend/tests/attributes/override/020.phpt
+++ b/Zend/tests/attributes/override/020.phpt
@@ -18,4 +18,4 @@ public function c(): void {}
?>
--EXPECTF--
-Fatal error: I@anonymous::c() has #[\Override] attribute, but no matching parent method exists in %s on line %d
+Fatal error: I@anonymous%0%s::c() has #[\Override] attribute, but no matching parent method exists in %s on line %d
diff --git a/Zend/tests/object_types/return_type_in_class.phpt b/Zend/tests/object_types/return_type_in_class.phpt
index 3e441073737..632e94d2589 100644
--- a/Zend/tests/object_types/return_type_in_class.phpt
+++ b/Zend/tests/object_types/return_type_in_class.phpt
@@ -19,7 +19,7 @@ public function a() : object {
$three->a();
?>
--EXPECTF--
-Fatal error: Uncaught TypeError: Two@anonymous::a(): Return value must be of type object, int returned in %s:%d
+Fatal error: Uncaught TypeError: Two@anonymous%0%s::a(): Return value must be of type object, int returned in %s:%d
Stack trace:
#0 %s(%d): Two@anonymous->a()
#1 {main}
diff --git a/Zend/tests/object_types/return_type_inheritance_in_class.phpt b/Zend/tests/object_types/return_type_inheritance_in_class.phpt
index 6e2dbac55cf..518aea3e97e 100644
--- a/Zend/tests/object_types/return_type_inheritance_in_class.phpt
+++ b/Zend/tests/object_types/return_type_inheritance_in_class.phpt
@@ -19,7 +19,7 @@ public function a() : object {
$three->a();
?>
--EXPECTF--
-Fatal error: Uncaught TypeError: Two@anonymous::a(): Return value must be of type object, int returned in %s:%d
+Fatal error: Uncaught TypeError: Two@anonymous%0%s::a(): Return value must be of type object, int returned in %s:%d
Stack trace:
#0 %s(%d): Two@anonymous->a()
#1 {main}
diff --git a/Zend/tests/object_types/return_type_inheritance_in_interface.phpt b/Zend/tests/object_types/return_type_inheritance_in_interface.phpt
index 8bb5ad1616e..9f5b43f900c 100644
--- a/Zend/tests/object_types/return_type_inheritance_in_interface.phpt
+++ b/Zend/tests/object_types/return_type_inheritance_in_interface.phpt
@@ -19,7 +19,7 @@ public function a() : object {
$three->a();
?>
--EXPECTF--
-Fatal error: Uncaught TypeError: Two@anonymous::a(): Return value must be of type object, int returned in %s:%d
+Fatal error: Uncaught TypeError: Two@anonymous%0%s::a(): Return value must be of type object, int returned in %s:%d
Stack trace:
#0 %s(%d): Two@anonymous->a()
#1 {main}
diff --git a/Zend/tests/readonly_classes/gh10377.phpt b/Zend/tests/readonly_classes/gh10377.phpt
index faa4ae330bf..c31c875f718 100644
--- a/Zend/tests/readonly_classes/gh10377.phpt
+++ b/Zend/tests/readonly_classes/gh10377.phpt
@@ -34,9 +34,9 @@ function __construct() {
var_dump($anon->field);
?>
---EXPECT--
+--EXPECTF--
int(2)
-Error: Cannot modify readonly property class@anonymous::$field
+Error: Cannot modify readonly property class@anonymous%0%s::$field
int(2)
int(2)
int(123)
diff --git a/Zend/tests/type_declarations/union_types/anonymous_class.phpt b/Zend/tests/type_declarations/union_types/anonymous_class.phpt
index 0fd4e379a3c..4ae0f77c06d 100644
--- a/Zend/tests/type_declarations/union_types/anonymous_class.phpt
+++ b/Zend/tests/type_declarations/union_types/anonymous_class.phpt
@@ -27,4 +27,4 @@ public function test(): self|string
?>
--EXPECTF--
TypeError: class@anonymous%0%s::testParam(): Argument #1 ($a) must be of type class@anonymous|string, null given, called in %s on line %d
-TypeError: class@anonymous::test(): Return value must be of type class@anonymous|string, stdClass returned
+TypeError: class@anonymous%0%s::test(): Return value must be of type class@anonymous|string, stdClass returned
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index ab5b2e1b190..4dba7fa25b0 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -1786,7 +1786,7 @@ ZEND_API void object_properties_load(zend_object *object, const HashTable *prope
/* Mimick unserialize behaviour for virtual properties. */
if (UNEXPECTED(property_info->flags & ZEND_ACC_VIRTUAL)) {
- zend_throw_error(NULL, "Cannot unserialize value for virtual property %s::$%s", ZSTR_VAL(object->ce->name), zend_get_unmangled_property_name(property_info->name));
+ zend_throw_error(NULL, "Cannot unserialize value for virtual property %pS::$%s", object->ce->name, zend_get_unmangled_property_name(property_info->name));
return;
}
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 8cc7cd7ca49..f3bbb821f9d 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -707,18 +707,8 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_throw_non_object_erro
static zend_never_inline ZEND_COLD void zend_verify_type_error_common(
const zend_function *zf, const zend_arg_info *arg_info, const zval *value,
- const char **fname, const char **fsep, const char **fclass,
zend_string **need_msg, const char **given_kind)
{
- *fname = ZSTR_VAL(zf->common.function_name);
- if (zf->common.scope) {
- *fsep = "::";
- *fclass = ZSTR_VAL(zf->common.scope->name);
- } else {
- *fsep = "";
- *fclass = "";
- }
-
*need_msg = zend_type_to_string_resolved(arg_info->type, zf->common.scope);
if (value) {
@@ -732,12 +722,10 @@ ZEND_API zend_never_inline ZEND_COLD void zend_verify_arg_error(
const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, const zval *value)
{
const zend_execute_data *ptr = EG(current_execute_data)->prev_execute_data;
- const char *fname, *fsep, *fclass;
zend_string *need_msg;
const char *given_msg;
- zend_verify_type_error_common(
- zf, arg_info, value, &fname, &fsep, &fclass, &need_msg, &given_msg);
+ zend_verify_type_error_common(zf, arg_info, value, &need_msg, &given_msg);
ZEND_ASSERT(zf->common.type == ZEND_USER_FUNCTION
&& "Arginfo verification is not performed for internal functions");
@@ -930,8 +918,8 @@ zend_never_inline ZEND_COLD void zend_match_unhandled_error(const zval *value)
ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(
const zend_property_info *info) {
- zend_readonly_property_modification_error_ex(
- ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name));
+ zend_throw_error(NULL, "Cannot modify readonly property %pS::$%s",
+ info->ce->name, zend_get_unmangled_property_name(info->name));
}
ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error_ex(
@@ -978,12 +966,12 @@ ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_asymmetric_visibili
}
}
- zend_throw_error(NULL, "Cannot %s %s property %pS::$%pS from %s%s",
+ zend_throw_error(NULL, "Cannot %s %s property %pS::$%pS from %s%pS",
operation,
visibility,
prop_info->ce->name,
prop_info->name,
- scope ? "scope " : "global scope", scope ? ZSTR_VAL(scope->name) : "");
+ scope ? "scope " : "global scope", scope ? scope->name : ZSTR_EMPTY_ALLOC());
}
static const zend_class_entry *resolve_single_class_type(zend_string *name, const zend_class_entry *self_ce) {
@@ -1431,8 +1419,8 @@ ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_missing_arg_error(c
const zend_execute_data *ptr = EX(prev_execute_data);
if (ptr && ptr->func && ZEND_USER_CODE(ptr->func->common.type)) {
- zend_throw_error(zend_ce_argument_count_error, "Too few arguments to function %s%s%pS(), %d passed in %pS on line %d and %s %d expected",
- EX(func)->common.scope ? ZSTR_VAL(EX(func)->common.scope->name) : "",
+ zend_throw_error(zend_ce_argument_count_error, "Too few arguments to function %pS%s%pS(), %d passed in %pS on line %d and %s %d expected",
+ EX(func)->common.scope ? EX(func)->common.scope->name : ZSTR_EMPTY_ALLOC(),
EX(func)->common.scope ? "::" : "",
EX(func)->common.function_name,
EX_NUM_ARGS(),
@@ -1441,8 +1429,8 @@ ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_missing_arg_error(c
EX(func)->common.required_num_args == EX(func)->common.num_args ? "exactly" : "at least",
EX(func)->common.required_num_args);
} else {
- zend_throw_error(zend_ce_argument_count_error, "Too few arguments to function %s%s%pS(), %d passed and %s %d expected",
- EX(func)->common.scope ? ZSTR_VAL(EX(func)->common.scope->name) : "",
+ zend_throw_error(zend_ce_argument_count_error, "Too few arguments to function %pS%s%pS(), %d passed and %s %d expected",
+ EX(func)->common.scope ? EX(func)->common.scope->name : ZSTR_EMPTY_ALLOC(),
EX(func)->common.scope ? "::" : "",
EX(func)->common.function_name,
EX_NUM_ARGS(),
@@ -1454,17 +1442,17 @@ ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_missing_arg_error(c
ZEND_API zend_never_inline ZEND_COLD void zend_verify_return_error(const zend_function *zf, const zval *value)
{
const zend_arg_info *arg_info = &zf->common.arg_info[-1];
- const char *fname, *fsep, *fclass;
+ zend_string *func_name = get_function_or_method_name(zf);
zend_string *need_msg;
const char *given_msg;
- zend_verify_type_error_common(
- zf, arg_info, value, &fname, &fsep, &fclass, &need_msg, &given_msg);
+ zend_verify_type_error_common(zf, arg_info, value, &need_msg, &given_msg);
- zend_type_error("%s%s%s(): Return value must be of type %pS, %s returned",
- fclass, fsep, fname, need_msg, given_msg);
+ zend_type_error("%pS(): Return value must be of type %pS, %s returned",
+ func_name, need_msg, given_msg);
zend_string_release(need_msg);
+ zend_string_release(func_name);
}
ZEND_API zend_never_inline ZEND_COLD void zend_verify_never_error(const zend_function *zf)
@@ -1481,33 +1469,24 @@ ZEND_API zend_never_inline ZEND_COLD void zend_verify_never_error(const zend_fun
static zend_never_inline ZEND_COLD void zend_verify_internal_return_error(const zend_function *zf, const zval *value)
{
const zend_arg_info *arg_info = &zf->common.arg_info[-1];
- const char *fname, *fsep, *fclass;
+ zend_string *func_name = get_function_or_method_name(zf);
zend_string *need_msg;
const char *given_msg;
- zend_verify_type_error_common(
- zf, arg_info, value, &fname, &fsep, &fclass, &need_msg, &given_msg);
+ zend_verify_type_error_common(zf, arg_info, value, &need_msg, &given_msg);
- zend_error_noreturn(E_CORE_ERROR, "%s%s%s(): Return value must be of type %pS, %s returned",
- fclass, fsep, fname, need_msg, given_msg);
+ zend_error_noreturn(E_CORE_ERROR, "%pS(): Return value must be of type %pS, %s returned",
+ func_name, need_msg, given_msg);
}
static zend_never_inline ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, const char *returned_msg, const char *returned_kind)
{
- const char *fname = ZSTR_VAL(zf->common.function_name);
- const char *fsep;
- const char *fclass;
+ zend_string *func_name = get_function_or_method_name(zf);
- if (zf->common.scope) {
- fsep = "::";
- fclass = ZSTR_VAL(zf->common.scope->name);
- } else {
- fsep = "";
- fclass = "";
- }
+ zend_type_error("%pS() must not return a value, %s%s returned",
+ func_name, returned_msg, returned_kind);
- zend_type_error("%s%s%s() must not return a value, %s%s returned",
- fclass, fsep, fname, returned_msg, returned_kind);
+ zend_string_release(func_name);
}
ZEND_API bool zend_verify_internal_return_type(const zend_function *zf, zval *ret)
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index 5a11a5d8bd8..dd4fbb8e586 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -1153,8 +1153,8 @@ static inheritance_status do_inheritance_check_on_method(
return INHERITANCE_ERROR;
}
zend_error_at_noreturn(E_COMPILE_ERROR, func_filename(child), func_lineno(child),
- "Cannot override final method %s::%pS()",
- ZEND_FN_SCOPE_NAME(parent), child->common.function_name);
+ "Cannot override final method %pS::%pS()",
+ parent->common.scope->name, child->common.function_name);
}
child_flags = child->common.fn_flags;
@@ -1167,12 +1167,12 @@ static inheritance_status do_inheritance_check_on_method(
}
if (child_flags & ZEND_ACC_STATIC) {
zend_error_at_noreturn(E_COMPILE_ERROR, func_filename(child), func_lineno(child),
- "Cannot make non static method %s::%pS() static in class %s",
- ZEND_FN_SCOPE_NAME(parent), child->common.function_name, ZEND_FN_SCOPE_NAME(child));
+ "Cannot make non static method %pS::%pS() static in class %pS",
+ parent->common.scope->name, child->common.function_name, child->common.scope->name);
} else {
zend_error_at_noreturn(E_COMPILE_ERROR, func_filename(child), func_lineno(child),
- "Cannot make static method %s::%pS() non static in class %s",
- ZEND_FN_SCOPE_NAME(parent), child->common.function_name, ZEND_FN_SCOPE_NAME(child));
+ "Cannot make static method %pS::%pS() non static in class %pS",
+ parent->common.scope->name, child->common.function_name, child->common.scope->name);
}
}
@@ -1183,8 +1183,8 @@ static inheritance_status do_inheritance_check_on_method(
return INHERITANCE_ERROR;
}
zend_error_at_noreturn(E_COMPILE_ERROR, func_filename(child), func_lineno(child),
- "Cannot make non abstract method %s::%pS() abstract in class %s",
- ZEND_FN_SCOPE_NAME(parent), child->common.function_name, ZEND_FN_SCOPE_NAME(child));
+ "Cannot make non abstract method %pS::%pS() abstract in class %pS",
+ parent->common.scope->name, child->common.function_name, child->common.scope->name);
}
if ((flags & ZEND_INHERITANCE_SET_CHILD_CHANGED)
@@ -1218,8 +1218,8 @@ static inheritance_status do_inheritance_check_on_method(
return INHERITANCE_ERROR;
}
zend_error_at_noreturn(E_COMPILE_ERROR, func_filename(child), func_lineno(child),
- "Access level to %s::%pS() must be %s (as in class %s)%s",
- ZEND_FN_SCOPE_NAME(child), child->common.function_name, zend_visibility_string(parent_flags), ZEND_FN_SCOPE_NAME(parent), (parent_flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");
+ "Access level to %pS::%pS() must be %s (as in class %pS)%s",
+ child->common.scope->name, child->common.function_name, zend_visibility_string(parent_flags), parent->common.scope->name, (parent_flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");
}
if (flags & ZEND_INHERITANCE_CHECK_PROTO) {
@@ -2318,8 +2318,8 @@ void zend_inheritance_check_override(const zend_class_entry *ce)
zend_error_at_noreturn(
E_COMPILE_ERROR, f->op_array.filename, f->op_array.line_start,
- "%s::%pS() has #[\\Override] attribute, but no matching parent method exists",
- ZEND_FN_SCOPE_NAME(f), f->common.function_name);
+ "%pS::%pS() has #[\\Override] attribute, but no matching parent method exists",
+ f->common.scope->name, f->common.function_name);
}
} ZEND_HASH_FOREACH_END();
@@ -2348,8 +2348,8 @@ void zend_inheritance_check_override(const zend_class_entry *ce)
zend_error_at_noreturn(
E_COMPILE_ERROR, f->op_array.filename, f->op_array.line_start,
- "%s::%pS() has #[\\Override] attribute, but no matching parent method exists",
- ZEND_FN_SCOPE_NAME(f), f->common.function_name);
+ "%pS::%pS() has #[\\Override] attribute, but no matching parent method exists",
+ f->common.scope->name, f->common.function_name);
}
}
}
@@ -3064,9 +3064,9 @@ ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int
}
#define MAX_ABSTRACT_INFO_CNT 3
-#define MAX_ABSTRACT_INFO_FMT "%s%s%s%s"
+#define MAX_ABSTRACT_INFO_FMT "%pS%s%s%s"
#define DISPLAY_ABSTRACT_FN(idx) \
- ai.afn[idx] ? ZEND_FN_SCOPE_NAME(ai.afn[idx]) : "", \
+ ai.afn[idx] ? ai.afn[idx]->common.scope->name : ZSTR_EMPTY_ALLOC(), \
ai.afn[idx] ? "::" : "", \
ai.afn[idx] ? ZSTR_VAL(ai.afn[idx]->common.function_name) : "", \
ai.afn[idx] && ai.afn[idx + 1] ? ", " : (ai.afn[idx] && ai.cnt > MAX_ABSTRACT_INFO_CNT ? ", ..." : "")
diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c
index a537f6b2a3e..0019d27a5a0 100644
--- a/Zend/zend_object_handlers.c
+++ b/Zend/zend_object_handlers.c
@@ -1959,10 +1959,10 @@ ZEND_API zend_function *zend_get_property_hook_trampoline(
ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(const zend_function *fbc, const zend_string *method_name, const zend_class_entry *scope) /* {{{ */
{
- zend_throw_error(NULL, "Call to %s method %s::%pS() from %s%s",
- zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), method_name,
+ zend_throw_error(NULL, "Call to %s method %pS::%pS() from %s%pS",
+ zend_visibility_string(fbc->common.fn_flags), fbc->common.scope->name, method_name,
scope ? "scope " : "global scope",
- scope ? ZSTR_VAL(scope->name) : ""
+ scope ? scope->name : ZSTR_EMPTY_ALLOC()
);
}
/* }}} */
diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c
index 447956752fa..a1655662c16 100644
--- a/Zend/zend_objects.c
+++ b/Zend/zend_objects.c
@@ -129,10 +129,10 @@ ZEND_API void zend_objects_destroy_object(zend_object *object)
ZEND_ASSERT(!(destructor->common.fn_flags & ZEND_ACC_PUBLIC));
if (!zend_check_method_accessible(destructor, scope)) {
zend_throw_error(NULL,
- "Call to %s %pS::__destruct() from %s%s",
+ "Call to %s %pS::__destruct() from %s%pS",
zend_visibility_string(destructor->common.fn_flags), object->ce->name,
scope ? "scope " : "global scope",
- scope ? ZSTR_VAL(scope->name) : ""
+ scope ? scope->name : ZSTR_EMPTY_ALLOC()
);
return;
}
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 351c67f2ab9..1596838195b 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -5460,9 +5460,9 @@ ZEND_VM_C_LABEL(send_again):
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
zend_error(
- E_WARNING, "Cannot pass by-reference argument %d of %s%s%pS()"
+ E_WARNING, "Cannot pass by-reference argument %d of %pS%s%pS()"
" by unpacking a Traversable, passing by-value instead", arg_num,
- EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
+ EX(call)->func->common.scope ? EX(call)->func->common.scope->name : ZSTR_EMPTY_ALLOC(),
EX(call)->func->common.scope ? "::" : "",
EX(call)->func->common.function_name
);
@@ -5486,9 +5486,9 @@ ZEND_VM_C_LABEL(send_again):
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
zend_error(
- E_WARNING, "Cannot pass by-reference argument %d of %s%s%pS()"
+ E_WARNING, "Cannot pass by-reference argument %d of %pS%s%pS()"
" by unpacking a Traversable, passing by-value instead", arg_num,
- EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
+ EX(call)->func->common.scope ? EX(call)->func->common.scope->name : ZSTR_EMPTY_ALLOC(),
EX(call)->func->common.scope ? "::" : "",
EX(call)->func->common.function_name
);
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 96405697644..b196dcc01f0 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
diff --git a/ext/dom/tests/dom_anon_class_name_in_errors.phpt b/ext/dom/tests/dom_anon_class_name_in_errors.phpt
new file mode 100644
index 00000000000..29884c8ff9d
--- /dev/null
+++ b/ext/dom/tests/dom_anon_class_name_in_errors.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Anonymous class names are not truncated in DOM error messages
+--EXTENSIONS--
+dom
+--FILE--
+<?php
+
+function reconstruct() {
+ try {
+ $GLOBALS['xpath']->__construct($GLOBALS['doc']);
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+}
+
+$doc = new DOMDocument();
+$doc->loadXML('<root/>');
+
+$xpath = new class($doc) extends DOMXPath {};
+$xpath->registerNamespace('php', 'http://php.net/xpath');
+$xpath->registerPhpFunctions();
+$xpath->evaluate('string(php:function("reconstruct"))');
+
+?>
+--EXPECTF--
+Error: Cannot call DOMXPath@anonymous%0%s::__construct() while an XPath evaluation is in progress
diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c
index e3c5e48f6cd..48c4126abbc 100644
--- a/ext/dom/xpath.c
+++ b/ext/dom/xpath.c
@@ -165,8 +165,8 @@ static void dom_xpath_construct(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry *
dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS);
if (UNEXPECTED(intern->evaluation_depth > 0)) {
- zend_throw_error(NULL, "Cannot call %s::__construct() while an XPath evaluation is in progress",
- ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
+ zend_throw_error(NULL, "Cannot call %pS::__construct() while an XPath evaluation is in progress",
+ Z_OBJCE_P(ZEND_THIS)->name);
RETURN_THROWS();
}
diff --git a/ext/xmlreader/php_xmlreader.c b/ext/xmlreader/php_xmlreader.c
index d547b9b3e90..aea0e23e4a5 100644
--- a/ext/xmlreader/php_xmlreader.c
+++ b/ext/xmlreader/php_xmlreader.c
@@ -199,7 +199,7 @@ static zval *xmlreader_write_property(zend_object *object, zend_string *name, zv
xmlreader_prop_handler *hnd = xmlreader_get_prop_handler(name, cache_slot);
if (hnd != NULL) {
- zend_readonly_property_modification_error_ex(ZSTR_VAL(object->ce->name), ZSTR_VAL(name));
+ zend_throw_error(NULL, "Cannot modify readonly property %pS::$%pS", object->ce->name, name);
} else {
value = zend_std_write_property(object, name, value, cache_slot);
}
diff --git a/ext/xmlreader/tests/xmlreader_anon_class_name_in_errors.phpt b/ext/xmlreader/tests/xmlreader_anon_class_name_in_errors.phpt
new file mode 100644
index 00000000000..70ade87100b
--- /dev/null
+++ b/ext/xmlreader/tests/xmlreader_anon_class_name_in_errors.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Anonymous class names are not truncated in XMLReader error messages
+--EXTENSIONS--
+xmlreader
+--FILE--
+<?php
+
+$reader = new class extends XMLReader {};
+try {
+ $reader->nodeType = 1;
+} catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECTF--
+Error: Cannot modify readonly property XMLReader@anonymous%0%s::$nodeType
diff --git a/main/main.c b/main/main.c
index 43c5fa087c5..815edf52dae 100644
--- a/main/main.c
+++ b/main/main.c
@@ -78,6 +78,7 @@
#include "zend_observer.h"
#include "zend_system_id.h"
#include "zend_smart_string.h"
+#include "zend_smart_str.h"
#include "php_content_types.h"
#include "php_ticks.h"
@@ -949,6 +950,24 @@ PHPAPI bool php_get_module_initialized(void)
}
/* }}} */
+/* Log sinks take C strings, so escape NUL bytes instead of truncating at them. */
+static zend_string *php_escape_nul_for_log(zend_string *message)
+{
+ if (!memchr(ZSTR_VAL(message), '\0', ZSTR_LEN(message))) {
+ return zend_string_copy(message);
+ }
+
+ smart_str buf = {0};
+ for (size_t i = 0; i < ZSTR_LEN(message); i++) {
+ if (ZSTR_VAL(message)[i] == '\0') {
+ smart_str_appendl(&buf, "\\0", 2);
+ } else {
+ smart_str_appendc(&buf, ZSTR_VAL(message)[i]);
+ }
+ }
+ return smart_str_extract(&buf);
+}
+
/* {{{ php_log_err_with_severity */
PHPAPI ZEND_COLD void php_log_err_with_severity(const char *log_message, int syslog_type_int)
{
@@ -1438,19 +1457,21 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c
if (PG(log_errors)
|| (!module_initialized && (!PG(display_startup_errors) || !PG(display_errors)))) {
char *log_buffer;
+ zend_string *log_message = php_escape_nul_for_log(message);
#ifdef PHP_WIN32
if (type == E_CORE_ERROR || type == E_CORE_WARNING) {
- syslog(LOG_ALERT, "PHP %s: %s (%s)", error_type_str, ZSTR_VAL(message), GetCommandLine());
+ syslog(LOG_ALERT, "PHP %s: %s (%s)", error_type_str, ZSTR_VAL(log_message), GetCommandLine());
}
#endif
- spprintf(&log_buffer, 0, "PHP %s: %s in %s on line %" PRIu32 "%s%s", error_type_str, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace));
+ spprintf(&log_buffer, 0, "PHP %s: %s in %s on line %" PRIu32 "%s%s", error_type_str, ZSTR_VAL(log_message), ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace));
+ zend_string_release(log_message);
php_log_err_with_severity(log_buffer, syslog_type_int);
efree(log_buffer);
}
if (PG(display_errors) && ((module_initialized && !PG(during_request_startup)) || (PG(display_startup_errors)))) {
if (PG(xmlrpc_errors)) {
- php_printf("<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>" ZEND_LONG_FMT "</int></value></member><member><name>faultString</name><value><string>%s:%s in %s on line %" PRIu32 "%s%s</string></value></member></struct></value></fault></methodResponse>", PG(xmlrpc_error_number), error_type_str, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace));
+ php_printf("<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>" ZEND_LONG_FMT "</int></value></member><member><name>faultString</name><value><string>%s:%pS in %s on line %" PRIu32 "%s%s</string></value></member></struct></value></fault></methodResponse>", PG(xmlrpc_error_number), error_type_str, message, ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace));
} else {
const char *prepend_string = zend_ini_string_literal("error_prepend_string");
const char *append_string = zend_ini_string_literal("error_append_string");
@@ -1458,7 +1479,7 @@ static ZEND_COLD void php_error_cb(int orig_type, zend_string *error_filename, c
if (PG(html_errors)) {
if (type == E_ERROR || type == E_PARSE) {
zend_string *buf = escape_html(ZSTR_VAL(message), ZSTR_LEN(message));
- php_printf("%s<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%" PRIu32 "</b><br />\n%s", STR_PRINT(prepend_string), error_type_str, ZSTR_VAL(buf), ZSTR_VAL(error_filename), error_lineno, STR_PRINT(append_string));
+ php_printf("%s<br />\n<b>%s</b>: %pS in <b>%s</b> on line <b>%" PRIu32 "</b><br />\n%s", STR_PRINT(prepend_string), error_type_str, buf, ZSTR_VAL(error_filename), error_lineno, STR_PRINT(append_string));
zend_string_free(buf);
} else {
php_printf_unchecked("%s<br />\n<b>%s</b>: %S in <b>%s</b> on line <b>%" PRIu32 "</b><br />%s%s\n%s", STR_PRINT(prepend_string), error_type_str, message, ZSTR_VAL(error_filename), error_lineno, ZSTR_LEN(backtrace) ? "\nStack trace:\n" : "", ZSTR_VAL(backtrace), STR_PRINT(append_string));
diff --git a/tests/basic/errorlog_nul_byte.phpt b/tests/basic/errorlog_nul_byte.phpt
new file mode 100644
index 00000000000..b53f1cee687
--- /dev/null
+++ b/tests/basic/errorlog_nul_byte.phpt
@@ -0,0 +1,20 @@
+--TEST--
+NUL bytes in error messages are escaped in the error log
+--INI--
+log_errors=1
+display_errors=0
+error_log={PWD}/errorlog_nul_byte.log
+--FILE--
+<?php
+$o = new class {};
+$o->dyn = 1;
+trigger_error("foo\0bar", E_USER_WARNING);
+echo file_get_contents(__DIR__ . '/errorlog_nul_byte.log');
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/errorlog_nul_byte.log');
+?>
+--EXPECTF--
+[%s] PHP Deprecated: Creation of dynamic property class@anonymous\0%s::$dyn is deprecated in %s on line %d
+[%s] PHP Warning: foo\0bar in %s on line %d
diff --git a/tests/basic/html_errors_nul_byte.phpt b/tests/basic/html_errors_nul_byte.phpt
new file mode 100644
index 00000000000..2f9582aa28e
--- /dev/null
+++ b/tests/basic/html_errors_nul_byte.phpt
@@ -0,0 +1,11 @@
+--TEST--
+NUL bytes in fatal error messages are not truncated with html_errors
+--INI--
+html_errors=1
+--FILE--
+<?php
+new class { abstract public $p { get; } };
+?>
+--EXPECTF--
+<br />
+<b>Fatal error</b>: Class class@anonymous%0%s must implement 1 abstract method (class@anonymous%0%s::$p::get) in <b>%s</b> on line <b>%d</b><br />
diff --git a/tests/basic/xmlrpc_errors_nul_byte.phpt b/tests/basic/xmlrpc_errors_nul_byte.phpt
new file mode 100644
index 00000000000..750a28e8f8b
--- /dev/null
+++ b/tests/basic/xmlrpc_errors_nul_byte.phpt
@@ -0,0 +1,10 @@
+--TEST--
+NUL bytes in error messages are not truncated with xmlrpc_errors
+--INI--
+xmlrpc_errors=1
+--FILE--
+<?php
+new class { abstract public $p { get; } };
+?>
+--EXPECTF--
+<?xml version="1.0"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>0</int></value></member><member><name>faultString</name><value><string>Fatal error:Class class@anonymous%0%s must implement 1 abstract method (class@anonymous%0%s::$p::get) in %s on line %d</string></value></member></struct></value></fault></methodResponse>