blob: bdba790ad65674768ec7f85d93df98fb6c737c36 [file] [log] [blame]
George Burgess IV87565fe2019-03-12 17:48:53 +00001#!/usr/bin/env python
2"""Calls C-Reduce to create a minimal reproducer for clang crashes.
3
4Requires C-Reduce and not (part of LLVM utils) to be installed.
5"""
6
7from argparse import ArgumentParser
8import os
9import re
10import stat
11import sys
12import subprocess
13import pipes
14from distutils.spawn import find_executable
15
16def 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
52def 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
117if __name__ == '__main__':
118 sys.exit(main())