Skip to content

Fix negative SimpleXML offsets aliasing the first element - #23068

Open
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/sxe-negative-offset
Open

Fix negative SimpleXML offsets aliasing the first element#23068
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/sxe-negative-offset

Conversation

@iliaal

@iliaal iliaal commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

sxe_get_element_by_offset scans with nodendx <= offset, which is already false on the first iteration for a negative offset, so it returns the node it was given. $xml->item[-1] therefore reports isset() true and reads the first item. Assigning to it overwrites $xml->item[0]. Negative offsets now miss, and writing to one warns like an out-of-range positive offset instead of creating an element.

sxe_get_element_by_offset scanned with nodendx <= offset, so a negative
offset skipped the loop and returned the node it started from. Reads and
isset() reported the first element, and a write overwrote it. Negative
offsets now miss, and writing to one warns like an out-of-range positive
offset instead of creating a node.

Closes phpGH-23068
@devnexen

devnexen commented Aug 5, 2026

Copy link
Copy Markdown
Member

is a bug indeed, however can the following test being added ?

--TEST--
Integer offsets that cannot resolve must never alias or mutate a node
--EXTENSIONS--
simplexml
--FILE--
<?php
function fresh(): SimpleXMLElement {
    return simplexml_load_string('<r a="1" b="2"><item>a</item><item>b</item><item>c</item></r>');
}

function state(SimpleXMLElement $x): string {
    return trim(strstr($x->asXML(), '<r'));
}

echo '== element list ==', PHP_EOL;
$x = fresh();
var_dump(isset($x->item[-1]));
var_dump($x->item[-1]);
$x->item[-1] = 'Z';
echo state($x), PHP_EOL;
unset($x->item[-1]);
echo state($x), PHP_EOL;

echo '== single element (SXE_ITER_NONE) ==', PHP_EOL;
$x = fresh();
$n = $x->item[0];
var_dump(isset($n[-1]));
var_dump($n[-1]);
$n[-1] = 'Z';
echo state($x), PHP_EOL;
$n[5] = 'Y';
echo state($x), PHP_EOL;
unset($n[-1]);
echo state($x), PHP_EOL;

echo '== nested write ==', PHP_EOL;
$x = fresh();
try {
    $x->item[-1]->kid = 'K';
} catch (Throwable $e) {
    echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
}
echo state($x), PHP_EOL;

echo '== attributes ==', PHP_EOL;
$x = fresh();
$at = $x->attributes();
var_dump(isset($at[-1]));
var_dump($at[-1]);
$at[-1] = 'z';
echo state($x), PHP_EOL;
?>
--EXPECTF--
== element list ==
bool(false)
NULL

Warning: main(): Cannot add element item number -1 when only 3 such elements exist in %s on line %d
<r a="1" b="2"><item>a</item><item>b</item><item>c</item></r>
<r a="1" b="2"><item>a</item><item>b</item><item>c</item></r>
== single element (SXE_ITER_NONE) ==
bool(false)
NULL

Warning: main(): Cannot add element item number -1 when only 0 such %d
<r a="1" b="2"><item>a</item><item>b</item><item>c</item></r>

Warning: main(): Cannot add element item number 5 when only 0 such elements exist in %s on line %d
<r a="1" b="2"><item>a</item><item>b</item><item>c</item></r>
<r a="1" b="2"><item>a</item><item>b</item><item>c</item></r>
== nested write ==
ValueError: Cannot use a negative offset
<r a="1" b="2"><item>a</item><item>b</item><item>c</item></r>
== attributes ==
bool(false)
NULL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants