diff --git a/Gemfile b/Gemfile index cf2982acd..d35624b74 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,7 @@ gem "net-smtp" gem 'csv' gem 'ostruct' gem 'pstore' +gem "timeout" group :minitest do gem "minitest" diff --git a/Gemfile.lock b/Gemfile.lock index ada025774..0a1f7d0a4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -231,6 +231,7 @@ DEPENDENCIES steep! tempfile test-unit + timeout BUNDLED WITH 4.0.1 diff --git a/ext/rbs_extension/main.c b/ext/rbs_extension/main.c index edb4bf304..bcbb73386 100644 --- a/ext/rbs_extension/main.c +++ b/ext/rbs_extension/main.c @@ -145,10 +145,17 @@ static VALUE parse_type_try(VALUE a) { return rbs_struct_to_ruby_value(ctx, type); } -static rbs_lexer_t *alloc_lexer_from_buffer(rbs_allocator_t *allocator, VALUE string, rb_encoding *encoding, int start_pos, int end_pos) { +static void validate_position_range(int start_pos, int end_pos) { if (start_pos < 0 || end_pos < 0) { rb_raise(rb_eArgError, "negative position range: %d...%d", start_pos, end_pos); } + if (start_pos > end_pos) { + rb_raise(rb_eArgError, "invalid position range: %d...%d", start_pos, end_pos); + } +} + +static rbs_lexer_t *alloc_lexer_from_buffer(rbs_allocator_t *allocator, VALUE string, rb_encoding *encoding, int start_pos, int end_pos) { + validate_position_range(start_pos, end_pos); const char *encoding_name = rb_enc_name(encoding); @@ -162,9 +169,7 @@ static rbs_lexer_t *alloc_lexer_from_buffer(rbs_allocator_t *allocator, VALUE st } static rbs_parser_t *alloc_parser_from_buffer(VALUE buffer, int start_pos, int end_pos) { - if (start_pos < 0 || end_pos < 0) { - rb_raise(rb_eArgError, "negative position range: %d...%d", start_pos, end_pos); - } + validate_position_range(start_pos, end_pos); VALUE string = rb_funcall(buffer, rb_intern("content"), 0); StringValue(string); diff --git a/test/rbs/parser_test.rb b/test/rbs/parser_test.rb index ea160cc9d..352a60d86 100644 --- a/test/rbs/parser_test.rb +++ b/test/rbs/parser_test.rb @@ -1,4 +1,5 @@ require "test_helper" +require "timeout" class RBS::ParserTest < Test::Unit::TestCase def buffer(source) @@ -1028,4 +1029,23 @@ class Foo[T < Integer] < Bar # Comment assert_equal [:tTRIVIA, "\n", 56...57], tokens.shift.then { |t| [t[0], t[1].source, t[1].range] } assert_equal [:pEOF, '', 57...57], tokens.shift.then { |t| [t[0], t[1].source, t[1].range] } end + + def test_invalid_position_range_raises + # Regression: start_pos > end_pos used to cause an infinite loop in the lexer. + Timeout.timeout(5) do + assert_raises(ArgumentError) do + RBS::Parser._parse_signature(buffer(""), 1, 0) + end + end + end + + def test_invalid_byte_range_in_parse_type_raises + # Regression: parse_type's byte_range: keyword reaches _parse_type directly, + # which used to hang on reversed ranges. + Timeout.timeout(5) do + assert_raises(ArgumentError) do + RBS::Parser.parse_type("", byte_range: 1..0) + end + end + end end