diff options
Diffstat (limited to 'crates/ra_syntax/tests/data/parser/fuzz-failures')
-rw-r--r-- | crates/ra_syntax/tests/data/parser/fuzz-failures/0000.rs | 199 | ||||
-rw-r--r-- | crates/ra_syntax/tests/data/parser/fuzz-failures/0001.rs | 106 |
2 files changed, 305 insertions, 0 deletions
diff --git a/crates/ra_syntax/tests/data/parser/fuzz-failures/0000.rs b/crates/ra_syntax/tests/data/parser/fuzz-failures/0000.rs new file mode 100644 index 000000000..53c93d9e9 --- /dev/null +++ b/crates/ra_syntax/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/ra_syntax/tests/data/parser/fuzz-failures/0001.rs b/crates/ra_syntax/tests/data/parser/fuzz-failures/0001.rs new file mode 100644 index 000000000..099cc5f84 --- /dev/null +++ b/crates/ra_syntax/tests/data/parser/fuzz-failures/0001.rs | |||
@@ -0,0 +1,106 @@ | |||
1 | use ra_syntax::{ | ||
2 | File, TextRange, SyntaxNodeRef, TextUnit, | ||
3 | SyntaxKind::*, | ||
4 | algo::{find_leaf_at_offset, LeafAtOffset, find_covering_node, ancestors, Direction, siblings}, | ||
5 | }; | ||
6 | |||
7 | pub fn extend_selection(file: &File, range: TextRange) -> Option<TextRange> { | ||
8 | let syntax = file.syntax(); | ||
9 | extend(syntax.borrowed(), range) | ||
10 | } | ||
11 | |||
12 | pub(crate) fn extend(root: SyntaxNodeRef, range: TextRange) -> Option<TextRange> { | ||
13 | if range.is_empty() { | ||
14 | let offset = range.start(); | ||
15 | let mut leaves = find_leaf_at_offset(root, offset); | ||
16 | if leaves.clone().all(|it| it.kind() == WHITESPACE) { | ||
17 | return Some(extend_ws(root, leaves.next()?, offset)); | ||
18 | } | ||
19 | let leaf = match leaves { | ||
20 | LeafAtOffset::None => return None, | ||
21 | LeafAtOffset::Single(l) => l, | ||
22 | LeafAtOffset::Between(l, r) => pick_best(l, r), | ||
23 | }; | ||
24 | return Some(leaf.range()); | ||
25 | }; | ||
26 | let node = find_covering_node(root, range); | ||
27 | if node.kind() == COMMENT && range == node.range() { | ||
28 | if let Some(range) = extend_comments(node) { | ||
29 | return Some(range); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | match ancestors(node).skip_while(|n| n.range() == range).next() { | ||
34 | None => None, | ||
35 | Some(parent) => Some(parent.range()), | ||
36 | } | ||
37 | } | ||
38 | |||
39 | fn extend_ws(root: SyntaxNodeRef, ws: SyntaxNodeRef, offset: TextUnit) -> TextRange { | ||
40 | let ws_text = ws.leaf_text().unwrap(); | ||
41 | let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start(); | ||
42 | let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start(); | ||
43 | let ws_suffix = &ws_text.as_str()[suffix]; | ||
44 | let ws_prefix = &ws_text.as_str()[prefix]; | ||
45 | if ws_text.contains("\n") && !ws_suffix.contains("\n") { | ||
46 | if let Some(node) = ws.next_sibling() { | ||
47 | let start = match ws_prefix.rfind('\n') { | ||
48 | Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32), | ||
49 | None => node.range().start() | ||
50 | }; | ||
51 | let end = if root.text().char_at(node.range().end()) == Some('\n') { | ||
52 | node.range().end() + TextUnit::of_char('\n') | ||
53 | } else { | ||
54 | node.range().end() | ||
55 | }; | ||
56 | return TextRange::from_to(start, end); | ||
57 | } | ||
58 | } | ||
59 | ws.range() | ||
60 | } | ||
61 | |||
62 | fn pick_best<'a>(l: SyntaxNodeRef<'a>, r: Syntd[axNodeRef<'a>) -> SyntaxNodeRef<'a> { | ||
63 | return if priority(r) > priority(l) { r } else { l }; | ||
64 | fn priority(n: SyntaxNodeRef) -> usize { | ||
65 | match n.kind() { | ||
66 | WHITESPACE => 0, | ||
67 | IDENT | SELF_KW | SUPER_KW | CRATE_KW => 2, | ||
68 | _ => 1, | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | |||
73 | fn extend_comments(node: SyntaxNodeRef) -> Option<TextRange> { | ||
74 | let left = adj_com[ments(node, Direction::Backward); | ||
75 | let right = adj_comments(node, Direction::Forward); | ||
76 | if left != right { | ||
77 | Some(TextRange::from_to( | ||
78 | left.range().start(), | ||
79 | right.range().end(), | ||
80 | )) | ||
81 | } else { | ||
82 | None | ||
83 | } | ||
84 | } | ||
85 | |||
86 | fn adj_comments(node: SyntaxNodeRef, dir: Direction) -> SyntaxNodeRef { | ||
87 | let mut res = node; | ||
88 | for node in siblings(node, dir) { | ||
89 | match node.kind() { | ||
90 | COMMENT => res = node, | ||
91 | WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (), | ||
92 | _ => break | ||
93 | } | ||
94 | } | ||
95 | res | ||
96 | } | ||
97 | |||
98 | #[cfg(test)] | ||
99 | mod tests { | ||
100 | use super::*; | ||
101 | use test_utils::extract_offset; | ||
102 | |||
103 | fn do_check(before: &str, afters: &[&str]) { | ||
104 | let (cursor, before) = extract_offset(before); | ||
105 | let file = File::parse(&before); | ||
106 | let mut range = TextRange::of | ||