Skip to content

Fix lexer infinite loop / abort on invalid UTF-8 byte#2973

Merged
soutaro merged 1 commit into
ruby:masterfrom
ksss:ksss/fix-lexer-invalid-utf8
May 30, 2026
Merged

Fix lexer infinite loop / abort on invalid UTF-8 byte#2973
soutaro merged 1 commit into
ruby:masterfrom
ksss:ksss/fix-lexer-invalid-utf8

Conversation

@ksss

@ksss ksss commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

rbs_next_char left byte_len = 0 when the active encoding's char_width() rejected a byte. Depending on where the byte appeared, the lexer either:

  • looped forever inside a comment (GVL held, SIGINT ineffective), or
  • tripped RBS_ASSERT(current_character_bytes > 0, ...) in rbs_skip at top level and called exit(1).

This PR makes rbs_next_char advance one byte in that case so the lexer always makes progress; the invalid byte then flows into the usual parser error path.

Reproducer

buf = RBS::Buffer.new(content: "# \xC2".force_encoding("UTF-8"), name: "x.rbs")
RBS::Parser._parse_signature(buf, 0, buf.content.bytesize)
# hangs forever; only `kill -9` stops the process

Fix

src/lexstate.c (rbs_next_char): when encoding->char_width() returns 0, treat the byte as a 1-byte garbage character and advance one byte. The lexer's invariant "cursor advances by at least one byte per step" is now preserved on every code path. Valid UTF-8 input is unaffected because char_width() never returns 0 for valid sequences.


This PR description was written by Claude Code.

When the active encoding's `char_width` returned 0 for a byte,
`rbs_next_char` left `byte_len = 0`. The lexer then either looped
forever (when the byte was inside a comment) or tripped
`RBS_ASSERT(current_character_bytes > 0, ...)` in `rbs_skip` at top
level.

Treat such a byte as a 1-byte garbage character so the lexer always
advances at least one byte. The invalid byte then surfaces as a
regular parsing error through the existing error path.

Minimal reproducer that used to hang the host process indefinitely
with the GVL held:

  RBS::Parser._parse_signature(
    RBS::Buffer.new(content: "# \xC2".force_encoding("UTF-8"),
                    name: "x.rbs"),
    0, 3
  )

Found by fuzzing the parser entry points with random byte mutations
of the existing seed RBS files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ksss ksss added this to the RBS 4.1 milestone May 25, 2026
@ksss
ksss force-pushed the ksss/fix-lexer-invalid-utf8 branch from 4d292ab to e9612b4 Compare May 25, 2026 06:55

@soutaro soutaro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better if we can report an error when invalid token is included in the comment, but I cannot figure out how to do that right now...

@soutaro
soutaro merged commit 81a3502 into ruby:master May 30, 2026
24 checks passed
@ksss

ksss commented May 30, 2026

Copy link
Copy Markdown
Collaborator Author

@soutaro I had claude consider how to handle this.
#2983

soutaro pushed a commit that referenced this pull request Jul 18, 2026
PR #2973 fixed the lexer hang on an invalid UTF-8 byte by advancing one
byte, but a byte inside a comment was then silently swallowed (comments
scan until newline or EOF), so a malformed comment parsed successfully.

Map an invalid byte to a sentinel code point (U+FFFD) and exclude that
sentinel from the comment rule's character class. The comment now stops
at the invalid byte, which the catch-all rule turns into an ErrorToken,
so the parser reports a normal ParsingError. Valid input is unaffected:
valid multibyte characters map to the existing dummy code point and
valid single bytes to their own value, so U+FFFD is never produced by
valid input.

src/lexer.c is regenerated with re2c 4.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants