Skip to content

Commit a04555c

Browse files
Earlopainmatzbot
authored andcommitted
[ruby/prism] Be more defensive in the parser translator lexer
Generally I have been good about safely accessing the tokens but failed to properly guard against no tokens in places where it could theoretically happen through invalid syntax. I added a test case for one occurance, other changes are theoretical only. ruby/prism@4a3866af19
1 parent a677220 commit a04555c

2 files changed

Lines changed: 32 additions & 15 deletions

File tree

lib/prism/translation/parser/lexer.rb

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -277,20 +277,20 @@ def to_a
277277
when :tCOMMENT
278278
if token.type == :EMBDOC_BEGIN
279279

280-
while !((next_token = lexed[index][0]) && next_token.type == :EMBDOC_END) && (index < length - 1)
280+
while !((next_token = lexed[index]&.first) && next_token.type == :EMBDOC_END) && (index < length - 1)
281281
value += next_token.value
282282
index += 1
283283
end
284284

285285
value += next_token.value
286-
location = range(token.location.start_offset, lexed[index][0].location.end_offset)
286+
location = range(token.location.start_offset, next_token.location.end_offset)
287287
index += 1
288288
else
289289
is_at_eol = value.chomp!.nil?
290290
location = range(token.location.start_offset, token.location.end_offset + (is_at_eol ? 0 : -1))
291291

292-
prev_token = lexed[index - 2][0] if index - 2 >= 0
293-
next_token = lexed[index][0]
292+
prev_token, _ = lexed[index - 2] if index - 2 >= 0
293+
next_token, _ = lexed[index]
294294

295295
is_inline_comment = prev_token&.location&.start_line == token.location.start_line
296296
if is_inline_comment && !is_at_eol && !COMMENT_CONTINUATION_TYPES.include?(next_token&.type)
@@ -309,7 +309,7 @@ def to_a
309309
end
310310
end
311311
when :tNL
312-
next_token = next_token = lexed[index][0]
312+
next_token, _ = lexed[index]
313313
# Newlines after comments are emitted out of order.
314314
if next_token&.type == :COMMENT
315315
comment_newline_location = location
@@ -346,8 +346,8 @@ def to_a
346346
location = range(token.location.start_offset, token.location.start_offset + percent_array_leading_whitespace(value))
347347
value = nil
348348
when :tSTRING_BEG
349-
next_token = lexed[index][0]
350-
next_next_token = lexed[index + 1][0]
349+
next_token, _ = lexed[index]
350+
next_next_token, _ = lexed[index + 1]
351351
basic_quotes = value == '"' || value == "'"
352352

353353
if basic_quotes && next_token&.type == :STRING_END
@@ -415,7 +415,8 @@ def to_a
415415
while token.type == :STRING_CONTENT
416416
current_length += token.value.bytesize
417417
# Heredoc interpolation can have multiple STRING_CONTENT nodes on the same line.
418-
is_first_token_on_line = lexed[index - 1] && token.location.start_line != lexed[index - 2][0].location&.start_line
418+
prev_token, _ = lexed[index - 2] if index - 2 >= 0
419+
is_first_token_on_line = prev_token && token.location.start_line != prev_token.location.start_line
419420
# The parser gem only removes indentation when the heredoc is not nested
420421
not_nested = heredoc_stack.size == 1
421422
if is_percent_array
@@ -434,7 +435,7 @@ def to_a
434435
tokens << [:tSTRING_CONTENT, [current_string, range(start_offset, start_offset + current_length)]]
435436
break
436437
end
437-
token = lexed[index][0]
438+
token, _ = lexed[index]
438439
index += 1
439440
end
440441
else
@@ -489,7 +490,7 @@ def to_a
489490
end
490491

491492
if percent_array?(quote_stack.pop)
492-
prev_token = lexed[index - 2][0] if index - 2 >= 0
493+
prev_token, _ = lexed[index - 2] if index - 2 >= 0
493494
empty = %i[PERCENT_LOWER_I PERCENT_LOWER_W PERCENT_UPPER_I PERCENT_UPPER_W].include?(prev_token&.type)
494495
ends_with_whitespace = prev_token&.type == :WORDS_SEP
495496
# parser always emits a space token after content in a percent array, even if no actual whitespace is present.
@@ -498,7 +499,7 @@ def to_a
498499
end
499500
end
500501
when :tSYMBEG
501-
if (next_token = lexed[index][0]) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END
502+
if (next_token = lexed[index]&.first) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END
502503
next_location = token.location.join(next_token.location)
503504
type = :tSYMBOL
504505
value = next_token.value
@@ -513,13 +514,13 @@ def to_a
513514
type = :tIDENTIFIER
514515
end
515516
when :tXSTRING_BEG
516-
if (next_token = lexed[index][0]) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type)
517+
if (next_token = lexed[index]&.first) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type)
517518
# self.`()
518519
type = :tBACK_REF2
519520
end
520521
quote_stack.push(value)
521522
when :tSYMBOLS_BEG, :tQSYMBOLS_BEG, :tWORDS_BEG, :tQWORDS_BEG
522-
if (next_token = lexed[index][0]) && next_token.type == :WORDS_SEP
523+
if (next_token = lexed[index]&.first) && next_token.type == :WORDS_SEP
523524
index += 1
524525
end
525526

@@ -595,9 +596,9 @@ def calculate_heredoc_whitespace(heredoc_token_index)
595596
previous_line = -1
596597
result = Float::MAX
597598

598-
while (lexed[next_token_index] && next_token = lexed[next_token_index][0])
599+
while (next_token = lexed[next_token_index]&.first)
599600
next_token_index += 1
600-
next_next_token = lexed[next_token_index] && lexed[next_token_index][0]
601+
next_next_token, _ = lexed[next_token_index]
601602
first_token_on_line = next_token.location.start_column == 0
602603

603604
# String content inside nested heredocs and interpolation is ignored

test/prism/ruby/parser_test.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,22 @@ def test_current_parser_for_current_ruby
163163
end
164164
end
165165

166+
def test_invalid_syntax
167+
code = <<~RUBY
168+
foo do
169+
case bar
170+
when
171+
end
172+
end
173+
RUBY
174+
buffer = Parser::Source::Buffer.new("(string)")
175+
buffer.source = code
176+
177+
parser = Prism::Translation::Parser33.new
178+
parser.diagnostics.all_errors_are_fatal = true
179+
assert_raise(Parser::SyntaxError) { parser.tokenize(buffer) }
180+
end
181+
166182
def test_it_block_parameter_syntax
167183
it_fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures/it.txt")
168184

0 commit comments

Comments
 (0)