diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/libsyntax2/src/grammar/patterns.rs | 37 | ||||
-rw-r--r-- | crates/libsyntax2/tests/data/parser/fuzz-failures/0001.rs | 106 |
2 files changed, 122 insertions, 21 deletions
diff --git a/crates/libsyntax2/src/grammar/patterns.rs b/crates/libsyntax2/src/grammar/patterns.rs index 29a55cb46..420bae7a7 100644 --- a/crates/libsyntax2/src/grammar/patterns.rs +++ b/crates/libsyntax2/src/grammar/patterns.rs | |||
@@ -102,21 +102,7 @@ fn path_pat(p: &mut Parser) -> CompletedMarker { | |||
102 | fn tuple_pat_fields(p: &mut Parser) { | 102 | fn tuple_pat_fields(p: &mut Parser) { |
103 | assert!(p.at(L_PAREN)); | 103 | assert!(p.at(L_PAREN)); |
104 | p.bump(); | 104 | p.bump(); |
105 | while !p.at(EOF) && !p.at(R_PAREN) { | 105 | pat_list(p, R_PAREN); |
106 | match p.current() { | ||
107 | DOTDOT => p.bump(), | ||
108 | _ => { | ||
109 | if !p.at_ts(PATTERN_FIRST) { | ||
110 | p.error("expected a pattern"); | ||
111 | break; | ||
112 | } | ||
113 | pattern(p) | ||
114 | } | ||
115 | } | ||
116 | if !p.at(R_PAREN) { | ||
117 | p.expect(COMMA); | ||
118 | } | ||
119 | } | ||
120 | p.expect(R_PAREN); | 106 | p.expect(R_PAREN); |
121 | } | 107 | } |
122 | 108 | ||
@@ -194,18 +180,27 @@ fn slice_pat(p: &mut Parser) -> CompletedMarker { | |||
194 | assert!(p.at(L_BRACK)); | 180 | assert!(p.at(L_BRACK)); |
195 | let m = p.start(); | 181 | let m = p.start(); |
196 | p.bump(); | 182 | p.bump(); |
197 | while !p.at(EOF) && !p.at(R_BRACK) { | 183 | pat_list(p, R_BRACK); |
184 | p.expect(R_BRACK); | ||
185 | m.complete(p, SLICE_PAT) | ||
186 | } | ||
187 | |||
188 | fn pat_list(p: &mut Parser, ket: SyntaxKind) { | ||
189 | while !p.at(EOF) && !p.at(ket) { | ||
198 | match p.current() { | 190 | match p.current() { |
199 | DOTDOT => p.bump(), | 191 | DOTDOT => p.bump(), |
200 | _ => pattern(p), | 192 | _ => { |
193 | if !p.at_ts(PATTERN_FIRST) { | ||
194 | p.error("expected a pattern"); | ||
195 | break; | ||
196 | } | ||
197 | pattern(p) | ||
198 | }, | ||
201 | } | 199 | } |
202 | if !p.at(R_BRACK) { | 200 | if !p.at(ket) { |
203 | p.expect(COMMA); | 201 | p.expect(COMMA); |
204 | } | 202 | } |
205 | } | 203 | } |
206 | p.expect(R_BRACK); | ||
207 | |||
208 | m.complete(p, SLICE_PAT) | ||
209 | } | 204 | } |
210 | 205 | ||
211 | // test bind_pat | 206 | // test bind_pat |
diff --git a/crates/libsyntax2/tests/data/parser/fuzz-failures/0001.rs b/crates/libsyntax2/tests/data/parser/fuzz-failures/0001.rs new file mode 100644 index 000000000..cf98cf7a7 --- /dev/null +++ b/crates/libsyntax2/tests/data/parser/fuzz-failures/0001.rs | |||
@@ -0,0 +1,106 @@ | |||
1 | use libsyntax2::{ | ||
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 | ||