diff options
Diffstat (limited to 'crates/ra_syntax')
-rw-r--r-- | crates/ra_syntax/fuzz/Cargo.toml | 5 | ||||
-rw-r--r-- | crates/ra_syntax/fuzz/fuzz_targets/reparse.rs | 9 | ||||
-rw-r--r-- | crates/ra_syntax/src/fuzz.rs | 42 | ||||
-rw-r--r-- | crates/ra_syntax/tests/test.rs | 9 |
4 files changed, 64 insertions, 1 deletions
diff --git a/crates/ra_syntax/fuzz/Cargo.toml b/crates/ra_syntax/fuzz/Cargo.toml index c54d12813..613ad2857 100644 --- a/crates/ra_syntax/fuzz/Cargo.toml +++ b/crates/ra_syntax/fuzz/Cargo.toml | |||
@@ -11,6 +11,7 @@ cargo-fuzz = true | |||
11 | 11 | ||
12 | [dependencies] | 12 | [dependencies] |
13 | ra_syntax = { path = ".." } | 13 | ra_syntax = { path = ".." } |
14 | ra_text_edit = { path = "../../ra_text_edit" } | ||
14 | libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } | 15 | libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } |
15 | 16 | ||
16 | # Prevent this from interfering with workspaces | 17 | # Prevent this from interfering with workspaces |
@@ -20,3 +21,7 @@ members = ["."] | |||
20 | [[bin]] | 21 | [[bin]] |
21 | name = "parser" | 22 | name = "parser" |
22 | path = "fuzz_targets/parser.rs" | 23 | path = "fuzz_targets/parser.rs" |
24 | |||
25 | [[bin]] | ||
26 | name = "reparse" | ||
27 | path = "fuzz_targets/reparse.rs" | ||
diff --git a/crates/ra_syntax/fuzz/fuzz_targets/reparse.rs b/crates/ra_syntax/fuzz/fuzz_targets/reparse.rs new file mode 100644 index 000000000..45524d4c1 --- /dev/null +++ b/crates/ra_syntax/fuzz/fuzz_targets/reparse.rs | |||
@@ -0,0 +1,9 @@ | |||
1 | #![no_main] | ||
2 | use libfuzzer_sys::fuzz_target; | ||
3 | use ra_syntax::fuzz::CheckReparse; | ||
4 | |||
5 | fuzz_target!(|data: &[u8]| { | ||
6 | if let Some(check) = CheckReparse::from_data(data) { | ||
7 | check.run(); | ||
8 | } | ||
9 | }); | ||
diff --git a/crates/ra_syntax/src/fuzz.rs b/crates/ra_syntax/src/fuzz.rs index 03f453a6e..efb080ac2 100644 --- a/crates/ra_syntax/src/fuzz.rs +++ b/crates/ra_syntax/src/fuzz.rs | |||
@@ -1,4 +1,6 @@ | |||
1 | use crate::{SourceFile, validation, AstNode}; | 1 | use crate::{SourceFile, validation, TextUnit, TextRange, AstNode}; |
2 | use ra_text_edit::AtomTextEdit; | ||
3 | use std::str::{self, FromStr}; | ||
2 | 4 | ||
3 | fn check_file_invariants(file: &SourceFile) { | 5 | fn check_file_invariants(file: &SourceFile) { |
4 | let root = file.syntax(); | 6 | let root = file.syntax(); |
@@ -10,3 +12,41 @@ pub fn check_parser(text: &str) { | |||
10 | let file = SourceFile::parse(text); | 12 | let file = SourceFile::parse(text); |
11 | check_file_invariants(&file); | 13 | check_file_invariants(&file); |
12 | } | 14 | } |
15 | |||
16 | #[derive(Debug, Clone)] | ||
17 | pub struct CheckReparse { | ||
18 | text: String, | ||
19 | edit: AtomTextEdit, | ||
20 | edited_text: String, | ||
21 | } | ||
22 | |||
23 | impl CheckReparse { | ||
24 | pub fn from_data(data: &[u8]) -> Option<Self> { | ||
25 | let data = str::from_utf8(data).ok()?; | ||
26 | let mut lines = data.lines(); | ||
27 | let delete_start = usize::from_str(lines.next()?).ok()?; | ||
28 | let delete_len = usize::from_str(lines.next()?).ok()?; | ||
29 | let insert = lines.next()?.to_string(); | ||
30 | let text = lines.collect::<Vec<_>>().join("\n"); | ||
31 | text.get(delete_start..delete_start.checked_add(delete_len)?)?; // make sure delete is a valid range | ||
32 | let delete = TextRange::offset_len( | ||
33 | TextUnit::from_usize(delete_start), | ||
34 | TextUnit::from_usize(delete_len), | ||
35 | ); | ||
36 | let edited_text = | ||
37 | format!("{}{}{}", &text[..delete_start], &insert, &text[delete_start + delete_len..]); | ||
38 | let edit = AtomTextEdit { delete, insert }; | ||
39 | Some(CheckReparse { text, edit, edited_text }) | ||
40 | } | ||
41 | |||
42 | pub fn run(&self) { | ||
43 | let file = SourceFile::parse(&self.text); | ||
44 | let new_file = file.reparse(&self.edit); | ||
45 | check_file_invariants(&new_file); | ||
46 | assert_eq!(&new_file.syntax().text().to_string(), &self.edited_text); | ||
47 | let full_reparse = SourceFile::parse(&self.edited_text); | ||
48 | for (a, b) in new_file.syntax().descendants().zip(full_reparse.syntax().descendants()) { | ||
49 | assert_eq!(a.kind(), b.kind(), "different syntax tree produced by a full reparse"); | ||
50 | } | ||
51 | } | ||
52 | } | ||
diff --git a/crates/ra_syntax/tests/test.rs b/crates/ra_syntax/tests/test.rs index 3de4a65af..537b01368 100644 --- a/crates/ra_syntax/tests/test.rs +++ b/crates/ra_syntax/tests/test.rs | |||
@@ -51,6 +51,15 @@ fn parser_fuzz_tests() { | |||
51 | } | 51 | } |
52 | } | 52 | } |
53 | 53 | ||
54 | #[test] | ||
55 | fn reparse_fuzz_tests() { | ||
56 | for (_, text) in collect_tests(&test_data_dir(), &["reparse/fuzz-failures"]) { | ||
57 | let check = fuzz::CheckReparse::from_data(text.as_bytes()).unwrap(); | ||
58 | println!("{:?}", check); | ||
59 | check.run(); | ||
60 | } | ||
61 | } | ||
62 | |||
54 | /// Test that Rust-analyzer can parse and validate the rust-analyser | 63 | /// Test that Rust-analyzer can parse and validate the rust-analyser |
55 | /// TODO: Use this as a benchmark | 64 | /// TODO: Use this as a benchmark |
56 | #[test] | 65 | #[test] |