annotate clang/utils/creduce-clang-crash.py @ 206:f17a3b42b08b

Added tag before-12 for changeset b7591485f4cd
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Mon, 07 Jun 2021 21:25:57 +0900
parents 1d019706d866
children 2e18cbf3894f
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
150
anatofuz
parents:
diff changeset
1 #!/usr/bin/env python
anatofuz
parents:
diff changeset
2 """Calls C-Reduce to create a minimal reproducer for clang crashes.
anatofuz
parents:
diff changeset
3
anatofuz
parents:
diff changeset
4 Output files:
anatofuz
parents:
diff changeset
5 *.reduced.sh -- crash reproducer with minimal arguments
anatofuz
parents:
diff changeset
6 *.reduced.cpp -- the reduced file
anatofuz
parents:
diff changeset
7 *.test.sh -- interestingness test for C-Reduce
anatofuz
parents:
diff changeset
8 """
anatofuz
parents:
diff changeset
9
anatofuz
parents:
diff changeset
10 from __future__ import print_function
anatofuz
parents:
diff changeset
11 from argparse import ArgumentParser, RawTextHelpFormatter
anatofuz
parents:
diff changeset
12 import os
anatofuz
parents:
diff changeset
13 import re
anatofuz
parents:
diff changeset
14 import stat
anatofuz
parents:
diff changeset
15 import sys
anatofuz
parents:
diff changeset
16 import subprocess
anatofuz
parents:
diff changeset
17 import pipes
anatofuz
parents:
diff changeset
18 import shlex
anatofuz
parents:
diff changeset
19 import tempfile
anatofuz
parents:
diff changeset
20 import shutil
anatofuz
parents:
diff changeset
21 from distutils.spawn import find_executable
anatofuz
parents:
diff changeset
22
anatofuz
parents:
diff changeset
23 verbose = False
anatofuz
parents:
diff changeset
24 creduce_cmd = None
anatofuz
parents:
diff changeset
25 clang_cmd = None
anatofuz
parents:
diff changeset
26 not_cmd = None
anatofuz
parents:
diff changeset
27
anatofuz
parents:
diff changeset
28 def verbose_print(*args, **kwargs):
anatofuz
parents:
diff changeset
29 if verbose:
anatofuz
parents:
diff changeset
30 print(*args, **kwargs)
anatofuz
parents:
diff changeset
31
anatofuz
parents:
diff changeset
32 def check_file(fname):
anatofuz
parents:
diff changeset
33 fname = os.path.normpath(fname)
anatofuz
parents:
diff changeset
34 if not os.path.isfile(fname):
anatofuz
parents:
diff changeset
35 sys.exit("ERROR: %s does not exist" % (fname))
anatofuz
parents:
diff changeset
36 return fname
anatofuz
parents:
diff changeset
37
anatofuz
parents:
diff changeset
38 def check_cmd(cmd_name, cmd_dir, cmd_path=None):
anatofuz
parents:
diff changeset
39 """
anatofuz
parents:
diff changeset
40 Returns absolute path to cmd_path if it is given,
anatofuz
parents:
diff changeset
41 or absolute path to cmd_dir/cmd_name.
anatofuz
parents:
diff changeset
42 """
anatofuz
parents:
diff changeset
43 if cmd_path:
anatofuz
parents:
diff changeset
44 # Make the path absolute so the creduce test can be run from any directory.
anatofuz
parents:
diff changeset
45 cmd_path = os.path.abspath(cmd_path)
anatofuz
parents:
diff changeset
46 cmd = find_executable(cmd_path)
anatofuz
parents:
diff changeset
47 if cmd:
anatofuz
parents:
diff changeset
48 return cmd
anatofuz
parents:
diff changeset
49 sys.exit("ERROR: executable `%s` not found" % (cmd_path))
anatofuz
parents:
diff changeset
50
anatofuz
parents:
diff changeset
51 cmd = find_executable(cmd_name, path=cmd_dir)
anatofuz
parents:
diff changeset
52 if cmd:
anatofuz
parents:
diff changeset
53 return cmd
anatofuz
parents:
diff changeset
54
anatofuz
parents:
diff changeset
55 if not cmd_dir:
anatofuz
parents:
diff changeset
56 cmd_dir = "$PATH"
anatofuz
parents:
diff changeset
57 sys.exit("ERROR: `%s` not found in %s" % (cmd_name, cmd_dir))
anatofuz
parents:
diff changeset
58
anatofuz
parents:
diff changeset
59 def quote_cmd(cmd):
anatofuz
parents:
diff changeset
60 return ' '.join(pipes.quote(arg) for arg in cmd)
anatofuz
parents:
diff changeset
61
anatofuz
parents:
diff changeset
62 def write_to_script(text, filename):
anatofuz
parents:
diff changeset
63 with open(filename, 'w') as f:
anatofuz
parents:
diff changeset
64 f.write(text)
anatofuz
parents:
diff changeset
65 os.chmod(filename, os.stat(filename).st_mode | stat.S_IEXEC)
anatofuz
parents:
diff changeset
66
anatofuz
parents:
diff changeset
67 class Reduce(object):
anatofuz
parents:
diff changeset
68 def __init__(self, crash_script, file_to_reduce):
anatofuz
parents:
diff changeset
69 crash_script_name, crash_script_ext = os.path.splitext(crash_script)
anatofuz
parents:
diff changeset
70 file_reduce_name, file_reduce_ext = os.path.splitext(file_to_reduce)
anatofuz
parents:
diff changeset
71
anatofuz
parents:
diff changeset
72 self.testfile = file_reduce_name + '.test.sh'
anatofuz
parents:
diff changeset
73 self.crash_script = crash_script_name + '.reduced' + crash_script_ext
anatofuz
parents:
diff changeset
74 self.file_to_reduce = file_reduce_name + '.reduced' + file_reduce_ext
anatofuz
parents:
diff changeset
75 shutil.copy(file_to_reduce, self.file_to_reduce)
anatofuz
parents:
diff changeset
76
anatofuz
parents:
diff changeset
77 self.clang = clang_cmd
anatofuz
parents:
diff changeset
78 self.clang_args = []
anatofuz
parents:
diff changeset
79 self.expected_output = []
anatofuz
parents:
diff changeset
80 self.is_crash = True
anatofuz
parents:
diff changeset
81 self.creduce_flags = ["--tidy"]
anatofuz
parents:
diff changeset
82
anatofuz
parents:
diff changeset
83 self.read_clang_args(crash_script, file_to_reduce)
anatofuz
parents:
diff changeset
84 self.read_expected_output()
anatofuz
parents:
diff changeset
85
anatofuz
parents:
diff changeset
86 def get_crash_cmd(self, cmd=None, args=None, filename=None):
anatofuz
parents:
diff changeset
87 if not cmd:
anatofuz
parents:
diff changeset
88 cmd = self.clang
anatofuz
parents:
diff changeset
89 if not args:
anatofuz
parents:
diff changeset
90 args = self.clang_args
anatofuz
parents:
diff changeset
91 if not filename:
anatofuz
parents:
diff changeset
92 filename = self.file_to_reduce
anatofuz
parents:
diff changeset
93
anatofuz
parents:
diff changeset
94 return [cmd] + args + [filename]
anatofuz
parents:
diff changeset
95
anatofuz
parents:
diff changeset
96 def read_clang_args(self, crash_script, filename):
anatofuz
parents:
diff changeset
97 print("\nReading arguments from crash script...")
anatofuz
parents:
diff changeset
98 with open(crash_script) as f:
anatofuz
parents:
diff changeset
99 # Assume clang call is the first non comment line.
anatofuz
parents:
diff changeset
100 cmd = []
anatofuz
parents:
diff changeset
101 for line in f:
anatofuz
parents:
diff changeset
102 if not line.lstrip().startswith('#'):
anatofuz
parents:
diff changeset
103 cmd = shlex.split(line)
anatofuz
parents:
diff changeset
104 break
anatofuz
parents:
diff changeset
105 if not cmd:
anatofuz
parents:
diff changeset
106 sys.exit("Could not find command in the crash script.");
anatofuz
parents:
diff changeset
107
anatofuz
parents:
diff changeset
108 # Remove clang and filename from the command
anatofuz
parents:
diff changeset
109 # Assume the last occurrence of the filename is the clang input file
anatofuz
parents:
diff changeset
110 del cmd[0]
anatofuz
parents:
diff changeset
111 for i in range(len(cmd)-1, -1, -1):
anatofuz
parents:
diff changeset
112 if cmd[i] == filename:
anatofuz
parents:
diff changeset
113 del cmd[i]
anatofuz
parents:
diff changeset
114 break
anatofuz
parents:
diff changeset
115 self.clang_args = cmd
anatofuz
parents:
diff changeset
116 verbose_print("Clang arguments:", quote_cmd(self.clang_args))
anatofuz
parents:
diff changeset
117
anatofuz
parents:
diff changeset
118 def read_expected_output(self):
anatofuz
parents:
diff changeset
119 print("\nGetting expected crash output...")
anatofuz
parents:
diff changeset
120 p = subprocess.Popen(self.get_crash_cmd(),
anatofuz
parents:
diff changeset
121 stdout=subprocess.PIPE,
anatofuz
parents:
diff changeset
122 stderr=subprocess.STDOUT)
anatofuz
parents:
diff changeset
123 crash_output, _ = p.communicate()
anatofuz
parents:
diff changeset
124 result = []
anatofuz
parents:
diff changeset
125
anatofuz
parents:
diff changeset
126 # Remove color codes
anatofuz
parents:
diff changeset
127 ansi_escape = r'\x1b\[[0-?]*m'
anatofuz
parents:
diff changeset
128 crash_output = re.sub(ansi_escape, '', crash_output.decode('utf-8'))
anatofuz
parents:
diff changeset
129
anatofuz
parents:
diff changeset
130 # Look for specific error messages
anatofuz
parents:
diff changeset
131 regexes = [r"Assertion `(.+)' failed", # Linux assert()
anatofuz
parents:
diff changeset
132 r"Assertion failed: (.+),", # FreeBSD/Mac assert()
anatofuz
parents:
diff changeset
133 r"fatal error: error in backend: (.+)",
anatofuz
parents:
diff changeset
134 r"LLVM ERROR: (.+)",
anatofuz
parents:
diff changeset
135 r"UNREACHABLE executed (at .+)?!",
anatofuz
parents:
diff changeset
136 r"LLVM IR generation of declaration '(.+)'",
anatofuz
parents:
diff changeset
137 r"Generating code for declaration '(.+)'",
anatofuz
parents:
diff changeset
138 r"\*\*\* Bad machine code: (.+) \*\*\*"]
anatofuz
parents:
diff changeset
139 for msg_re in regexes:
anatofuz
parents:
diff changeset
140 match = re.search(msg_re, crash_output)
anatofuz
parents:
diff changeset
141 if match:
anatofuz
parents:
diff changeset
142 msg = match.group(1)
anatofuz
parents:
diff changeset
143 result = [msg]
anatofuz
parents:
diff changeset
144 print("Found message:", msg)
anatofuz
parents:
diff changeset
145
anatofuz
parents:
diff changeset
146 if "fatal error:" in msg_re:
anatofuz
parents:
diff changeset
147 self.is_crash = False
anatofuz
parents:
diff changeset
148 break
anatofuz
parents:
diff changeset
149
anatofuz
parents:
diff changeset
150 # If no message was found, use the top five stack trace functions,
anatofuz
parents:
diff changeset
151 # ignoring some common functions
anatofuz
parents:
diff changeset
152 # Five is a somewhat arbitrary number; the goal is to get a small number
anatofuz
parents:
diff changeset
153 # of identifying functions with some leeway for common functions
anatofuz
parents:
diff changeset
154 if not result:
anatofuz
parents:
diff changeset
155 stacktrace_re = r'[0-9]+\s+0[xX][0-9a-fA-F]+\s*([^(]+)\('
anatofuz
parents:
diff changeset
156 filters = ["PrintStackTraceSignalHandler",
anatofuz
parents:
diff changeset
157 "llvm::sys::RunSignalHandlers",
anatofuz
parents:
diff changeset
158 "SignalHandler", "__restore_rt", "gsignal", "abort"]
anatofuz
parents:
diff changeset
159 matches = re.findall(stacktrace_re, crash_output)
anatofuz
parents:
diff changeset
160 result = [x for x in matches if x and x.strip() not in filters][:5]
anatofuz
parents:
diff changeset
161 for msg in result:
anatofuz
parents:
diff changeset
162 print("Found stack trace function:", msg)
anatofuz
parents:
diff changeset
163
anatofuz
parents:
diff changeset
164 if not result:
anatofuz
parents:
diff changeset
165 print("ERROR: no crash was found")
anatofuz
parents:
diff changeset
166 print("The crash output was:\n========\n%s========" % crash_output)
anatofuz
parents:
diff changeset
167 sys.exit(1)
anatofuz
parents:
diff changeset
168
anatofuz
parents:
diff changeset
169 self.expected_output = result
anatofuz
parents:
diff changeset
170
anatofuz
parents:
diff changeset
171 def check_expected_output(self, args=None, filename=None):
anatofuz
parents:
diff changeset
172 if not args:
anatofuz
parents:
diff changeset
173 args = self.clang_args
anatofuz
parents:
diff changeset
174 if not filename:
anatofuz
parents:
diff changeset
175 filename = self.file_to_reduce
anatofuz
parents:
diff changeset
176
anatofuz
parents:
diff changeset
177 p = subprocess.Popen(self.get_crash_cmd(args=args, filename=filename),
anatofuz
parents:
diff changeset
178 stdout=subprocess.PIPE,
anatofuz
parents:
diff changeset
179 stderr=subprocess.STDOUT)
anatofuz
parents:
diff changeset
180 crash_output, _ = p.communicate()
anatofuz
parents:
diff changeset
181 return all(msg in crash_output.decode('utf-8') for msg in
anatofuz
parents:
diff changeset
182 self.expected_output)
anatofuz
parents:
diff changeset
183
anatofuz
parents:
diff changeset
184 def write_interestingness_test(self):
anatofuz
parents:
diff changeset
185 print("\nCreating the interestingness test...")
anatofuz
parents:
diff changeset
186
anatofuz
parents:
diff changeset
187 crash_flag = "--crash" if self.is_crash else ""
anatofuz
parents:
diff changeset
188
anatofuz
parents:
diff changeset
189 output = "#!/bin/bash\n%s %s %s >& t.log || exit 1\n" % \
anatofuz
parents:
diff changeset
190 (pipes.quote(not_cmd), crash_flag, quote_cmd(self.get_crash_cmd()))
anatofuz
parents:
diff changeset
191
anatofuz
parents:
diff changeset
192 for msg in self.expected_output:
anatofuz
parents:
diff changeset
193 output += 'grep -F %s t.log || exit 1\n' % pipes.quote(msg)
anatofuz
parents:
diff changeset
194
anatofuz
parents:
diff changeset
195 write_to_script(output, self.testfile)
anatofuz
parents:
diff changeset
196 self.check_interestingness()
anatofuz
parents:
diff changeset
197
anatofuz
parents:
diff changeset
198 def check_interestingness(self):
anatofuz
parents:
diff changeset
199 testfile = os.path.abspath(self.testfile)
anatofuz
parents:
diff changeset
200
anatofuz
parents:
diff changeset
201 # Check that the test considers the original file interesting
anatofuz
parents:
diff changeset
202 with open(os.devnull, 'w') as devnull:
anatofuz
parents:
diff changeset
203 returncode = subprocess.call(testfile, stdout=devnull)
anatofuz
parents:
diff changeset
204 if returncode:
anatofuz
parents:
diff changeset
205 sys.exit("The interestingness test does not pass for the original file.")
anatofuz
parents:
diff changeset
206
anatofuz
parents:
diff changeset
207 # Check that an empty file is not interesting
anatofuz
parents:
diff changeset
208 # Instead of modifying the filename in the test file, just run the command
anatofuz
parents:
diff changeset
209 with tempfile.NamedTemporaryFile() as empty_file:
anatofuz
parents:
diff changeset
210 is_interesting = self.check_expected_output(filename=empty_file.name)
anatofuz
parents:
diff changeset
211 if is_interesting:
anatofuz
parents:
diff changeset
212 sys.exit("The interestingness test passes for an empty file.")
anatofuz
parents:
diff changeset
213
anatofuz
parents:
diff changeset
214 def clang_preprocess(self):
anatofuz
parents:
diff changeset
215 print("\nTrying to preprocess the source file...")
anatofuz
parents:
diff changeset
216 with tempfile.NamedTemporaryFile() as tmpfile:
anatofuz
parents:
diff changeset
217 cmd_preprocess = self.get_crash_cmd() + ['-E', '-o', tmpfile.name]
anatofuz
parents:
diff changeset
218 cmd_preprocess_no_lines = cmd_preprocess + ['-P']
anatofuz
parents:
diff changeset
219 try:
anatofuz
parents:
diff changeset
220 subprocess.check_call(cmd_preprocess_no_lines)
anatofuz
parents:
diff changeset
221 if self.check_expected_output(filename=tmpfile.name):
anatofuz
parents:
diff changeset
222 print("Successfully preprocessed with line markers removed")
anatofuz
parents:
diff changeset
223 shutil.copy(tmpfile.name, self.file_to_reduce)
anatofuz
parents:
diff changeset
224 else:
anatofuz
parents:
diff changeset
225 subprocess.check_call(cmd_preprocess)
anatofuz
parents:
diff changeset
226 if self.check_expected_output(filename=tmpfile.name):
anatofuz
parents:
diff changeset
227 print("Successfully preprocessed without removing line markers")
anatofuz
parents:
diff changeset
228 shutil.copy(tmpfile.name, self.file_to_reduce)
anatofuz
parents:
diff changeset
229 else:
anatofuz
parents:
diff changeset
230 print("No longer crashes after preprocessing -- "
anatofuz
parents:
diff changeset
231 "using original source")
anatofuz
parents:
diff changeset
232 except subprocess.CalledProcessError:
anatofuz
parents:
diff changeset
233 print("Preprocessing failed")
anatofuz
parents:
diff changeset
234
anatofuz
parents:
diff changeset
235 @staticmethod
anatofuz
parents:
diff changeset
236 def filter_args(args, opts_equal=[], opts_startswith=[],
anatofuz
parents:
diff changeset
237 opts_one_arg_startswith=[]):
anatofuz
parents:
diff changeset
238 result = []
anatofuz
parents:
diff changeset
239 skip_next = False
anatofuz
parents:
diff changeset
240 for arg in args:
anatofuz
parents:
diff changeset
241 if skip_next:
anatofuz
parents:
diff changeset
242 skip_next = False
anatofuz
parents:
diff changeset
243 continue
anatofuz
parents:
diff changeset
244 if any(arg == a for a in opts_equal):
anatofuz
parents:
diff changeset
245 continue
anatofuz
parents:
diff changeset
246 if any(arg.startswith(a) for a in opts_startswith):
anatofuz
parents:
diff changeset
247 continue
anatofuz
parents:
diff changeset
248 if any(arg.startswith(a) for a in opts_one_arg_startswith):
anatofuz
parents:
diff changeset
249 skip_next = True
anatofuz
parents:
diff changeset
250 continue
anatofuz
parents:
diff changeset
251 result.append(arg)
anatofuz
parents:
diff changeset
252 return result
anatofuz
parents:
diff changeset
253
anatofuz
parents:
diff changeset
254 def try_remove_args(self, args, msg=None, extra_arg=None, **kwargs):
anatofuz
parents:
diff changeset
255 new_args = self.filter_args(args, **kwargs)
anatofuz
parents:
diff changeset
256
anatofuz
parents:
diff changeset
257 if extra_arg:
anatofuz
parents:
diff changeset
258 if extra_arg in new_args:
anatofuz
parents:
diff changeset
259 new_args.remove(extra_arg)
anatofuz
parents:
diff changeset
260 new_args.append(extra_arg)
anatofuz
parents:
diff changeset
261
anatofuz
parents:
diff changeset
262 if (new_args != args and
anatofuz
parents:
diff changeset
263 self.check_expected_output(args=new_args)):
anatofuz
parents:
diff changeset
264 if msg:
anatofuz
parents:
diff changeset
265 verbose_print(msg)
anatofuz
parents:
diff changeset
266 return new_args
anatofuz
parents:
diff changeset
267 return args
anatofuz
parents:
diff changeset
268
anatofuz
parents:
diff changeset
269 def try_remove_arg_by_index(self, args, index):
anatofuz
parents:
diff changeset
270 new_args = args[:index] + args[index+1:]
anatofuz
parents:
diff changeset
271 removed_arg = args[index]
anatofuz
parents:
diff changeset
272
anatofuz
parents:
diff changeset
273 # Heuristic for grouping arguments:
anatofuz
parents:
diff changeset
274 # remove next argument if it doesn't start with "-"
anatofuz
parents:
diff changeset
275 if index < len(new_args) and not new_args[index].startswith('-'):
anatofuz
parents:
diff changeset
276 del new_args[index]
anatofuz
parents:
diff changeset
277 removed_arg += ' ' + args[index+1]
anatofuz
parents:
diff changeset
278
anatofuz
parents:
diff changeset
279 if self.check_expected_output(args=new_args):
anatofuz
parents:
diff changeset
280 verbose_print("Removed", removed_arg)
anatofuz
parents:
diff changeset
281 return new_args, index
anatofuz
parents:
diff changeset
282 return args, index+1
anatofuz
parents:
diff changeset
283
anatofuz
parents:
diff changeset
284 def simplify_clang_args(self):
anatofuz
parents:
diff changeset
285 """Simplify clang arguments before running C-Reduce to reduce the time the
anatofuz
parents:
diff changeset
286 interestingness test takes to run.
anatofuz
parents:
diff changeset
287 """
anatofuz
parents:
diff changeset
288 print("\nSimplifying the clang command...")
anatofuz
parents:
diff changeset
289
anatofuz
parents:
diff changeset
290 # Remove some clang arguments to speed up the interestingness test
anatofuz
parents:
diff changeset
291 new_args = self.clang_args
anatofuz
parents:
diff changeset
292 new_args = self.try_remove_args(new_args,
anatofuz
parents:
diff changeset
293 msg="Removed debug info options",
anatofuz
parents:
diff changeset
294 opts_startswith=["-gcodeview",
anatofuz
parents:
diff changeset
295 "-debug-info-kind=",
anatofuz
parents:
diff changeset
296 "-debugger-tuning="])
anatofuz
parents:
diff changeset
297
anatofuz
parents:
diff changeset
298 new_args = self.try_remove_args(new_args,
anatofuz
parents:
diff changeset
299 msg="Removed --show-includes",
anatofuz
parents:
diff changeset
300 opts_startswith=["--show-includes"])
anatofuz
parents:
diff changeset
301 # Not suppressing warnings (-w) sometimes prevents the crash from occurring
anatofuz
parents:
diff changeset
302 # after preprocessing
anatofuz
parents:
diff changeset
303 new_args = self.try_remove_args(new_args,
anatofuz
parents:
diff changeset
304 msg="Replaced -W options with -w",
anatofuz
parents:
diff changeset
305 extra_arg='-w',
anatofuz
parents:
diff changeset
306 opts_startswith=["-W"])
anatofuz
parents:
diff changeset
307 new_args = self.try_remove_args(new_args,
anatofuz
parents:
diff changeset
308 msg="Replaced optimization level with -O0",
anatofuz
parents:
diff changeset
309 extra_arg="-O0",
anatofuz
parents:
diff changeset
310 opts_startswith=["-O"])
anatofuz
parents:
diff changeset
311
anatofuz
parents:
diff changeset
312 # Try to remove compilation steps
anatofuz
parents:
diff changeset
313 new_args = self.try_remove_args(new_args, msg="Added -emit-llvm",
anatofuz
parents:
diff changeset
314 extra_arg="-emit-llvm")
anatofuz
parents:
diff changeset
315 new_args = self.try_remove_args(new_args, msg="Added -fsyntax-only",
anatofuz
parents:
diff changeset
316 extra_arg="-fsyntax-only")
anatofuz
parents:
diff changeset
317
anatofuz
parents:
diff changeset
318 # Try to make implicit int an error for more sensible test output
anatofuz
parents:
diff changeset
319 new_args = self.try_remove_args(new_args, msg="Added -Werror=implicit-int",
anatofuz
parents:
diff changeset
320 opts_equal=["-w"],
anatofuz
parents:
diff changeset
321 extra_arg="-Werror=implicit-int")
anatofuz
parents:
diff changeset
322
anatofuz
parents:
diff changeset
323 self.clang_args = new_args
anatofuz
parents:
diff changeset
324 verbose_print("Simplified command:", quote_cmd(self.get_crash_cmd()))
anatofuz
parents:
diff changeset
325
anatofuz
parents:
diff changeset
326 def reduce_clang_args(self):
anatofuz
parents:
diff changeset
327 """Minimize the clang arguments after running C-Reduce, to get the smallest
anatofuz
parents:
diff changeset
328 command that reproduces the crash on the reduced file.
anatofuz
parents:
diff changeset
329 """
anatofuz
parents:
diff changeset
330 print("\nReducing the clang crash command...")
anatofuz
parents:
diff changeset
331
anatofuz
parents:
diff changeset
332 new_args = self.clang_args
anatofuz
parents:
diff changeset
333
anatofuz
parents:
diff changeset
334 # Remove some often occurring args
anatofuz
parents:
diff changeset
335 new_args = self.try_remove_args(new_args, msg="Removed -D options",
anatofuz
parents:
diff changeset
336 opts_startswith=["-D"])
anatofuz
parents:
diff changeset
337 new_args = self.try_remove_args(new_args, msg="Removed -D options",
anatofuz
parents:
diff changeset
338 opts_one_arg_startswith=["-D"])
anatofuz
parents:
diff changeset
339 new_args = self.try_remove_args(new_args, msg="Removed -I options",
anatofuz
parents:
diff changeset
340 opts_startswith=["-I"])
anatofuz
parents:
diff changeset
341 new_args = self.try_remove_args(new_args, msg="Removed -I options",
anatofuz
parents:
diff changeset
342 opts_one_arg_startswith=["-I"])
anatofuz
parents:
diff changeset
343 new_args = self.try_remove_args(new_args, msg="Removed -W options",
anatofuz
parents:
diff changeset
344 opts_startswith=["-W"])
anatofuz
parents:
diff changeset
345
anatofuz
parents:
diff changeset
346 # Remove other cases that aren't covered by the heuristic
anatofuz
parents:
diff changeset
347 new_args = self.try_remove_args(new_args, msg="Removed -mllvm",
anatofuz
parents:
diff changeset
348 opts_one_arg_startswith=["-mllvm"])
anatofuz
parents:
diff changeset
349
anatofuz
parents:
diff changeset
350 i = 0
anatofuz
parents:
diff changeset
351 while i < len(new_args):
anatofuz
parents:
diff changeset
352 new_args, i = self.try_remove_arg_by_index(new_args, i)
anatofuz
parents:
diff changeset
353
anatofuz
parents:
diff changeset
354 self.clang_args = new_args
anatofuz
parents:
diff changeset
355
anatofuz
parents:
diff changeset
356 reduced_cmd = quote_cmd(self.get_crash_cmd())
anatofuz
parents:
diff changeset
357 write_to_script(reduced_cmd, self.crash_script)
anatofuz
parents:
diff changeset
358 print("Reduced command:", reduced_cmd)
anatofuz
parents:
diff changeset
359
anatofuz
parents:
diff changeset
360 def run_creduce(self):
anatofuz
parents:
diff changeset
361 print("\nRunning C-Reduce...")
anatofuz
parents:
diff changeset
362 try:
anatofuz
parents:
diff changeset
363 p = subprocess.Popen([creduce_cmd] + self.creduce_flags +
anatofuz
parents:
diff changeset
364 [self.testfile, self.file_to_reduce])
anatofuz
parents:
diff changeset
365 p.communicate()
anatofuz
parents:
diff changeset
366 except KeyboardInterrupt:
anatofuz
parents:
diff changeset
367 # Hack to kill C-Reduce because it jumps into its own pgid
anatofuz
parents:
diff changeset
368 print('\n\nctrl-c detected, killed creduce')
anatofuz
parents:
diff changeset
369 p.kill()
anatofuz
parents:
diff changeset
370
anatofuz
parents:
diff changeset
371 def main():
anatofuz
parents:
diff changeset
372 global verbose
anatofuz
parents:
diff changeset
373 global creduce_cmd
anatofuz
parents:
diff changeset
374 global clang_cmd
anatofuz
parents:
diff changeset
375 global not_cmd
anatofuz
parents:
diff changeset
376
anatofuz
parents:
diff changeset
377 parser = ArgumentParser(description=__doc__,
anatofuz
parents:
diff changeset
378 formatter_class=RawTextHelpFormatter)
anatofuz
parents:
diff changeset
379 parser.add_argument('crash_script', type=str, nargs=1,
anatofuz
parents:
diff changeset
380 help="Name of the script that generates the crash.")
anatofuz
parents:
diff changeset
381 parser.add_argument('file_to_reduce', type=str, nargs=1,
anatofuz
parents:
diff changeset
382 help="Name of the file to be reduced.")
anatofuz
parents:
diff changeset
383 parser.add_argument('--llvm-bin', dest='llvm_bin', type=str,
anatofuz
parents:
diff changeset
384 help="Path to the LLVM bin directory.")
anatofuz
parents:
diff changeset
385 parser.add_argument('--llvm-not', dest='llvm_not', type=str,
anatofuz
parents:
diff changeset
386 help="The path to the `not` executable. "
anatofuz
parents:
diff changeset
387 "By default uses the llvm-bin directory.")
anatofuz
parents:
diff changeset
388 parser.add_argument('--clang', dest='clang', type=str,
anatofuz
parents:
diff changeset
389 help="The path to the `clang` executable. "
anatofuz
parents:
diff changeset
390 "By default uses the llvm-bin directory.")
anatofuz
parents:
diff changeset
391 parser.add_argument('--creduce', dest='creduce', type=str,
anatofuz
parents:
diff changeset
392 help="The path to the `creduce` executable. "
anatofuz
parents:
diff changeset
393 "Required if `creduce` is not in PATH environment.")
anatofuz
parents:
diff changeset
394 parser.add_argument('-v', '--verbose', action='store_true')
anatofuz
parents:
diff changeset
395 args = parser.parse_args()
anatofuz
parents:
diff changeset
396
anatofuz
parents:
diff changeset
397 verbose = args.verbose
anatofuz
parents:
diff changeset
398 llvm_bin = os.path.abspath(args.llvm_bin) if args.llvm_bin else None
anatofuz
parents:
diff changeset
399 creduce_cmd = check_cmd('creduce', None, args.creduce)
anatofuz
parents:
diff changeset
400 clang_cmd = check_cmd('clang', llvm_bin, args.clang)
anatofuz
parents:
diff changeset
401 not_cmd = check_cmd('not', llvm_bin, args.llvm_not)
anatofuz
parents:
diff changeset
402
anatofuz
parents:
diff changeset
403 crash_script = check_file(args.crash_script[0])
anatofuz
parents:
diff changeset
404 file_to_reduce = check_file(args.file_to_reduce[0])
anatofuz
parents:
diff changeset
405
anatofuz
parents:
diff changeset
406 r = Reduce(crash_script, file_to_reduce)
anatofuz
parents:
diff changeset
407
anatofuz
parents:
diff changeset
408 r.simplify_clang_args()
anatofuz
parents:
diff changeset
409 r.write_interestingness_test()
anatofuz
parents:
diff changeset
410 r.clang_preprocess()
anatofuz
parents:
diff changeset
411 r.run_creduce()
anatofuz
parents:
diff changeset
412 r.reduce_clang_args()
anatofuz
parents:
diff changeset
413
anatofuz
parents:
diff changeset
414 if __name__ == '__main__':
anatofuz
parents:
diff changeset
415 main()