Skip to content

Commit eb151e3

Browse files
committed
Properly handle reference return value from __toString()
It's possible to return a reference from __toString(), but this is not handled and results in a (confusing) error telling that the return value must be a string. Properly handle this by unwrapping the reference. Closes GH-18810.
1 parent 8f3e555 commit eb151e3

File tree

6 files changed

+48
-0
lines changed

6 files changed

+48
-0
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ PHP NEWS
5454
released on bailout). (DanielEScherzer and ilutov)
5555
. Fixed AST printing for immediately invoked Closure. (Dmitrii Derepko)
5656
. Properly handle __debugInfo() returning an array reference. (nielsdos)
57+
. Properly handle reference return value from __toString(). (nielsdos)
5758
. Added the pipe (|>) operator. (crell)
5859

5960
- Curl:
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Exception fatal uncaught error with reference __toString
3+
--FILE--
4+
<?php
5+
6+
class MyException extends Exception {
7+
private $field = 'my string';
8+
public function &__toString(): string {
9+
return $this->field;
10+
}
11+
}
12+
13+
// Must not be caught to trigger the issue!
14+
throw new MyException;
15+
16+
?>
17+
--EXPECTF--
18+
Fatal error: Uncaught my string
19+
thrown in %s on line %d
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
String cast with reference __toString
3+
--FILE--
4+
<?php
5+
6+
class MyClass {
7+
private $field = 'my string';
8+
public function &__toString(): string {
9+
return $this->field;
10+
}
11+
}
12+
13+
echo new MyClass;
14+
15+
?>
16+
--EXPECT--
17+
my string

Zend/zend_exceptions.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,9 @@ ZEND_API ZEND_COLD zend_result zend_exception_error(zend_object *ex, int severit
969969

970970
zend_call_known_instance_method_with_0_params(ex->ce->__tostring, ex, &tmp);
971971
if (!EG(exception)) {
972+
if (UNEXPECTED(Z_ISREF(tmp))) {
973+
zend_unwrap_reference(&tmp);
974+
}
972975
if (Z_TYPE(tmp) != IS_STRING) {
973976
zend_error(E_WARNING, "%s::__toString() must return a string", ZSTR_VAL(ce_exception->name));
974977
} else {

Zend/zend_object_handlers.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2440,8 +2440,12 @@ ZEND_API zend_result zend_std_cast_object_tostring(zend_object *readobj, zval *w
24402440
zend_call_known_instance_method_with_0_params(ce->__tostring, readobj, &retval);
24412441
zend_object_release(readobj);
24422442
if (EXPECTED(Z_TYPE(retval) == IS_STRING)) {
2443+
is_string:
24432444
ZVAL_COPY_VALUE(writeobj, &retval);
24442445
return SUCCESS;
2446+
} else if (Z_ISREF(retval)) {
2447+
zend_unwrap_reference(&retval);
2448+
goto is_string;
24452449
}
24462450
zval_ptr_dtor(&retval);
24472451
if (!EG(exception)) {

sapi/phpdbg/phpdbg_prompt.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,10 @@ static inline void phpdbg_handle_exception(void) /* {{{ */
702702
EG(exception) = NULL;
703703
msg = ZSTR_EMPTY_ALLOC();
704704
} else {
705+
if (UNEXPECTED(Z_ISREF(tmp))) {
706+
zend_unwrap_reference(&tmp);
707+
}
708+
ZEND_ASSERT(Z_TYPE(tmp) == IS_STRING);
705709
zend_update_property_string(zend_get_exception_base(ex), ex, ZEND_STRL("string"), Z_STRVAL(tmp));
706710
zval_ptr_dtor(&tmp);
707711
msg = zval_get_string(zend_read_property_ex(zend_get_exception_base(ex), ex, ZSTR_KNOWN(ZEND_STR_STRING), /* silent */ true, &rv));

0 commit comments

Comments
 (0)