From 76a2ff46ed6d52bbad5fda07ee9a7dce42cb217c Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 11 Sep 2026 12:23:00 +0200 Subject: [PATCH] skip null nodes when a create callback fails xml5ever accepts processing instruction targets that the DOM rejects, like . createProcessingInstruction then fails and returns null, but xml5ever still appends the null node, and getNode segfaults. Make NodeOrText.node optional and skip failed nodes in the append callbacks. Parser.err is already set, so the XML parse returns a document. --- src/browser/parser/Parser.zig | 2 ++ src/browser/parser/html5ever.zig | 9 +++++++-- src/browser/tests/domparser.html | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/browser/parser/Parser.zig b/src/browser/parser/Parser.zig index e313a3dba..d1c87e9b5 100644 --- a/src/browser/parser/Parser.zig +++ b/src/browser/parser/Parser.zig @@ -728,6 +728,7 @@ fn _appendCallback(self: *Parser, parent: *Node, node_or_text: h5e.NodeOrText) ! try self.frame.appendNew(parent, child); }, .text => |txt| try self.appendTextChunk(parent, txt), + .failed => {}, } } @@ -801,6 +802,7 @@ fn _appendBeforeSiblingCallback(self: *Parser, sibling: *Node, node_or_text: h5e break :blk child; }, .text => |txt| try Frame.node_factory.createTextNode(self.frame, txt), + .failed => return, }; try self.frame.insertNodeRelative(parent, node, .{ .before = sibling }, .{}); } diff --git a/src/browser/parser/html5ever.zig b/src/browser/parser/html5ever.zig index 4d1a29776..4ccbe73b0 100644 --- a/src/browser/parser/html5ever.zig +++ b/src/browser/parser/html5ever.zig @@ -184,12 +184,15 @@ pub const AttributeIterator = extern struct { pub const NodeOrText = extern struct { tag: u8, - node: *anyopaque, + // Null for text. Also null for a node when its create callback failed: + // html5ever still appends the null ref it got back. + node: ?*anyopaque, text: StringSlice, pub fn toUnion(self: NodeOrText) Union { if (self.tag == 0) { - return .{ .node = @ptrCast(@alignCast(self.node)) }; + const node = self.node orelse return .failed; + return .{ .node = @ptrCast(@alignCast(node)) }; } return .{ .text = self.text.slice() }; } @@ -197,6 +200,8 @@ pub const NodeOrText = extern struct { const Union = union(enum) { node: *ParsedNode, text: []const u8, + // The create callback failed and already set Parser.err. + failed, }; }; diff --git a/src/browser/tests/domparser.html b/src/browser/tests/domparser.html index ec1b4c29a..4f328b625 100644 --- a/src/browser/tests/domparser.html +++ b/src/browser/tests/domparser.html @@ -506,6 +506,8 @@ '', // duplicate attribute '1', // undeclared prefix '', + '', // invalid processing instruction target + '', ]) { testing.expectEqual(bad + ' -> error', bad + (isError(p.parseFromString(bad, 'text/xml')) ? ' -> error' : ' -> ok')); }