Skip to content

Stop the lexer reading past the end of a byte_range - #3040

Merged
soutaro merged 1 commit into
ruby:masterfrom
ksss:ksss/lexer-end-pos
Aug 18, 2026
Merged

Stop the lexer reading past the end of a byte_range#3040
soutaro merged 1 commit into
ruby:masterfrom
ksss:ksss/lexer-end-pos

Conversation

@ksss

@ksss ksss commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

rbs_next_char ends the input when byte_pos == end_pos. rbs_skip advances by a whole character, so a multibyte character starting before end_pos and ending after it steps over the boundary — equality never holds again, and the lexer reads to the end of the string.

The realistic way to hit this is passing a character offset where a byte offset is expected — the mistake #2945 fixed in parse_inline_*_annotation. "日本語" is 5 characters but 11 bytes, so offset 5 falls inside :

RBS::Parser.parse_type('"日本語" | Integer', byte_range: 0...5)
#=> RBS::Types::Union spanning the whole input, rather than an error

require_eof: true does not catch it, because by then the lexer really is at EOF. It takes a character straddling the boundary, so ASCII-only input never hits it. Now that #3082 allows non-ASCII identifiers, more inputs can reach this.

Fix

Compare with >= so stepping over the boundary still ends the input.

Test plan

  • rake test
  • New test_parse__byte_range_ending_mid_character, confirmed to fail with the == comparison restored.

@ksss
ksss force-pushed the ksss/lexer-end-pos branch 2 times, most recently from 7becc4e to 8df3c79 Compare July 28, 2026 11:06
@ksss
ksss force-pushed the ksss/lexer-end-pos branch 2 times, most recently from fc60d48 to 798d5b3 Compare August 13, 2026 09:52
`rbs_next_char` ends the input when `byte_pos == end_pos`. `rbs_skip`
advances by a whole character, so a multibyte character starting before
`end_pos` and ending after it steps over the boundary and equality never
holds again — the lexer then reads to the end of the string.

The realistic way to land inside a character is to pass a character
offset where a byte offset is expected, the mistake ruby#2945 fixed in
`parse_inline_*_annotation`. `"日本語"` is 5 characters but 11 bytes, so
offset 5 falls inside `本`:

    Parser.parse_type('"日本語" | Integer', byte_range: 0...5)
    #=> Types::Union spanning the whole input, rather than an error

`require_eof: true` does not catch it, because the lexer really is at
EOF by then. It needs a character to straddle the boundary, so
ASCII-only input never hits it.

Compare with `>=` so stepping over the boundary still ends the input.
@ksss
ksss force-pushed the ksss/lexer-end-pos branch from 798d5b3 to 7c1006a Compare August 13, 2026 09:55
@soutaro
soutaro added this pull request to the merge queue Aug 18, 2026
Merged via the queue into ruby:master with commit 3d8ffc4 Aug 18, 2026
25 checks passed
@ksss
ksss deleted the ksss/lexer-end-pos branch August 18, 2026 11:26
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