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.
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.
12 #[link(name = "fuzzer",
14 uuid = "d6418797-2736-4833-bd82-d3c684b7c1b0",
15 url = "https://github.com/mozilla/rust/tree/master/src/libfuzzer")];
17 #[comment = "The Rust fuzzer library"];
18 #[license = "MIT/ASL2"];
19 #[crate_type = "lib"];
21 #[allow(non_camel_case_types)];
23 extern mod std(vers = "0.7-pre");
24 extern mod syntax(vers = "0.7-pre");
28 use syntax::diagnostic;
29 use syntax::parse::token::ident_interner;
30 use syntax::parse::token;
32 use syntax::print::pprust;
33 use syntax::{ast, fold, visit, codemap};
36 pub enum test_mode { tm_converge, tm_run, }
38 pub struct Context { mode: test_mode } // + rng
40 pub fn write_file(filename: &Path, content: &str) {
41 result::get(&io::file_writer(filename, ~[io::Create, io::Truncate]))
45 pub fn contains(haystack: &str, needle: &str) -> bool {
46 str::contains(haystack, needle)
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);
63 pub fn common_exprs() -> ~[@ast::expr] {
64 fn dse(e: ast::expr_) -> @ast::expr {
69 span: codemap::dummy_sp(),
73 fn dsl(l: ast::lit_) -> ast::lit {
74 codemap::spanned { node: l, span: codemap::dummy_sp() }
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))))))
90 pub fn safe_to_steal_expr(e: @ast::expr, tm: test_mode) -> bool {
91 safe_to_use_expr(e, tm)
94 pub fn safe_to_use_expr(e: @ast::expr, tm: test_mode) -> bool {
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 }
104 // https://github.com/mozilla/rust/issues/929
105 ast::expr_cast(*) | ast::expr_binary(*) | ast::expr_assign(*) |
106 ast::expr_assign_op(*) => { false }
108 ast::expr_ret(option::None) => { false }
110 // https://github.com/mozilla/rust/issues/953
111 //ast::expr_fail(option::Some(_)) => { false }
113 // https://github.com/mozilla/rust/issues/928
114 //ast::expr_cast(_, _) { false }
116 // https://github.com/mozilla/rust/issues/1458
117 ast::expr_call(_, _, _) => { false }
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)
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],
139 /* now my indices are wrong :( */
143 pub fn stash_ty_if(c: @fn(@ast::Ty, test_mode) -> bool,
144 es: @mut ~[@ast::Ty],
150 /* now my indices are wrong :( */
154 pub struct StolenStuff {
155 exprs: ~[@ast::expr],
159 pub fn steal(crate: @ast::crate, tm: test_mode) -> StolenStuff {
160 let exprs = @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()
167 visit::visit_crate(crate, (), v);
169 exprs: (*exprs).clone(),
175 pub fn safe_to_replace_expr(e: &ast::expr_, _tm: test_mode) -> bool {
177 // https://github.com/mozilla/rust/issues/652
178 ast::expr_if(*) => false,
179 ast::expr_block(_) => false,
181 // expr_call is also missing a constraint
182 ast::expr_fn_block(*) => false,
188 pub fn safe_to_replace_ty(t: &ast::ty_, _tm: test_mode) -> bool {
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 }
198 // Replace the |i|th expr (in fold order) of |crate| with |newexpr|.
199 pub fn replace_expr_in_crate(crate: @ast::crate,
204 let j: @mut uint = @mut 0u;
205 fn fold_expr_rep(j_: @mut uint,
207 newexpr_: &ast::expr_,
208 original: &ast::expr_,
209 fld: @fold::ast_fold,
213 if i_ + 1 == *j_ && safe_to_replace_expr(original, tm_) {
216 fold::noop_fold_expr(original, fld)
219 let afp = @fold::AstFoldFns {
220 fold_expr: fold::wrap(|a,b| {
221 fold_expr_rep(j, i, &newexpr.node, a, b, tm)
223 .. *fold::default_ast_fold()
225 let af = fold::make_fold(afp);
226 let crate2: @ast::crate = @af.fold_crate(crate);
231 // Replace the |i|th ty (in fold order) of |crate| with |newty|.
232 pub fn replace_ty_in_crate(crate: @ast::crate,
237 let j: @mut uint = @mut 0u;
238 fn fold_ty_rep(j_: @mut uint,
242 fld: @fold::ast_fold,
246 if i_ + 1 == *j_ && safe_to_replace_ty(original, tm_) {
249 fold::noop_fold_ty(original, fld)
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()
256 let af = fold::make_fold(afp);
257 let crate2: @ast::crate = @af.fold_crate(crate);
261 pub fn under(n: uint, it: &fn(uint)) {
262 let mut i: uint = 0u;
263 while i < n { it(i); i += 1u; }
266 pub fn as_str(f: @fn(+x: @io::Writer)) -> ~str {
267 io::with_str_writer(f)
270 pub fn check_variants_of_ast(crate: @ast::crate,
271 codemap: @codemap::CodeMap,
274 let stolen = steal(crate, cx.mode);
275 let extra_exprs = do common_exprs().filtered |&a| {
276 safe_to_use_expr(a, cx.mode)
278 check_variants_T(crate,
282 extra_exprs + stolen.exprs,
284 replace_expr_in_crate,
286 check_variants_T(crate,
296 pub fn check_variants_T<T:Copy>(crate: @ast::crate,
297 codemap: @codemap::CodeMap,
301 stringifier: @fn(T, @ident_interner) -> ~str,
302 replacer: @fn(@ast::crate,
308 error!("%s contains %u %s objects", filename.to_str(),
309 things.len(), thing_label);
311 // Assuming we're not generating any token_trees
312 let intr = syntax::parse::token::mk_fake_ident_interner();
314 let L = things.len();
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| {
331 diagnostic::mk_span_handler(handler, codemap);
332 pprust::print_crate(codemap,
345 tm_converge => check_roundtrip_convergence(str3, 1),
347 let file_label = fmt!("rusttmp/%s_%s_%u_%u",
348 last_part(filename.to_str()),
352 let safe_to_run = !(content_is_dangerous_to_run(*str3)
353 || has_raw_pointers(crate2));
354 check_whole_compiler(*str3,
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()
371 cleanly_rejected(~str),
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
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);
387 let compile_result = check_compiling(filename);
389 let run_result = match (compile_result, allow_running) {
390 (passed, true) => { check_running(suggested_filename_prefix) }
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"));
401 error!("check_whole_compiler failure: %?", s);
402 error!("Saved as: %?", filename.to_str());
407 pub fn removeIfExists(filename: &Path) {
409 assert!(!contains(filename.to_str(), ~" "));
410 run::program_output(~"bash", ~[~"-c", ~"rm " + filename.to_str()]);
413 pub fn removeDirIfExists(filename: &Path) {
415 assert!(!contains(filename.to_str(), ~" "));
416 run::program_output(~"bash", ~[~"-c", ~"rm -r " + filename.to_str()]);
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);
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
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")
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")
448 ~"SIGFPE - https://github.com/mozilla/rust/issues/944")
451 failed(~"Rust program ran but exited with status " +
458 pub fn check_compiling(filename: &Path) -> happiness {
459 let p = run::program_output(
460 ~"/Users/jruderman/code/rust/build/x86_64-apple-darwin/\
462 ~[filename.to_str()]);
464 //error!("Status: %d", p.status);
467 } else if p.err != ~"" {
468 if contains(p.err, ~"error:") {
469 cleanly_rejected(~"rejected with span_error")
471 error!("Stderr: %?", p.err);
472 failed(~"Unfamiliar error message")
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")
484 error!("%?", p.status);
485 error!("!Stdout: %?", p.out);
486 failed(~"What happened?")
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(),
499 do io::with_str_reader(*code) |rdr| {
500 let filename = filename.to_str();
502 pprust::print_crate(sess.cm,
503 // Assuming there are no token_trees
504 token::mk_fake_ident_interner(),
505 copy sess.span_diagnostic,
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) {
520 ast::ty_ptr(_) => { *flag = true; }
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);
532 pub fn content_is_dangerous_to_run(code: &str) -> bool {
533 let dangerous_patterns =
535 ~"import", // espeically fs, run
538 ~"log"]; // python --> rust pipe deadlock?
540 for dangerous_patterns.each |p| { if contains(code, *p) { return true; } }
544 pub fn content_is_dangerous_to_compile(code: &str) -> bool {
545 let dangerous_patterns =
548 for dangerous_patterns.each |p| { if contains(code, *p) { return true; } }
552 pub fn content_might_not_converge(code: &str) -> bool {
553 let confusing_patterns =
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
564 for confusing_patterns.each |p| { if contains(code, *p) { return true; } }
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?
578 for confusing_files.each |f| {
579 if contains(filename.to_str(), *f) {
587 pub fn check_roundtrip_convergence(code: @~str, maxIters: uint) {
594 if content_might_not_converge(*oldv) { return; }
595 newv = @parse_and_print(oldv);
596 if oldv == newv { break; }
601 error!("Converged after %u iterations", i);
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"]);
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);
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");
637 let s = @result::get(&io::read_whole_file_str(file));
638 if contains(*s, ~"#") {
639 loop; // Macros are confusing
641 if cx.mode == tm_converge && content_might_not_converge(*s) {
644 if cx.mode == tm_run && content_is_dangerous_to_compile(*s) {
648 let file_str = file.to_str();
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(),
656 io::with_str_reader(*s, |rdr| {
657 let file_str = file_str.to_str();
662 // Assuming no token_trees
663 token::mk_fake_ident_interner(),
664 copy sess.span_diagnostic,
673 check_variants_of_ast(crate, sess.cm, file, cx);
678 let args = os::args();
679 if vec::len(args) != 2u {
680 error!("usage: %s <testdir>", args[0]);
684 let root = Path(args[1]);
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 });
694 error!("Fuzzer done");
699 // indent-tabs-mode: nil
701 // buffer-file-coding-system: utf-8-unix