Drop malformed attributes instead of trapping on them (fixes #392) - #400
Merged
Conversation
Attribute materialization used try\! on the throwing Attribute/BooleanAttribute initialisers, so any key that fails validation crashes the process fatally and uncatchably. The reproducible case: a vertical tab (0x0B) consumed as an attribute name survives parsing (the tokeniser's attribute-name delimiter set excludes 0x0B) but trims to empty in ByteSlice.trim() (which strips 0x09–0x0D + 0x20), reaching the initialiser as an empty key and trapping with IllegalArgumentException "String must not be empty" — typically during the hot-attribute query-index rebuild in select(). Replace the try\! sites in appendPending()/ensureMaterialized() and the single-attribute fallback with a throwing helper invoked via try?, dropping any attribute that fails validation. This matches jsoup, which silently drops malformed empty-key attributes, and makes materialization resilient to any validation failure rather than only the empty-key case. Adds a public-API regression test (boolean, valued, and meta selector cases). Fixes #392
lukeredpath
marked this pull request as ready for review
June 19, 2026 13:56
Collaborator
|
LGTM. Thanks so much!!! Very glad to have this solved properly |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #392. That issue reported a
try!crash in attribute materialization but was closed pending a reproducer — here is a minimal public-API one, plus a fix that removes thetry!traps entirely.Reproducer (stock 2.13.x)
A vertical tab (
0x0B) immediately after a quoted attribute value is the trigger.Root cause
Attributes are parsed lazily into a deferred store and only materialized on first access (
size(),getIgnoreCase(), the hot-attribute query-index rebuild duringselect(), …). A key can be non-empty as raw bytes (passing every parse-time!isEmptyguard) yet become empty afterByteSlice.trim():ByteSlice.trim()strips0x09…0x0D+0x20— this range includes0x0B— but the HTML5 tokeniser's attribute-name delimiter set (CharacterReader.attributeNameDelims) is{\t \n \r \f space / = > NUL " ' <}, which does not include0x0B, so0x0Bis read as a normal attribute-name character.A
0x0B-only name therefore survives parsing, then trims to empty when materialized.appendPending/ensureMaterializedfeed that empty key to the throwingAttribute/BooleanAttributeinitialisers viatry!, turning a recoverable validation error into a fatal, uncatchable trap.Fix
Rather than guarding the one known-bad input, this removes the
try!traps from attribute materialization altogether. A throwing helper (makeMaterializedAttribute) builds the attribute, and the three call sites (appendPending,ensureMaterialized, and the single-attribute fallback) invoke it viatry?, dropping any attribute that fails validation. This matches jsoup (which silently drops malformed empty-key attributes) and makes materialization resilient to any validation failure, not just the empty-key case — a third-party parser should nevertry!on uncontrolled input.Tests / verification
Tests/SwiftSoupTests/PublicEmptyAttributeKeyTest.swift— boolean, valued, and<meta>selector cases, public API only. Traps before the fix; after,selectreturns 0 matches (attribute dropped).BenchmarkProfileTest, release,SET=base,large, 500 iterations, same machine): baselinef474b1132586.93 ms vs fixedd33de7b32324.84 ms (−0.80%) — within run-to-run noise; the helper is@inline(__always), so codegen at the call sites is unchanged.