[php-src] Issue #22825: `DOMElement::setAttribute()` reaches `EMPTY_SWITCH_DEFAULT_CASE()` with DTD #FIXED default attributes

[email protected] (alexandre-daubois) Mon, 20 Jul 2026 11:41:46 +0000
Newsgroups php.bugs
Message-ID <[email protected]>
Issue: https://github.com/php/php-src/issues/22825
Author: alexandre-daubois

### Description

Originally reported by @ExPatch-LLC

Calling `DOMElement::setAttribute()` with an attribute name that resolves to a DTD #FIXED default attribute reaches an `EMPTY_SWITCH_DEFAULT_CASE()` in `ext/dom/element.c`.

The following code:

```php
<?php

$doc = new DOMDocument();
$doc->loadXML('<!DOCTYPE root [<!ATTLIST root A CDATA #FIXED "d">]><root/>');
$doc->documentElement->setAttribute('A', 'v');
```

Resulted in this output (debug build):

```
php: ext/dom/element.c:469: zim_DOMElement_setAttribute: Assertion `0' failed.
Aborted (core dumped)
```

But I expected this output instead: no crash, a new attribute is created on the element (as happens on 8.1-8.3).

On the legacy (non-spec-compliant) path, `dom_get_attribute_or_nsdecl()` -> `xmlHasNsProp()` falls back to the DTD and returns an `xmlAttribute` node (`XML_ATTRIBUTE_DECL`, type 16). The `switch (attr->type)` in `setAttribute()` only handles `XML_ATTRIBUTE_NODE` and `XML_NAMESPACE_DECL`, so a type-16 node hits the `EMPTY_SWITCH_DEFAULT_CASE()` branch.

This was introduced in 14b6c981c37. Before that, the default branch was a plain `default: break;`, which is why 8.1-8.3 are unaffected.

### PHP Version

```plain
PHP >= 8.4.0
```

### Operating System

_No response_