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>
* Optimize str_repeat
The memmove got me thinking "can src and dst actually overlap?", and it turns out no:
- e always points to populated data + 1
- l may reach the end of populated data, but will never exceed it
memcpy is safe here, and faster (unless the compiler already spotted it in an optimization pass and replaced it internally, entirely possible)
* comment pinning the non-overlap invariant
Co-authored-by: Xinchen Hui <laruence@gmail.com>
---------
Co-authored-by: Xinchen Hui <laruence@gmail.com>
When a multi-character break or $cut_long_words is used, an existing
break was only recognized when at least one character followed it. A
trailing break was handled as regular text, so wordwrap("abc\n", 3,
"\n", true) returned "abc\n\n" and wordwrap("ab cd\n", 5, "\n", true)
returned "ab\ncd\n". The single-character fast path was not affected.
- Reuse the basename string for PATHINFO_BASENAME
and extensionless PATHINFO_FILENAME results.
- Share the last-dot lookup for extension and filename
when returning PATHINFO_ALL.
- (From my perspective) cleaner code.
This PR optimizes the user-visible `str_pad()` by reducing repeated small
copy operations during padding. In `php_str_pad_fill()`, the algorithm now uses
doubling copies to grow the written region exponentially. This lowers memcpy counts
and improves throughput for large repeated-pattern padding workloads.
Added `str_pad_repeated_pattern.phpt` to validate repeated pattern and partial-tail
boundaries, including multi-byte patterns and all pad directions. Behavior is
unchanged; this is a performance-only change and is now recorded in the PHP 8.6
performance changelog.
Both are single-argument wrappers around zend_string_tolower/upper;
static analyzers and general string handling call them at very high
frequency. The frameless call convention removes the call frame setup
and argument copying.
Co-authored-by: Markus Staab <maggus.staab@googlemail.com>
Closes GH-23084
implode() walks the array with ZEND_HASH_FOREACH_VAL while holding no
reference on it. Converting a Stringable element runs user code, and if
that code drops the last remaining reference to the array (`$a = null;`
from __toString()), arData is freed and the next iteration reads freed
memory. strtr() and str_replace() read their array arguments the same
way and crash the same way, so they are fixed here too.
Taking a reference on the table for the duration of the read keeps it
alive and turns an in-place mutation into a separation instead, same as
zend_compare_symbol_tables() does around zend_hash_compare(). In
implode() the reference is released after the pieces have been
concatenated, since the collected zend_strings are still owned by the
array until then.
Close GH-23207
Followup-PR for #21724. Using the new API in #21724 to replace dependencies
on /ext/hash in
- ext/opcache/ZendAccelerator/
- ext/soap/php_http.c
- Zend/zend_system_id.c
In the following ext, there are independent implementation of bin2hex. This PR
also change them to directly using the new Zend API.
- ext/standard/string.c
- ext/hash/php_hash.h
The HAVE_LIBINTL macro is defined only when the ext/gettext is enabled
during the build. Also, LC_MESSAGES have been moved to other places
since this header was initially included.
* tree-wide: Replace `WRONG_PARAM_COUNT` by `ZEND_WRONG_PARAM_COUNT()`
This is a direct alias.
* tree-wide: Replace `ZEND_WRONG_PARAM_COUNT()` by its definition
This macro was hiding control flow (the return statement) and thus was
particularly unhygienic.
Three optimizations:
- If the entire string is returned, we don't need to duplicate it.
- Use packed filling logic.
- Use fast construction of strings. This is useful when splitting
strings on length=1. In that case I get a 6x speedup in the code
below.
Bench:
```php
$x = str_repeat('A', 100);
for ($i = 0; $i < 1000000; $i++)
str_split($x, 10);
```
On an i7-4790:
```
Benchmark 1: ./sapi/cli/php x.php
Time (mean ± σ): 160.1 ms ± 6.4 ms [User: 157.3 ms, System: 1.8 ms]
Range (min … max): 155.6 ms … 184.7 ms 18 runs
Benchmark 2: ./sapi/cli/php_old x.php
Time (mean ± σ): 202.6 ms ± 4.0 ms [User: 199.1 ms, System: 1.9 ms]
Range (min … max): 197.4 ms … 209.2 ms 14 runs
Summary
./sapi/cli/php x.php ran
1.27 ± 0.06 times faster than ./sapi/cli/php_old x.php
```
The performance gain increases with smaller lengths.