George Burgess IV | 87565fe | 2019-03-12 17:48:53 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | """Calls C-Reduce to create a minimal reproducer for clang crashes. |
| 3 | |
| 4 | Requires C-Reduce and not (part of LLVM utils) to be installed. |
| 5 | """ |
| 6 | |
| 7 | from argparse import ArgumentParser |
| 8 | import os |
| 9 | import re |
| 10 | import stat |
| 11 | import sys |
| 12 | import subprocess |
| 13 | import pipes |
| 14 | from distutils.spawn import find_executable |
| 15 | |
| 16 | def create_test(build_script, llvm_not): |
| 17 | """ |
| 18 | Create an interestingness test from the crash output. |
| 19 | Return as a string. |
| 20 | """ |
| 21 | # Get clang call from build script |
| 22 | # Assumes the call is the last line of the script |
| 23 | with open(build_script) as f: |
| 24 | cmd = f.readlines()[-1].rstrip('\n\r') |
| 25 | |
| 26 | # Get crash output |
| 27 | p = subprocess.Popen(build_script, |
| 28 | stdout=subprocess.PIPE, |
| 29 | stderr=subprocess.STDOUT) |
| 30 | crash_output, _ = p.communicate() |
| 31 | |
| 32 | output = ['#!/bin/bash'] |
| 33 | output.append('%s --crash %s >& t.log || exit 1' % (pipes.quote(llvm_not), |
| 34 | cmd)) |
| 35 | |
| 36 | # Add messages from crash output to the test |
| 37 | # If there is an Assertion failure, use that; otherwise use the |
| 38 | # last five stack trace functions |
| 39 | assertion_re = r'Assertion `([^\']+)\' failed' |
| 40 | assertion_match = re.search(assertion_re, crash_output) |
| 41 | if assertion_match: |
| 42 | msg = assertion_match.group(1) |
| 43 | output.append('grep %s t.log || exit 1' % pipes.quote(msg)) |
| 44 | else: |
| 45 | stacktrace_re = r'#[0-9]+\s+0[xX][0-9a-fA-F]+\s*([^(]+)\(' |
| 46 | matches = re.findall(stacktrace_re, crash_output) |
| 47 | del matches[:-5] |
| 48 | output += ['grep %s t.log || exit 1' % pipes.quote(msg) for msg in matches] |
| 49 | |
| 50 | return output |
| 51 | |
| 52 | def main(): |
| 53 | parser = ArgumentParser(description=__doc__) |
| 54 | parser.add_argument('build_script', type=str, nargs=1, |
| 55 | help='Name of the script that generates the crash.') |
| 56 | parser.add_argument('file_to_reduce', type=str, nargs=1, |
| 57 | help='Name of the file to be reduced.') |
| 58 | parser.add_argument('-o', '--output', dest='output', type=str, |
| 59 | help='Name of the output file for the reduction. Optional.') |
| 60 | parser.add_argument('--llvm-not', dest='llvm_not', type=str, |
| 61 | help="The path to the llvm-not executable. " |
| 62 | "Required if 'not' is not in PATH environment."); |
| 63 | parser.add_argument('--creduce', dest='creduce', type=str, |
| 64 | help="The path to the C-Reduce executable. " |
| 65 | "Required if 'creduce' is not in PATH environment."); |
| 66 | args = parser.parse_args() |
| 67 | |
| 68 | build_script = os.path.abspath(args.build_script[0]) |
| 69 | file_to_reduce = os.path.abspath(args.file_to_reduce[0]) |
| 70 | llvm_not = (find_executable(args.llvm_not) if args.llvm_not else |
| 71 | find_executable('not')) |
| 72 | creduce = (find_executable(args.creduce) if args.creduce else |
| 73 | find_executable('creduce')) |
| 74 | |
| 75 | if not os.path.isfile(build_script): |
| 76 | print(("ERROR: input file '%s' does not exist") % build_script) |
| 77 | return 1 |
| 78 | |
| 79 | if not os.path.isfile(file_to_reduce): |
| 80 | print(("ERROR: input file '%s' does not exist") % file_to_reduce) |
| 81 | return 1 |
| 82 | |
| 83 | if not llvm_not: |
| 84 | parser.print_help() |
| 85 | return 1 |
| 86 | |
| 87 | if not creduce: |
| 88 | parser.print_help() |
| 89 | return 1 |
| 90 | |
| 91 | # Write interestingness test to file |
| 92 | test_contents = create_test(build_script, llvm_not) |
| 93 | testname, _ = os.path.splitext(file_to_reduce) |
| 94 | testfile = testname + '.test.sh' |
| 95 | with open(testfile, 'w') as f: |
| 96 | f.write('\n'.join(test_contents)) |
| 97 | os.chmod(testfile, os.stat(testfile).st_mode | stat.S_IEXEC) |
| 98 | |
| 99 | # Confirm that the interestingness test passes |
| 100 | try: |
| 101 | with open(os.devnull, 'w') as devnull: |
| 102 | subprocess.check_call(testfile, stdout=devnull) |
| 103 | except subprocess.CalledProcessError: |
| 104 | print("For some reason the interestingness test does not return zero") |
| 105 | return 1 |
| 106 | |
| 107 | # FIXME: try running clang preprocessor first |
| 108 | |
| 109 | try: |
| 110 | p = subprocess.Popen([creduce, testfile, file_to_reduce]) |
| 111 | p.communicate() |
| 112 | except KeyboardInterrupt: |
| 113 | # Hack to kill C-Reduce because it jumps into its own pgid |
| 114 | print('\n\nctrl-c detected, killed creduce') |
| 115 | p.kill() |
| 116 | |
| 117 | if __name__ == '__main__': |
| 118 | sys.exit(main()) |