swc-project/swc · warning · swc_html_parser::error::Error
Unexpected null character
Error message
Unexpected null character
What it means
Thrown by process_token_in_foreign_content (crates/swc_html_parser/src/parser/mod.rs:668) when a character token U+0000 NULL arrives while the adjusted current node is an SVG/MathML element. Per the HTML5 spec this is a parse error: the parser records UnexpectedNullCharacter, rewrites the token to U+FFFD REPLACEMENT CHARACTER, and keeps inserting text, so the document is still produced. A literal NUL in the input almost always signals upstream data corruption (truncated buffers, UTF-16 read as bytes, C-string terminators leaking in).
Source
Thrown at crates/swc_html_parser/src/parser/mod.rs:668
if self.is_fragment_case && self.open_elements_stack.items.len() == 1 {
return self.context_element.as_ref();
}
self.open_elements_stack.items.last()
}
fn process_token_in_foreign_content(
&mut self,
token_and_info: &mut TokenAndInfo,
) -> PResult<()> {
let TokenAndInfo { token, .. } = &token_and_info;
match token {
// A character token that is U+0000 NULL
//
// Parse error. Insert a U+FFFD REPLACEMENT CHARACTER character.
Token::Character { value, .. } if *value == '\x00' => {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::UnexpectedNullCharacter,
));
token_and_info.token = Token::Character {
value: '\u{FFFD}',
raw: Some(Raw::Atom(Atom::new(String::from('\x00')))),
};
println!("{:?}", token_and_info.token);
self.insert_character(token_and_info)?;
}
// A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF),
// U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
//
// Insert the token's character.
Token::Character {View on GitHub (pinned to 5176682b65)
Solutions
- Strip NUL bytes at the ingestion boundary before parsing (html.replace('\0', "") or map to U+FFFD to match the parser's own recovery)
- Fix the producer: validate with String::from_utf8 / detect UTF-16 BOM before handing data to the parser
- If NUL bytes are expected and the U+FFFD recovery is acceptable, filter ErrorKind::UnexpectedNullCharacter out of take_errors()
- In fuzz/property tests, discard inputs containing \x00 before asserting a clean parse
Example fix
// before
let fm = cm.new_source_file(file.into(), raw_with_nuls);
let doc = parse_file_as_document(&fm, config, &mut errors)?;
// after
let sanitized = raw_with_nuls.replace('\0', "\u{FFFD}");
let fm = cm.new_source_file(file.into(), sanitized);
let doc = parse_file_as_document(&fm, config, &mut errors)?; Defensive patterns
Strategy: validation
Validate before calling
fn contains_null_bytes(html: &str) -> bool {
html.as_bytes().contains(&0)
}
if contains_null_bytes(&html) {
html = html.replace('\0', "\u{FFFD}"); // same recovery the parser applies
} Try / catch
use swc_html_parser::error::ErrorKind;
let mut errors = Vec::new();
let doc = swc_html_parser::parse_file_as_document(&fm, config, &mut errors)?;
// Recoverable: the NUL was already rewritten to U+FFFD in the tree.
for err in &errors {
if matches!(err.kind(), ErrorKind::UnexpectedNullCharacter) {
log::warn!("null byte in foreign content; input source may be corrupt");
}
} Prevention
- Validate encoding (String::from_utf8) before parsing; NUL bytes usually mean UTF-16 or binary mishandling
- Strip or map NUL bytes once at the ingestion boundary, not per parse call
- Treat UnexpectedNullCharacter in test suites as an upstream-corruption signal, not a parser defect
When it happens
Trigger: Calling Parser::parse_document or parse_file_as_document on input where a literal \x00 byte sits inside an <svg>...</svg> or <math>...</math> subtree, e.g. `<svg>te\x00xt</svg>`. The foreign-content dispatcher (not the HTML in-body path) is what routes the character token into this arm, so the same byte in plain HTML body text reports the lexer's UnexpectedNullCharacter instead.
Common situations: Reading files with from_utf8_unchecked over truncated buffers, concatenating NUL-terminated C strings into HTML, decoding UTF-16 content with leftover NULs, or property/fuzz tests feeding arbitrary bytes. In this arm swc also contains a leftover debug println! that dumps the replacement token to stdout on every occurrence.
Related errors
- Stray doctype
- HTML start tag "{tag_name}" in a foreign namespace context
- End tag "{end_tag_name}" did not match the name of the curre
- Stray end tag "{tag_name}"
- failed to parse input as document fragment
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/035fec3bfccf6368.
Report an issue: GitHub.