Skip to content

Commit a0d0b35

Browse files
authored
Merge pull request tree-sitter#2566 from amaanq/incl-range
fix: do not increment `current_included_range_index` past `included_range_count` in `__do_advance`
2 parents 8e718fc + a9c4965 commit a0d0b35

File tree

7 files changed

+135
-3
lines changed

7 files changed

+135
-3
lines changed

.github/workflows/sanitize.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ jobs:
3636
env:
3737
UBSAN_OPTIONS: halt_on_error=1
3838
CFLAGS: -fsanitize=undefined
39-
RUSTFLAGS: -lubsan
39+
RUSTFLAGS: ${{ env.RUSTFLAGS }} -lubsan
4040
run: cargo test -- --test-threads 1
4141

4242
- name: Run main tests with address sanitizer (ASAN)
4343
env:
4444
ASAN_OPTIONS: halt_on_error=1
4545
CFLAGS: -fsanitize=address
46-
RUSTFLAGS: -Zsanitizer=address
46+
RUSTFLAGS: ${{ env.RUSTFLAGS }} -Zsanitizer=address --cfg=sanitizing
4747
run: |
4848
rustup install nightly
4949
rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu

cli/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod helpers;
55
mod highlight_test;
66
mod language_test;
77
mod node_test;
8+
mod parser_hang_test;
89
mod parser_test;
910
mod pathological_test;
1011
mod query_test;

cli/src/tests/parser_hang_test.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// For some reasons `Command::spawn` doesn't work in CI env for many exotic arches.
2+
#![cfg(all(any(target_arch = "x86_64", target_arch = "x86"), not(sanitizing)))]
3+
4+
use crate::{
5+
generate::{generate_parser_for_grammar, load_grammar_file},
6+
tests::helpers::fixtures::{fixtures_dir, get_test_language},
7+
};
8+
use std::{
9+
env::VarError,
10+
process::{Command, Stdio},
11+
};
12+
use tree_sitter::Parser;
13+
14+
// The `sanitizing` cfg is required to don't run tests under specific sunitizer
15+
// because they don't work well with subprocesses _(it's an assumption)_.
16+
//
17+
// Bellow are two alternative examples of how to disable tests for some arches
18+
// if a way with excluding the whole mod from compilation would work well.
19+
//
20+
// #[cfg(all(any(target_arch = "x86_64", target_arch = "x86"), not(sanitizing)))]
21+
// #[cfg_attr(not(all(any(target_arch = "x86_64", target_arch = "x86"), not(sanitizing))), ignore)]
22+
//
23+
#[test]
24+
fn test_grammar_that_should_hang_and_not_segfault() {
25+
let parent_sleep_millis = 1000;
26+
let test_name = "test_grammar_that_should_hang_and_not_segfault";
27+
let test_var = "CARGO_HANG_TEST";
28+
29+
eprintln!(" {test_name}");
30+
31+
let tests_exec_path = std::env::args()
32+
.nth(0)
33+
.expect("Failed get get tests executable path");
34+
35+
match std::env::var(test_var) {
36+
Ok(v) if v == test_name => {
37+
eprintln!(" child process id {}", std::process::id());
38+
hang_test();
39+
}
40+
41+
Err(VarError::NotPresent) => {
42+
eprintln!(" parent process id {}", std::process::id());
43+
if true {
44+
let mut command = Command::new(tests_exec_path);
45+
command.arg(test_name).env(test_var, test_name);
46+
if std::env::args().any(|x| x == "--nocapture") {
47+
command.arg("--nocapture");
48+
} else {
49+
command.stdout(Stdio::null()).stderr(Stdio::null());
50+
}
51+
match command.spawn() {
52+
Ok(mut child) => {
53+
std::thread::sleep(std::time::Duration::from_millis(parent_sleep_millis));
54+
match child.try_wait() {
55+
Ok(Some(status)) if status.success() => {
56+
panic!("Child wasn't hang and exited successfully")
57+
}
58+
Ok(Some(status)) => panic!(
59+
"Child wasn't hang and exited with status code: {:?}",
60+
status.code()
61+
),
62+
_ => (),
63+
}
64+
if let Err(e) = child.kill() {
65+
eprintln!(
66+
"Failed to kill hang test sub process id: {}, error: {e}",
67+
child.id()
68+
);
69+
}
70+
}
71+
Err(e) => panic!("{e}"),
72+
}
73+
}
74+
}
75+
76+
Err(e) => panic!("Env var error: {e}"),
77+
_ => unreachable!(),
78+
}
79+
80+
fn hang_test() {
81+
let test_grammar_dir = fixtures_dir()
82+
.join("test_grammars")
83+
.join("get_col_should_hang_not_crash");
84+
85+
let grammar_json = load_grammar_file(&test_grammar_dir.join("grammar.js"), None).unwrap();
86+
let (parser_name, parser_code) =
87+
generate_parser_for_grammar(grammar_json.as_str()).unwrap();
88+
89+
let language =
90+
get_test_language(&parser_name, &parser_code, Some(test_grammar_dir.as_path()));
91+
92+
let mut parser = Parser::new();
93+
parser.set_language(language).unwrap();
94+
95+
let code_that_should_hang = "\nHello";
96+
97+
parser.parse(code_that_should_hang, None).unwrap();
98+
}
99+
}

lib/src/lexer.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ static void ts_lexer__do_advance(Lexer *self, bool skip) {
172172
self->current_position.bytes >= current_range->end_byte ||
173173
current_range->end_byte == current_range->start_byte
174174
) {
175-
self->current_included_range_index++;
175+
if (self->current_included_range_index < self->included_range_count) {
176+
self->current_included_range_index++;
177+
}
176178
if (self->current_included_range_index < self->included_range_count) {
177179
current_range++;
178180
self->current_position = (Length) {

test/fixtures/test_grammars/get_col_should_hang_not_crash/corpus.txt

Whitespace-only changes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module.exports = grammar({
2+
name: 'get_col_should_hang_not_crash',
3+
4+
externals: $ => [
5+
$.test,
6+
],
7+
8+
rules: {
9+
source_file: $ => seq(
10+
$.test
11+
),
12+
},
13+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <tree_sitter/parser.h>
2+
3+
unsigned tree_sitter_get_col_should_hang_not_crash_external_scanner_serialize() { return 0; }
4+
5+
void tree_sitter_get_col_should_hang_not_crash_external_scanner_deserialize() {}
6+
7+
void *tree_sitter_get_col_should_hang_not_crash_external_scanner_create() { return NULL; }
8+
9+
void tree_sitter_get_col_should_hang_not_crash_external_scanner_destroy() {}
10+
11+
bool tree_sitter_get_col_should_hang_not_crash_external_scanner_scan(void *payload, TSLexer *lexer,
12+
const bool *valid_symbols) {
13+
while (true) {
14+
lexer->advance(lexer, false);
15+
lexer->get_column(lexer);
16+
}
17+
}

0 commit comments

Comments
 (0)