summaryrefslogtreecommitdiff
path: root/bindgen/csmith-fuzzing/driver.py
diff options
context:
space:
mode:
Diffstat (limited to 'bindgen/csmith-fuzzing/driver.py')
-rwxr-xr-xbindgen/csmith-fuzzing/driver.py284
1 files changed, 284 insertions, 0 deletions
diff --git a/bindgen/csmith-fuzzing/driver.py b/bindgen/csmith-fuzzing/driver.py
new file mode 100755
index 00000000..1d3af540
--- /dev/null
+++ b/bindgen/csmith-fuzzing/driver.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import re
+import shlex
+import sys
+from subprocess import run, SubprocessError, DEVNULL, PIPE
+from tempfile import NamedTemporaryFile
+
+DESC = """
+
+A `csmith` fuzzing driver for `bindgen`.
+
+Generates random C source files with `csmith` and then passes them to `bindgen`
+(via `predicate.py`). If `bindgen` can't emit bindings, `rustc` can't compile
+those bindings, or the compiled bindings' layout tests fail, then the driver has
+found a bug, and will report the problematic test case to you.
+
+"""
+
+parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=DESC.strip())
+
+parser.add_argument(
+ "--keep-going",
+ action="store_true",
+ help="Do not stop after finding a test case that exhibits a bug in `bindgen`. Instead, keep going.")
+
+CSMITH_ARGS="\
+--no-checksum \
+--nomain \
+--max-block-size 1 \
+--max-block-depth 1"
+
+parser.add_argument(
+ "--csmith-args",
+ type=str,
+ default=CSMITH_ARGS,
+ help="Pass this argument string to `csmith`. By default, very small functions are generated.")
+
+BINDGEN_ARGS = "--with-derive-partialeq \
+--with-derive-eq \
+--with-derive-partialord \
+--with-derive-ord \
+--with-derive-hash \
+--with-derive-default"
+
+parser.add_argument(
+ "--bindgen-args",
+ type=str,
+ default=BINDGEN_ARGS,
+ help="Pass this argument string to `bindgen`. By default, all traits are derived.")
+
+parser.add_argument(
+ "--no-creduce",
+ action="store_false",
+ dest="creduce",
+ help="Do not run `creduce` on any buggy test case(s) discovered.")
+
+################################################################################
+
+def cat(path, title=None):
+ if not title:
+ title = path
+ print("-------------------- {} --------------------".format(title))
+ print()
+ print()
+ run(["cat", path])
+
+def decode(f):
+ return f.decode(encoding="utf-8", errors="ignore")
+
+def run_logged(cmd):
+ result = run(cmd, stdin=DEVNULL, stdout=PIPE, stderr=PIPE)
+ result.stdout = decode(result.stdout)
+ result.stderr = decode(result.stderr)
+ if result.returncode != 0:
+ print()
+ print()
+ print("Error: {} exited with code {}".format(cmd, result.returncode))
+ print()
+ print()
+ for line in result.stdout.splitlines():
+ sys.stdout.write("+")
+ sys.stdout.write(line)
+ sys.stdout.write("\n")
+ for line in result.stderr.splitlines():
+ sys.stderr.write("+")
+ sys.stderr.write(line)
+ sys.stderr.write("\n")
+ return result
+
+def main():
+ os.environ["RUST_BACKTRACE"] = "full"
+ args = parser.parse_args()
+
+ bindgen_args = args.bindgen_args
+ if bindgen_args.find(" -- ") == -1:
+ bindgen_args = bindgen_args + " -- "
+ bindgen_args = bindgen_args + " -I{}".format(os.path.abspath(os.path.dirname(sys.argv[0])))
+ args.bindgen_args = bindgen_args
+
+ print()
+ print()
+ print("Fuzzing `bindgen` with C-Smith...")
+ print()
+ print()
+
+ iterations = 0
+ while True:
+ print("\rIteration: {}".format(iterations), end="", flush=True)
+ iterations += 1
+
+ input = NamedTemporaryFile(delete=False, prefix="input-", suffix=".h")
+ input.close()
+ result = run_logged(["csmith", "-o", input.name] + shlex.split(args.csmith_args))
+ if result.returncode != 0:
+ exit(1)
+
+ predicate_command = [
+ "./predicate.py",
+ "--bindgen-args",
+ args.bindgen_args,
+ input.name
+ ]
+ result = run_logged(predicate_command)
+
+ if result.returncode != 0:
+ print()
+ print()
+ cat(input.name, title="Failing test case: {}".format(input.name))
+ print()
+ print()
+
+ if args.creduce:
+ creduce(args, input.name, result)
+
+ print_issue_template(args, input.name, predicate_command, result)
+
+ if args.keep_going:
+ continue
+ exit(1)
+
+ os.remove(input.name)
+
+RUSTC_ERROR_REGEX = re.compile(r".*(error\[.*].*)")
+LAYOUT_TEST_FAILURE = re.compile(r".*(test bindgen_test_layout_.* \.\.\. FAILED)")
+
+def creduce(args, failing_test_case, result):
+ print()
+ print()
+ print("Reducing failing test case with `creduce`...")
+
+ match = re.search(RUSTC_ERROR_REGEX, result.stderr)
+ if match:
+ error_msg = match.group(1)
+ print("...searching for \"{}\".".format(error_msg))
+ return creduce_with_predicate_flags(
+ args,
+ failing_test_case,
+ "--bindgen-args '{}' --expect-compile-fail --rustc-grep '{}'".format(
+ args.bindgen_args,
+ re.escape(error_msg)
+ )
+ )
+
+ match = re.search(LAYOUT_TEST_FAILURE, result.stdout)
+ if match:
+ layout_failure = match.group(1)
+ struct_name = layout_failure[len("test bindgen_test_layout_"):layout_failure.rindex(" ... FAILED")]
+ print("...searching for \"{}\".".format(layout_failure))
+ return creduce_with_predicate_flags(
+ args,
+ failing_test_case,
+ "--bindgen-args '{}' --expect-layout-tests-fail --bindings-grep '{}' --layout-tests-grep '{}'".format(
+ args.bindgen_args,
+ re.escape(struct_name),
+ re.escape(layout_failure)
+ )
+ )
+
+ print("...nevermind, don't know how to `creduce` this bug. Skipping.")
+
+def creduce_with_predicate_flags(args, failing_test_case, predicate_flags):
+ predicate = """
+#!/usr/bin/env bash
+set -eu
+{} {} {}
+ """.format(
+ os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "predicate.py")),
+ predicate_flags,
+ os.path.basename(failing_test_case)
+ )
+
+ print("...and reducing with this script:")
+ print()
+ print()
+ print(predicate)
+ print()
+ print()
+
+ predicate_path = failing_test_case + ".predicate.sh"
+ with open(predicate_path, "w") as p:
+ p.write(predicate)
+ os.chmod(predicate_path, 0o755)
+
+ creduce_command = ["creduce", "--n", str(os.cpu_count()), predicate_path, failing_test_case]
+ print("Running:", creduce_command)
+ result = run(creduce_command)
+ if result.returncode == 0:
+ print()
+ print()
+ print("`creduce` reduced the failing test case to:")
+ print()
+ print()
+ cat(failing_test_case)
+ print()
+ print()
+ else:
+ print()
+ print()
+ print("`creduce` failed!")
+ if not args.keep_going:
+ sys.exit(1)
+
+def print_issue_template(args, failing_test_case, predicate_command, result):
+ test_case_contents = None
+ with open(failing_test_case, "r") as f:
+ test_case_contents = f.read()
+
+ print("""
+
+! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
+! File this issue at https://github.com/rust-lang/rust-bindgen/issues/new !
+! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
+
+ --------------- 8< --------------- 8< --------------- 8< ---------------
+
+This bug was found with `csmith` and `driver.py`.
+
+### Input Header
+
+```c
+{}
+```
+
+### `bindgen` Invocation
+
+```
+$ {}
+```
+
+### Actual Results
+
+<details>
+
+```
+{}
+```
+
+</details>
+
+### Expected Results
+
+`bindgen` emits bindings OK, then `rustc` compiles those bindings OK, then the
+compiled bindings' layout tests pass OK.
+
+ --------------- 8< --------------- 8< --------------- 8< ---------------
+
+ <3 <3 <3 Thank you! <3 <3 <3
+ """.format(
+ test_case_contents,
+ " ".join(map(lambda s: "'{}'".format(s), predicate_command)),
+ result.stdout + result.stderr
+ ))
+
+if __name__ == "__main__":
+ try:
+ os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
+ main()
+ except KeyboardInterrupt:
+ exit()