17330f729Sjoerg#!/usr/bin/env python 27330f729Sjoerg"""Calls C-Reduce to create a minimal reproducer for clang crashes. 37330f729Sjoerg 47330f729SjoergOutput files: 57330f729Sjoerg *.reduced.sh -- crash reproducer with minimal arguments 67330f729Sjoerg *.reduced.cpp -- the reduced file 77330f729Sjoerg *.test.sh -- interestingness test for C-Reduce 87330f729Sjoerg""" 97330f729Sjoerg 107330f729Sjoergfrom __future__ import print_function 117330f729Sjoergfrom argparse import ArgumentParser, RawTextHelpFormatter 127330f729Sjoergimport os 137330f729Sjoergimport re 147330f729Sjoergimport stat 157330f729Sjoergimport sys 167330f729Sjoergimport subprocess 177330f729Sjoergimport pipes 187330f729Sjoergimport shlex 197330f729Sjoergimport tempfile 207330f729Sjoergimport shutil 217330f729Sjoergfrom distutils.spawn import find_executable 22*e038c9c4Sjoergimport multiprocessing 237330f729Sjoerg 247330f729Sjoergverbose = False 257330f729Sjoergcreduce_cmd = None 267330f729Sjoergclang_cmd = None 277330f729Sjoerg 287330f729Sjoergdef verbose_print(*args, **kwargs): 297330f729Sjoerg if verbose: 307330f729Sjoerg print(*args, **kwargs) 317330f729Sjoerg 327330f729Sjoergdef check_file(fname): 33*e038c9c4Sjoerg fname = os.path.normpath(fname) 347330f729Sjoerg if not os.path.isfile(fname): 357330f729Sjoerg sys.exit("ERROR: %s does not exist" % (fname)) 367330f729Sjoerg return fname 377330f729Sjoerg 387330f729Sjoergdef check_cmd(cmd_name, cmd_dir, cmd_path=None): 397330f729Sjoerg """ 407330f729Sjoerg Returns absolute path to cmd_path if it is given, 417330f729Sjoerg or absolute path to cmd_dir/cmd_name. 427330f729Sjoerg """ 437330f729Sjoerg if cmd_path: 44*e038c9c4Sjoerg # Make the path absolute so the creduce test can be run from any directory. 45*e038c9c4Sjoerg cmd_path = os.path.abspath(cmd_path) 467330f729Sjoerg cmd = find_executable(cmd_path) 477330f729Sjoerg if cmd: 487330f729Sjoerg return cmd 497330f729Sjoerg sys.exit("ERROR: executable `%s` not found" % (cmd_path)) 507330f729Sjoerg 517330f729Sjoerg cmd = find_executable(cmd_name, path=cmd_dir) 527330f729Sjoerg if cmd: 537330f729Sjoerg return cmd 547330f729Sjoerg 557330f729Sjoerg if not cmd_dir: 567330f729Sjoerg cmd_dir = "$PATH" 577330f729Sjoerg sys.exit("ERROR: `%s` not found in %s" % (cmd_name, cmd_dir)) 587330f729Sjoerg 597330f729Sjoergdef quote_cmd(cmd): 607330f729Sjoerg return ' '.join(pipes.quote(arg) for arg in cmd) 617330f729Sjoerg 627330f729Sjoergdef write_to_script(text, filename): 637330f729Sjoerg with open(filename, 'w') as f: 647330f729Sjoerg f.write(text) 657330f729Sjoerg os.chmod(filename, os.stat(filename).st_mode | stat.S_IEXEC) 667330f729Sjoerg 677330f729Sjoergclass Reduce(object): 68*e038c9c4Sjoerg def __init__(self, crash_script, file_to_reduce, core_number): 697330f729Sjoerg crash_script_name, crash_script_ext = os.path.splitext(crash_script) 707330f729Sjoerg file_reduce_name, file_reduce_ext = os.path.splitext(file_to_reduce) 717330f729Sjoerg 727330f729Sjoerg self.testfile = file_reduce_name + '.test.sh' 737330f729Sjoerg self.crash_script = crash_script_name + '.reduced' + crash_script_ext 747330f729Sjoerg self.file_to_reduce = file_reduce_name + '.reduced' + file_reduce_ext 757330f729Sjoerg shutil.copy(file_to_reduce, self.file_to_reduce) 767330f729Sjoerg 777330f729Sjoerg self.clang = clang_cmd 787330f729Sjoerg self.clang_args = [] 797330f729Sjoerg self.expected_output = [] 80*e038c9c4Sjoerg self.needs_stack_trace = False 817330f729Sjoerg self.creduce_flags = ["--tidy"] 82*e038c9c4Sjoerg self.creduce_flags = ["--n", str(core_number)] 837330f729Sjoerg 847330f729Sjoerg self.read_clang_args(crash_script, file_to_reduce) 857330f729Sjoerg self.read_expected_output() 867330f729Sjoerg 877330f729Sjoerg def get_crash_cmd(self, cmd=None, args=None, filename=None): 887330f729Sjoerg if not cmd: 897330f729Sjoerg cmd = self.clang 907330f729Sjoerg if not args: 917330f729Sjoerg args = self.clang_args 927330f729Sjoerg if not filename: 937330f729Sjoerg filename = self.file_to_reduce 947330f729Sjoerg 957330f729Sjoerg return [cmd] + args + [filename] 967330f729Sjoerg 977330f729Sjoerg def read_clang_args(self, crash_script, filename): 987330f729Sjoerg print("\nReading arguments from crash script...") 997330f729Sjoerg with open(crash_script) as f: 1007330f729Sjoerg # Assume clang call is the first non comment line. 1017330f729Sjoerg cmd = [] 1027330f729Sjoerg for line in f: 1037330f729Sjoerg if not line.lstrip().startswith('#'): 1047330f729Sjoerg cmd = shlex.split(line) 1057330f729Sjoerg break 1067330f729Sjoerg if not cmd: 1077330f729Sjoerg sys.exit("Could not find command in the crash script."); 1087330f729Sjoerg 1097330f729Sjoerg # Remove clang and filename from the command 1107330f729Sjoerg # Assume the last occurrence of the filename is the clang input file 1117330f729Sjoerg del cmd[0] 1127330f729Sjoerg for i in range(len(cmd)-1, -1, -1): 1137330f729Sjoerg if cmd[i] == filename: 1147330f729Sjoerg del cmd[i] 1157330f729Sjoerg break 1167330f729Sjoerg self.clang_args = cmd 1177330f729Sjoerg verbose_print("Clang arguments:", quote_cmd(self.clang_args)) 1187330f729Sjoerg 1197330f729Sjoerg def read_expected_output(self): 1207330f729Sjoerg print("\nGetting expected crash output...") 1217330f729Sjoerg p = subprocess.Popen(self.get_crash_cmd(), 1227330f729Sjoerg stdout=subprocess.PIPE, 1237330f729Sjoerg stderr=subprocess.STDOUT) 1247330f729Sjoerg crash_output, _ = p.communicate() 1257330f729Sjoerg result = [] 1267330f729Sjoerg 1277330f729Sjoerg # Remove color codes 1287330f729Sjoerg ansi_escape = r'\x1b\[[0-?]*m' 1297330f729Sjoerg crash_output = re.sub(ansi_escape, '', crash_output.decode('utf-8')) 1307330f729Sjoerg 1317330f729Sjoerg # Look for specific error messages 132*e038c9c4Sjoerg regexes = [r"Assertion .+ failed", # Linux assert() 133*e038c9c4Sjoerg r"Assertion failed: .+,", # FreeBSD/Mac assert() 134*e038c9c4Sjoerg r"fatal error: error in backend: .+", 135*e038c9c4Sjoerg r"LLVM ERROR: .+", 136*e038c9c4Sjoerg r"UNREACHABLE executed at .+?!", 137*e038c9c4Sjoerg r"LLVM IR generation of declaration '.+'", 138*e038c9c4Sjoerg r"Generating code for declaration '.+'", 139*e038c9c4Sjoerg r"\*\*\* Bad machine code: .+ \*\*\*", 140*e038c9c4Sjoerg r"ERROR: .*Sanitizer: [^ ]+ "] 1417330f729Sjoerg for msg_re in regexes: 1427330f729Sjoerg match = re.search(msg_re, crash_output) 1437330f729Sjoerg if match: 144*e038c9c4Sjoerg msg = match.group(0) 1457330f729Sjoerg result = [msg] 1467330f729Sjoerg print("Found message:", msg) 1477330f729Sjoerg break 1487330f729Sjoerg 1497330f729Sjoerg # If no message was found, use the top five stack trace functions, 1507330f729Sjoerg # ignoring some common functions 1517330f729Sjoerg # Five is a somewhat arbitrary number; the goal is to get a small number 1527330f729Sjoerg # of identifying functions with some leeway for common functions 1537330f729Sjoerg if not result: 154*e038c9c4Sjoerg self.needs_stack_trace = True 1557330f729Sjoerg stacktrace_re = r'[0-9]+\s+0[xX][0-9a-fA-F]+\s*([^(]+)\(' 156*e038c9c4Sjoerg filters = ["PrintStackTrace", "RunSignalHandlers", "CleanupOnSignal", 157*e038c9c4Sjoerg "HandleCrash", "SignalHandler", "__restore_rt", "gsignal", "abort"] 158*e038c9c4Sjoerg def skip_function(func_name): 159*e038c9c4Sjoerg return any(name in func_name for name in filters) 160*e038c9c4Sjoerg 1617330f729Sjoerg matches = re.findall(stacktrace_re, crash_output) 162*e038c9c4Sjoerg result = [x for x in matches if x and not skip_function(x)][:5] 1637330f729Sjoerg for msg in result: 1647330f729Sjoerg print("Found stack trace function:", msg) 1657330f729Sjoerg 1667330f729Sjoerg if not result: 1677330f729Sjoerg print("ERROR: no crash was found") 1687330f729Sjoerg print("The crash output was:\n========\n%s========" % crash_output) 1697330f729Sjoerg sys.exit(1) 1707330f729Sjoerg 1717330f729Sjoerg self.expected_output = result 1727330f729Sjoerg 1737330f729Sjoerg def check_expected_output(self, args=None, filename=None): 1747330f729Sjoerg if not args: 1757330f729Sjoerg args = self.clang_args 1767330f729Sjoerg if not filename: 1777330f729Sjoerg filename = self.file_to_reduce 1787330f729Sjoerg 1797330f729Sjoerg p = subprocess.Popen(self.get_crash_cmd(args=args, filename=filename), 1807330f729Sjoerg stdout=subprocess.PIPE, 1817330f729Sjoerg stderr=subprocess.STDOUT) 1827330f729Sjoerg crash_output, _ = p.communicate() 1837330f729Sjoerg return all(msg in crash_output.decode('utf-8') for msg in 1847330f729Sjoerg self.expected_output) 1857330f729Sjoerg 1867330f729Sjoerg def write_interestingness_test(self): 1877330f729Sjoerg print("\nCreating the interestingness test...") 1887330f729Sjoerg 189*e038c9c4Sjoerg # Disable symbolization if it's not required to avoid slow symbolization. 190*e038c9c4Sjoerg disable_symbolization = '' 191*e038c9c4Sjoerg if not self.needs_stack_trace: 192*e038c9c4Sjoerg disable_symbolization = 'export LLVM_DISABLE_SYMBOLIZATION=1' 1937330f729Sjoerg 194*e038c9c4Sjoerg output = """#!/bin/bash 195*e038c9c4Sjoerg%s 196*e038c9c4Sjoergif %s >& t.log ; then 197*e038c9c4Sjoerg exit 1 198*e038c9c4Sjoergfi 199*e038c9c4Sjoerg""" % (disable_symbolization, quote_cmd(self.get_crash_cmd())) 2007330f729Sjoerg 2017330f729Sjoerg for msg in self.expected_output: 2027330f729Sjoerg output += 'grep -F %s t.log || exit 1\n' % pipes.quote(msg) 2037330f729Sjoerg 2047330f729Sjoerg write_to_script(output, self.testfile) 2057330f729Sjoerg self.check_interestingness() 2067330f729Sjoerg 2077330f729Sjoerg def check_interestingness(self): 2087330f729Sjoerg testfile = os.path.abspath(self.testfile) 2097330f729Sjoerg 2107330f729Sjoerg # Check that the test considers the original file interesting 2117330f729Sjoerg with open(os.devnull, 'w') as devnull: 2127330f729Sjoerg returncode = subprocess.call(testfile, stdout=devnull) 2137330f729Sjoerg if returncode: 2147330f729Sjoerg sys.exit("The interestingness test does not pass for the original file.") 2157330f729Sjoerg 2167330f729Sjoerg # Check that an empty file is not interesting 2177330f729Sjoerg # Instead of modifying the filename in the test file, just run the command 2187330f729Sjoerg with tempfile.NamedTemporaryFile() as empty_file: 2197330f729Sjoerg is_interesting = self.check_expected_output(filename=empty_file.name) 2207330f729Sjoerg if is_interesting: 2217330f729Sjoerg sys.exit("The interestingness test passes for an empty file.") 2227330f729Sjoerg 2237330f729Sjoerg def clang_preprocess(self): 2247330f729Sjoerg print("\nTrying to preprocess the source file...") 2257330f729Sjoerg with tempfile.NamedTemporaryFile() as tmpfile: 2267330f729Sjoerg cmd_preprocess = self.get_crash_cmd() + ['-E', '-o', tmpfile.name] 2277330f729Sjoerg cmd_preprocess_no_lines = cmd_preprocess + ['-P'] 2287330f729Sjoerg try: 2297330f729Sjoerg subprocess.check_call(cmd_preprocess_no_lines) 2307330f729Sjoerg if self.check_expected_output(filename=tmpfile.name): 2317330f729Sjoerg print("Successfully preprocessed with line markers removed") 2327330f729Sjoerg shutil.copy(tmpfile.name, self.file_to_reduce) 2337330f729Sjoerg else: 2347330f729Sjoerg subprocess.check_call(cmd_preprocess) 2357330f729Sjoerg if self.check_expected_output(filename=tmpfile.name): 2367330f729Sjoerg print("Successfully preprocessed without removing line markers") 2377330f729Sjoerg shutil.copy(tmpfile.name, self.file_to_reduce) 2387330f729Sjoerg else: 2397330f729Sjoerg print("No longer crashes after preprocessing -- " 2407330f729Sjoerg "using original source") 2417330f729Sjoerg except subprocess.CalledProcessError: 2427330f729Sjoerg print("Preprocessing failed") 2437330f729Sjoerg 2447330f729Sjoerg @staticmethod 2457330f729Sjoerg def filter_args(args, opts_equal=[], opts_startswith=[], 2467330f729Sjoerg opts_one_arg_startswith=[]): 2477330f729Sjoerg result = [] 2487330f729Sjoerg skip_next = False 2497330f729Sjoerg for arg in args: 2507330f729Sjoerg if skip_next: 2517330f729Sjoerg skip_next = False 2527330f729Sjoerg continue 2537330f729Sjoerg if any(arg == a for a in opts_equal): 2547330f729Sjoerg continue 2557330f729Sjoerg if any(arg.startswith(a) for a in opts_startswith): 2567330f729Sjoerg continue 2577330f729Sjoerg if any(arg.startswith(a) for a in opts_one_arg_startswith): 2587330f729Sjoerg skip_next = True 2597330f729Sjoerg continue 2607330f729Sjoerg result.append(arg) 2617330f729Sjoerg return result 2627330f729Sjoerg 2637330f729Sjoerg def try_remove_args(self, args, msg=None, extra_arg=None, **kwargs): 2647330f729Sjoerg new_args = self.filter_args(args, **kwargs) 2657330f729Sjoerg 2667330f729Sjoerg if extra_arg: 2677330f729Sjoerg if extra_arg in new_args: 2687330f729Sjoerg new_args.remove(extra_arg) 2697330f729Sjoerg new_args.append(extra_arg) 2707330f729Sjoerg 2717330f729Sjoerg if (new_args != args and 2727330f729Sjoerg self.check_expected_output(args=new_args)): 2737330f729Sjoerg if msg: 2747330f729Sjoerg verbose_print(msg) 2757330f729Sjoerg return new_args 2767330f729Sjoerg return args 2777330f729Sjoerg 2787330f729Sjoerg def try_remove_arg_by_index(self, args, index): 2797330f729Sjoerg new_args = args[:index] + args[index+1:] 2807330f729Sjoerg removed_arg = args[index] 2817330f729Sjoerg 2827330f729Sjoerg # Heuristic for grouping arguments: 2837330f729Sjoerg # remove next argument if it doesn't start with "-" 2847330f729Sjoerg if index < len(new_args) and not new_args[index].startswith('-'): 2857330f729Sjoerg del new_args[index] 2867330f729Sjoerg removed_arg += ' ' + args[index+1] 2877330f729Sjoerg 2887330f729Sjoerg if self.check_expected_output(args=new_args): 2897330f729Sjoerg verbose_print("Removed", removed_arg) 2907330f729Sjoerg return new_args, index 2917330f729Sjoerg return args, index+1 2927330f729Sjoerg 2937330f729Sjoerg def simplify_clang_args(self): 2947330f729Sjoerg """Simplify clang arguments before running C-Reduce to reduce the time the 2957330f729Sjoerg interestingness test takes to run. 2967330f729Sjoerg """ 2977330f729Sjoerg print("\nSimplifying the clang command...") 2987330f729Sjoerg 2997330f729Sjoerg # Remove some clang arguments to speed up the interestingness test 3007330f729Sjoerg new_args = self.clang_args 3017330f729Sjoerg new_args = self.try_remove_args(new_args, 3027330f729Sjoerg msg="Removed debug info options", 3037330f729Sjoerg opts_startswith=["-gcodeview", 3047330f729Sjoerg "-debug-info-kind=", 3057330f729Sjoerg "-debugger-tuning="]) 3067330f729Sjoerg 3077330f729Sjoerg new_args = self.try_remove_args(new_args, 3087330f729Sjoerg msg="Removed --show-includes", 3097330f729Sjoerg opts_startswith=["--show-includes"]) 3107330f729Sjoerg # Not suppressing warnings (-w) sometimes prevents the crash from occurring 3117330f729Sjoerg # after preprocessing 3127330f729Sjoerg new_args = self.try_remove_args(new_args, 3137330f729Sjoerg msg="Replaced -W options with -w", 3147330f729Sjoerg extra_arg='-w', 3157330f729Sjoerg opts_startswith=["-W"]) 3167330f729Sjoerg new_args = self.try_remove_args(new_args, 3177330f729Sjoerg msg="Replaced optimization level with -O0", 3187330f729Sjoerg extra_arg="-O0", 3197330f729Sjoerg opts_startswith=["-O"]) 3207330f729Sjoerg 3217330f729Sjoerg # Try to remove compilation steps 3227330f729Sjoerg new_args = self.try_remove_args(new_args, msg="Added -emit-llvm", 3237330f729Sjoerg extra_arg="-emit-llvm") 3247330f729Sjoerg new_args = self.try_remove_args(new_args, msg="Added -fsyntax-only", 3257330f729Sjoerg extra_arg="-fsyntax-only") 3267330f729Sjoerg 3277330f729Sjoerg # Try to make implicit int an error for more sensible test output 3287330f729Sjoerg new_args = self.try_remove_args(new_args, msg="Added -Werror=implicit-int", 3297330f729Sjoerg opts_equal=["-w"], 3307330f729Sjoerg extra_arg="-Werror=implicit-int") 3317330f729Sjoerg 3327330f729Sjoerg self.clang_args = new_args 3337330f729Sjoerg verbose_print("Simplified command:", quote_cmd(self.get_crash_cmd())) 3347330f729Sjoerg 3357330f729Sjoerg def reduce_clang_args(self): 3367330f729Sjoerg """Minimize the clang arguments after running C-Reduce, to get the smallest 3377330f729Sjoerg command that reproduces the crash on the reduced file. 3387330f729Sjoerg """ 3397330f729Sjoerg print("\nReducing the clang crash command...") 3407330f729Sjoerg 3417330f729Sjoerg new_args = self.clang_args 3427330f729Sjoerg 3437330f729Sjoerg # Remove some often occurring args 3447330f729Sjoerg new_args = self.try_remove_args(new_args, msg="Removed -D options", 3457330f729Sjoerg opts_startswith=["-D"]) 3467330f729Sjoerg new_args = self.try_remove_args(new_args, msg="Removed -D options", 3477330f729Sjoerg opts_one_arg_startswith=["-D"]) 3487330f729Sjoerg new_args = self.try_remove_args(new_args, msg="Removed -I options", 3497330f729Sjoerg opts_startswith=["-I"]) 3507330f729Sjoerg new_args = self.try_remove_args(new_args, msg="Removed -I options", 3517330f729Sjoerg opts_one_arg_startswith=["-I"]) 3527330f729Sjoerg new_args = self.try_remove_args(new_args, msg="Removed -W options", 3537330f729Sjoerg opts_startswith=["-W"]) 3547330f729Sjoerg 3557330f729Sjoerg # Remove other cases that aren't covered by the heuristic 3567330f729Sjoerg new_args = self.try_remove_args(new_args, msg="Removed -mllvm", 3577330f729Sjoerg opts_one_arg_startswith=["-mllvm"]) 3587330f729Sjoerg 3597330f729Sjoerg i = 0 3607330f729Sjoerg while i < len(new_args): 3617330f729Sjoerg new_args, i = self.try_remove_arg_by_index(new_args, i) 3627330f729Sjoerg 3637330f729Sjoerg self.clang_args = new_args 3647330f729Sjoerg 3657330f729Sjoerg reduced_cmd = quote_cmd(self.get_crash_cmd()) 3667330f729Sjoerg write_to_script(reduced_cmd, self.crash_script) 3677330f729Sjoerg print("Reduced command:", reduced_cmd) 3687330f729Sjoerg 3697330f729Sjoerg def run_creduce(self): 3707330f729Sjoerg print("\nRunning C-Reduce...") 3717330f729Sjoerg try: 3727330f729Sjoerg p = subprocess.Popen([creduce_cmd] + self.creduce_flags + 3737330f729Sjoerg [self.testfile, self.file_to_reduce]) 3747330f729Sjoerg p.communicate() 3757330f729Sjoerg except KeyboardInterrupt: 3767330f729Sjoerg # Hack to kill C-Reduce because it jumps into its own pgid 3777330f729Sjoerg print('\n\nctrl-c detected, killed creduce') 3787330f729Sjoerg p.kill() 3797330f729Sjoerg 3807330f729Sjoergdef main(): 3817330f729Sjoerg global verbose 3827330f729Sjoerg global creduce_cmd 3837330f729Sjoerg global clang_cmd 3847330f729Sjoerg 3857330f729Sjoerg parser = ArgumentParser(description=__doc__, 3867330f729Sjoerg formatter_class=RawTextHelpFormatter) 3877330f729Sjoerg parser.add_argument('crash_script', type=str, nargs=1, 3887330f729Sjoerg help="Name of the script that generates the crash.") 3897330f729Sjoerg parser.add_argument('file_to_reduce', type=str, nargs=1, 3907330f729Sjoerg help="Name of the file to be reduced.") 3917330f729Sjoerg parser.add_argument('--llvm-bin', dest='llvm_bin', type=str, 3927330f729Sjoerg help="Path to the LLVM bin directory.") 3937330f729Sjoerg parser.add_argument('--clang', dest='clang', type=str, 3947330f729Sjoerg help="The path to the `clang` executable. " 3957330f729Sjoerg "By default uses the llvm-bin directory.") 3967330f729Sjoerg parser.add_argument('--creduce', dest='creduce', type=str, 3977330f729Sjoerg help="The path to the `creduce` executable. " 3987330f729Sjoerg "Required if `creduce` is not in PATH environment.") 399*e038c9c4Sjoerg parser.add_argument('--n', dest='core_number', type=int, 400*e038c9c4Sjoerg default=max(4, multiprocessing.cpu_count() / 2), 401*e038c9c4Sjoerg help="Number of cores to use.") 4027330f729Sjoerg parser.add_argument('-v', '--verbose', action='store_true') 4037330f729Sjoerg args = parser.parse_args() 4047330f729Sjoerg 4057330f729Sjoerg verbose = args.verbose 4067330f729Sjoerg llvm_bin = os.path.abspath(args.llvm_bin) if args.llvm_bin else None 4077330f729Sjoerg creduce_cmd = check_cmd('creduce', None, args.creduce) 4087330f729Sjoerg clang_cmd = check_cmd('clang', llvm_bin, args.clang) 409*e038c9c4Sjoerg core_number = args.core_number 4107330f729Sjoerg 4117330f729Sjoerg crash_script = check_file(args.crash_script[0]) 4127330f729Sjoerg file_to_reduce = check_file(args.file_to_reduce[0]) 4137330f729Sjoerg 414*e038c9c4Sjoerg r = Reduce(crash_script, file_to_reduce, core_number) 4157330f729Sjoerg 4167330f729Sjoerg r.simplify_clang_args() 4177330f729Sjoerg r.write_interestingness_test() 4187330f729Sjoerg r.clang_preprocess() 4197330f729Sjoerg r.run_creduce() 4207330f729Sjoerg r.reduce_clang_args() 4217330f729Sjoerg 4227330f729Sjoergif __name__ == '__main__': 4237330f729Sjoerg main() 424