php_array_data_shuffle() first compacts the holes of a packed array and
only then updates nNumUsed. When the engine threw in between, the stale
tail slots were still counted, so the moved values were released twice
when the copied array was destroyed. Update nNumUsed right after the
compaction.
Closes GH-24553.
PHP 8.2 made case-insensitive comparisons ASCII-only, but ksort() and
krsort() with SORT_STRING | SORT_FLAG_CASE and substr_compare() with
case_insensitive=true still used the locale-sensitive _l variants.
These call tolower() and depend on LC_CTYPE.
Use zend_binary_strcasecmp() and zend_binary_strncasecmp(), consistent
with sort() and strncasecmp(), so these comparisons also use ASCII case
folding regardless of the current locale.
Fixes#24552
Co-authored-by: Jon Surrell <sirreal@users.noreply.github.com>
Closes#24558
- Replace bailout on OOM with UnwindExit
- Properly reset various globals that may be accessed during startup
Fix GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
Fix GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
Co-authored-by: Ilija Tovilo <ilija.tovilo@me.com>
Co-authored-by: Jakub Zelenka <bukka@php.net>
Co-authored-by: Nora Dossche <7771979+ndossche@users.noreply.github.com>
Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
When $preserve_keys is not passed, every chunk is a list of at most
$size elements, so each chunk can be built with ZEND_HASH_FILL_PACKED
instead of one zend_hash_next_index_insert() call per element.
Close GH-23556
Just move some numeric-string key normalization stuff out
of the main loop. Also return early for empty input.
Co-authored-by: David CARLIER <devnexen@gmail.com>
`array_keys()` reused the input array when it was empty. For an array
that was previously populated, this preserved the next free index and
caused later appends to start at a non-zero key, even though
`array_keys()` returns a list.
Return a fresh empty array, matching `array_values()`, and cover both
previously populated and fresh empty arrays across all argument forms.
Closes#23577
php_compact_var() recurses once per nesting level with no stack check, so passing a deeply nested array to compact() exhausts the native stack and the process dies with a segfault.
This adds the same stack limit check ext/standard already uses in var.c and http.c, so the call throws an Error instead of crashing.
Fixes GH-23115
php_array_replace_recursive() recurses once per nesting level with no stack check, so array_replace_recursive() on a deeply nested array exhausts the native stack and the process dies with a segfault.
Fixes GH-23113
php_array_walk() recurses once per nesting level with no stack check, so array_walk_recursive() on a deeply nested array exhausts the native stack and the process dies with a segfault.
This adds the same stack limit check ext/standard already uses in var.c and http.c, so the call throws an Error instead of crashing. The existing GC_IS_RECURSIVE guard only covers self-referential arrays, not plain deep nesting.
Fixes GH-23111
* standard: remove sole user of php_error_docref_unchecked
After GH-22390 was merged, we can now use a %pS format specifier for
zend_string without having to use the unchecked variant (so that gcc et
al doesn't complain that %S should be for wchar_t*).
* main: Remove php_error_docref_unchecked
There are no external users AFAIK, and now no internal users.
array_multisort() snapshotted each input array's buckets without holding a
reference, then ran a comparator that under SORT_STRING invokes
Stringable::__toString(); a callback that unset or reassigned the array freed
its backing store and elements mid-sort, a use-after-free read in the
comparator and a write-back through the freed table during the restructure.
Hold a reference on each input HashTable for the duration of the sort and
restructure the cached tables, mirroring zend_array_sort_ex() (GH-16648).
HT_ALLOW_COW_VIOLATION allows the in-place repack under the held reference.
Fixes GH-22678
Closes GH-22680
Cleaning up the manually constructed standard parameter errors to use existing Zend helpers in bulk. This enhance readability and align with how those errors have been thrown in most places.
* [ext/standard] Specialize array_sum()/array_product() for long arrays
The per-element cost of array_sum() and array_product() is dominated by
the add_function/mul_function call dispatch. Add a specialized fast
path for the IS_LONG + IS_LONG case that inlines overflow-aware
arithmetic via fast_long_add_function() and ZEND_SIGNED_MULTIPLY_LONG()
-- the same engine helpers that add_function_fast() dispatches to
internally. The fast path applies to both packed and hash arrays.
On overflow, non-IS_LONG entries, objects, resources or strings,
execution falls through to the generic path in php_array_binop_apply(),
so the PHP 8.3 warning behavior, operator overloading and BC casts for
resources/non-numeric strings are preserved.
Benchmarks (Apple M1, -O2 -DNDEBUG release, median of 7 runs, n=10000
with ~20M total elements per case):
array_sum, packed long 42.24 ms -> 12.99 ms 3.25x
array_product, packed small long (0..9) 144.63 ms -> 19.39 ms 7.46x
array_product, packed range(1, 100) 82.94 ms -> 58.66 ms 1.41x
array_sum, hash long 41.95 ms -> 12.90 ms 3.25x
array_product, hash small long (0..9) 69.90 ms -> 19.40 ms 3.60x
packed/hash float, mixed IS_LONG/IS_DOUBLE ~ ~ ~1.00x
Tests cover the overflow transition from fast path to generic, and
the integration with array_column() together with the PHP 8.3
nested-array warning behavior.
* [ext/standard] Simplify array_sum/product iteration
* [ext/standard] Force inline array binop wrapper
There are several issues with this macro:
- It is incorrectly named: It's not an empty default case. It's an unreachable
default case.
- It is hiding control flow in its definition, which can be confusing for
humans and tools (such as Coccinelle) alike, because it looks like it would
be a statement that belongs to the "current" case rather than opening a new
one.
Since this macro is exactly as long as explicitly spelling out its definition
(excluding the useless `break;`), it is not even making the code any more
succinct.