112a9d9c8Sopenharmony_ci#!/usr/bin/env python3 212a9d9c8Sopenharmony_ci 312a9d9c8Sopenharmony_ciimport argparse 412a9d9c8Sopenharmony_ciimport os 512a9d9c8Sopenharmony_ciimport re 612a9d9c8Sopenharmony_ciimport shlex 712a9d9c8Sopenharmony_ciimport sys 812a9d9c8Sopenharmony_cifrom subprocess import run, SubprocessError, DEVNULL, PIPE 912a9d9c8Sopenharmony_cifrom tempfile import NamedTemporaryFile 1012a9d9c8Sopenharmony_ci 1112a9d9c8Sopenharmony_ciDESC = """ 1212a9d9c8Sopenharmony_ci 1312a9d9c8Sopenharmony_ciA `csmith` fuzzing driver for `bindgen`. 1412a9d9c8Sopenharmony_ci 1512a9d9c8Sopenharmony_ciGenerates random C source files with `csmith` and then passes them to `bindgen` 1612a9d9c8Sopenharmony_ci(via `predicate.py`). If `bindgen` can't emit bindings, `rustc` can't compile 1712a9d9c8Sopenharmony_cithose bindings, or the compiled bindings' layout tests fail, then the driver has 1812a9d9c8Sopenharmony_cifound a bug, and will report the problematic test case to you. 1912a9d9c8Sopenharmony_ci 2012a9d9c8Sopenharmony_ci""" 2112a9d9c8Sopenharmony_ci 2212a9d9c8Sopenharmony_ciparser = argparse.ArgumentParser( 2312a9d9c8Sopenharmony_ci formatter_class=argparse.RawDescriptionHelpFormatter, 2412a9d9c8Sopenharmony_ci description=DESC.strip()) 2512a9d9c8Sopenharmony_ci 2612a9d9c8Sopenharmony_ciparser.add_argument( 2712a9d9c8Sopenharmony_ci "--keep-going", 2812a9d9c8Sopenharmony_ci action="store_true", 2912a9d9c8Sopenharmony_ci help="Do not stop after finding a test case that exhibits a bug in `bindgen`. Instead, keep going.") 3012a9d9c8Sopenharmony_ci 3112a9d9c8Sopenharmony_ciCSMITH_ARGS="\ 3212a9d9c8Sopenharmony_ci--no-checksum \ 3312a9d9c8Sopenharmony_ci--nomain \ 3412a9d9c8Sopenharmony_ci--max-block-size 1 \ 3512a9d9c8Sopenharmony_ci--max-block-depth 1" 3612a9d9c8Sopenharmony_ci 3712a9d9c8Sopenharmony_ciparser.add_argument( 3812a9d9c8Sopenharmony_ci "--csmith-args", 3912a9d9c8Sopenharmony_ci type=str, 4012a9d9c8Sopenharmony_ci default=CSMITH_ARGS, 4112a9d9c8Sopenharmony_ci help="Pass this argument string to `csmith`. By default, very small functions are generated.") 4212a9d9c8Sopenharmony_ci 4312a9d9c8Sopenharmony_ciBINDGEN_ARGS = "--with-derive-partialeq \ 4412a9d9c8Sopenharmony_ci--with-derive-eq \ 4512a9d9c8Sopenharmony_ci--with-derive-partialord \ 4612a9d9c8Sopenharmony_ci--with-derive-ord \ 4712a9d9c8Sopenharmony_ci--with-derive-hash \ 4812a9d9c8Sopenharmony_ci--with-derive-default" 4912a9d9c8Sopenharmony_ci 5012a9d9c8Sopenharmony_ciparser.add_argument( 5112a9d9c8Sopenharmony_ci "--bindgen-args", 5212a9d9c8Sopenharmony_ci type=str, 5312a9d9c8Sopenharmony_ci default=BINDGEN_ARGS, 5412a9d9c8Sopenharmony_ci help="Pass this argument string to `bindgen`. By default, all traits are derived.") 5512a9d9c8Sopenharmony_ci 5612a9d9c8Sopenharmony_ciparser.add_argument( 5712a9d9c8Sopenharmony_ci "--no-creduce", 5812a9d9c8Sopenharmony_ci action="store_false", 5912a9d9c8Sopenharmony_ci dest="creduce", 6012a9d9c8Sopenharmony_ci help="Do not run `creduce` on any buggy test case(s) discovered.") 6112a9d9c8Sopenharmony_ci 6212a9d9c8Sopenharmony_ci################################################################################ 6312a9d9c8Sopenharmony_ci 6412a9d9c8Sopenharmony_cidef cat(path, title=None): 6512a9d9c8Sopenharmony_ci if not title: 6612a9d9c8Sopenharmony_ci title = path 6712a9d9c8Sopenharmony_ci print("-------------------- {} --------------------".format(title)) 6812a9d9c8Sopenharmony_ci print() 6912a9d9c8Sopenharmony_ci print() 7012a9d9c8Sopenharmony_ci run(["cat", path]) 7112a9d9c8Sopenharmony_ci 7212a9d9c8Sopenharmony_cidef decode(f): 7312a9d9c8Sopenharmony_ci return f.decode(encoding="utf-8", errors="ignore") 7412a9d9c8Sopenharmony_ci 7512a9d9c8Sopenharmony_cidef run_logged(cmd): 7612a9d9c8Sopenharmony_ci result = run(cmd, stdin=DEVNULL, stdout=PIPE, stderr=PIPE) 7712a9d9c8Sopenharmony_ci result.stdout = decode(result.stdout) 7812a9d9c8Sopenharmony_ci result.stderr = decode(result.stderr) 7912a9d9c8Sopenharmony_ci if result.returncode != 0: 8012a9d9c8Sopenharmony_ci print() 8112a9d9c8Sopenharmony_ci print() 8212a9d9c8Sopenharmony_ci print("Error: {} exited with code {}".format(cmd, result.returncode)) 8312a9d9c8Sopenharmony_ci print() 8412a9d9c8Sopenharmony_ci print() 8512a9d9c8Sopenharmony_ci for line in result.stdout.splitlines(): 8612a9d9c8Sopenharmony_ci sys.stdout.write("+") 8712a9d9c8Sopenharmony_ci sys.stdout.write(line) 8812a9d9c8Sopenharmony_ci sys.stdout.write("\n") 8912a9d9c8Sopenharmony_ci for line in result.stderr.splitlines(): 9012a9d9c8Sopenharmony_ci sys.stderr.write("+") 9112a9d9c8Sopenharmony_ci sys.stderr.write(line) 9212a9d9c8Sopenharmony_ci sys.stderr.write("\n") 9312a9d9c8Sopenharmony_ci return result 9412a9d9c8Sopenharmony_ci 9512a9d9c8Sopenharmony_cidef main(): 9612a9d9c8Sopenharmony_ci os.environ["RUST_BACKTRACE"] = "full" 9712a9d9c8Sopenharmony_ci args = parser.parse_args() 9812a9d9c8Sopenharmony_ci 9912a9d9c8Sopenharmony_ci bindgen_args = args.bindgen_args 10012a9d9c8Sopenharmony_ci if bindgen_args.find(" -- ") == -1: 10112a9d9c8Sopenharmony_ci bindgen_args = bindgen_args + " -- " 10212a9d9c8Sopenharmony_ci bindgen_args = bindgen_args + " -I{}".format(os.path.abspath(os.path.dirname(sys.argv[0]))) 10312a9d9c8Sopenharmony_ci args.bindgen_args = bindgen_args 10412a9d9c8Sopenharmony_ci 10512a9d9c8Sopenharmony_ci print() 10612a9d9c8Sopenharmony_ci print() 10712a9d9c8Sopenharmony_ci print("Fuzzing `bindgen` with C-Smith...") 10812a9d9c8Sopenharmony_ci print() 10912a9d9c8Sopenharmony_ci print() 11012a9d9c8Sopenharmony_ci 11112a9d9c8Sopenharmony_ci iterations = 0 11212a9d9c8Sopenharmony_ci while True: 11312a9d9c8Sopenharmony_ci print("\rIteration: {}".format(iterations), end="", flush=True) 11412a9d9c8Sopenharmony_ci iterations += 1 11512a9d9c8Sopenharmony_ci 11612a9d9c8Sopenharmony_ci input = NamedTemporaryFile(delete=False, prefix="input-", suffix=".h") 11712a9d9c8Sopenharmony_ci input.close() 11812a9d9c8Sopenharmony_ci result = run_logged(["csmith", "-o", input.name] + shlex.split(args.csmith_args)) 11912a9d9c8Sopenharmony_ci if result.returncode != 0: 12012a9d9c8Sopenharmony_ci exit(1) 12112a9d9c8Sopenharmony_ci 12212a9d9c8Sopenharmony_ci predicate_command = [ 12312a9d9c8Sopenharmony_ci "./predicate.py", 12412a9d9c8Sopenharmony_ci "--bindgen-args", 12512a9d9c8Sopenharmony_ci args.bindgen_args, 12612a9d9c8Sopenharmony_ci input.name 12712a9d9c8Sopenharmony_ci ] 12812a9d9c8Sopenharmony_ci result = run_logged(predicate_command) 12912a9d9c8Sopenharmony_ci 13012a9d9c8Sopenharmony_ci if result.returncode != 0: 13112a9d9c8Sopenharmony_ci print() 13212a9d9c8Sopenharmony_ci print() 13312a9d9c8Sopenharmony_ci cat(input.name, title="Failing test case: {}".format(input.name)) 13412a9d9c8Sopenharmony_ci print() 13512a9d9c8Sopenharmony_ci print() 13612a9d9c8Sopenharmony_ci 13712a9d9c8Sopenharmony_ci if args.creduce: 13812a9d9c8Sopenharmony_ci creduce(args, input.name, result) 13912a9d9c8Sopenharmony_ci 14012a9d9c8Sopenharmony_ci print_issue_template(args, input.name, predicate_command, result) 14112a9d9c8Sopenharmony_ci 14212a9d9c8Sopenharmony_ci if args.keep_going: 14312a9d9c8Sopenharmony_ci continue 14412a9d9c8Sopenharmony_ci exit(1) 14512a9d9c8Sopenharmony_ci 14612a9d9c8Sopenharmony_ci os.remove(input.name) 14712a9d9c8Sopenharmony_ci 14812a9d9c8Sopenharmony_ciRUSTC_ERROR_REGEX = re.compile(r".*(error\[.*].*)") 14912a9d9c8Sopenharmony_ciLAYOUT_TEST_FAILURE = re.compile(r".*(test bindgen_test_layout_.* \.\.\. FAILED)") 15012a9d9c8Sopenharmony_ci 15112a9d9c8Sopenharmony_cidef creduce(args, failing_test_case, result): 15212a9d9c8Sopenharmony_ci print() 15312a9d9c8Sopenharmony_ci print() 15412a9d9c8Sopenharmony_ci print("Reducing failing test case with `creduce`...") 15512a9d9c8Sopenharmony_ci 15612a9d9c8Sopenharmony_ci match = re.search(RUSTC_ERROR_REGEX, result.stderr) 15712a9d9c8Sopenharmony_ci if match: 15812a9d9c8Sopenharmony_ci error_msg = match.group(1) 15912a9d9c8Sopenharmony_ci print("...searching for \"{}\".".format(error_msg)) 16012a9d9c8Sopenharmony_ci return creduce_with_predicate_flags( 16112a9d9c8Sopenharmony_ci args, 16212a9d9c8Sopenharmony_ci failing_test_case, 16312a9d9c8Sopenharmony_ci "--bindgen-args '{}' --expect-compile-fail --rustc-grep '{}'".format( 16412a9d9c8Sopenharmony_ci args.bindgen_args, 16512a9d9c8Sopenharmony_ci re.escape(error_msg) 16612a9d9c8Sopenharmony_ci ) 16712a9d9c8Sopenharmony_ci ) 16812a9d9c8Sopenharmony_ci 16912a9d9c8Sopenharmony_ci match = re.search(LAYOUT_TEST_FAILURE, result.stdout) 17012a9d9c8Sopenharmony_ci if match: 17112a9d9c8Sopenharmony_ci layout_failure = match.group(1) 17212a9d9c8Sopenharmony_ci struct_name = layout_failure[len("test bindgen_test_layout_"):layout_failure.rindex(" ... FAILED")] 17312a9d9c8Sopenharmony_ci print("...searching for \"{}\".".format(layout_failure)) 17412a9d9c8Sopenharmony_ci return creduce_with_predicate_flags( 17512a9d9c8Sopenharmony_ci args, 17612a9d9c8Sopenharmony_ci failing_test_case, 17712a9d9c8Sopenharmony_ci "--bindgen-args '{}' --expect-layout-tests-fail --bindings-grep '{}' --layout-tests-grep '{}'".format( 17812a9d9c8Sopenharmony_ci args.bindgen_args, 17912a9d9c8Sopenharmony_ci re.escape(struct_name), 18012a9d9c8Sopenharmony_ci re.escape(layout_failure) 18112a9d9c8Sopenharmony_ci ) 18212a9d9c8Sopenharmony_ci ) 18312a9d9c8Sopenharmony_ci 18412a9d9c8Sopenharmony_ci print("...nevermind, don't know how to `creduce` this bug. Skipping.") 18512a9d9c8Sopenharmony_ci 18612a9d9c8Sopenharmony_cidef creduce_with_predicate_flags(args, failing_test_case, predicate_flags): 18712a9d9c8Sopenharmony_ci predicate = """ 18812a9d9c8Sopenharmony_ci#!/usr/bin/env bash 18912a9d9c8Sopenharmony_ciset -eu 19012a9d9c8Sopenharmony_ci{} {} {} 19112a9d9c8Sopenharmony_ci """.format( 19212a9d9c8Sopenharmony_ci os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "predicate.py")), 19312a9d9c8Sopenharmony_ci predicate_flags, 19412a9d9c8Sopenharmony_ci os.path.basename(failing_test_case) 19512a9d9c8Sopenharmony_ci ) 19612a9d9c8Sopenharmony_ci 19712a9d9c8Sopenharmony_ci print("...and reducing with this script:") 19812a9d9c8Sopenharmony_ci print() 19912a9d9c8Sopenharmony_ci print() 20012a9d9c8Sopenharmony_ci print(predicate) 20112a9d9c8Sopenharmony_ci print() 20212a9d9c8Sopenharmony_ci print() 20312a9d9c8Sopenharmony_ci 20412a9d9c8Sopenharmony_ci predicate_path = failing_test_case + ".predicate.sh" 20512a9d9c8Sopenharmony_ci with open(predicate_path, "w") as p: 20612a9d9c8Sopenharmony_ci p.write(predicate) 20712a9d9c8Sopenharmony_ci os.chmod(predicate_path, 0o755) 20812a9d9c8Sopenharmony_ci 20912a9d9c8Sopenharmony_ci creduce_command = ["creduce", "--n", str(os.cpu_count()), predicate_path, failing_test_case] 21012a9d9c8Sopenharmony_ci print("Running:", creduce_command) 21112a9d9c8Sopenharmony_ci result = run(creduce_command) 21212a9d9c8Sopenharmony_ci if result.returncode == 0: 21312a9d9c8Sopenharmony_ci print() 21412a9d9c8Sopenharmony_ci print() 21512a9d9c8Sopenharmony_ci print("`creduce` reduced the failing test case to:") 21612a9d9c8Sopenharmony_ci print() 21712a9d9c8Sopenharmony_ci print() 21812a9d9c8Sopenharmony_ci cat(failing_test_case) 21912a9d9c8Sopenharmony_ci print() 22012a9d9c8Sopenharmony_ci print() 22112a9d9c8Sopenharmony_ci else: 22212a9d9c8Sopenharmony_ci print() 22312a9d9c8Sopenharmony_ci print() 22412a9d9c8Sopenharmony_ci print("`creduce` failed!") 22512a9d9c8Sopenharmony_ci if not args.keep_going: 22612a9d9c8Sopenharmony_ci sys.exit(1) 22712a9d9c8Sopenharmony_ci 22812a9d9c8Sopenharmony_cidef print_issue_template(args, failing_test_case, predicate_command, result): 22912a9d9c8Sopenharmony_ci test_case_contents = None 23012a9d9c8Sopenharmony_ci with open(failing_test_case, "r") as f: 23112a9d9c8Sopenharmony_ci test_case_contents = f.read() 23212a9d9c8Sopenharmony_ci 23312a9d9c8Sopenharmony_ci print(""" 23412a9d9c8Sopenharmony_ci 23512a9d9c8Sopenharmony_ci! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! 23612a9d9c8Sopenharmony_ci! File this issue at https://github.com/rust-lang/rust-bindgen/issues/new ! 23712a9d9c8Sopenharmony_ci! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! 23812a9d9c8Sopenharmony_ci 23912a9d9c8Sopenharmony_ci --------------- 8< --------------- 8< --------------- 8< --------------- 24012a9d9c8Sopenharmony_ci 24112a9d9c8Sopenharmony_ciThis bug was found with `csmith` and `driver.py`. 24212a9d9c8Sopenharmony_ci 24312a9d9c8Sopenharmony_ci### Input Header 24412a9d9c8Sopenharmony_ci 24512a9d9c8Sopenharmony_ci```c 24612a9d9c8Sopenharmony_ci{} 24712a9d9c8Sopenharmony_ci``` 24812a9d9c8Sopenharmony_ci 24912a9d9c8Sopenharmony_ci### `bindgen` Invocation 25012a9d9c8Sopenharmony_ci 25112a9d9c8Sopenharmony_ci``` 25212a9d9c8Sopenharmony_ci$ {} 25312a9d9c8Sopenharmony_ci``` 25412a9d9c8Sopenharmony_ci 25512a9d9c8Sopenharmony_ci### Actual Results 25612a9d9c8Sopenharmony_ci 25712a9d9c8Sopenharmony_ci<details> 25812a9d9c8Sopenharmony_ci 25912a9d9c8Sopenharmony_ci``` 26012a9d9c8Sopenharmony_ci{} 26112a9d9c8Sopenharmony_ci``` 26212a9d9c8Sopenharmony_ci 26312a9d9c8Sopenharmony_ci</details> 26412a9d9c8Sopenharmony_ci 26512a9d9c8Sopenharmony_ci### Expected Results 26612a9d9c8Sopenharmony_ci 26712a9d9c8Sopenharmony_ci`bindgen` emits bindings OK, then `rustc` compiles those bindings OK, then the 26812a9d9c8Sopenharmony_cicompiled bindings' layout tests pass OK. 26912a9d9c8Sopenharmony_ci 27012a9d9c8Sopenharmony_ci --------------- 8< --------------- 8< --------------- 8< --------------- 27112a9d9c8Sopenharmony_ci 27212a9d9c8Sopenharmony_ci <3 <3 <3 Thank you! <3 <3 <3 27312a9d9c8Sopenharmony_ci """.format( 27412a9d9c8Sopenharmony_ci test_case_contents, 27512a9d9c8Sopenharmony_ci " ".join(map(lambda s: "'{}'".format(s), predicate_command)), 27612a9d9c8Sopenharmony_ci result.stdout + result.stderr 27712a9d9c8Sopenharmony_ci )) 27812a9d9c8Sopenharmony_ci 27912a9d9c8Sopenharmony_ciif __name__ == "__main__": 28012a9d9c8Sopenharmony_ci try: 28112a9d9c8Sopenharmony_ci os.chdir(os.path.abspath(os.path.dirname(sys.argv[0]))) 28212a9d9c8Sopenharmony_ci main() 28312a9d9c8Sopenharmony_ci except KeyboardInterrupt: 28412a9d9c8Sopenharmony_ci exit() 285