Merge branch 'PHP-8.4'

* PHP-8.4:
  Fix GH-18744: PHP 8.4 classList works not correctly if copy HTMLElement by clone keyword.
This commit is contained in:
Niels Dossche 2025-06-04 18:59:21 +02:00
commit 307ff3bdea
No known key found for this signature in database
GPG key ID: B8A8AD166DF0E2E5
4 changed files with 62 additions and 7 deletions

View file

@ -177,10 +177,7 @@ zend_result dom_element_class_name_write(dom_object *obj, zval *newval)
}
/* }}} */
/* {{{ classList TokenList
URL: https://dom.spec.whatwg.org/#dom-element-classlist
*/
zend_result dom_element_class_list_read(dom_object *obj, zval *retval)
zval *dom_element_class_list_zval(dom_object *obj)
{
const uint32_t PROP_INDEX = 0;
@ -191,7 +188,15 @@ zend_result dom_element_class_list_read(dom_object *obj, zval *retval)
ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == PROP_INDEX);
#endif
zval *cached_token_list = OBJ_PROP_NUM(&obj->std, PROP_INDEX);
return OBJ_PROP_NUM(&obj->std, PROP_INDEX);
}
/* {{{ classList TokenList
URL: https://dom.spec.whatwg.org/#dom-element-classlist
*/
zend_result dom_element_class_list_read(dom_object *obj, zval *retval)
{
zval *cached_token_list = dom_element_class_list_zval(obj);
if (Z_ISUNDEF_P(cached_token_list)) {
object_init_ex(cached_token_list, dom_token_list_class_entry);
dom_token_list_object *intern = php_dom_token_list_from_obj(Z_OBJ_P(cached_token_list));

View file

@ -99,6 +99,7 @@ static zend_object_handlers dom_modern_nodelist_object_handlers;
static zend_object_handlers dom_html_collection_object_handlers;
static zend_object_handlers dom_object_namespace_node_handlers;
static zend_object_handlers dom_modern_domimplementation_object_handlers;
static zend_object_handlers dom_modern_element_object_handlers;
static zend_object_handlers dom_token_list_object_handlers;
#ifdef LIBXML_XPATH_ENABLED
zend_object_handlers dom_xpath_object_handlers;
@ -662,6 +663,21 @@ static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */
}
/* }}} */
static zend_object *dom_modern_element_clone_obj(zend_object *zobject)
{
zend_object *clone = dom_objects_store_clone_obj(zobject);
/* The $classList property is unique per element, and cached due to its [[SameObject]] requirement.
* Remove it from the clone so the clone will get a fresh instance upon demand. */
zval *class_list = dom_element_class_list_zval(php_dom_obj_from_obj(clone));
if (!Z_ISUNDEF_P(class_list)) {
zval_ptr_dtor(class_list);
ZVAL_UNDEF(class_list);
}
return clone;
}
static zend_object *dom_object_namespace_node_clone_obj(zend_object *zobject)
{
dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(zobject);
@ -756,6 +772,9 @@ PHP_MINIT_FUNCTION(dom)
* one instance per parent object. */
dom_modern_domimplementation_object_handlers.clone_obj = NULL;
memcpy(&dom_modern_element_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
dom_modern_element_object_handlers.clone_obj = dom_modern_element_clone_obj;
memcpy(&dom_nnodemap_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
dom_nnodemap_object_handlers.free_obj = dom_nnodemap_objects_free_storage;
dom_nnodemap_object_handlers.read_dimension = dom_nodemap_read_dimension;
@ -1086,7 +1105,7 @@ PHP_MINIT_FUNCTION(dom)
dom_modern_element_class_entry = register_class_Dom_Element(dom_modern_node_class_entry, dom_modern_parentnode_class_entry, dom_modern_childnode_class_entry);
dom_modern_element_class_entry->create_object = dom_objects_new;
dom_modern_element_class_entry->default_object_handlers = &dom_object_handlers;
dom_modern_element_class_entry->default_object_handlers = &dom_modern_element_object_handlers;
zend_hash_init(&dom_modern_element_prop_handlers, 0, NULL, NULL, true);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL);
@ -1111,7 +1130,7 @@ PHP_MINIT_FUNCTION(dom)
dom_html_element_class_entry = register_class_Dom_HTMLElement(dom_modern_element_class_entry);
dom_html_element_class_entry->create_object = dom_objects_new;
dom_html_element_class_entry->default_object_handlers = &dom_object_handlers;
dom_html_element_class_entry->default_object_handlers = &dom_modern_element_object_handlers;
zend_hash_add_new_ptr(&classes, dom_html_element_class_entry->name, &dom_modern_element_prop_handlers);
dom_text_class_entry = register_class_DOMText(dom_characterdata_class_entry);

View file

@ -179,6 +179,7 @@ bool php_dom_create_nullable_object(xmlNodePtr obj, zval *return_value, dom_obje
xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive);
void dom_set_document_ref_pointers(xmlNodePtr node, php_libxml_ref_obj *document);
void dom_set_document_ref_pointers_attr(xmlAttrPtr attr, php_libxml_ref_obj *document);
zval *dom_element_class_list_zval(dom_object *obj);
typedef enum {
DOM_LOAD_STRING = 0,

View file

@ -0,0 +1,30 @@
--TEST--
GH-18744 (classList works not correctly if copy HTMLElement by clone keyword.)
--EXTENSIONS--
dom
--FILE--
<?php
$doc = \Dom\HTMLDocument::createEmpty();
$ele1 = $doc->createElement('div');
$ele1->classList->add('foo');
$ele2 = clone $ele1;
$ele2->classList->add('bar');
echo "Element1 class: " . $ele1->getAttribute('class');
echo "\n";
echo "Element2 class: " . $ele2->getAttribute('class');
echo "\n";
var_dump($ele1->classList !== $ele2->classList);
// These comparisons are not pointless: they're getters and should not create new objects
var_dump($ele1->classList === $ele1->classList);
var_dump($ele2->classList === $ele2->classList);
?>
--EXPECT--
Element1 class: foo
Element2 class: foo bar
bool(true)
bool(true)
bool(true)