diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/libsyntax2/src/grammar/expressions/atom.rs | 6 | ||||
-rw-r--r-- | crates/libsyntax2/src/grammar/patterns.rs | 12 | ||||
-rw-r--r-- | crates/libsyntax2/tests/data/parser/fuzz-failures/0000.rs | 199 | ||||
-rw-r--r-- | crates/libsyntax2/tests/test/main.rs | 18 |
4 files changed, 223 insertions, 12 deletions
diff --git a/crates/libsyntax2/src/grammar/expressions/atom.rs b/crates/libsyntax2/src/grammar/expressions/atom.rs index fdb4718ba..8335c700f 100644 --- a/crates/libsyntax2/src/grammar/expressions/atom.rs +++ b/crates/libsyntax2/src/grammar/expressions/atom.rs | |||
@@ -323,11 +323,9 @@ fn match_arm_list(p: &mut Parser) { | |||
323 | // } | 323 | // } |
324 | fn match_arm(p: &mut Parser) -> BlockLike { | 324 | fn match_arm(p: &mut Parser) -> BlockLike { |
325 | let m = p.start(); | 325 | let m = p.start(); |
326 | loop { | 326 | patterns::pattern_r(p, TokenSet::EMPTY); |
327 | while p.eat(PIPE) { | ||
327 | patterns::pattern(p); | 328 | patterns::pattern(p); |
328 | if !p.eat(PIPE) { | ||
329 | break; | ||
330 | } | ||
331 | } | 329 | } |
332 | if p.eat(IF_KW) { | 330 | if p.eat(IF_KW) { |
333 | expr_no_struct(p); | 331 | expr_no_struct(p); |
diff --git a/crates/libsyntax2/src/grammar/patterns.rs b/crates/libsyntax2/src/grammar/patterns.rs index 6dd3ab2fa..29a55cb46 100644 --- a/crates/libsyntax2/src/grammar/patterns.rs +++ b/crates/libsyntax2/src/grammar/patterns.rs | |||
@@ -8,7 +8,11 @@ pub(super) const PATTERN_FIRST: TokenSet = | |||
8 | ]; | 8 | ]; |
9 | 9 | ||
10 | pub(super) fn pattern(p: &mut Parser) { | 10 | pub(super) fn pattern(p: &mut Parser) { |
11 | if let Some(lhs) = atom_pat(p) { | 11 | pattern_r(p, PAT_RECOVERY_SET) |
12 | } | ||
13 | |||
14 | pub(super) fn pattern_r(p: &mut Parser, recovery_set: TokenSet) { | ||
15 | if let Some(lhs) = atom_pat(p, recovery_set) { | ||
12 | // test range_pat | 16 | // test range_pat |
13 | // fn main() { | 17 | // fn main() { |
14 | // match 92 { 0 ... 100 => () } | 18 | // match 92 { 0 ... 100 => () } |
@@ -16,7 +20,7 @@ pub(super) fn pattern(p: &mut Parser) { | |||
16 | if p.at(DOTDOTDOT) { | 20 | if p.at(DOTDOTDOT) { |
17 | let m = lhs.precede(p); | 21 | let m = lhs.precede(p); |
18 | p.bump(); | 22 | p.bump(); |
19 | atom_pat(p); | 23 | atom_pat(p, recovery_set); |
20 | m.complete(p, RANGE_PAT); | 24 | m.complete(p, RANGE_PAT); |
21 | } | 25 | } |
22 | } | 26 | } |
@@ -26,7 +30,7 @@ const PAT_RECOVERY_SET: TokenSet = | |||
26 | token_set![LET_KW, IF_KW, WHILE_KW, LOOP_KW, MATCH_KW, R_PAREN, COMMA]; | 30 | token_set![LET_KW, IF_KW, WHILE_KW, LOOP_KW, MATCH_KW, R_PAREN, COMMA]; |
27 | 31 | ||
28 | 32 | ||
29 | fn atom_pat(p: &mut Parser) -> Option<CompletedMarker> { | 33 | fn atom_pat(p: &mut Parser, recovery_set: TokenSet) -> Option<CompletedMarker> { |
30 | let la0 = p.nth(0); | 34 | let la0 = p.nth(0); |
31 | let la1 = p.nth(1); | 35 | let la1 = p.nth(1); |
32 | if la0 == REF_KW || la0 == MUT_KW | 36 | if la0 == REF_KW || la0 == MUT_KW |
@@ -56,7 +60,7 @@ fn atom_pat(p: &mut Parser) -> Option<CompletedMarker> { | |||
56 | L_PAREN => tuple_pat(p), | 60 | L_PAREN => tuple_pat(p), |
57 | L_BRACK => slice_pat(p), | 61 | L_BRACK => slice_pat(p), |
58 | _ => { | 62 | _ => { |
59 | p.err_recover("expected pattern", PAT_RECOVERY_SET); | 63 | p.err_recover("expected pattern", recovery_set); |
60 | return None; | 64 | return None; |
61 | } | 65 | } |
62 | }; | 66 | }; |
diff --git a/crates/libsyntax2/tests/data/parser/fuzz-failures/0000.rs b/crates/libsyntax2/tests/data/parser/fuzz-failures/0000.rs new file mode 100644 index 000000000..53c93d9e9 --- /dev/null +++ b/crates/libsyntax2/tests/data/parser/fuzz-failures/0000.rs | |||
@@ -0,0 +1,199 @@ | |||
1 | //! An experimental implementation of [Rust RFC#2256 lrs); | ||
2 | let root = SyntaxNode::new_owned(root); | ||
3 | validate_block_structure(root.borrowed()); | ||
4 | File { root } | ||
5 | } | ||
6 | pub fn parse(text: &str) -> File { | ||
7 | let tokens = tokenize(&text); | ||
8 | let (green, errors) = parser_impl::parse_with::<yellow::GreenBuilder>( | ||
9 | text, &tokens, grammar::root, | ||
10 | ); | ||
11 | File::new(green, errors) | ||
12 | } | ||
13 | pub fn reparse(&self, edit: &AtomEdit) -> File { | ||
14 | self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit)) | ||
15 | } | ||
16 | pub fn incremental_reparse(&self, edit: &AtomEdit) -> Option<File> { | ||
17 | let (node, reparser) = find_reparsable_node(self.syntax(), edit.delete)?; | ||
18 | let text = replace_range( | ||
19 | node.text().to_string(), | ||
20 | edit.delete - node.range().start(), | ||
21 | &edit.insert, | ||
22 | ); | ||
23 | let tokens = tokenize(&text); | ||
24 | if !is_balanced(&tokens) { | ||
25 | return None; | ||
26 | } | ||
27 | let (green, new_errors) = parser_impl::parse_with::<yellow::GreenBuilder>( | ||
28 | &te2t, &tokens, reparser, | ||
29 | ); | ||
30 | let green_root = node.replace_with(green); | ||
31 | let errors = merge_errors(self.errors(), new_errors, node, edit); | ||
32 | Some(File::new(green_root, errors)) | ||
33 | } | ||
34 | fn full_reparse(&self, edit: &AtomEdit) -> File { | ||
35 | let text = replace_range(self.syntax().text().to_string(), edit.delete, &edit.insert); | ||
36 | File::parse(&text) | ||
37 | } | ||
38 | pub fn ast(&self) -> ast::Root { | ||
39 | ast::Root::cast(self.syntax()).unwrap() | ||
40 | } | ||
41 | pub fn syntax(&self) -> SyntaxNodeRef { | ||
42 | self.root.brroowed() | ||
43 | } | ||
44 | mp_tree(root), | ||
45 | ); | ||
46 | assert!( | ||
47 | node.next_sibling().is_none() && pair.prev_sibling().is_none(), | ||
48 | "\nfloating curlys at {:?}\nfile:\n{}\nerror:\n{}\n", | ||
49 | node, | ||
50 | root.text(), | ||
51 | node.text(), | ||
52 | ); | ||
53 | } | ||
54 | } | ||
55 | _ => (), | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | |||
60 | #[derive(Debug, Clone)] | ||
61 | pub struct AtomEdit { | ||
62 | pub delete: TextRange, | ||
63 | pub insert: String, | ||
64 | } | ||
65 | |||
66 | impl AtomEdit { | ||
67 | pub fn replace(range: TextRange, replace_with: String) -> AtomEdit { | ||
68 | AtomEdit { delete: range, insert: replace_with } | ||
69 | } | ||
70 | |||
71 | pub fn delete(range: TextRange) -> AtomEdit { | ||
72 | AtomEdit::replace(range, String::new()) | ||
73 | } | ||
74 | |||
75 | pub fn insert(offset: TextUnit, text: String) -> AtomEdit { | ||
76 | AtomEdit::replace(TextRange::offset_len(offset, 0.into()), text) | ||
77 | } | ||
78 | } | ||
79 | |||
80 | fn find_reparsable_node(node: SyntaxNodeRef, range: TextRange) -> Option<(SyntaxNodeRef, fn(&mut Parser))> { | ||
81 | let node = algo::find_covering_node(node, range); | ||
82 | return algo::ancestors(node) | ||
83 | .filter_map(|node| reparser(node).map(|r| (node, r))) | ||
84 | .next(); | ||
85 | |||
86 | fn reparser(node: SyntaxNodeRef) -> Option<fn(&mut Parser)> { | ||
87 | let res = match node.kind() { | ||
88 | BLOCK => grammar::block, | ||
89 | NAMED_FIELD_DEF_LIST => grammar::named_field_def_list, | ||
90 | _ => return None, | ||
91 | }; | ||
92 | Some(res) | ||
93 | } | ||
94 | } | ||
95 | |||
96 | pub /*(meh)*/ fn replace_range(mut text: String, range: TextRange, replace_with: &str) -> String { | ||
97 | let start = u32::from(range.start()) as usize; | ||
98 | let end = u32::from(range.end()) as usize; | ||
99 | text.replace_range(start..end, replace_with); | ||
100 | text | ||
101 | } | ||
102 | |||
103 | fn is_balanced(tokens: &[Token]) -> bool { | ||
104 | if tokens.len() == 0 | ||
105 | || tokens.first().unwrap().kind != L_CURLY | ||
106 | || tokens.last().unwrap().kind != R_CURLY { | ||
107 | return false | ||
108 | } | ||
109 | let mut balance = 0usize; | ||
110 | for t in tokens.iter() { | ||
111 | match t.kind { | ||
112 | L_CURLYt { | ||
113 | pub delete: TextRange, | ||
114 | pub insert: String, | ||
115 | } | ||
116 | |||
117 | impl AtomEdit { | ||
118 | pub fn replace(range: TextRange, replace_with: String) -> AtomEdit { | ||
119 | AtomEdit { delete: range, insert: replace_with } | ||
120 | } | ||
121 | |||
122 | pub fn delete(range: TextRange) -> AtomEdit { | ||
123 | AtomEdit::replace(range, String::new()) | ||
124 | } | ||
125 | |||
126 | pub fn insert(offset: TextUnit, text: String) -> AtomEdit { | ||
127 | AtomEdit::replace(TextRange::offset_len(offset, 0.into()), text) | ||
128 | } | ||
129 | } | ||
130 | |||
131 | fn find_reparsable_node(node: SyntaxNodeRef, range: TextRange) -> Option<(SyntaxNodeRef, fn(&mut Parser))> { | ||
132 | let node = algo::find_covering_node(node, range); | ||
133 | return algo::ancestors(node) | ||
134 | .filter_map(|node| reparser(node).map(|r| (node, r))) | ||
135 | .next(); | ||
136 | |||
137 | fn reparser(node: SyntaxNodeRef) -> Option<fn(&mut Parser)> { | ||
138 | let res = match node.kind() { | ||
139 | ; | ||
140 | let end = u32::from(range.end()) as usize; | ||
141 | text.replaT => grammar::named_field_def_list, | ||
142 | _ => return None, | ||
143 | }; | ||
144 | Some(res) | ||
145 | } | ||
146 | } | ||
147 | |||
148 | pub /*(meh)*/ fn replace_range(mut text: String, range: TextRange, replace_with: &str) -> String { | ||
149 | let start = u32::from(range.start()) as usize; | ||
150 | let end = u32::from(range.end()) as usize; | ||
151 | text.replace_range(start..end, replace_with); | ||
152 | text | ||
153 | } | ||
154 | |||
155 | fn is_balanced(tokens: &[Token]) -> bool { | ||
156 | if tokens.len() == 0 | ||
157 | || tokens.first().unwrap().kind != L_CURLY | ||
158 | || tokens.last().unwrap().kind != R_CURLY { | ||
159 | return false | ||
160 | } | ||
161 | let mut balance = 0usize; | ||
162 | for t in tokens.iter() { | ||
163 | match t.kind { | ||
164 | L_CURLY => balance += 1, | ||
165 | R_CURLY => balance = match balance.checked_sub(1) { | ||
166 | Some(b) => b, | ||
167 | None => return false, | ||
168 | }, | ||
169 | _ => (), | ||
170 | } | ||
171 | } | ||
172 | balance == 0 | ||
173 | } | ||
174 | |||
175 | fn merge_errors( | ||
176 | old_errors: Vec<SyntaxError>, | ||
177 | new_errors: Vec<SyntaxError>, | ||
178 | old_node: SyntaxNodeRef, | ||
179 | edit: &AtomEdit, | ||
180 | ) -> Vec<SyntaxError> { | ||
181 | let mut res = Vec::new(); | ||
182 | for e in old_errors { | ||
183 | if e.offset < old_node.range().start() { | ||
184 | res.push(e) | ||
185 | } else if e.offset > old_node.range().end() { | ||
186 | res.push(SyntaxError { | ||
187 | msg: e.msg, | ||
188 | offset: e.offset + TextUnit::of_str(&edit.insert) - edit.delete.len(), | ||
189 | }) | ||
190 | } | ||
191 | } | ||
192 | for e in new_errors { | ||
193 | res.push(SyntaxError { | ||
194 | msg: e.msg, | ||
195 | offset: e.offset + old_node.range().start(), | ||
196 | }) | ||
197 | } | ||
198 | res | ||
199 | } | ||
diff --git a/crates/libsyntax2/tests/test/main.rs b/crates/libsyntax2/tests/test/main.rs index 596f32216..014faa2c6 100644 --- a/crates/libsyntax2/tests/test/main.rs +++ b/crates/libsyntax2/tests/test/main.rs | |||
@@ -12,7 +12,7 @@ use std::{ | |||
12 | use test_utils::extract_range; | 12 | use test_utils::extract_range; |
13 | use libsyntax2::{ | 13 | use libsyntax2::{ |
14 | File, AtomEdit, | 14 | File, AtomEdit, |
15 | utils::dump_tree, | 15 | utils::{dump_tree, check_fuzz_invariants}, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | #[test] | 18 | #[test] |
@@ -32,6 +32,13 @@ fn parser_tests() { | |||
32 | } | 32 | } |
33 | 33 | ||
34 | #[test] | 34 | #[test] |
35 | fn parser_fuzz_tests() { | ||
36 | for (_, text) in collect_tests(&["parser/fuzz-failures"]) { | ||
37 | check_fuzz_invariants(&text) | ||
38 | } | ||
39 | } | ||
40 | |||
41 | #[test] | ||
35 | fn reparse_test() { | 42 | fn reparse_test() { |
36 | fn do_check(before: &str, replace_with: &str) { | 43 | fn do_check(before: &str, replace_with: &str) { |
37 | let (range, before) = extract_range(before); | 44 | let (range, before) = extract_range(before); |
@@ -88,8 +95,7 @@ pub fn dir_tests<F>(paths: &[&str], f: F) | |||
88 | where | 95 | where |
89 | F: Fn(&str) -> String, | 96 | F: Fn(&str) -> String, |
90 | { | 97 | { |
91 | for path in collect_tests(paths) { | 98 | for (path, input_code) in collect_tests(paths) { |
92 | let input_code = read_text(&path); | ||
93 | let parse_tree = f(&input_code); | 99 | let parse_tree = f(&input_code); |
94 | let path = path.with_extension("txt"); | 100 | let path = path.with_extension("txt"); |
95 | if !path.exists() { | 101 | if !path.exists() { |
@@ -128,13 +134,17 @@ fn assert_equal_text(expected: &str, actual: &str, path: &Path) { | |||
128 | assert_eq_text!(expected, actual, "file: {}", pretty_path.display()); | 134 | assert_eq_text!(expected, actual, "file: {}", pretty_path.display()); |
129 | } | 135 | } |
130 | 136 | ||
131 | fn collect_tests(paths: &[&str]) -> Vec<PathBuf> { | 137 | fn collect_tests(paths: &[&str]) -> Vec<(PathBuf, String)> { |
132 | paths | 138 | paths |
133 | .iter() | 139 | .iter() |
134 | .flat_map(|path| { | 140 | .flat_map(|path| { |
135 | let path = test_data_dir().join(path); | 141 | let path = test_data_dir().join(path); |
136 | test_from_dir(&path).into_iter() | 142 | test_from_dir(&path).into_iter() |
137 | }) | 143 | }) |
144 | .map(|path| { | ||
145 | let text = read_text(&path); | ||
146 | (path, text) | ||
147 | }) | ||
138 | .collect() | 148 | .collect() |
139 | } | 149 | } |
140 | 150 | ||