]> git.lizzy.rs Git - rust.git/blob - src/fuzzer/fuzzer.rs
1074f9c073bfa7598be66750b82b86789b91060a
[rust.git] / src / fuzzer / fuzzer.rs
1 use std;
2 use rustc;
3
4 import std::{fs, io, getopts, vec, str, int, uint, option};
5 import std::getopts::{optopt, opt_present, opt_str};
6 import std::io::stdout;
7
8 import rustc::syntax::{ast, ast_util, fold, visit, codemap};
9 import rustc::syntax::parse::parser;
10 import rustc::syntax::print::pprust;
11
12 fn write_file(filename: str, content: str) {
13     io::file_writer(filename, [io::create, io::truncate]).write_str(content);
14     // Work around https://github.com/graydon/rust/issues/726
15     std::run::run_program("chmod", ["644", filename]);
16 }
17
18 fn file_contains(filename: str, needle: str) -> bool {
19     let contents = io::read_whole_file_str(filename);
20     ret str::find(contents, needle) != -1;
21 }
22
23 fn contains(haystack: str, needle: str) -> bool {
24     str::find(haystack, needle) != -1
25 }
26
27 fn find_rust_files(&files: [str], path: str) {
28     if str::ends_with(path, ".rs") {
29         if file_contains(path, "xfail-test") {
30             //log_err "Skipping " + path + " because it is marked as xfail-test";
31         } else { files += [path]; }
32     } else if fs::file_is_dir(path)
33         && str::find(path, "compile-fail") == -1 {
34         for p in fs::list_dir(path) {
35             find_rust_files(files, p);
36         }
37     }
38 }
39
40
41 fn common_exprs() -> [ast::expr] {
42     fn dse(e: ast::expr_) -> ast::expr {
43         { id: 0, node: e, span: ast_util::dummy_sp() }
44     }
45
46     fn dsl(l: ast::lit_) -> ast::lit {
47         { node: l, span: ast_util::dummy_sp() }
48     }
49
50     [dse(ast::expr_break),
51      dse(ast::expr_cont),
52      dse(ast::expr_fail(option::none)),
53      dse(ast::expr_fail(option::some(@dse(ast::expr_lit(@dsl(ast::lit_str("boo"))))))),
54      dse(ast::expr_ret(option::none)),
55      dse(ast::expr_put(option::none)),
56      dse(ast::expr_lit(@dsl(ast::lit_nil))),
57      dse(ast::expr_lit(@dsl(ast::lit_bool(false)))),
58      dse(ast::expr_lit(@dsl(ast::lit_bool(true)))),
59      dse(ast::expr_unary(ast::box(ast::imm), @dse(ast::expr_lit(@dsl(ast::lit_bool(true))))))
60     ]
61 }
62
63 fn safe_to_steal_expr(e: @ast::expr) -> bool {
64     alt e.node {
65       /*
66       // For compiling (rather than pretty-printing)
67       // combination of https://github.com/graydon/rust/issues/924 with unwind hang?
68       ast::expr_ret(option::none.) { false }
69       */
70
71       // If the fuzzer moves a block-ending-in-semicolon into callee position,
72       // the pretty-printer can't preserve this even by parenthesizing!!
73       // See email to marijn.
74       ast::expr_if(_, _, _) { false }
75       ast::expr_block(_) { false }
76       ast::expr_alt(_, _) { false }
77       ast::expr_for(_, _, _) { false }
78       ast::expr_for_each(_, _, _) { false }
79       ast::expr_while(_, _) { false }
80
81       // https://github.com/graydon/rust/issues/929
82       ast::expr_cast(_, _) { false }
83       ast::expr_assert(_) { false }
84       ast::expr_binary(_, _, _) { false }
85       ast::expr_assign(_, _) { false }
86       ast::expr_assign_op(_, _, _) { false }
87
88       ast::expr_fail(option::none.) { false }
89       ast::expr_ret(option::none.) { false }
90       ast::expr_put(option::none.) { false }
91
92       // https://github.com/graydon/rust/issues/927
93       //ast::expr_assert(_) { false }
94       ast::expr_check(_, _) { false }
95
96       // https://github.com/graydon/rust/issues/928
97       //ast::expr_cast(_, _) { false }
98
99       _ { true }
100     }
101 }
102
103 fn safe_to_steal_ty(t: @ast::ty) -> bool {
104     // Same restrictions
105     safe_to_replace_ty(t.node)
106 }
107
108 // Not type-parameterized: https://github.com/graydon/rust/issues/898
109 fn stash_expr_if(c: fn(@ast::expr)->bool, es: @mutable [ast::expr], e: @ast::expr) {
110     if c(e) {
111         *es += [*e];
112     } else {/* now my indices are wrong :( */ }
113 }
114
115 fn stash_ty_if(c: fn(@ast::ty)->bool, es: @mutable [ast::ty], e: @ast::ty) {
116     if c(e) {
117         *es += [*e];
118     } else {/* now my indices are wrong :( */ }
119 }
120
121 type stolen_stuff = {exprs: [ast::expr], tys: [ast::ty]};
122
123 fn steal(crate: ast::crate) -> stolen_stuff {
124     let exprs = @mutable [];
125     let tys = @mutable [];
126     let v = visit::mk_simple_visitor(@{
127         visit_expr: bind stash_expr_if(safe_to_steal_expr, exprs, _),
128         visit_ty: bind stash_ty_if(safe_to_steal_ty, tys, _)
129         with *visit::default_simple_visitor()
130     });
131     visit::visit_crate(crate, (), v);
132     {exprs: *exprs, tys: *tys}
133 }
134
135 // https://github.com/graydon/rust/issues/652
136 fn safe_to_replace_expr(e: ast::expr_) -> bool {
137     alt e {
138       ast::expr_if(_, _, _) { false }
139       ast::expr_block(_) { false }
140       _ { true }
141     }
142 }
143
144 fn safe_to_replace_ty(t: ast::ty_) -> bool {
145     alt t {
146       ast::ty_infer. { false } // always implicit, always top level
147       ast::ty_bot. { false }   // in source, can only appear as the out type of a function
148       ast::ty_mac(_) { false }
149       _ { true }
150     }
151 }
152
153 // Replace the |i|th expr (in fold order) of |crate| with |newexpr|.
154 fn replace_expr_in_crate(crate: ast::crate, i: uint, newexpr: ast::expr) ->
155    ast::crate {
156     let j: @mutable uint = @mutable 0u;
157     fn fold_expr_rep(j_: @mutable uint, i_: uint, newexpr_: ast::expr_,
158                      original: ast::expr_, fld: fold::ast_fold) ->
159        ast::expr_ {
160         *j_ += 1u;
161         if i_ + 1u == *j_ && safe_to_replace_expr(original) {
162             newexpr_
163         } else {
164             alt(original) {
165               ast::expr_fail(_) { original /* Don't replace inside fail: https://github.com/graydon/rust/issues/930 */ }
166               _ { fold::noop_fold_expr(original, fld) }
167             }
168         }
169     }
170     let afp =
171         {fold_expr: bind fold_expr_rep(j, i, newexpr.node, _, _)
172             with *fold::default_ast_fold()};
173     let af = fold::make_fold(afp);
174     let crate2: @ast::crate = @af.fold_crate(crate);
175     *crate2
176 }
177
178
179 // Replace the |i|th ty (in fold order) of |crate| with |newty|.
180 fn replace_ty_in_crate(crate: ast::crate, i: uint, newty: ast::ty) ->
181    ast::crate {
182     let j: @mutable uint = @mutable 0u;
183     fn fold_ty_rep(j_: @mutable uint, i_: uint, newty_: ast::ty_,
184                      original: ast::ty_, fld: fold::ast_fold) ->
185        ast::ty_ {
186         *j_ += 1u;
187         if i_ + 1u == *j_ && safe_to_replace_ty(original) {
188             newty_
189         } else { fold::noop_fold_ty(original, fld) }
190     }
191     let afp =
192         {fold_ty: bind fold_ty_rep(j, i, newty.node, _, _)
193             with *fold::default_ast_fold()};
194     let af = fold::make_fold(afp);
195     let crate2: @ast::crate = @af.fold_crate(crate);
196     *crate2
197 }
198
199 iter under(n: uint) -> uint {
200     let i: uint = 0u;
201     while i < n { put i; i += 1u; }
202 }
203
204 fn devnull() -> io::writer { std::io::string_writer().get_writer() }
205
206 fn as_str(f: fn(io::writer)) -> str {
207     let w = std::io::string_writer();
208     f(w.get_writer());
209     ret w.get_str();
210 }
211
212 fn check_variants_of_ast(crate: ast::crate, codemap: codemap::codemap,
213                          filename: str) {
214     let stolen = steal(crate);
215     check_variants_T(crate, codemap, filename, "expr", /*common_exprs() +*/ stolen.exprs, pprust::expr_to_str, replace_expr_in_crate);
216     check_variants_T(crate, codemap, filename, "ty", stolen.tys, pprust::ty_to_str, replace_ty_in_crate);
217 }
218
219 fn check_variants_T<T>(
220   crate: ast::crate,
221   codemap: codemap::codemap,
222   filename: str,
223   thing_label: str,
224   things: [T],
225   stringifier: fn(@T) -> str,
226   replacer: fn(ast::crate, uint, T) -> ast::crate
227   ) {
228     log_err #fmt("%s contains %u %s objects", filename, vec::len(things), thing_label);
229
230     let L = vec::len(things);
231
232     if L < 100u {
233         for each i: uint in under(uint::min(L, 20u)) {
234             log_err "Replacing... #" + uint::str(i);
235             for each j: uint in under(uint::min(L, 30u)) {
236                 log_err "With... " + stringifier(@things[j]);
237                 let crate2 = @replacer(crate, i, things[j]);
238                 // It would be best to test the *crate* for stability, but testing the
239                 // string for stability is easier and ok for now.
240                 let str3 =
241                     as_str(bind pprust::print_crate(codemap, crate2,
242                                                     filename,
243                                                     io::string_reader(""), _,
244                                                     pprust::no_ann()));
245                 check_roundtrip_convergence(str3, 1u);
246                 //let file_label = #fmt("rusttmp/%s_%s_%u_%u", last_part(filename), thing_label, i, j);
247                 //let safe_to_run = !(content_is_dangerous_to_run(str3) || has_raw_pointers(*crate2));
248                 //check_whole_compiler(str3, file_label, safe_to_run);
249             }
250         }
251     }
252 }
253
254 fn last_part(filename: str) -> str {
255   let ix = str::rindex(filename, 47u8 /* '/' */);
256   assert ix >= 0;
257   str::slice(filename, ix as uint + 1u, str::byte_len(filename) - 3u)
258 }
259
260 tag happiness { passed; cleanly_rejected(str); known_bug(str); failed(str); }
261
262 // We'd find more bugs if we could take an AST here, but
263 // - that would find many "false positives" or unimportant bugs
264 // - that would be tricky, requiring use of tasks or serialization or randomness.
265 // This seems to find plenty of bugs as it is :)
266 fn check_whole_compiler(code: str, suggested_filename_prefix: str, allow_running: bool) {
267     let filename = suggested_filename_prefix + ".rs";
268     write_file(filename, code);
269
270     let compile_result = check_compiling(filename);
271
272     let run_result = alt (compile_result, allow_running) {
273       (passed., true) { check_running(suggested_filename_prefix) }
274       (h, _) { h }
275     };
276
277     alt run_result {
278       passed. | cleanly_rejected(_) | known_bug(_) {
279         removeIfExists(suggested_filename_prefix);
280         removeIfExists(suggested_filename_prefix + ".rs");
281         removeDirIfExists(suggested_filename_prefix + ".dSYM");
282       }
283       failed(s) {
284         log_err "check_whole_compiler failure: " + s;
285         log_err "Saved as: " + filename;
286       }
287     }
288 }
289
290 fn removeIfExists(filename: str) {
291     // So sketchy!
292     assert !contains(filename, " ");
293     std::run::program_output("bash", ["-c", "rm " + filename]);
294 }
295
296 fn removeDirIfExists(filename: str) {
297     // So sketchy!
298     assert !contains(filename, " ");
299     std::run::program_output("bash", ["-c", "rm -r " + filename]);
300 }
301
302 fn check_running(exe_filename: str) -> happiness {
303     let p = std::run::program_output("/Users/jruderman/scripts/timed_run_rust_program.py", [exe_filename]);
304     let comb = p.out + "\n" + p.err;
305     if str::byte_len(comb) > 1u {
306         log_err "comb comb comb: " + comb;
307     }
308
309     if contains(comb, "Assertion failed: (0), function alloc, file ../src/rt/rust_obstack.cpp") {
310         known_bug("https://github.com/graydon/rust/issues/32 / https://github.com/graydon/rust/issues/445")
311     } else if contains(comb, "Assertion failed:") {
312         failed("C++ assertion failure")
313     } else if contains(comb, "src/rt/") {
314         failed("Mentioned src/rt/")
315     } else if contains(comb, "malloc") {
316         failed("Mentioned malloc")
317     } else if contains(comb, "leaked memory in rust main loop") {
318         failed("Leaked") // might also use exit code 134
319     } else {
320         alt p.status {
321             0         { passed }
322             100       { cleanly_rejected("running: explicit fail") }
323             101 | 247 { cleanly_rejected("running: timed out") }
324             245 | 246 { known_bug("https://github.com/graydon/rust/issues/32 ??") }
325             136 | 248 { known_bug("SIGFPE - https://github.com/graydon/rust/issues/944") }
326             rc        { failed("Rust program ran but exited with status " + int::str(rc)) }
327         }
328     }
329 }
330
331 fn check_compiling(filename: str) -> happiness {
332     /*
333     let p = std::run::program_output(
334             "/Users/jruderman/code/rust/build/stage1/rustc",
335             ["-c", filename]);
336     */
337
338     let p = std::run::program_output("bash", ["-c", "DYLD_LIBRARY_PATH=/Users/jruderman/code/rust/build/stage0/lib:/Users/jruderman/code/rust/build/rustllvm/ /Users/jruderman/code/rust/build/stage1/rustc " + filename]);
339
340     //log_err #fmt("Status: %d", p.status);
341     if p.err != "" {
342         if contains(p.err, "May only branch on boolean predicates") {
343             known_bug("https://github.com/graydon/rust/issues/892 or https://github.com/graydon/rust/issues/943")
344         } else if contains(p.err, "All operands to PHI node must be the same type as the PHI node!") {
345             known_bug("https://github.com/graydon/rust/issues/943")
346         } else if contains(p.err, "(S->getType()->isPointerTy() && \"Invalid cast\")") {
347             known_bug("https://github.com/graydon/rust/issues/895")
348         } else if contains(p.err, "Ptr must be a pointer to Val type") {
349             known_bug("https://github.com/graydon/rust/issues/897")
350         } else if contains(p.err, "(castIsValid(op, S, Ty) && \"Invalid cast!\"), function Create") {
351             known_bug("https://github.com/graydon/rust/issues/901")
352         } else if contains(p.err, "Invoking a function with a bad signature!") {
353             known_bug("https://github.com/graydon/rust/issues/946")
354         } else {
355             log_err "Stderr: " + p.err;
356             failed("Unfamiliar error message")
357         }
358     } else if p.status == 0 {
359         passed
360     } else if contains(p.out, "Out of stack space, sorry") {
361         known_bug("Recursive types - https://github.com/graydon/rust/issues/742")
362     } else if contains(p.out, "Assertion !cx.terminated failed") {
363         known_bug("https://github.com/graydon/rust/issues/893 or https://github.com/graydon/rust/issues/894")
364 //  } else if contains(p.out, "upcall fail 'non-exhaustive match failure', ../src/comp/middle/trans.rs") {
365     } else if contains(p.out, "trans_rec expected a rec but found _|_") {
366         known_bug("https://github.com/graydon/rust/issues/924")
367     } else if contains(p.out, "Assertion failed: (S->getType()->isPointerTy() && \"Invalid cast\")") {
368         known_bug("https://github.com/graydon/rust/issues/935")
369     } else if contains(p.out, "Assertion") && contains(p.out, "failed") {
370         log_err "Stdout: " + p.out;
371         failed("Looks like an llvm assertion failure")
372
373     } else if contains(p.out, "internal compiler error fail called with unsupported type _|_") {
374         known_bug("https://github.com/graydon/rust/issues/942")
375     } else if contains(p.out, "internal compiler error Translating unsupported cast") {
376         known_bug("https://github.com/graydon/rust/issues/932")
377     } else if contains(p.out, "internal compiler error sequence_element_type called on non-sequence value") {
378         known_bug("https://github.com/graydon/rust/issues/931")
379     } else if contains(p.out, "internal compiler error bit_num: asked for pred constraint, found an init constraint") {
380         known_bug("https://github.com/graydon/rust/issues/933")
381     } else if contains(p.out, "internal compiler error unimplemented") {
382         known_bug("Something unimplemented")
383     } else if contains(p.out, "internal compiler error") {
384         log_err "Stdout: " + p.out;
385         failed("internal compiler error")
386
387     } else if contains(p.out, "error:") {
388         cleanly_rejected("rejected with span_error")
389     } else {
390         log_err p.status;
391         log_err "!Stdout: " + p.out;
392         failed("What happened?")
393     }
394 }
395
396
397 fn parse_and_print(code: str) -> str {
398     let filename = "tmp.rs";
399     let sess = @{cm: codemap::new_codemap(), mutable next_id: 0};
400     //write_file(filename, code);
401     let crate = parser::parse_crate_from_source_str(
402         filename, code, [], sess);
403     ret as_str(bind pprust::print_crate(sess.cm, crate,
404                                         filename,
405                                         io::string_reader(code), _,
406                                         pprust::no_ann()));
407 }
408
409 fn has_raw_pointers(c: ast::crate) -> bool {
410     let has_rp = @mutable false;
411     fn visit_ty(flag: @mutable bool, t: @ast::ty) {
412         alt t.node {
413           ast::ty_ptr(_) { *flag = true; }
414           _ { }
415         }
416     }
417     let v =
418         visit::mk_simple_visitor(@{visit_ty: bind visit_ty(has_rp, _)
419                                       with *visit::default_simple_visitor()});
420     visit::visit_crate(c, (), v);
421     ret *has_rp;
422 }
423
424 fn content_is_dangerous_to_run(code: str) -> bool {
425     let dangerous_patterns =
426         ["import", // espeically fs, run
427          "native",
428          "unsafe",
429          "log"]; // python --> rust pipe deadlock?
430
431     for p: str in dangerous_patterns { if contains(code, p) { ret true; } }
432     ret false;
433 }
434
435 fn content_is_dangerous_to_modify(code: str) -> bool {
436     let dangerous_patterns =
437         ["#macro", // not safe to steal things inside of it, because they have a special syntax
438          "#",      // strange representation of the arguments to #fmt, for example
439          "tag",    // typeck hang: https://github.com/graydon/rust/issues/742 (from dup #900)
440          "with",   // tstate hang: https://github.com/graydon/rust/issues/948
441          " be "];  // don't want to replace its child with a non-call: "Non-call expression in tail call"
442
443     for p: str in dangerous_patterns { if contains(code, p) { ret true; } }
444     ret false;
445 }
446
447 fn content_is_confusing(code: str) -> bool {
448     let confusing_patterns =
449         ["self",       // crazy rules enforced by parser rather than typechecker?
450         "spawn",       // precedence issues?
451          "bind",       // precedence issues?
452          "\n\n\n\n\n"  // https://github.com/graydon/rust/issues/850
453         ];
454
455     for p: str in confusing_patterns { if contains(code, p) { ret true; } }
456     ret false;
457 }
458
459 fn file_is_confusing(filename: str) -> bool {
460     let confusing_files = ["expr-alt.rs"]; // pretty-printing "(a = b) = c" vs "a = b = c" and wrapping
461
462     for f in confusing_files { if contains(filename, f) { ret true; } }
463
464     ret false;
465 }
466
467 fn check_roundtrip_convergence(code: str, maxIters: uint) {
468
469     let i = 0u;
470     let new = code;
471     let old = code;
472
473     while i < maxIters {
474         old = new;
475         if content_is_confusing(old) { ret; }
476         new = parse_and_print(old);
477         if old == new { break; }
478         i += 1u;
479     }
480
481     if old == new {
482         log_err #fmt["Converged after %u iterations", i];
483     } else {
484         log_err #fmt["Did not converge after %u iterations!", i];
485         write_file("round-trip-a.rs", old);
486         write_file("round-trip-b.rs", new);
487         std::run::run_program("diff",
488                               ["-w", "-u", "round-trip-a.rs",
489                                "round-trip-b.rs"]);
490         fail "Mismatch";
491     }
492 }
493
494 fn check_convergence(files: [str]) {
495     log_err #fmt["pp convergence tests: %u files", vec::len(files)];
496     for file in files {
497         if !file_is_confusing(file) {
498             let s = io::read_whole_file_str(file);
499             if !content_is_confusing(s) {
500                 log_err #fmt["pp converge: %s", file];
501                 // Change from 7u to 2u once https://github.com/graydon/rust/issues/850 is fixed
502                 check_roundtrip_convergence(s, 7u);
503             }
504         }
505     }
506 }
507
508 fn check_variants(files: [str]) {
509     for file in files {
510         if !file_is_confusing(file) {
511             let s = io::read_whole_file_str(file);
512             if content_is_dangerous_to_modify(s) || content_is_confusing(s) {
513                 cont;
514             }
515             log_err "check_variants: " + file;
516             let sess = @{cm: codemap::new_codemap(), mutable next_id: 0};
517             let crate =
518                 parser::parse_crate_from_source_str(
519                     file,
520                     s, [], sess);
521             log_err as_str(bind pprust::print_crate(sess.cm, crate,
522                                                     file,
523                                                     io::string_reader(s), _,
524                                                     pprust::no_ann()));
525             check_variants_of_ast(*crate, sess.cm, file);
526         }
527     }
528 }
529
530 fn main(args: [str]) {
531     if vec::len(args) != 2u {
532         log_err #fmt["usage: %s <testdir>", args[0]];
533         ret;
534     }
535     let files = [];
536     let root = args[1];
537
538     find_rust_files(files, root);
539     check_convergence(files);
540     check_variants(files);
541
542     log_err "Fuzzer done";
543 }
544
545 // Local Variables:
546 // mode: rust;
547 // fill-column: 78;
548 // indent-tabs-mode: nil
549 // c-basic-offset: 4
550 // buffer-file-coding-system: utf-8-unix
551 // compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
552 // End: