112a9d9c8Sopenharmony_ci#!/usr/bin/env python3 212a9d9c8Sopenharmony_ci 312a9d9c8Sopenharmony_ciimport argparse 412a9d9c8Sopenharmony_ciimport os 512a9d9c8Sopenharmony_ciimport re 612a9d9c8Sopenharmony_ciimport shlex 712a9d9c8Sopenharmony_ciimport subprocess 812a9d9c8Sopenharmony_ciimport sys 912a9d9c8Sopenharmony_cifrom tempfile import NamedTemporaryFile 1012a9d9c8Sopenharmony_ci 1112a9d9c8Sopenharmony_ciDESC = """ 1212a9d9c8Sopenharmony_ci 1312a9d9c8Sopenharmony_ciDetermine whether `bindgen` can successfully process a C or C++ input header. 1412a9d9c8Sopenharmony_ci 1512a9d9c8Sopenharmony_ciFirst, `bindgen` is run on the input header. Then the emitted bindings are 1612a9d9c8Sopenharmony_cicompiled with `rustc`. Finally, the compiled bindings' layout tests are run. 1712a9d9c8Sopenharmony_ci 1812a9d9c8Sopenharmony_ciBy default, this script will exit zero if all of the above steps are successful, 1912a9d9c8Sopenharmony_ciand non-zero if any of them fail. This is appropriate for determining if some 2012a9d9c8Sopenharmony_citest case (perhaps generated with `csmith` or another fuzzer) uncovers any bugs 2112a9d9c8Sopenharmony_ciin `bindgen`. 2212a9d9c8Sopenharmony_ci 2312a9d9c8Sopenharmony_ciHowever, this script can also be used when reducing (perhaps with `creduce`) a 2412a9d9c8Sopenharmony_ciknown-bad test case into a new, smaller test case that exhibits the same bad 2512a9d9c8Sopenharmony_cibehavior. In this mode, you might expect that the emitted bindings fail to 2612a9d9c8Sopenharmony_cicompile with `rustc`, and want to exit non-zero early if that is not the 2712a9d9c8Sopenharmony_cicase. See the "reducing arguments" for details and what knobs are available. 2812a9d9c8Sopenharmony_ci 2912a9d9c8Sopenharmony_ci""" 3012a9d9c8Sopenharmony_ci 3112a9d9c8Sopenharmony_ciparser = argparse.ArgumentParser( 3212a9d9c8Sopenharmony_ci formatter_class=argparse.RawDescriptionHelpFormatter, 3312a9d9c8Sopenharmony_ci description=DESC.strip()) 3412a9d9c8Sopenharmony_ci 3512a9d9c8Sopenharmony_ciBINDGEN_ARGS = "--with-derive-partialeq \ 3612a9d9c8Sopenharmony_ci--with-derive-eq \ 3712a9d9c8Sopenharmony_ci--with-derive-partialord \ 3812a9d9c8Sopenharmony_ci--with-derive-ord \ 3912a9d9c8Sopenharmony_ci--with-derive-hash \ 4012a9d9c8Sopenharmony_ci--with-derive-default" 4112a9d9c8Sopenharmony_ci 4212a9d9c8Sopenharmony_ciparser.add_argument( 4312a9d9c8Sopenharmony_ci "--bindgen-args", 4412a9d9c8Sopenharmony_ci type=str, 4512a9d9c8Sopenharmony_ci default=BINDGEN_ARGS, 4612a9d9c8Sopenharmony_ci help="An argument string that `bindgen` should be invoked with. By default, all traits are derived. Note that the input header and output bindings file will automatically be provided by this script, and you should not manually specify them.") 4712a9d9c8Sopenharmony_ci 4812a9d9c8Sopenharmony_ciparser.add_argument( 4912a9d9c8Sopenharmony_ci "--save-temp-files", 5012a9d9c8Sopenharmony_ci action="store_true", 5112a9d9c8Sopenharmony_ci help="Do not delete temporary files.") 5212a9d9c8Sopenharmony_ci 5312a9d9c8Sopenharmony_ciparser.add_argument( 5412a9d9c8Sopenharmony_ci "input", 5512a9d9c8Sopenharmony_ci type=str, 5612a9d9c8Sopenharmony_ci default="input.h", 5712a9d9c8Sopenharmony_ci help="The input header file. Defaults to 'input.h'.") 5812a9d9c8Sopenharmony_ci 5912a9d9c8Sopenharmony_ciREDUCING_DESC = """ 6012a9d9c8Sopenharmony_ci 6112a9d9c8Sopenharmony_ciArguments that are useful when reducing known-bad test cases into 6212a9d9c8Sopenharmony_ciequivalent-but-smaller test cases that exhibit the same bug with `creduce`. 6312a9d9c8Sopenharmony_ci 6412a9d9c8Sopenharmony_ci""" 6512a9d9c8Sopenharmony_ci 6612a9d9c8Sopenharmony_cireducing = parser.add_argument_group("reducing arguments", REDUCING_DESC.strip()) 6712a9d9c8Sopenharmony_ci 6812a9d9c8Sopenharmony_cireducing.add_argument( 6912a9d9c8Sopenharmony_ci "--release", 7012a9d9c8Sopenharmony_ci action="store_true", 7112a9d9c8Sopenharmony_ci help="Use a release instead of a debug build.") 7212a9d9c8Sopenharmony_cireducing.add_argument( 7312a9d9c8Sopenharmony_ci "--expect-bindgen-fail", 7412a9d9c8Sopenharmony_ci action="store_true", 7512a9d9c8Sopenharmony_ci help="Exit non-zero if `bindgen` successfully emits bindings.") 7612a9d9c8Sopenharmony_cireducing.add_argument( 7712a9d9c8Sopenharmony_ci "--bindgen-grep", 7812a9d9c8Sopenharmony_ci type=str, 7912a9d9c8Sopenharmony_ci help="Exit non-zero if the given regexp pattern is not found in `bindgen`'s output.") 8012a9d9c8Sopenharmony_cireducing.add_argument( 8112a9d9c8Sopenharmony_ci "--bindings-grep", 8212a9d9c8Sopenharmony_ci type=str, 8312a9d9c8Sopenharmony_ci nargs='*', 8412a9d9c8Sopenharmony_ci help="Exit non-zero if the given regexp pattern is not found in the emitted bindings.") 8512a9d9c8Sopenharmony_ci 8612a9d9c8Sopenharmony_cireducing.add_argument( 8712a9d9c8Sopenharmony_ci "--no-compile-bindings", 8812a9d9c8Sopenharmony_ci action="store_false", 8912a9d9c8Sopenharmony_ci dest="rustc", 9012a9d9c8Sopenharmony_ci help="Do not attempt to compile the emitted bindings with `rustc`.") 9112a9d9c8Sopenharmony_cireducing.add_argument( 9212a9d9c8Sopenharmony_ci "--extra-compile-file", 9312a9d9c8Sopenharmony_ci type=str, 9412a9d9c8Sopenharmony_ci help="Append the content of this extra file to the end of the emitted bindings just before compiling it.") 9512a9d9c8Sopenharmony_cireducing.add_argument( 9612a9d9c8Sopenharmony_ci "--expect-compile-fail", 9712a9d9c8Sopenharmony_ci action="store_true", 9812a9d9c8Sopenharmony_ci help="Exit non-zero if `rustc` successfully compiles the emitted bindings.") 9912a9d9c8Sopenharmony_cireducing.add_argument( 10012a9d9c8Sopenharmony_ci "--rustc-grep", 10112a9d9c8Sopenharmony_ci type=str, 10212a9d9c8Sopenharmony_ci help="Exit non-zero if the output from compiling the bindings with `rustc` does not contain the given regexp pattern") 10312a9d9c8Sopenharmony_ci 10412a9d9c8Sopenharmony_cireducing.add_argument( 10512a9d9c8Sopenharmony_ci "--no-layout-tests", 10612a9d9c8Sopenharmony_ci action="store_false", 10712a9d9c8Sopenharmony_ci dest="layout_tests", 10812a9d9c8Sopenharmony_ci help="Do not run the compiled bindings' layout tests.") 10912a9d9c8Sopenharmony_cireducing.add_argument( 11012a9d9c8Sopenharmony_ci "--expect-layout-tests-fail", 11112a9d9c8Sopenharmony_ci action="store_true", 11212a9d9c8Sopenharmony_ci help="Exit non-zero if the compiled bindings' layout tests pass.") 11312a9d9c8Sopenharmony_cireducing.add_argument( 11412a9d9c8Sopenharmony_ci "--layout-tests-grep", 11512a9d9c8Sopenharmony_ci type=str, 11612a9d9c8Sopenharmony_ci help="Exit non-zero if the output of running the compiled bindings' layout tests does not contain the given regexp pattern.") 11712a9d9c8Sopenharmony_ci 11812a9d9c8Sopenharmony_ci################################################################################ 11912a9d9c8Sopenharmony_ci 12012a9d9c8Sopenharmony_ciclass ExitOne(Exception): 12112a9d9c8Sopenharmony_ci pass 12212a9d9c8Sopenharmony_ci 12312a9d9c8Sopenharmony_cidef exit_1(msg, child=None): 12412a9d9c8Sopenharmony_ci print(msg) 12512a9d9c8Sopenharmony_ci 12612a9d9c8Sopenharmony_ci if child: 12712a9d9c8Sopenharmony_ci stdout = decode(child.stdout) 12812a9d9c8Sopenharmony_ci for line in stdout.splitlines(): 12912a9d9c8Sopenharmony_ci sys.stdout.write("+") 13012a9d9c8Sopenharmony_ci sys.stdout.write(line) 13112a9d9c8Sopenharmony_ci sys.stdout.write("\n") 13212a9d9c8Sopenharmony_ci stderr = decode(child.stderr) 13312a9d9c8Sopenharmony_ci for line in stderr.splitlines(): 13412a9d9c8Sopenharmony_ci sys.stderr.write("+") 13512a9d9c8Sopenharmony_ci sys.stderr.write(line) 13612a9d9c8Sopenharmony_ci sys.stderr.write("\n") 13712a9d9c8Sopenharmony_ci 13812a9d9c8Sopenharmony_ci raise ExitOne() 13912a9d9c8Sopenharmony_ci 14012a9d9c8Sopenharmony_cidef main(): 14112a9d9c8Sopenharmony_ci args = parser.parse_args() 14212a9d9c8Sopenharmony_ci os.environ["RUST_BACKTRACE"] = "full" 14312a9d9c8Sopenharmony_ci 14412a9d9c8Sopenharmony_ci exit_code = 0 14512a9d9c8Sopenharmony_ci try: 14612a9d9c8Sopenharmony_ci bindings = new_temp_file(prefix="bindings-", suffix=".rs") 14712a9d9c8Sopenharmony_ci run_bindgen(args, bindings) 14812a9d9c8Sopenharmony_ci 14912a9d9c8Sopenharmony_ci if args.rustc and not args.expect_bindgen_fail: 15012a9d9c8Sopenharmony_ci test_exe = new_temp_file(prefix="layout-tests-") 15112a9d9c8Sopenharmony_ci run_rustc(args, bindings, test_exe) 15212a9d9c8Sopenharmony_ci 15312a9d9c8Sopenharmony_ci if args.layout_tests and not args.expect_compile_fail: 15412a9d9c8Sopenharmony_ci run_layout_tests(args, test_exe) 15512a9d9c8Sopenharmony_ci except ExitOne: 15612a9d9c8Sopenharmony_ci exit_code = 1 15712a9d9c8Sopenharmony_ci except Exception as e: 15812a9d9c8Sopenharmony_ci exit_code = 2 15912a9d9c8Sopenharmony_ci print("Unexpected exception:", e) 16012a9d9c8Sopenharmony_ci 16112a9d9c8Sopenharmony_ci if not args.save_temp_files: 16212a9d9c8Sopenharmony_ci for path in TEMP_FILES: 16312a9d9c8Sopenharmony_ci try: 16412a9d9c8Sopenharmony_ci os.remove(path) 16512a9d9c8Sopenharmony_ci except Exception as e: 16612a9d9c8Sopenharmony_ci print("Unexpected exception:", e) 16712a9d9c8Sopenharmony_ci 16812a9d9c8Sopenharmony_ci sys.exit(exit_code) 16912a9d9c8Sopenharmony_ci 17012a9d9c8Sopenharmony_cidef run(cmd, **kwargs): 17112a9d9c8Sopenharmony_ci print("Running:", cmd) 17212a9d9c8Sopenharmony_ci return subprocess.run(cmd, **kwargs) 17312a9d9c8Sopenharmony_ci 17412a9d9c8Sopenharmony_cidef decode(f): 17512a9d9c8Sopenharmony_ci return f.decode(encoding="utf-8", errors="ignore") 17612a9d9c8Sopenharmony_ci 17712a9d9c8Sopenharmony_ciTEMP_FILES = [] 17812a9d9c8Sopenharmony_ci 17912a9d9c8Sopenharmony_cidef new_temp_file(prefix, suffix=None): 18012a9d9c8Sopenharmony_ci temp = NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) 18112a9d9c8Sopenharmony_ci temp.close() 18212a9d9c8Sopenharmony_ci TEMP_FILES.append(temp.name) 18312a9d9c8Sopenharmony_ci return temp.name 18412a9d9c8Sopenharmony_ci 18512a9d9c8Sopenharmony_cidef contains(pattern, lines): 18612a9d9c8Sopenharmony_ci for line in lines: 18712a9d9c8Sopenharmony_ci if re.match(pattern, line): 18812a9d9c8Sopenharmony_ci return True 18912a9d9c8Sopenharmony_ci return False 19012a9d9c8Sopenharmony_ci 19112a9d9c8Sopenharmony_cidef regexp(pattern): 19212a9d9c8Sopenharmony_ci if not pattern.startswith("^"): 19312a9d9c8Sopenharmony_ci pattern = ".*" + pattern 19412a9d9c8Sopenharmony_ci if not pattern.endswith("$"): 19512a9d9c8Sopenharmony_ci pattern = pattern + ".*" 19612a9d9c8Sopenharmony_ci return re.compile(pattern) 19712a9d9c8Sopenharmony_ci 19812a9d9c8Sopenharmony_cidef run_bindgen(args, bindings): 19912a9d9c8Sopenharmony_ci manifest_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), 20012a9d9c8Sopenharmony_ci "..", 20112a9d9c8Sopenharmony_ci "Cargo.toml")) 20212a9d9c8Sopenharmony_ci command = ["cargo", "run", "--manifest-path", manifest_path] 20312a9d9c8Sopenharmony_ci if args.release: 20412a9d9c8Sopenharmony_ci command += ["--release"] 20512a9d9c8Sopenharmony_ci command += ["--", args.input, "-o", bindings] 20612a9d9c8Sopenharmony_ci command += shlex.split(args.bindgen_args) 20712a9d9c8Sopenharmony_ci 20812a9d9c8Sopenharmony_ci child = run(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE) 20912a9d9c8Sopenharmony_ci 21012a9d9c8Sopenharmony_ci if args.bindgen_grep: 21112a9d9c8Sopenharmony_ci pattern = regexp(args.bindgen_grep) 21212a9d9c8Sopenharmony_ci if not (contains(pattern, decode(child.stdout).splitlines()) or 21312a9d9c8Sopenharmony_ci contains(pattern, decode(child.stderr).splitlines())): 21412a9d9c8Sopenharmony_ci exit_1("Error: did not find '{}' in `bindgen`'s output".format(args.bindgen_grep), child) 21512a9d9c8Sopenharmony_ci 21612a9d9c8Sopenharmony_ci if args.expect_bindgen_fail and child.returncode == 0: 21712a9d9c8Sopenharmony_ci exit_1("Error: expected running `bindgen` to fail, but it didn't", child) 21812a9d9c8Sopenharmony_ci 21912a9d9c8Sopenharmony_ci if not args.expect_bindgen_fail and child.returncode != 0: 22012a9d9c8Sopenharmony_ci exit_1("Error: running `bindgen` failed", child) 22112a9d9c8Sopenharmony_ci 22212a9d9c8Sopenharmony_ci for arg in args.bindings_grep or []: 22312a9d9c8Sopenharmony_ci pattern = regexp(arg) 22412a9d9c8Sopenharmony_ci with open(bindings, mode="r") as f: 22512a9d9c8Sopenharmony_ci if not contains(pattern, f): 22612a9d9c8Sopenharmony_ci print("Error: expected the emitted bindings to contain '{}', but they didn't".format(arg)) 22712a9d9c8Sopenharmony_ci print("---------- {} ----------------------------------------------".format(bindings)) 22812a9d9c8Sopenharmony_ci f.seek(0) 22912a9d9c8Sopenharmony_ci print(f.read()) 23012a9d9c8Sopenharmony_ci raise ExitOne() 23112a9d9c8Sopenharmony_ci 23212a9d9c8Sopenharmony_cidef run_rustc(args, bindings, test_exe): 23312a9d9c8Sopenharmony_ci if args.extra_compile_file: 23412a9d9c8Sopenharmony_ci with open(bindings, mode="a") as outfile: 23512a9d9c8Sopenharmony_ci with open(args.extra_compile_file, mode="r") as infile: 23612a9d9c8Sopenharmony_ci outfile.write(infile.read()) 23712a9d9c8Sopenharmony_ci child = run( 23812a9d9c8Sopenharmony_ci ["rustc", "--crate-type", "lib", "--test", "-o", test_exe, bindings], 23912a9d9c8Sopenharmony_ci stdout=subprocess.PIPE, 24012a9d9c8Sopenharmony_ci stderr=subprocess.PIPE) 24112a9d9c8Sopenharmony_ci 24212a9d9c8Sopenharmony_ci if args.rustc_grep: 24312a9d9c8Sopenharmony_ci pattern = regexp(args.rustc_grep) 24412a9d9c8Sopenharmony_ci if not (contains(pattern, decode(child.stdout).splitlines()) or 24512a9d9c8Sopenharmony_ci contains(pattern, decode(child.stderr).splitlines())): 24612a9d9c8Sopenharmony_ci exit_1("Error: did not find '{}' in `rustc`'s output".format(args.rustc_grep), child) 24712a9d9c8Sopenharmony_ci 24812a9d9c8Sopenharmony_ci if args.expect_compile_fail and child.returncode == 0: 24912a9d9c8Sopenharmony_ci exit_1("Error: expected running `rustc` on the emitted bindings to fail, but it didn't", child) 25012a9d9c8Sopenharmony_ci 25112a9d9c8Sopenharmony_ci if not args.expect_compile_fail and child.returncode != 0: 25212a9d9c8Sopenharmony_ci exit_1("Error: running `rustc` on the emitted bindings failed", child) 25312a9d9c8Sopenharmony_ci 25412a9d9c8Sopenharmony_cidef run_layout_tests(args, test_exe): 25512a9d9c8Sopenharmony_ci child = run( 25612a9d9c8Sopenharmony_ci [test_exe], 25712a9d9c8Sopenharmony_ci stdout=subprocess.PIPE, 25812a9d9c8Sopenharmony_ci stderr=subprocess.PIPE) 25912a9d9c8Sopenharmony_ci 26012a9d9c8Sopenharmony_ci if args.layout_tests_grep: 26112a9d9c8Sopenharmony_ci pattern = regexp(args.layout_tests_grep) 26212a9d9c8Sopenharmony_ci if not (contains(pattern, decode(child.stdout).splitlines()) or 26312a9d9c8Sopenharmony_ci contains(pattern, decode(child.stderr).splitlines())): 26412a9d9c8Sopenharmony_ci exit_1("Error: did not find '{}' in the compiled bindings' layout tests' output".format(args.layout_tests_grep), child) 26512a9d9c8Sopenharmony_ci 26612a9d9c8Sopenharmony_ci if args.expect_layout_tests_fail and child.returncode == 0: 26712a9d9c8Sopenharmony_ci exit_1("Error: expected running the compiled bindings' layout tests to fail, but it didn't", child) 26812a9d9c8Sopenharmony_ci 26912a9d9c8Sopenharmony_ci if not args.expect_layout_tests_fail and child.returncode != 0: 27012a9d9c8Sopenharmony_ci exit_1("Error: running the compiled bindings' layout tests failed", child) 27112a9d9c8Sopenharmony_ci 27212a9d9c8Sopenharmony_ciif __name__ == "__main__": 27312a9d9c8Sopenharmony_ci main() 274