]> git.lizzy.rs Git - rust.git/blob - src/libfuzzer/fuzzer.rc
Remove 'Local Variable' comments
[rust.git] / src / libfuzzer / fuzzer.rc
1 // Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11
12 #[link(name = "fuzzer",
13        vers = "0.7-pre",
14        uuid = "d6418797-2736-4833-bd82-d3c684b7c1b0",
15        url = "https://github.com/mozilla/rust/tree/master/src/libfuzzer")];
16
17 #[comment = "The Rust fuzzer library"];
18 #[license = "MIT/ASL2"];
19 #[crate_type = "lib"];
20
21 #[allow(non_camel_case_types)];
22
23 extern mod std(vers = "0.7-pre");
24 extern mod syntax(vers = "0.7-pre");
25
26 use core::run;
27
28 use syntax::diagnostic;
29 use syntax::parse::token::ident_interner;
30 use syntax::parse::token;
31 use syntax::parse;
32 use syntax::print::pprust;
33 use syntax::{ast, fold, visit, codemap};
34
35 #[deriving(Eq)]
36 pub enum test_mode { tm_converge, tm_run, }
37
38 pub struct Context { mode: test_mode } // + rng
39
40 pub fn write_file(filename: &Path, content: &str) {
41     result::get(&io::file_writer(filename, ~[io::Create, io::Truncate]))
42                     .write_str(content);
43 }
44
45 pub fn contains(haystack: &str, needle: &str) -> bool {
46     str::contains(haystack, needle)
47 }
48
49 pub fn find_rust_files(files: &mut ~[Path], path: &Path) {
50     if path.filetype() == Some(~".rs") && !contains(path.to_str(), ~"utf8") {
51         // ignoring "utf8" tests because something is broken
52         files.push(path.clone());
53     } else if os::path_is_dir(path)
54         && !contains(path.to_str(), ~"compile-fail")
55         && !contains(path.to_str(), ~"build") {
56         for os::list_dir_path(path).each |p| {
57             find_rust_files(&mut *files, *p);
58         }
59     }
60 }
61
62
63 pub fn common_exprs() -> ~[@ast::expr] {
64     fn dse(e: ast::expr_) -> @ast::expr {
65         @ast::expr {
66             id: 0,
67             callee_id: -1,
68             node: e,
69             span: codemap::dummy_sp(),
70         }
71     }
72
73     fn dsl(l: ast::lit_) -> ast::lit {
74         codemap::spanned { node: l, span: codemap::dummy_sp() }
75     }
76
77     ~[dse(ast::expr_break(option::None)),
78      dse(ast::expr_again(option::None)),
79      dse(ast::expr_ret(option::None)),
80      dse(ast::expr_lit(@dsl(ast::lit_nil))),
81      dse(ast::expr_lit(@dsl(ast::lit_bool(false)))),
82      dse(ast::expr_lit(@dsl(ast::lit_bool(true)))),
83      dse(ast::expr_unary(ast::box(ast::m_imm),
84                          dse(ast::expr_lit(@dsl(ast::lit_bool(true)))))),
85      dse(ast::expr_unary(ast::uniq(ast::m_imm),
86                          dse(ast::expr_lit(@dsl(ast::lit_bool(true))))))
87     ]
88 }
89
90 pub fn safe_to_steal_expr(e: @ast::expr, tm: test_mode) -> bool {
91     safe_to_use_expr(e, tm)
92 }
93
94 pub fn safe_to_use_expr(e: @ast::expr, tm: test_mode) -> bool {
95     match tm {
96       tm_converge => {
97         match e.node {
98           // If the fuzzer moves a block-ending-in-semicolon into callee
99           // position, the pretty-printer can't preserve this even by
100           // parenthesizing!!  See email to marijn.
101           ast::expr_if(*) | ast::expr_block(*)
102           | ast::expr_match(*) | ast::expr_while(*)  => { false }
103
104           // https://github.com/mozilla/rust/issues/929
105           ast::expr_cast(*) | ast::expr_binary(*) | ast::expr_assign(*) |
106           ast::expr_assign_op(*) => { false }
107
108           ast::expr_ret(option::None) => { false }
109
110           // https://github.com/mozilla/rust/issues/953
111           //ast::expr_fail(option::Some(_)) => { false }
112
113           // https://github.com/mozilla/rust/issues/928
114           //ast::expr_cast(_, _) { false }
115
116           // https://github.com/mozilla/rust/issues/1458
117           ast::expr_call(_, _, _) => { false }
118
119           _ => { true }
120         }
121       }
122       tm_run => { true }
123     }
124 }
125
126 pub fn safe_to_steal_ty(t: @ast::Ty, tm: test_mode) -> bool {
127     // Restrictions happen to be the same.
128     safe_to_replace_ty(&t.node, tm)
129 }
130
131 // Not type-parameterized: https://github.com/mozilla/rust/issues/898 (FIXED)
132 pub fn stash_expr_if(c: @fn(@ast::expr, test_mode)->bool,
133                      es: @mut ~[@ast::expr],
134                      e: @ast::expr,
135                      tm: test_mode) {
136     if c(e, tm) {
137         *es += ~[e];
138     } else {
139         /* now my indices are wrong :( */
140     }
141 }
142
143 pub fn stash_ty_if(c: @fn(@ast::Ty, test_mode) -> bool,
144                    es: @mut ~[@ast::Ty],
145                    e: @ast::Ty,
146                    tm: test_mode) {
147     if c(e, tm) {
148         es.push(e);
149     } else {
150         /* now my indices are wrong :( */
151     }
152 }
153
154 pub struct StolenStuff {
155     exprs: ~[@ast::expr],
156     tys: ~[@ast::Ty]
157 }
158
159 pub fn steal(crate: @ast::crate, tm: test_mode) -> StolenStuff {
160     let exprs = @mut ~[];
161     let tys = @mut ~[];
162     let v = visit::mk_simple_visitor(@visit::SimpleVisitor {
163         visit_expr: |a| stash_expr_if(safe_to_steal_expr, exprs, a, tm),
164         visit_ty: |a| stash_ty_if(safe_to_steal_ty, tys, a, tm),
165         .. *visit::default_simple_visitor()
166     });
167     visit::visit_crate(crate, (), v);
168     StolenStuff {
169         exprs: (*exprs).clone(),
170         tys: (*tys).clone(),
171     }
172 }
173
174
175 pub fn safe_to_replace_expr(e: &ast::expr_, _tm: test_mode) -> bool {
176     match *e {
177         // https://github.com/mozilla/rust/issues/652
178         ast::expr_if(*) => false,
179         ast::expr_block(_) => false,
180
181         // expr_call is also missing a constraint
182         ast::expr_fn_block(*) => false,
183
184         _ => true,
185     }
186 }
187
188 pub fn safe_to_replace_ty(t: &ast::ty_, _tm: test_mode) -> bool {
189     match *t {
190       ast::ty_infer => { false } // always implicit, always top level
191       ast::ty_bot => { false }   // in source, can only appear
192                               // as the out type of a function
193       ast::ty_mac(_) => { false }
194       _ => { true }
195     }
196 }
197
198 // Replace the |i|th expr (in fold order) of |crate| with |newexpr|.
199 pub fn replace_expr_in_crate(crate: @ast::crate,
200                              i: uint,
201                              newexpr: @ast::expr,
202                              tm: test_mode)
203                              -> @ast::crate {
204     let j: @mut uint = @mut 0u;
205     fn fold_expr_rep(j_: @mut uint,
206                      i_: uint,
207                      newexpr_: &ast::expr_,
208                      original: &ast::expr_,
209                      fld: @fold::ast_fold,
210                      tm_: test_mode)
211                      -> ast::expr_ {
212         *j_ += 1;
213         if i_ + 1 == *j_ && safe_to_replace_expr(original, tm_) {
214             copy *newexpr_
215         } else {
216             fold::noop_fold_expr(original, fld)
217         }
218     }
219     let afp = @fold::AstFoldFns {
220         fold_expr: fold::wrap(|a,b| {
221             fold_expr_rep(j, i, &newexpr.node, a, b, tm)
222         }),
223         .. *fold::default_ast_fold()
224     };
225     let af = fold::make_fold(afp);
226     let crate2: @ast::crate = @af.fold_crate(crate);
227     crate2
228 }
229
230
231 // Replace the |i|th ty (in fold order) of |crate| with |newty|.
232 pub fn replace_ty_in_crate(crate: @ast::crate,
233                            i: uint,
234                            newty: @ast::Ty,
235                            tm: test_mode)
236                            -> @ast::crate {
237     let j: @mut uint = @mut 0u;
238     fn fold_ty_rep(j_: @mut uint,
239                    i_: uint,
240                    newty_: &ast::ty_,
241                    original: &ast::ty_,
242                    fld: @fold::ast_fold,
243                    tm_: test_mode)
244                    -> ast::ty_ {
245         *j_ += 1;
246         if i_ + 1 == *j_ && safe_to_replace_ty(original, tm_) {
247             copy *newty_
248         } else {
249             fold::noop_fold_ty(original, fld)
250         }
251     }
252     let afp = @fold::AstFoldFns {
253         fold_ty: fold::wrap(|a,b| fold_ty_rep(j, i, &newty.node, a, b, tm)),
254         .. *fold::default_ast_fold()
255     };
256     let af = fold::make_fold(afp);
257     let crate2: @ast::crate = @af.fold_crate(crate);
258     crate2
259 }
260
261 pub fn under(n: uint, it: &fn(uint)) {
262     let mut i: uint = 0u;
263     while i < n { it(i); i += 1u; }
264 }
265
266 pub fn as_str(f: @fn(+x: @io::Writer)) -> ~str {
267     io::with_str_writer(f)
268 }
269
270 pub fn check_variants_of_ast(crate: @ast::crate,
271                              codemap: @codemap::CodeMap,
272                              filename: &Path,
273                              cx: Context) {
274     let stolen = steal(crate, cx.mode);
275     let extra_exprs = do common_exprs().filtered |&a| {
276         safe_to_use_expr(a, cx.mode)
277     };
278     check_variants_T(crate,
279                      codemap,
280                      filename,
281                      ~"expr",
282                      extra_exprs + stolen.exprs,
283                      pprust::expr_to_str,
284                      replace_expr_in_crate,
285                      cx);
286     check_variants_T(crate,
287                      codemap,
288                      filename,
289                      ~"ty",
290                      stolen.tys,
291                      pprust::ty_to_str,
292                      replace_ty_in_crate,
293                      cx);
294 }
295
296 pub fn check_variants_T<T:Copy>(crate: @ast::crate,
297                                 codemap: @codemap::CodeMap,
298                                 filename: &Path,
299                                 thing_label: ~str,
300                                 things: &[T],
301                                 stringifier: @fn(T, @ident_interner) -> ~str,
302                                 replacer: @fn(@ast::crate,
303                                               uint,
304                                               T,
305                                               test_mode)
306                                               -> @ast::crate,
307                                 cx: Context) {
308     error!("%s contains %u %s objects", filename.to_str(),
309            things.len(), thing_label);
310
311     // Assuming we're not generating any token_trees
312     let intr = syntax::parse::token::mk_fake_ident_interner();
313
314     let L = things.len();
315
316     if L < 100 {
317         do under(uint::min(L, 20)) |i| {
318             error!("Replacing... #%?", uint::to_str(i));
319             let fname = str::from_slice(filename.to_str());
320             do under(uint::min(L, 30)) |j| {
321                 let fname = fname.to_str();
322                 error!("With... %?", stringifier(things[j], intr));
323                 let crate2 = replacer(crate, i, things[j], cx.mode);
324                 // It would be best to test the *crate* for stability, but
325                 // testing the string for stability is easier and ok for now.
326                 let handler = diagnostic::mk_handler(None);
327                 let str3 = do io::with_str_reader("") |rdr| {
328                     let fname = fname.to_str();
329                     let string = do as_str |a| {
330                         let span_handler =
331                             diagnostic::mk_span_handler(handler, codemap);
332                         pprust::print_crate(codemap,
333                                             intr,
334                                             span_handler,
335                                             crate2,
336                                             fname.to_str(),
337                                             rdr,
338                                             a,
339                                             pprust::no_ann(),
340                                             false)
341                     };
342                     @string
343                 };
344                 match cx.mode {
345                     tm_converge => check_roundtrip_convergence(str3, 1),
346                     tm_run => {
347                         let file_label = fmt!("rusttmp/%s_%s_%u_%u",
348                                               last_part(filename.to_str()),
349                                               thing_label,
350                                               i,
351                                               j);
352                         let safe_to_run = !(content_is_dangerous_to_run(*str3)
353                                             || has_raw_pointers(crate2));
354                         check_whole_compiler(*str3,
355                                              &Path(file_label),
356                                              safe_to_run);
357                     }
358                 }
359             }
360         }
361     }
362 }
363
364 pub fn last_part(filename: ~str) -> ~str {
365     let ix = str::rfind_char(filename, '/').get();
366     str::slice(filename, ix + 1u, str::len(filename) - 3u).to_owned()
367 }
368
369 pub enum happiness {
370     passed,
371     cleanly_rejected(~str),
372     known_bug(~str),
373     failed(~str),
374 }
375
376 // We'd find more bugs if we could take an AST here, but
377 // - that would find many "false positives" or unimportant bugs
378 // - that would be tricky, requiring use of tasks or serialization
379 //   or randomness.
380 // This seems to find plenty of bugs as it is :)
381 pub fn check_whole_compiler(code: &str,
382                             suggested_filename_prefix: &Path,
383                             allow_running: bool) {
384     let filename = &suggested_filename_prefix.with_filetype("rs");
385     write_file(filename, code);
386
387     let compile_result = check_compiling(filename);
388
389     let run_result = match (compile_result, allow_running) {
390       (passed, true) => { check_running(suggested_filename_prefix) }
391       (h, _) => { h }
392     };
393
394     match run_result {
395       passed | cleanly_rejected(_) | known_bug(_) => {
396         removeIfExists(suggested_filename_prefix);
397         removeIfExists(&suggested_filename_prefix.with_filetype("rs"));
398         removeDirIfExists(&suggested_filename_prefix.with_filetype("dSYM"));
399       }
400       failed(s) => {
401         error!("check_whole_compiler failure: %?", s);
402         error!("Saved as: %?", filename.to_str());
403       }
404     }
405 }
406
407 pub fn removeIfExists(filename: &Path) {
408     // So sketchy!
409     assert!(!contains(filename.to_str(), ~" "));
410     run::program_output(~"bash", ~[~"-c", ~"rm " + filename.to_str()]);
411 }
412
413 pub fn removeDirIfExists(filename: &Path) {
414     // So sketchy!
415     assert!(!contains(filename.to_str(), ~" "));
416     run::program_output(~"bash", ~[~"-c", ~"rm -r " + filename.to_str()]);
417 }
418
419 pub fn check_running(exe_filename: &Path) -> happiness {
420     let p = run::program_output(
421         ~"/Users/jruderman/scripts/timed_run_rust_program.py",
422         ~[exe_filename.to_str()]);
423     let comb = p.out + ~"\n" + p.err;
424     if str::len(comb) > 1u {
425         error!("comb comb comb: %?", comb);
426     }
427
428     if contains(comb, ~"Assertion failed:") {
429         failed(~"C++ assertion failure")
430     } else if contains(comb, ~"leaked memory in rust main loop") {
431         // might also use exit code 134
432         //failed("Leaked")
433         known_bug(~"https://github.com/mozilla/rust/issues/910")
434     } else if contains(comb, ~"src/rt/") {
435         failed(~"Mentioned src/rt/")
436     } else if contains(comb, ~"malloc") {
437         failed(~"Mentioned malloc")
438     } else {
439         match p.status {
440             0         => { passed }
441             100       => { cleanly_rejected(~"running: explicit fail") }
442             101 | 247 => { cleanly_rejected(~"running: timed out") }
443             245 | 246 | 138 | 252 => {
444               known_bug(~"https://github.com/mozilla/rust/issues/1466")
445             }
446             136 | 248 => {
447               known_bug(
448                   ~"SIGFPE - https://github.com/mozilla/rust/issues/944")
449             }
450             rc => {
451               failed(~"Rust program ran but exited with status " +
452                      int::to_str(rc))
453             }
454         }
455     }
456 }
457
458 pub fn check_compiling(filename: &Path) -> happiness {
459     let p = run::program_output(
460         ~"/Users/jruderman/code/rust/build/x86_64-apple-darwin/\
461          stage1/bin/rustc",
462         ~[filename.to_str()]);
463
464     //error!("Status: %d", p.status);
465     if p.status == 0 {
466         passed
467     } else if p.err != ~"" {
468         if contains(p.err, ~"error:") {
469             cleanly_rejected(~"rejected with span_error")
470         } else {
471             error!("Stderr: %?", p.err);
472             failed(~"Unfamiliar error message")
473         }
474     } else if contains(p.out, ~"Assertion") && contains(p.out, ~"failed") {
475         error!("Stdout: %?", p.out);
476         failed(~"Looks like an llvm assertion failure")
477     } else if contains(p.out, ~"internal compiler error unimplemented") {
478         known_bug(~"Something unimplemented")
479     } else if contains(p.out, ~"internal compiler error") {
480         error!("Stdout: %?", p.out);
481         failed(~"internal compiler error")
482
483     } else {
484         error!("%?", p.status);
485         error!("!Stdout: %?", p.out);
486         failed(~"What happened?")
487     }
488 }
489
490
491 pub fn parse_and_print(code: @~str) -> ~str {
492     let filename = Path("tmp.rs");
493     let sess = parse::new_parse_sess(option::None);
494     write_file(&filename, *code);
495     let crate = parse::parse_crate_from_source_str(filename.to_str(),
496                                                    code,
497                                                    ~[],
498                                                    sess);
499     do io::with_str_reader(*code) |rdr| {
500         let filename = filename.to_str();
501         do as_str |a| {
502             pprust::print_crate(sess.cm,
503                                 // Assuming there are no token_trees
504                                 token::mk_fake_ident_interner(),
505                                 copy sess.span_diagnostic,
506                                 crate,
507                                 filename.to_str(),
508                                 rdr,
509                                 a,
510                                 pprust::no_ann(),
511                                 false)
512         }
513     }
514 }
515
516 pub fn has_raw_pointers(c: @ast::crate) -> bool {
517     let has_rp = @mut false;
518     fn visit_ty(flag: @mut bool, t: @ast::Ty) {
519         match t.node {
520           ast::ty_ptr(_) => { *flag = true; }
521           _ => { }
522         }
523     }
524     let v =
525         visit::mk_simple_visitor(@visit::SimpleVisitor {
526             visit_ty: |a| visit_ty(has_rp, a),
527             .. *visit::default_simple_visitor()});
528     visit::visit_crate(c, (), v);
529     return *has_rp;
530 }
531
532 pub fn content_is_dangerous_to_run(code: &str) -> bool {
533     let dangerous_patterns =
534         ~[~"xfail-test",
535          ~"import",  // espeically fs, run
536          ~"extern",
537          ~"unsafe",
538          ~"log"];    // python --> rust pipe deadlock?
539
540     for dangerous_patterns.each |p| { if contains(code, *p) { return true; } }
541     return false;
542 }
543
544 pub fn content_is_dangerous_to_compile(code: &str) -> bool {
545     let dangerous_patterns =
546         ~[~"xfail-test"];
547
548     for dangerous_patterns.each |p| { if contains(code, *p) { return true; } }
549     return false;
550 }
551
552 pub fn content_might_not_converge(code: &str) -> bool {
553     let confusing_patterns =
554         ~[~"xfail-test",
555          ~"xfail-pretty",
556          ~"self",       // crazy rules enforced by parser not typechecker?
557          ~"spawn",      // precedence issues?
558          ~"bind",       // precedence issues?
559          ~" be ",       // don't want to replace its child with a non-call:
560                        // "Non-call expression in tail call"
561          ~"\n\n\n\n\n"  // https://github.com/mozilla/rust/issues/850
562         ];
563
564     for confusing_patterns.each |p| { if contains(code, *p) { return true; } }
565     return false;
566 }
567
568 pub fn file_might_not_converge(filename: &Path) -> bool {
569     let confusing_files = ~[
570       ~"expr-alt.rs", // pretty-printing "(a = b) = c"
571                      // vs "a = b = c" and wrapping
572       ~"block-arg-in-ternary.rs", // wrapping
573       ~"move-3-unique.rs", // 0 becomes (0), but both seem reasonable. wtf?
574       ~"move-3.rs"  // 0 becomes (0), but both seem reasonable. wtf?
575     ];
576
577
578     for confusing_files.each |f| {
579         if contains(filename.to_str(), *f) {
580             return true;
581         }
582     }
583
584     return false;
585 }
586
587 pub fn check_roundtrip_convergence(code: @~str, maxIters: uint) {
588     let mut i = 0u;
589     let mut newv = code;
590     let mut oldv = code;
591
592     while i < maxIters {
593         oldv = newv;
594         if content_might_not_converge(*oldv) { return; }
595         newv = @parse_and_print(oldv);
596         if oldv == newv { break; }
597         i += 1u;
598     }
599
600     if oldv == newv {
601         error!("Converged after %u iterations", i);
602     } else {
603         error!("Did not converge after %u iterations!", i);
604         write_file(&Path("round-trip-a.rs"), *oldv);
605         write_file(&Path("round-trip-b.rs"), *newv);
606         run::run_program(~"diff",
607                          ~[~"-w", ~"-u", ~"round-trip-a.rs",
608                           ~"round-trip-b.rs"]);
609         fail!(~"Mismatch");
610     }
611 }
612
613 pub fn check_convergence(files: &[Path]) {
614     error!("pp convergence tests: %u files", vec::len(files));
615     for files.each |file| {
616         if !file_might_not_converge(file) {
617             let s = @result::get(&io::read_whole_file_str(file));
618             if !content_might_not_converge(*s) {
619                 error!("pp converge: %s", file.to_str());
620                 // Change from 7u to 2u once
621                 // https://github.com/mozilla/rust/issues/850 is fixed
622                 check_roundtrip_convergence(s, 7u);
623             }
624         }
625     }
626 }
627
628 pub fn check_variants(files: &[Path], cx: Context) {
629     for files.each |file| {
630         if cx.mode == tm_converge &&
631             file_might_not_converge(file) {
632             error!("Skipping convergence test based on\
633                     file_might_not_converge");
634             loop;
635         }
636
637         let s = @result::get(&io::read_whole_file_str(file));
638         if contains(*s, ~"#") {
639             loop; // Macros are confusing
640         }
641         if cx.mode == tm_converge && content_might_not_converge(*s) {
642             loop;
643         }
644         if cx.mode == tm_run && content_is_dangerous_to_compile(*s) {
645             loop;
646         }
647
648         let file_str = file.to_str();
649
650         error!("check_variants: %?", file_str);
651         let sess = parse::new_parse_sess(None);
652         let crate = parse::parse_crate_from_source_str(file_str.to_str(),
653                                                        s,
654                                                        ~[],
655                                                        sess);
656         io::with_str_reader(*s, |rdr| {
657             let file_str = file_str.to_str();
658             error!("%s",
659                    as_str(|a| {
660                     pprust::print_crate(
661                         sess.cm,
662                         // Assuming no token_trees
663                         token::mk_fake_ident_interner(),
664                         copy sess.span_diagnostic,
665                         crate,
666                         file_str.to_str(),
667                         rdr,
668                         a,
669                         pprust::no_ann(),
670                         false)
671                     }))
672         });
673         check_variants_of_ast(crate, sess.cm, file, cx);
674     }
675 }
676
677 pub fn main() {
678     let args = os::args();
679     if vec::len(args) != 2u {
680         error!("usage: %s <testdir>", args[0]);
681         return;
682     }
683     let mut files = ~[];
684     let root = Path(args[1]);
685
686     find_rust_files(&mut files, &root);
687     error!("== check_convergence ==");
688     check_convergence(files);
689     error!("== check_variants: converge ==");
690     check_variants(files, Context { mode: tm_converge });
691     error!("== check_variants: run ==");
692     check_variants(files, Context { mode: tm_run });
693
694     error!("Fuzzer done");
695 }