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