From a4ea4737ac13ec0e0a34a26624330010f51b27be Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Fri, 25 Aug 2023 18:28:27 -0400 Subject: [PATCH] fix: do not increment `current_included_range_index` past `included_range_count` in `__do_advance` --- cli/src/tests/mod.rs | 1 + cli/src/tests/parser_hang_test.rs | 61 +++++++++++++++++++ lib/src/lexer.c | 4 +- .../get_col_should_hang_not_crash/corpus.txt | 0 .../get_col_should_hang_not_crash/grammar.js | 13 ++++ .../get_col_should_hang_not_crash/scanner.c | 17 ++++++ 6 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 cli/src/tests/parser_hang_test.rs create mode 100644 test/fixtures/test_grammars/get_col_should_hang_not_crash/corpus.txt create mode 100644 test/fixtures/test_grammars/get_col_should_hang_not_crash/grammar.js create mode 100644 test/fixtures/test_grammars/get_col_should_hang_not_crash/scanner.c diff --git a/cli/src/tests/mod.rs b/cli/src/tests/mod.rs index e579209f..308fc2c5 100644 --- a/cli/src/tests/mod.rs +++ b/cli/src/tests/mod.rs @@ -5,6 +5,7 @@ mod helpers; mod highlight_test; mod language_test; mod node_test; +mod parser_hang_test; mod parser_test; mod pathological_test; mod query_test; diff --git a/cli/src/tests/parser_hang_test.rs b/cli/src/tests/parser_hang_test.rs new file mode 100644 index 00000000..cfa34957 --- /dev/null +++ b/cli/src/tests/parser_hang_test.rs @@ -0,0 +1,61 @@ +use pretty_assertions::assert_eq; +use tree_sitter::Parser; + +use crate::{ + generate::generate_parser_for_grammar, + tests::helpers::fixtures::{fixtures_dir, get_test_language}, +}; + +#[test] +fn test_grammar_that_should_hang_and_not_segfault() { + use std::sync::mpsc; + + let (tx, rx) = mpsc::channel(); + + std::thread::spawn(move || { + let (parser_name, parser_code) = generate_parser_for_grammar( + r#" + { + "name": "get_col_should_hang_not_crash", + "rules": { + "source_file": { + "type": "SEQ", + "members": [ { "type": "SYMBOL", "name": "test" } ] + } + }, + "extras": [ { "type": "PATTERN", "value": "\\s" } ], + "externals": [ { "type": "SYMBOL", "name": "test" } ] + } + "#, + ) + .unwrap(); + + let mut parser = Parser::new(); + parser + .set_language(get_test_language( + &parser_name, + &parser_code, + Some( + fixtures_dir() + .join("test_grammars") + .join("get_col_should_hang_not_crash") + .as_path(), + ), + )) + .unwrap(); + + let code_that_should_hang = "\nHello"; + + parser.parse(code_that_should_hang, None).unwrap(); + + // Won't be reached + let _ = tx.send(()); + }); + + // Ok signifies that it did not hang + // RecvTimeoutError::Disconnected signifies that the parser thread exited unexpectedly (crashed) + assert_eq!( + rx.recv_timeout(std::time::Duration::from_secs(5)), + Err(mpsc::RecvTimeoutError::Timeout) + ); +} diff --git a/lib/src/lexer.c b/lib/src/lexer.c index e32158b2..d108c04e 100644 --- a/lib/src/lexer.c +++ b/lib/src/lexer.c @@ -172,7 +172,9 @@ static void ts_lexer__do_advance(Lexer *self, bool skip) { self->current_position.bytes >= current_range->end_byte || current_range->end_byte == current_range->start_byte ) { - self->current_included_range_index++; + if (self->current_included_range_index < self->included_range_count) { + self->current_included_range_index++; + } if (self->current_included_range_index < self->included_range_count) { current_range++; self->current_position = (Length) { diff --git a/test/fixtures/test_grammars/get_col_should_hang_not_crash/corpus.txt b/test/fixtures/test_grammars/get_col_should_hang_not_crash/corpus.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/test_grammars/get_col_should_hang_not_crash/grammar.js b/test/fixtures/test_grammars/get_col_should_hang_not_crash/grammar.js new file mode 100644 index 00000000..83d57d2c --- /dev/null +++ b/test/fixtures/test_grammars/get_col_should_hang_not_crash/grammar.js @@ -0,0 +1,13 @@ +module.exports = grammar({ + name: 'get_col_should_hang_not_crash', + + externals: $ => [ + $.test, + ], + + rules: { + source_file: $ => seq( + $.test + ), + }, +}); diff --git a/test/fixtures/test_grammars/get_col_should_hang_not_crash/scanner.c b/test/fixtures/test_grammars/get_col_should_hang_not_crash/scanner.c new file mode 100644 index 00000000..d21ec6d4 --- /dev/null +++ b/test/fixtures/test_grammars/get_col_should_hang_not_crash/scanner.c @@ -0,0 +1,17 @@ +#include + +unsigned tree_sitter_get_col_should_hang_not_crash_external_scanner_serialize() { return 0; } + +void tree_sitter_get_col_should_hang_not_crash_external_scanner_deserialize() {} + +void *tree_sitter_get_col_should_hang_not_crash_external_scanner_create() { return NULL; } + +void tree_sitter_get_col_should_hang_not_crash_external_scanner_destroy() {} + +bool tree_sitter_get_col_should_hang_not_crash_external_scanner_scan(void *payload, TSLexer *lexer, + const bool *valid_symbols) { + while (true) { + lexer->advance(lexer, false); + lexer->get_column(lexer); + } +}