Skip to content

Commit 89cd93b

Browse files
authored
Workaround for libxml double-free in PHP 8.1.21 (#45)
There is a quirky behaviour of the libxml integration in PHP 8.1.21 that results in "DOMException: Not Found Error" or in some cases a SEGFAULT when nodes are appended to the DOM adjacent to nodes that are being / have been removed from the DOM. While we wait for our infrastructure (and especially mwcli (T388411)) to move away from the version that has the bug, add a workaround so that we can make progress with MEX development. Bug: T398821
1 parent 3e39150 commit 89cd93b

File tree

2 files changed

+35
-1
lines changed

2 files changed

+35
-1
lines changed

src/Component.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,31 @@ private function handleComponent( DOMElement $node, array $data ): bool {
139139
}
140140
$rendered = $this->app->renderComponentToDOM( $componentName, $componentData );
141141
// TODO use adoptNode() instead of importNode() in PHP 8.3+ (see php-src commit ed6df1f0ad)
142-
$node->replaceWith( $node->ownerDocument->importNode( $rendered, true ) );
142+
$importNode = $node->ownerDocument->importNode( $rendered, true );
143+
// TODO An issue in PHP 8.1.21's libxml integration causes a double-free if we replace a node
144+
// directly with itself. T398821
145+
if ( $node != $importNode ) {
146+
$node->replaceWith( $importNode );
147+
} else {
148+
// TODO To work around the double-free, we detach all the children of the parent node and
149+
// re-attach them in the correct sequence, replacing the target node with our newly-imported
150+
// node. Once `mwcli` has moved off this outdated version of PHP (T388411) we should be able
151+
// to remove this workaround. T398821
152+
$parent = $node->parentNode;
153+
$children = [];
154+
foreach ( iterator_to_array( $parent->childNodes ) as $child ) {
155+
if ( $child !== $node ) {
156+
$children[] = $child;
157+
} else {
158+
$children[] = $importNode;
159+
}
160+
$child->remove();
161+
}
162+
163+
foreach ( $children as $child ) {
164+
$parent->appendChild( $child );
165+
}
166+
}
143167
return true;
144168
}
145169

tests/php/AppTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ public function testNestedComponentObjectProp(): void {
6161
$this->assertSame( '<div><p>obj = { a: A, b: B }</p></div>', $result );
6262
}
6363

64+
public function testComponentSubstitutionPreservesOrder(): void {
65+
$app = new App( [] );
66+
$app->registerComponentTemplate( 'root', '<div><x-a></x-a><div><p>Following Text</p></div>' );
67+
$app->registerComponentTemplate( 'x-a', '<p>obj = { a: 1, b: 2 }</p>' );
68+
69+
$result = $app->renderComponent( 'root', [] );
70+
71+
$this->assertSame( '<div><p>obj = { a: 1, b: 2 }</p><div><p>Following Text</p></div></div>', $result );
72+
}
73+
6474
public function testComponentPropKebabCase(): void {
6575
$app = new App( [] );
6676
$app->registerComponentTemplate(

0 commit comments

Comments
 (0)