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;
8 import rustc::syntax::{ast, ast_util, fold, visit, codemap};
9 import rustc::syntax::parse::parser;
10 import rustc::syntax::print::pprust;
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]);
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;
23 fn contains(haystack: str, needle: str) -> bool {
24 str::find(haystack, needle) != -1
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);
41 fn common_exprs() -> [ast::expr] {
42 fn dse(e: ast::expr_) -> ast::expr {
43 { id: 0, node: e, span: ast_util::dummy_sp() }
46 fn dsl(l: ast::lit_) -> ast::lit {
47 { node: l, span: ast_util::dummy_sp() }
50 [dse(ast::expr_break),
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))))))
63 fn safe_to_steal_expr(e: @ast::expr) -> bool {
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 }
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 }
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 }
88 ast::expr_fail(option::none.) { false }
89 ast::expr_ret(option::none.) { false }
90 ast::expr_put(option::none.) { false }
92 // https://github.com/graydon/rust/issues/927
93 //ast::expr_assert(_) { false }
94 ast::expr_check(_, _) { false }
96 // https://github.com/graydon/rust/issues/928
97 //ast::expr_cast(_, _) { false }
103 fn safe_to_steal_ty(t: @ast::ty) -> bool {
105 safe_to_replace_ty(t.node)
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) {
112 } else {/* now my indices are wrong :( */ }
115 fn stash_ty_if(c: fn(@ast::ty)->bool, es: @mutable [ast::ty], e: @ast::ty) {
118 } else {/* now my indices are wrong :( */ }
121 type stolen_stuff = {exprs: [ast::expr], tys: [ast::ty]};
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()
131 visit::visit_crate(crate, (), v);
132 {exprs: *exprs, tys: *tys}
135 // https://github.com/graydon/rust/issues/652
136 fn safe_to_replace_expr(e: ast::expr_) -> bool {
138 ast::expr_if(_, _, _) { false }
139 ast::expr_block(_) { false }
144 fn safe_to_replace_ty(t: ast::ty_) -> bool {
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 }
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) ->
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) ->
161 if i_ + 1u == *j_ && safe_to_replace_expr(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) }
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);
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) ->
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) ->
187 if i_ + 1u == *j_ && safe_to_replace_ty(original) {
189 } else { fold::noop_fold_ty(original, fld) }
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);
199 iter under(n: uint) -> uint {
201 while i < n { put i; i += 1u; }
204 fn devnull() -> io::writer { std::io::string_writer().get_writer() }
206 fn as_str(f: fn(io::writer)) -> str {
207 let w = std::io::string_writer();
212 fn check_variants_of_ast(crate: ast::crate, codemap: codemap::codemap,
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);
219 fn check_variants_T<T>(
221 codemap: codemap::codemap,
225 stringifier: fn(@T) -> str,
226 replacer: fn(ast::crate, uint, T) -> ast::crate
228 log_err #fmt("%s contains %u %s objects", filename, vec::len(things), thing_label);
230 let L = vec::len(things);
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.
241 as_str(bind pprust::print_crate(codemap, crate2,
243 io::string_reader(""), _,
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);
254 fn last_part(filename: str) -> str {
255 let ix = str::rindex(filename, 47u8 /* '/' */);
257 str::slice(filename, ix as uint + 1u, str::byte_len(filename) - 3u)
260 tag happiness { passed; cleanly_rejected(str); known_bug(str); failed(str); }
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);
270 let compile_result = check_compiling(filename);
272 let run_result = alt (compile_result, allow_running) {
273 (passed., true) { check_running(suggested_filename_prefix) }
278 passed. | cleanly_rejected(_) | known_bug(_) {
279 removeIfExists(suggested_filename_prefix);
280 removeIfExists(suggested_filename_prefix + ".rs");
281 removeDirIfExists(suggested_filename_prefix + ".dSYM");
284 log_err "check_whole_compiler failure: " + s;
285 log_err "Saved as: " + filename;
290 fn removeIfExists(filename: str) {
292 assert !contains(filename, " ");
293 std::run::program_output("bash", ["-c", "rm " + filename]);
296 fn removeDirIfExists(filename: str) {
298 assert !contains(filename, " ");
299 std::run::program_output("bash", ["-c", "rm -r " + filename]);
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;
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
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)) }
331 fn check_compiling(filename: str) -> happiness {
333 let p = std::run::program_output(
334 "/Users/jruderman/code/rust/build/stage1/rustc",
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]);
340 //log_err #fmt("Status: %d", p.status);
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")
355 log_err "Stderr: " + p.err;
356 failed("Unfamiliar error message")
358 } else if p.status == 0 {
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")
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")
387 } else if contains(p.out, "error:") {
388 cleanly_rejected("rejected with span_error")
391 log_err "!Stdout: " + p.out;
392 failed("What happened?")
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,
405 io::string_reader(code), _,
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) {
413 ast::ty_ptr(_) { *flag = true; }
418 visit::mk_simple_visitor(@{visit_ty: bind visit_ty(has_rp, _)
419 with *visit::default_simple_visitor()});
420 visit::visit_crate(c, (), v);
424 fn content_is_dangerous_to_run(code: str) -> bool {
425 let dangerous_patterns =
426 ["import", // espeically fs, run
429 "log"]; // python --> rust pipe deadlock?
431 for p: str in dangerous_patterns { if contains(code, p) { ret true; } }
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"
443 for p: str in dangerous_patterns { if contains(code, p) { ret true; } }
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
455 for p: str in confusing_patterns { if contains(code, p) { ret true; } }
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
462 for f in confusing_files { if contains(filename, f) { ret true; } }
467 fn check_roundtrip_convergence(code: str, maxIters: uint) {
475 if content_is_confusing(old) { ret; }
476 new = parse_and_print(old);
477 if old == new { break; }
482 log_err #fmt["Converged after %u iterations", i];
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",
494 fn check_convergence(files: [str]) {
495 log_err #fmt["pp convergence tests: %u files", vec::len(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);
508 fn check_variants(files: [str]) {
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) {
515 log_err "check_variants: " + file;
516 let sess = @{cm: codemap::new_codemap(), mutable next_id: 0};
518 parser::parse_crate_from_source_str(
521 log_err as_str(bind pprust::print_crate(sess.cm, crate,
523 io::string_reader(s), _,
525 check_variants_of_ast(*crate, sess.cm, file);
530 fn main(args: [str]) {
531 if vec::len(args) != 2u {
532 log_err #fmt["usage: %s <testdir>", args[0]];
538 find_rust_files(files, root);
539 check_convergence(files);
540 check_variants(files);
542 log_err "Fuzzer done";
548 // indent-tabs-mode: nil
550 // buffer-file-coding-system: utf-8-unix
551 // compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";