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