Skip to content

Report invalid UTF-8 byte in a comment as a parsing error#2983

Open
ksss wants to merge 1 commit into
ruby:masterfrom
ksss:ksss/error-on-invalid-byte-in-comment
Open

Report invalid UTF-8 byte in a comment as a parsing error#2983
ksss wants to merge 1 commit into
ruby:masterfrom
ksss:ksss/error-on-invalid-byte-in-comment

Conversation

@ksss
Copy link
Copy Markdown
Collaborator

@ksss ksss commented May 30, 2026

Follow-up to #2973.

Background

In #2973 @soutaro noted:

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...

#2973 stopped the lexer from hanging by advancing one byte past an invalid UTF-8 byte, but a byte that fell inside a comment was then silently swallowed — comments scan until newline/EOF, so # \xC2 parsed successfully.

Approach

The lexer is code-point based (YYCTYPE = unsigned int, fed by rbs_peek). The trick is to give an invalid byte a sentinel code point and exclude it from the comment rule's character class:

  • src/lexstate.c: an invalid byte (char_width == 0) now maps to U+FFFD instead of its raw value.
  • src/lexer.re: the comment rule becomes "#" (. \ [\x00�])*, so the comment stops at the invalid byte. The catch-all rule (*) then turns it into an ErrorToken, which the parser reports as a normal RBS::ParsingError.

U+FFFD is safe as a sentinel because valid input never produces it as a code point: valid multibyte characters map to the existing dummy code point (12523) and valid single bytes map to their own value (0..255). Even a genuine U+FFFD in the input is a 3-byte sequence that decodes to the dummy code point, not to 0xFFFD.

buf = RBS::Buffer.new(content: "# \xC2".force_encoding("UTF-8"), name: "x.rbs")
RBS::Parser._parse_signature(buf, 0, buf.content.bytesize)
# before: parses successfully (invalid byte swallowed by the comment)
# after:  raises RBS::ParsingError

src/lexer.c is regenerated with re2c 4.3 (matching CI). rake test: 958 tests, 0 failures.

Question on scope

This PR only touches the comment rule, which is the case @soutaro mentioned. The same "scan until a delimiter" shape also appears in string literals (dqstring/sqstring), annotations (%a{...} etc.), quoted identifiers (`...`), and inline comments (--). An invalid byte there is still swallowed today.

Should I extend the same sentinel exclusion to those rules in this PR, or keep this one focused on comments and handle the rest separately? Happy to go either way.


This PR description was written by Claude Code.

PR ruby#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.

1 participant