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
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ PHP NEWS
as float rather than int on 64-bit platforms. (Weilin Du)
. Fixed UConverter::transcode() silently truncating from_subst and to_subst
option lengths greater than 127 bytes. (Weilin Du)
. Fixed IntlIterator::current() to return NULL instead of an undefined value
when the iterator is not positioned on a valid element. (Weilin Du)

- IO:
. Added new polling API. (Jakub Zelenka)
Expand Down
60 changes: 60 additions & 0 deletions Zend/tests/pipe_operator/gh22490.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
--TEST--
GH-22490: Null pointer dereference compiling a pipe on the lhs of ??=
--FILE--
<?php
$calls = 0;
function unset_prop($x) {
global $calls;
$calls++;
return new stdClass;
}
function set_prop($x) {
global $calls;
$calls++;
$o = new stdClass;
$o->p = 7;
return $o;
}

echo "property unset, assigns and returns default: ";
var_dump((1 |> unset_prop(...))->p ??= 99);
echo "pipe evaluated once: ";
var_dump($calls);

$calls = 0;
echo "property set, returns existing value: ";
var_dump((1 |> set_prop(...))->p ??= 99);
echo "pipe evaluated once: ";
var_dump($calls);

function new_array($x) {
global $calls;
$calls++;
return [];
}

$calls = 0;
echo "dim target on a pipe result: ";
var_dump((1 |> new_array(...))[0] ??= 42);
echo "pipe evaluated once: ";
var_dump($calls);

function identity($x) {
return $x;
}

$calls = 0;
echo "nested pipe on the lhs: ";
var_dump((1 |> identity(...) |> unset_prop(...))->p ??= 5);
echo "pipe evaluated once: ";
var_dump($calls);
?>
--EXPECT--
property unset, assigns and returns default: int(99)
pipe evaluated once: int(1)
property set, returns existing value: int(7)
pipe evaluated once: int(1)
dim target on a pipe result: int(42)
pipe evaluated once: int(1)
nested pipe on the lhs: int(5)
pipe evaluated once: int(1)
1 change: 1 addition & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -12270,6 +12270,7 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty
case ZEND_AST_METHOD_CALL:
case ZEND_AST_NULLSAFE_METHOD_CALL:
case ZEND_AST_STATIC_CALL:
case ZEND_AST_PIPE:
zend_compile_memoized_expr(result, ast, BP_VAR_W);
/* This might not actually produce an opcode, e.g. for expressions evaluated at comptime. */
return NULL;
Expand Down
6 changes: 3 additions & 3 deletions docs/source/core/data-structures/zend_string.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ in bytes, and the ``val`` field contains the actual string data.

You may wonder why the ``val`` field is declared as ``char val[1]``. This is called the `struct
hack`_ in C. It is used to create structs with a flexible size, namely by allowing the last element
to be expanded arbitrarily. In this case, the size of ``zend_string`` depends on the string's length,
which is determined at runtime (see ``_ZSTR_STRUCT_SIZE``). When allocating the string, we append
enough bytes to the allocation to hold the strings content.
to be expanded arbitrarily. In this case, the size of ``zend_string`` depends on the string's
length, which is determined at runtime (see ``_ZSTR_STRUCT_SIZE``). When allocating the string, we
append enough bytes to the allocation to hold the strings content.

.. _struct hack: https://www.geeksforgeeks.org/struct-hack/

Expand Down
2 changes: 1 addition & 1 deletion ext/intl/common/common_enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ PHP_METHOD(IntlIterator, current)

INTLITERATOR_METHOD_FETCH_OBJECT;
data = ii->iterator->funcs->get_current_data(ii->iterator);
if (data) {
if (data && !Z_ISUNDEF_P(data)) {
RETURN_COPY_DEREF(data);
}
}
Expand Down
22 changes: 22 additions & 0 deletions ext/intl/tests/intl_iterator_current_invalid.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
IntlIterator::current() returns null when the iterator is not valid
--EXTENSIONS--
intl
--FILE--
<?php
$iterator = IntlBreakIterator::createWordInstance('en_US')->getPartsIterator();
var_dump($iterator->current());

$breakIterator = IntlBreakIterator::createWordInstance('en_US');
$breakIterator->setText('foo');
$iterator = $breakIterator->getPartsIterator();
$iterator->rewind();
$iterator->next();
$iterator->next();
var_dump($iterator->valid());
var_dump($iterator->current());
?>
--EXPECT--
NULL
bool(false)
NULL