diff --git a/NEWS b/NEWS index 9df461452c1c..a4e0e3ba4f03 100644 --- a/NEWS +++ b/NEWS @@ -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) diff --git a/Zend/tests/pipe_operator/gh22490.phpt b/Zend/tests/pipe_operator/gh22490.phpt new file mode 100644 index 000000000000..c801d938c275 --- /dev/null +++ b/Zend/tests/pipe_operator/gh22490.phpt @@ -0,0 +1,60 @@ +--TEST-- +GH-22490: Null pointer dereference compiling a pipe on the lhs of ??= +--FILE-- +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) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 7e9f7ceac8db..9f8ebad8ab29 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -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; diff --git a/docs/source/core/data-structures/zend_string.rst b/docs/source/core/data-structures/zend_string.rst index 5b1d3ac0ccfb..292028e43497 100644 --- a/docs/source/core/data-structures/zend_string.rst +++ b/docs/source/core/data-structures/zend_string.rst @@ -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/ diff --git a/ext/intl/common/common_enum.cpp b/ext/intl/common/common_enum.cpp index 379561866941..4260e3cce57f 100644 --- a/ext/intl/common/common_enum.cpp +++ b/ext/intl/common/common_enum.cpp @@ -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); } } diff --git a/ext/intl/tests/intl_iterator_current_invalid.phpt b/ext/intl/tests/intl_iterator_current_invalid.phpt new file mode 100644 index 000000000000..5276bee5baeb --- /dev/null +++ b/ext/intl/tests/intl_iterator_current_invalid.phpt @@ -0,0 +1,22 @@ +--TEST-- +IntlIterator::current() returns null when the iterator is not valid +--EXTENSIONS-- +intl +--FILE-- +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