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