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

  1. Strip NUL bytes at the ingestion boundary before parsing (html.replace('\0', "") or map to U+FFFD to match the parser's own recovery)
  2. Fix the producer: validate with String::from_utf8 / detect UTF-16 BOM before handing data to the parser
  3. If NUL bytes are expected and the U+FFFD recovery is acceptable, filter ErrorKind::UnexpectedNullCharacter out of take_errors()
  4. 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

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


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/035fec3bfccf6368. Report an issue: GitHub.