Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
e832b5e
[DOM] Fix getNamedItemNS() with empty URI not matching null namespace
iliaal Aug 29, 2026
38a666f
Merge branch 'PHP-8.4' into PHP-8.5
iliaal Aug 30, 2026
f142b81
Merge branch 'PHP-8.5'
iliaal 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
17 changes: 17 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ PHP NEWS
. Fixed bug GH-23242 (PHP development server does not support Expect
100-continue flow control). (Sjoerd Langkemper)

- DOM:
. Fixed NamedNodeMap::getNamedItemNS() with an empty URI not matching
the null namespace in spec-following mode. (Ilia Alshanetsky)
. 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 @@ -33,6 +42,10 @@ PHP NEWS
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 @@ -42,6 +55,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
1 change: 1 addition & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -973,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
3 changes: 3 additions & 0 deletions ext/dom/namednodemap.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ PHP_METHOD(DOMNamedNodeMap, getNamedItemNS)
objmap = (dom_nnodemap_object *)intern->ptr;

if (objmap != NULL) {
if (urilen == 0 && objmap->baseobj != NULL && php_dom_follow_spec_intern(objmap->baseobj)) {
uri = NULL;
}
php_dom_obj_map_get_ns_named_item_into_zval(objmap, named, uri, return_value);
}
}
Expand Down
6 changes: 5 additions & 1 deletion ext/dom/obj_map.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,11 @@ static xmlNodePtr dom_map_get_ns_named_item_prop(dom_nnodemap_object *map, const
xmlNodePtr nodep = dom_object_get_node(map->baseobj);
if (nodep) {
if (ns) {
return (xmlNodePtr) xmlHasNsProp(nodep, BAD_CAST ZSTR_VAL(named), BAD_CAST ns);
xmlNodePtr itemnode = (xmlNodePtr) xmlHasNsProp(nodep, BAD_CAST ZSTR_VAL(named), BAD_CAST ns);
if (itemnode != NULL && itemnode->type == XML_ATTRIBUTE_DECL) {
return NULL;
}
return itemnode;
} else {
if (php_dom_follow_spec_intern(map->baseobj)) {
return (xmlNodePtr) php_dom_get_attribute_node(nodep, BAD_CAST ZSTR_VAL(named), ZSTR_LEN(named));
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
25 changes: 25 additions & 0 deletions ext/dom/tests/modern/spec/NamedNodeMap_getNamedItemNS.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
getNamedItemNS() with an empty URI must look up the null namespace
--EXTENSIONS--
dom
--FILE--
<?php
$d = new DOMDocument();
$d->loadXML('<root xmlns:q="urn:q" bar="no-ns" q:bar="ns"/>');
$a = $d->documentElement->attributes->getNamedItemNS('', 'bar');
var_dump($a === null ? null : $a->nodeValue);
$b = $d->documentElement->attributes->getNamedItemNS('urn:q', 'bar');
var_dump($b === null ? null : $b->nodeValue);
$d2 = Dom\XMLDocument::createFromString('<root xmlns:q="urn:q" bar="no-ns" q:bar="ns"/>');
$a2 = $d2->documentElement->attributes->getNamedItemNS('', 'bar');
var_dump($a2 === null ? null : $a2->nodeValue);
var_dump($d2->documentElement->hasAttributeNS('', 'bar'));
$c = $d2->documentElement->attributes->getNamedItemNS('urn:q', 'bar');
var_dump($c === null ? null : $c->nodeValue);
?>
--EXPECT--
NULL
string(2) "ns"
string(5) "no-ns"
bool(true)
string(2) "ns"
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
getNamedItemNS() with empty URI must not throw on DTD default attributes
--EXTENSIONS--
dom
--FILE--
<?php
$xml = <<<XML
<?xml version="1.0"?>
<!DOCTYPE root [
<!ELEMENT root EMPTY>
<!ATTLIST root defaulted CDATA "from-dtd">
]>
<root real="present"/>
XML;

$el = Dom\XMLDocument::createFromString($xml)->documentElement;
$defaulted = $el->attributes->getNamedItemNS('', 'defaulted');
var_dump($defaulted === null ? null : $defaulted->nodeValue);
$real = $el->attributes->getNamedItemNS('', 'real');
var_dump($real === null ? null : $real->nodeValue);
?>
--EXPECT--
NULL
string(7) "present"
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