aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/libsyntax2/src/grammar/expressions/atom.rs6
-rw-r--r--crates/libsyntax2/src/grammar/patterns.rs12
-rw-r--r--crates/libsyntax2/tests/data/parser/fuzz-failures/0000.rs199
-rw-r--r--crates/libsyntax2/tests/test/main.rs18
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// }
324fn match_arm(p: &mut Parser) -> BlockLike { 324fn 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
10pub(super) fn pattern(p: &mut Parser) { 10pub(super) fn pattern(p: &mut Parser) {
11 if let Some(lhs) = atom_pat(p) { 11 pattern_r(p, PAT_RECOVERY_SET)
12}
13
14pub(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
29fn atom_pat(p: &mut Parser) -> Option<CompletedMarker> { 33fn 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)]
61pub struct AtomEdit {
62 pub delete: TextRange,
63 pub insert: String,
64}
65
66impl 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
80fn 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
96pub /*(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
103fn 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
117impl 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
131fn 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
148pub /*(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
155fn 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
175fn 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::{
12use test_utils::extract_range; 12use test_utils::extract_range;
13use libsyntax2::{ 13use 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]
35fn parser_fuzz_tests() {
36 for (_, text) in collect_tests(&["parser/fuzz-failures"]) {
37 check_fuzz_invariants(&text)
38 }
39}
40
41#[test]
35fn reparse_test() { 42fn 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
131fn collect_tests(paths: &[&str]) -> Vec<PathBuf> { 137fn 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