Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8dafaf5
Fix GH-23447: segfault when the SoapServer class fails to initialize
lazerg Aug 30, 2026
89c7271
Merge branch 'PHP-8.4' into PHP-8.5
devnexen Aug 30, 2026
efc4cd2
Merge branch 'PHP-8.5'
devnexen Aug 30, 2026
1273587
Implement "Followup improvements for ext/uri" RFC - WHATWG URL percen…
kocsismate Aug 30, 2026
c98e107
ext/sockets: CBPF reuseport CBPF filter improvements/strengthening.
devnexen Aug 14, 2026
473a7e0
ext/zip: ZipArchive::getNameIndex() index truncated to int.
devnexen Aug 29, 2026
614afe7
ext/zip: php_zip_ops_stat() succeeds when the archive cannot be opened.
devnexen Aug 29, 2026
a185172
Merge branch 'PHP-8.4' into PHP-8.5
devnexen Aug 30, 2026
c09c2e9
Merge branch 'PHP-8.5'
devnexen Aug 30, 2026
9e5cf96
[mysqlnd] Fix OK packet message length buffer over-read
iliaal Aug 29, 2026
51581dd
Merge branch 'PHP-8.4' into PHP-8.5
iliaal Aug 30, 2026
0f36dcc
Merge branch 'PHP-8.5'
iliaal Aug 30, 2026
fc7a6b9
[SimpleXML] Fix creating new attributes via attributes() dimension write
iliaal Aug 29, 2026
8541719
Merge branch 'PHP-8.4' into PHP-8.5
iliaal Aug 30, 2026
860dda8
Merge branch 'PHP-8.5'
iliaal Aug 30, 2026
091cb33
[intl] Size sortWithSortKeys buffers based on array size (#23504)
iliaal Aug 30, 2026
9c74d7d
[mysqlnd] Fix result set field metadata length buffer over-read
iliaal Aug 29, 2026
3d89d9f
Merge branch 'PHP-8.4' into PHP-8.5
iliaal Aug 30, 2026
90ebbf7
Merge branch 'PHP-8.5'
iliaal Aug 30, 2026
b7a85ee
[intl] Fix leak of time zone wrapper in Calendar debug info
iliaal Aug 29, 2026
a7ee4de
Merge branch 'PHP-8.4' into PHP-8.5
iliaal Aug 30, 2026
a738269
Merge branch 'PHP-8.5'
iliaal Aug 30, 2026
fbca6cb
dom: invalidate node list caches on class attribute mutations
iliaal Aug 29, 2026
0db31e3
Merge branch 'PHP-8.5'
iliaal Aug 30, 2026
f09a81d
[SOAP] Fix WSDL cache corruption when header defines headerfaults
iliaal Aug 29, 2026
02d5d45
Merge branch 'PHP-8.4' into PHP-8.5
iliaal Aug 30, 2026
68ff04b
Merge branch 'PHP-8.5'
iliaal Aug 30, 2026
027a5f8
ext/standard: Make `str_ends_with` frameless (#23510)
LamentXU123 Aug 30, 2026
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
20 changes: 20 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ PHP NEWS
. Fixed bug GH-23242 (PHP development server does not support Expect
100-continue flow control). (Sjoerd Langkemper)

- DOM:
. Fixed stale getElementsByClassName() and other node list caches after
className/classList writes and attribute removals. (Ilia Alshanetsky)

- Intl:
. Fixed a memory leak when dumping IntlCalendar instances. (Ilia Alshanetsky)
. Fixed Collator::sortWithSortKeys() allocating fixed 2MiB buffers
regardless of array size. (Ilia Alshanetsky)
. Fixed a memory leak when iterating IntlBreakIterator::getPartsIterator()
results. (iliaal)
. Fixed a leak in Locale::getKeywords() when a keyword value cannot be
Expand All @@ -28,6 +35,15 @@ PHP NEWS
. Fixed bug GH-23477 (Memory leak on duplicate native Phar manifest entries).
(Weilin Du)

- Sockets:
. Fixed socket_set_option() with SO_ATTACH_REUSEPORT_CBPF and a zero value,
which detached the classic BPF filter instead of the reuseport program.
(David Carlier)

- SOAP:
. Fixed WSDL cache corruption when a soap:header defines headerfaults.
(Ilia Alshanetsky)

- Standard:
. Fixed a segfault when a stream filter callback unsets StreamBucket::$data
before re-attaching the bucket. (iliaal)
Expand All @@ -37,6 +53,10 @@ PHP NEWS
. Io\Poll\Context::wait() now rejects a $maxEvents value greater than
INT_MAX instead of truncating it. (marc-mabe)

- SimpleXML:
. Fixed writing to a dimension of the object returned by attributes() not
creating the attribute. (Ilia Alshanetsky)


27 Aug 2026, PHP 8.6.0beta2

Expand Down
18 changes: 15 additions & 3 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,16 @@ PHP 8.6 UPGRADE NOTES
rules" message. Code that compares the exact message may need to be
updated.

- Sockets:
. socket_set_option() with SO_ATTACH_REUSEPORT_CBPF now requires an int
$value and a $level of SOL_SOCKET. Any other value type throws a TypeError
instead of being coerced, and any other level raises a warning and returns
false.
. socket_set_option() with SO_ATTACH_REUSEPORT_CBPF and a $value of 0 now
detaches the reuseport filter through SO_DETACH_REUSEPORT_BPF. It
previously used SO_DETACH_BPF, an alias of SO_DETACH_FILTER, which left the
reuseport program attached.

- Sodium:
. The password-hashing functions sodium_crypto_pwhash(),
sodium_crypto_pwhash_str(),
Expand Down Expand Up @@ -484,10 +494,10 @@ PHP 8.6 UPGRADE NOTES
RFC: https://wiki.php.net/rfc/uri_followup#uri_type_detection
. Added Uri\Rfc3986\Uri::getHostType() and Uri\WhatWg\Url::getHostType().
RFC: https://wiki.php.net/rfc/uri_followup#host_type_detection
. Added Uri\Rfc3986\UriBuilder.
RFC: https://wiki.php.net/rfc/uri_followup#uri_building
. Added Uri\WhatWg\UrlBuilder.
. Added Uri\Rfc3986\UriBuilder and Uri\WhatWg\UrlBuilder.
RFC: https://wiki.php.net/rfc/uri_followup#uri_building
. Added Uri\url_percent_encode().
RFC: https://wiki.php.net/rfc/uri_followup#percent-encoding_support

========================================
3. Changes in SAPI modules
Expand Down Expand Up @@ -860,6 +870,7 @@ PHP 8.6 UPGRADE NOTES
. EAI_ALLDONE.
. EAI_INTR.
. EAI_IDN_ENCODE.
. SO_DETACH_REUSEPORT_BPF (Linux only).

- Standard:
. ARRAY_FILTER_USE_VALUE.
Expand Down Expand Up @@ -962,6 +973,7 @@ PHP 8.6 UPGRADE NOTES
. Reduced temporary allocations when iterating Phar directories.

- Standard:
. Improved performance of str_ends_with().
. Improved performance of array_fill_keys().
. Improved performance of array_intersect().
. Improved performance of array_map() with multiple arrays passed.
Expand Down
13 changes: 9 additions & 4 deletions ext/dom/element.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ static xmlAttrPtr dom_element_reflected_attribute_write(dom_object *obj, zval *n

/* Typed property, so it is a string already */
ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING);
php_libxml_invalidate_node_list_cache(obj->document);
return xmlSetNsProp(nodep, NULL, (const xmlChar *) name, (const xmlChar *) Z_STRVAL_P(newval));
}

Expand Down Expand Up @@ -542,7 +543,7 @@ static void dom_deep_ns_redef(xmlNodePtr node, xmlNsPtr ns_to_redefine)
efree(worklist);
}

static bool dom_remove_attribute(xmlNodePtr thisp, xmlNodePtr attrp)
static bool dom_remove_attribute(xmlNodePtr thisp, xmlNodePtr attrp, php_libxml_ref_obj *document)
{
ZEND_ASSERT(thisp != NULL);
ZEND_ASSERT(attrp != NULL);
Expand Down Expand Up @@ -597,6 +598,7 @@ static bool dom_remove_attribute(xmlNodePtr thisp, xmlNodePtr attrp)
return false;
default: ZEND_UNREACHABLE();
}
php_libxml_invalidate_node_list_cache(document);
return true;
}

Expand All @@ -622,7 +624,7 @@ PHP_METHOD(DOMElement, removeAttribute)
RETURN_FALSE;
}

RETURN_BOOL(dom_remove_attribute(nodep, attrp));
RETURN_BOOL(dom_remove_attribute(nodep, attrp, intern->document));
}

PHP_METHOD(Dom_Element, removeAttribute)
Expand All @@ -640,7 +642,7 @@ PHP_METHOD(Dom_Element, removeAttribute)

attrp = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len);
if (attrp != NULL) {
dom_remove_attribute(nodep, attrp);
dom_remove_attribute(nodep, attrp, intern->document);
}
}
/* }}} end dom_element_remove_attribute */
Expand Down Expand Up @@ -798,6 +800,7 @@ static void dom_element_remove_attribute_node(INTERNAL_FUNCTION_PARAMETERS, zend
RETURN_FALSE;
}

php_libxml_invalidate_node_list_cache(intern->document);
xmlUnlinkNode((xmlNodePtr) attrp);

DOM_RET_OBJ((xmlNodePtr) attrp, intern);
Expand Down Expand Up @@ -1198,6 +1201,7 @@ PHP_METHOD(DOMElement, removeAttributeNS)
if (nsptr != NULL) {
if (xmlStrEqual(BAD_CAST uri, nsptr->href)) {
dom_eliminate_ns(nodep, nsptr);
php_libxml_invalidate_node_list_cache(intern->document);
} else {
return;
}
Expand All @@ -1212,6 +1216,7 @@ PHP_METHOD(DOMElement, removeAttributeNS)
} else {
xmlUnlinkNode((xmlNodePtr) attrp);
}
php_libxml_invalidate_node_list_cache(intern->document);
}
}
/* }}} end dom_element_remove_attribute_ns */
Expand Down Expand Up @@ -1947,7 +1952,7 @@ PHP_METHOD(DOMElement, toggleAttribute)

/* Step 5 */
if (force_is_null || !force) {
retval = !dom_remove_attribute(thisp, attribute);
retval = !dom_remove_attribute(thisp, attribute, intern->document);
goto out;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
getElementsByClassName() cache must be invalidated by class attribute mutations
--EXTENSIONS--
dom
--FILE--
<?php
function mk($body) {
return Dom\HTMLDocument::createFromString("<!DOCTYPE html><html><body>$body</body></html>");
}

$checks = [
'className' => function ($doc, $span) { $span->className = 'zzz'; },
'classList-remove' => function ($doc, $span) { $span->classList->remove('foo'); },
'classList-value' => function ($doc, $span) { $span->classList->value = 'zzz'; },
'setAttribute' => function ($doc, $span) { $span->setAttribute('class', 'zzz'); },
'removeAttribute' => function ($doc, $span) { $span->removeAttribute('class'); },
'removeAttributeNode' => function ($doc, $span) { $span->removeAttributeNode($span->attributes['class']); },
];
foreach ($checks as $label => $fn) {
$doc = mk('<span class="foo"></span>');
$coll = $doc->getElementsByClassName('foo');
if ($coll->length !== 1) {
echo "$label: unexpected initial length\n";
continue;
}
$fn($doc, $doc->querySelector('span'));
echo "$label: ", $coll->length === 0 ? "OK" : "STALE {$coll->length}", "\n";
}

$doc = mk('<span></span>');
$coll = $doc->getElementsByClassName('foo');
var_dump($coll->length);
$doc->querySelector('span')->className = 'foo';
echo $coll->length === 1 ? "growth OK" : "growth STALE", "\n";
?>
--EXPECT--
className: OK
classList-remove: OK
classList-value: OK
setAttribute: OK
removeAttribute: OK
removeAttributeNode: OK
int(0)
growth OK
2 changes: 2 additions & 0 deletions ext/dom/token_list.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ static void dom_token_list_update(dom_token_list_object *intern)
HashTable *token_set = TOKEN_LIST_GET_SET(intern);

php_libxml_invalidate_cache_tag(&intern->cache_tag);
php_libxml_invalidate_node_list_cache(intern->dom.document);

/* 1. If the associated element does not have an associated attribute and token set is empty, then return. */
if (attr == NULL && zend_hash_num_elements(token_set) == 0) {
Expand Down Expand Up @@ -430,6 +431,7 @@ zend_result dom_token_list_value_write(dom_object *obj, zval *newval)
zend_value_error("Value must not contain any null bytes");
return FAILURE;
}
php_libxml_invalidate_node_list_cache(intern->dom.document);
xmlSetNsProp(dom_token_list_get_element(intern), NULL, BAD_CAST "class", BAD_CAST Z_STRVAL_P(newval));
/* Note: we don't update the set here, the set is always lazily updated for performance reasons. */
return SUCCESS;
Expand Down
2 changes: 2 additions & 0 deletions ext/intl/calendar/calendar_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ static HashTable *Calendar_get_debug_info(zend_object *object, int *is_temp)
FREE_HASHTABLE(debug_info_tz);

zend_hash_str_update(debug_info, "timeZone", sizeof("timeZone") - 1, &ztz_debug);

zval_ptr_dtor(&ztz);
}

{
Expand Down
44 changes: 26 additions & 18 deletions ext/intl/collator/collator_sort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ ZEND_EXTERN_MODULE_GLOBALS( intl )

static const size_t DEF_SORT_KEYS_BUF_SIZE = 1048576;
static const size_t DEF_SORT_KEYS_BUF_INCREMENT = 1048576;

static const size_t DEF_SORT_KEYS_INDX_BUF_SIZE = 1048576;
static const size_t DEF_SORT_KEYS_INDX_BUF_INCREMENT = 1048576;
static const size_t MIN_SORT_KEYS_BUF_SIZE = 4096;
static const size_t SORT_KEY_LENGTH_ESTIMATE = 32;

static const size_t DEF_UTF16_BUF_SIZE = 1024;

Expand Down Expand Up @@ -427,17 +426,17 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys )
zval* hashData = nullptr; /* currently processed item of input hash */

char* sortKeyBuf = nullptr; /* buffer to store sort keys */
uint32_t sortKeyBufSize = DEF_SORT_KEYS_BUF_SIZE; /* buffer size */
uint32_t sortKeyBufSize = 0; /* buffer size */
ptrdiff_t sortKeyBufOffset = 0; /* pos in buffer to store sort key */
uint32_t sortKeyLen = 0; /* the length of currently processing key */
uint32_t bufLeft = 0;
uint32_t bufIncrement = 0;

collator_sort_key_index_t* sortKeyIndxBuf = nullptr; /* buffer to store 'indexes' which will be passed to 'qsort' */
uint32_t sortKeyIndxBufSize = DEF_SORT_KEYS_INDX_BUF_SIZE;
uint32_t sortKeyIndxSize = sizeof( collator_sort_key_index_t );

uint32_t sortKeyCount = 0;
uint32_t numElements = 0;
uint32_t j = 0;

UChar* utf16_buf = nullptr; /* tmp buffer to hold current processing string in utf-16 */
Expand Down Expand Up @@ -472,9 +471,20 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys )
if( !hash || zend_hash_num_elements( hash ) == 0 )
RETURN_TRUE;

numElements = zend_hash_num_elements( hash );

if( numElements > DEF_SORT_KEYS_BUF_SIZE / SORT_KEY_LENGTH_ESTIMATE ) {
sortKeyBufSize = DEF_SORT_KEYS_BUF_SIZE;
} else {
sortKeyBufSize = numElements * SORT_KEY_LENGTH_ESTIMATE;
}
if( sortKeyBufSize < MIN_SORT_KEYS_BUF_SIZE ) {
sortKeyBufSize = MIN_SORT_KEYS_BUF_SIZE;
}

/* Create buffers */
sortKeyBuf = reinterpret_cast<char *>(ecalloc( sortKeyBufSize, sizeof( char ) ));
sortKeyIndxBuf = reinterpret_cast<collator_sort_key_index_t *>(ecalloc( sortKeyIndxBufSize, sizeof( uint8_t ) ));
sortKeyBuf = reinterpret_cast<char *>(ecalloc( sortKeyBufSize, sizeof( char ) ));
sortKeyIndxBuf = reinterpret_cast<collator_sort_key_index_t *>(ecalloc( numElements, sortKeyIndxSize ));
utf16_buf = eumalloc( utf16_buf_size );

/* Iterate through input hash and create a sort key for each value. */
Expand Down Expand Up @@ -524,7 +534,15 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys )
/* check for sortKeyBuf overflow, increasing its size of the buffer if needed */
if( sortKeyLen > bufLeft )
{
bufIncrement = ( sortKeyLen > DEF_SORT_KEYS_BUF_INCREMENT ) ? sortKeyLen : DEF_SORT_KEYS_BUF_INCREMENT;
bufIncrement = sortKeyBufSize;

if( bufIncrement > DEF_SORT_KEYS_BUF_INCREMENT ) {
bufIncrement = DEF_SORT_KEYS_BUF_INCREMENT;
}

if( bufIncrement < sortKeyLen ) {
bufIncrement = sortKeyLen;
}

sortKeyBufSize += bufIncrement;
bufLeft += bufIncrement;
Expand All @@ -534,16 +552,6 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys )
sortKeyLen = ucol_getSortKey( co->ucoll, utf16_buf, utf16_len, (uint8_t*)sortKeyBuf + sortKeyBufOffset, bufLeft );
}

/* check sortKeyIndxBuf overflow, increasing its size of the buffer if needed */
if( ( sortKeyCount + 1 ) * sortKeyIndxSize > sortKeyIndxBufSize )
{
bufIncrement = ( sortKeyIndxSize > DEF_SORT_KEYS_INDX_BUF_INCREMENT ) ? sortKeyIndxSize : DEF_SORT_KEYS_INDX_BUF_INCREMENT;

sortKeyIndxBufSize += bufIncrement;

sortKeyIndxBuf = reinterpret_cast<collator_sort_key_index_t *>(erealloc( sortKeyIndxBuf, sortKeyIndxBufSize ));
}

sortKeyIndxBuf[sortKeyCount].key = (char*)sortKeyBufOffset; /* remember just offset, cause address */
/* of 'sortKeyBuf' may be changed due to realloc. */
sortKeyIndxBuf[sortKeyCount].zstr = hashData;
Expand Down
26 changes: 26 additions & 0 deletions ext/intl/tests/calendar_get_debug_info_tz_leak.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
IntlCalendar get_debug_info() must not leak the time zone wrapper object
--EXTENSIONS--
intl
--FILE--
<?php
$cal = IntlCalendar::createInstance('UTC');
ob_start();
var_dump($cal);
ob_end_clean();

$o = new stdClass;
$before = spl_object_id($o);
unset($o);
for ($i = 0; $i < 10; $i++) {
ob_start();
var_dump($cal);
ob_end_clean();
}
$o = new stdClass;
$after = spl_object_id($o);

var_dump($after - $before);
?>
--EXPECT--
int(0)
Loading
Loading