Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -1772,7 +1772,6 @@ ZEND_API void zend_free_recorded_errors(void)
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) /* {{{ */
{
va_list va;
char *message = NULL;

if (!exception_ce) {
exception_ce = zend_ce_error;
Expand All @@ -1784,16 +1783,20 @@ ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const c
}

va_start(va, format);
zend_vspprintf(&message, 0, format, va);
zend_string *message = zend_vstrpprintf(0, format, va);

//TODO: we can't convert compile-time errors to exceptions yet???
if (EG(current_execute_data) && !CG(in_compilation)) {
zend_throw_exception(exception_ce, message, 0);
// %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
// Use "%S" so that the message can contain null bytes.
const char *format = "%S";
zend_throw_exception_ex(exception_ce, 0, format, message);
} else {
zend_error_noreturn(E_ERROR, "%s", message);
zend_error_noreturn(E_ERROR, "%s", ZSTR_VAL(message));
}

efree(message);
zend_string_release(message);
va_end(va);
}
/* }}} */
Expand Down
7 changes: 3 additions & 4 deletions Zend/zend_exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -888,14 +888,13 @@ ZEND_API ZEND_COLD zend_object *zend_throw_exception(zend_class_entry *exception
ZEND_API ZEND_COLD zend_object *zend_throw_exception_ex(zend_class_entry *exception_ce, zend_long code, const char *format, ...) /* {{{ */
{
va_list arg;
char *message;
zend_object *obj;

va_start(arg, format);
zend_vspprintf(&message, 0, format, arg);
zend_string *msg_str = zend_vstrpprintf(0, format, arg);
va_end(arg);
obj = zend_throw_exception(exception_ce, message, code);
efree(message);
obj = zend_throw_exception_zstr(exception_ce, msg_str, code);
zend_string_release(msg_str);
return obj;
}
/* }}} */
Expand Down
166 changes: 129 additions & 37 deletions ext/reflection/php_reflection.c

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
GH-22905: null bytes in ReflectionClassConstant::__construct() error messages (class name)
--FILE--
<?php

new ReflectionClassConstant("foo\0bar", "");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Class "foo%0bar" does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionClassConstant->__construct('foo\x00bar', '')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
GH-22905: null bytes in ReflectionClassConstant::__construct() error messages (constant name)
--FILE--
<?php

class Demo {}
new ReflectionClassConstant(Demo::class, "foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Constant Demo::foo%0bar does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionClassConstant->__construct('Demo', 'foo\x00bar')
#1 {main}
thrown in %s on line %d
14 changes: 14 additions & 0 deletions ext/reflection/tests/gh22905/ReflectionClass_construct.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
GH-22905: null bytes in ReflectionClass::__construct() error messages
--FILE--
<?php

new ReflectionClass("foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Class "foo%0bar" does not exist in %s:%d

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to have a class name with null bytes? If not then just using the Path ZPP specifier would be better

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anonymous classes have a null byte in their name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that is true, but isn't it assumed that the part after the null byte is truncate? I remember we added the Interface to the name as a way to distinguish between different anonymous classes.

@arnaud-lb arnaud-lb Jul 30, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. Right now anonymous class names are a concatenation of the parent class/interface if any, followed by @anonymous, a null byte, declaring file name, lineno, and a unique integer. ::class reports the entire name including the null byte and what follows.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm maybe @nicolas-grekas can chime in as IIRC he asked for the parent class/interface to be added, so he might have more insight in what might be expected here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean get_debug_type vs the real class name?

@DanielEScherzer DanielEScherzer Jul 30, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the null byte is part of the class name and is needed for ReflectionClass::__construct(), https://3v4l.org/7LSSp#v

Edit: I didn't think of this before, this shows there is some truncation remaining in ReflectionClass::__toString() and probably a few other places

Stack trace:
#0 %s(%d): ReflectionClass->__construct('foo\x00bar')
#1 {main}
thrown in %s on line %d
16 changes: 16 additions & 0 deletions ext/reflection/tests/gh22905/ReflectionClass_getMethod.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
GH-22905: null bytes in ReflectionClass::getMethod() error messages
--FILE--
<?php

class Demo {}
$r = new ReflectionClass(Demo::class);
$r->getMethod("foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Method Demo::foo%0bar() does not exist in %s:%d

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to have a method with a null byte? If not using path ZPP might be better.

Stack trace:
#0 %s(%d): ReflectionClass->getMethod('foo\x00bar')
#1 {main}
thrown in %s on line %d
16 changes: 16 additions & 0 deletions ext/reflection/tests/gh22905/ReflectionClass_getProperty.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
GH-22905: null bytes in ReflectionClass::getProperty() error messages
--FILE--
<?php

class Demo {}
$r = new ReflectionClass(Demo::class);
$r->getProperty("foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Property Demo::$foo%0bar does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionClass->getProperty('foo\x00bar')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
GH-22905: null bytes in ReflectionClass::getProperty() (fully qualified, base class) error messages
--FILE--
<?php

class Base {}
class Demo extends Base {}
$r = new ReflectionClass(Demo::class);
$r->getProperty("Base::foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Property Base::$foo%0bar does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionClass->getProperty('Base::foo\x00bar')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
GH-22905: null bytes in ReflectionClass::getProperty() (fully qualified, non-base class) error messages
--FILE--
<?php

class Base {}
class Demo {}
$r = new ReflectionClass(Demo::class);
$r->getProperty("Base::foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Fully qualified property name Base::$foo%0bar does not specify a base class of Demo in %s:%d
Stack trace:
#0 %s(%d): ReflectionClass->getProperty('Base::foo\x00bar')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
GH-22905: null bytes in ReflectionClass::getStaticPropertyValue() error messages
--FILE--
<?php

class Demo {}
$r = new ReflectionClass(Demo::class);
$r->getStaticPropertyValue("foo\0bar");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's possible to have a static property to have a NUL byte?


?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Property Demo::$foo%0bar does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionClass->getStaticPropertyValue('foo\x00bar')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
GH-22905: null bytes in ReflectionClass::implementsInterface() error messages
--FILE--
<?php

class Demo {}
$r = new ReflectionClass(Demo::class);
$r->implementsInterface("foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Interface "foo%0bar" does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionClass->implementsInterface('foo\x00bar')
#1 {main}
thrown in %s on line %d
16 changes: 16 additions & 0 deletions ext/reflection/tests/gh22905/ReflectionClass_isSubclassOf.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
GH-22905: null bytes in ReflectionClass::isSubclassOf() error messages
--FILE--
<?php

class Demo {}
$r = new ReflectionClass(Demo::class);
$r->isSubclassOf("foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Class "foo%0bar" does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionClass->isSubclassOf('foo\x00bar')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
GH-22905: null bytes in ReflectionClass::setStaticPropertyValue() error messages
--FILE--
<?php

class Demo {}
$r = new ReflectionClass(Demo::class);
$r->setStaticPropertyValue("foo\0bar", 123);

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Class Demo does not have a property named foo%0bar in %s:%d
Stack trace:
#0 %s(%d): ReflectionClass->setStaticPropertyValue('foo\x00bar', 123)
#1 {main}
thrown in %s on line %d
14 changes: 14 additions & 0 deletions ext/reflection/tests/gh22905/ReflectionConstant_construct.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
GH-22905: null bytes in ReflectionConstant::__construct() error messages
--FILE--
<?php

new ReflectionConstant("foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Constant "foo%0bar" does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionConstant->__construct('foo\x00bar')
#1 {main}
thrown in %s on line %d
16 changes: 16 additions & 0 deletions ext/reflection/tests/gh22905/ReflectionEnum_getCase.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
GH-22905: null bytes in ReflectionEnum::getCase() error messages
--FILE--
<?php

enum Demo {}
$r = new ReflectionEnum(Demo::class);
$r->getCase("foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Case Demo::foo%0bar does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionEnum->getCase('foo\x00bar')
#1 {main}
thrown in %s on line %d
14 changes: 14 additions & 0 deletions ext/reflection/tests/gh22905/ReflectionExtension_construct.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
GH-22905: null bytes in ReflectionExtension::__construct() error messages
--FILE--
<?php

new ReflectionExtension("foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Extension "foo%0bar" does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionExtension->__construct('foo\x00bar')
#1 {main}
thrown in %s on line %d
14 changes: 14 additions & 0 deletions ext/reflection/tests/gh22905/ReflectionFunction_construct.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
GH-22905: null bytes in ReflectionFunction::__construct() error messages
--FILE--
<?php

new ReflectionFunction("foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Function foo%0bar() does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionFunction->__construct('foo\x00bar')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
GH-22905: null bytes in ReflectionMethod::__construct() error messages (class name)
--FILE--
<?php

new ReflectionMethod("foo\0bar", "");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Class "foo%0bar" does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionMethod->__construct('foo\x00bar', '')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
GH-22905: null bytes in ReflectionMethod::__construct() error messages (method name)
--FILE--
<?php

class Demo {}
new ReflectionMethod(Demo::class, "foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Method Demo::foo%0bar() does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionMethod->__construct('Demo', 'foo\x00bar')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
GH-22905: null bytes in ReflectionMethod::createFromMethodName() error messages (method name)
--FILE--
<?php

class Demo {}
ReflectionMethod::createFromMethodName("Demo::foo\0bar");

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Method Demo::foo%0bar() does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionMethod::createFromMethodName('Demo::foo\x00bar')
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
GH-22905: null bytes in ReflectionParameter::__construct() error messages (array class name)
--FILE--
<?php

new ReflectionParameter(["foo\0bar", ""], 0);

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Class "foo%0bar" does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionParameter->__construct(Array, 0)
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
GH-22905: null bytes in ReflectionParameter::__construct() error messages (array method name)
--FILE--
<?php

class Demo {}
new ReflectionParameter([Demo::class, "foo\0bar"], 0);

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Method Demo::foo%0bar() does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionParameter->__construct(Array, 0)
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
GH-22905: null bytes in ReflectionParameter::__construct() error messages (string function)
--FILE--
<?php

new ReflectionParameter("foo\0bar", 0);

?>
--EXPECTF--
Fatal error: Uncaught ReflectionException: Function foo%0bar() does not exist in %s:%d
Stack trace:
#0 %s(%d): ReflectionParameter->__construct('foo\x00bar', 0)
#1 {main}
thrown in %s on line %d
Loading
Loading