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