From df8c0d2bfa6e96b86dfb71e017fce594cfce7640 Mon Sep 17 00:00:00 2001 From: Juan Versolato Lopes Date: Fri, 13 Nov 2026 10:04:20 -0300 Subject: [PATCH] Fix GH-25404: Unwrap by-reference returns forwarded by SPL methods RecursiveIteratorIterator::callHasChildren(), callGetChildren() and RecursiveFilterIterator::hasChildren() pass their return_value straight to the user method. When that method returns by reference, the reference ends up in the return value of a function that does not return by reference, which trips the assertion in the VM after the call. Unwrap the reference, like CallbackFilterIterator::accept() already does. zend_test's zend_call_method() had the same problem. --- NEWS | 4 +++ ext/spl/spl_iterators.c | 9 +++++++ ext/spl/tests/gh25404.phpt | 42 ++++++++++++++++++++++++++++++++ ext/zend_test/test.c | 3 +++ ext/zend_test/tests/gh25404.phpt | 18 ++++++++++++++ 5 files changed, 76 insertions(+) create mode 100644 ext/spl/tests/gh25404.phpt create mode 100644 ext/zend_test/tests/gh25404.phpt diff --git a/NEWS b/NEWS index 003160c8b56f..9d2e62bbbb12 100644 --- a/NEWS +++ b/NEWS @@ -127,6 +127,10 @@ PHP NEWS . Fix ReflectionProperty::isReadable() not working for indirect slots. (ndossche) +- SPL: + . Fixed bug GH-25404 (Missing zend_return_unwrap_ref() calls trigger + assertion failures). (Juan Versolato Lopes) + - Standard: . Fixed bug GH-25387 (Uploaded files leak in request_parse_body()). (Kévin Dunglas) diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c index 94c89448d540..a58beaa9a9c0 100644 --- a/ext/spl/spl_iterators.c +++ b/ext/spl/spl_iterators.c @@ -825,6 +825,9 @@ PHP_METHOD(RecursiveIteratorIterator, callHasChildren) if (Z_TYPE_P(return_value) == IS_UNDEF) { RETURN_FALSE; } + if (Z_ISREF_P(return_value)) { + zend_unwrap_reference(return_value); + } } } /* }}} */ @@ -847,6 +850,9 @@ PHP_METHOD(RecursiveIteratorIterator, callGetChildren) if (Z_TYPE_P(return_value) == IS_UNDEF) { RETURN_NULL(); } + if (Z_ISREF_P(return_value)) { + zend_unwrap_reference(return_value); + } } } /* }}} */ @@ -1672,6 +1678,9 @@ PHP_METHOD(RecursiveFilterIterator, hasChildren) SPL_FETCH_AND_CHECK_DUAL_IT(intern, ZEND_THIS); zend_call_method_with_0_params(Z_OBJ(intern->inner.zobject), intern->inner.ce, NULL, "haschildren", return_value); + if (Z_ISREF_P(return_value)) { + zend_unwrap_reference(return_value); + } } /* }}} */ /* {{{ Return the inner iterator's children contained in a RecursiveFilterIterator */ diff --git a/ext/spl/tests/gh25404.phpt b/ext/spl/tests/gh25404.phpt new file mode 100644 index 000000000000..ca437735d9a5 --- /dev/null +++ b/ext/spl/tests/gh25404.phpt @@ -0,0 +1,42 @@ +--TEST-- +GH-25404 (SPL methods forward by-reference returns of user methods) +--FILE-- +children; } + public function &hasChildren(): bool { return $this->hasChildren; } + + public function current(): mixed { return null; } + public function key(): mixed { return null; } + public function next(): void {} + public function rewind(): void {} + public function valid(): bool { return true; } +} + +class Filter extends RecursiveFilterIterator { + public function accept(): bool { return true; } +} + +$demo = new Demo(); + +$it = new RecursiveIteratorIterator($demo); +var_dump($it->callHasChildren()); +var_dump($it->callGetChildren()); + +$filter = new Filter($demo); +var_dump($filter->hasChildren()); + +$result = $it->callHasChildren(); +$result = false; +var_dump($demo->hasChildren); + +?> +--EXPECT-- +bool(true) +NULL +bool(true) +bool(true) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 56b0ef8bb21b..6b73daff19c4 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1104,6 +1104,9 @@ static ZEND_FUNCTION(zend_call_method) ZEND_ASSERT((argc >= 2) && (argc <= 4)); zend_call_method(obj, ce, NULL, ZSTR_VAL(method_name), ZSTR_LEN(method_name), return_value, argc - 2, arg1, arg2); + if (Z_ISREF_P(return_value)) { + zend_unwrap_reference(return_value); + } } /* Instantiate a class and run the constructor via object_init_with_constructor */ diff --git a/ext/zend_test/tests/gh25404.phpt b/ext/zend_test/tests/gh25404.phpt new file mode 100644 index 000000000000..47afd1d84e55 --- /dev/null +++ b/ext/zend_test/tests/gh25404.phpt @@ -0,0 +1,18 @@ +--TEST-- +GH-25404 (zend_call_method() forwards by-reference returns) +--EXTENSIONS-- +zend_test +--FILE-- +v; } +} + +$d = new Demo(); +var_dump(zend_call_method($d, 'returnsRef')); + +?> +--EXPECT-- +bool(true)