1#!/usr/bin/env python 2 3# To use: 4# 1) Update the 'decls' list below with your fuzzing configuration. 5# 2) Run with the clang binary as the command-line argument. 6 7from __future__ import absolute_import, division, print_function 8import random 9import subprocess 10import sys 11import os 12 13clang = sys.argv[1] 14none_opts = 0.3 15 16 17class Decl(object): 18 def __init__(self, text, depends=[], provides=[], conflicts=[]): 19 self.text = text 20 self.depends = depends 21 self.provides = provides 22 self.conflicts = conflicts 23 24 def valid(self, model): 25 for i in self.depends: 26 if i not in model.decls: 27 return False 28 for i in self.conflicts: 29 if i in model.decls: 30 return False 31 return True 32 33 def apply(self, model, name): 34 for i in self.provides: 35 model.decls[i] = True 36 model.source += self.text % {"name": name} 37 38 39decls = [ 40 Decl("struct X { int n; };\n", provides=["X"], conflicts=["X"]), 41 Decl('static_assert(X{.n=1}.n == 1, "");\n', depends=["X"]), 42 Decl("X %(name)s;\n", depends=["X"]), 43] 44 45 46class FS(object): 47 def __init__(self): 48 self.fs = {} 49 self.prevfs = {} 50 51 def write(self, path, contents): 52 self.fs[path] = contents 53 54 def done(self): 55 for f, s in self.fs.items(): 56 if self.prevfs.get(f) != s: 57 f = file(f, "w") 58 f.write(s) 59 f.close() 60 61 for f in self.prevfs: 62 if f not in self.fs: 63 os.remove(f) 64 65 self.prevfs, self.fs = self.fs, {} 66 67 68fs = FS() 69 70 71class CodeModel(object): 72 def __init__(self): 73 self.source = "" 74 self.modules = {} 75 self.decls = {} 76 self.i = 0 77 78 def make_name(self): 79 self.i += 1 80 return "n" + str(self.i) 81 82 def fails(self): 83 fs.write( 84 "module.modulemap", 85 "".join( 86 'module %s { header "%s.h" export * }\n' % (m, m) 87 for m in self.modules.keys() 88 ), 89 ) 90 91 for m, (s, _) in self.modules.items(): 92 fs.write("%s.h" % m, s) 93 94 fs.write("main.cc", self.source) 95 fs.done() 96 97 return ( 98 subprocess.call( 99 [clang, "-std=c++11", "-c", "-fmodules", "main.cc", "-o", "/dev/null"] 100 ) 101 != 0 102 ) 103 104 105def generate(): 106 model = CodeModel() 107 m = [] 108 109 try: 110 for d in mutations(model): 111 d(model) 112 m.append(d) 113 if not model.fails(): 114 return 115 except KeyboardInterrupt: 116 print() 117 return True 118 119 sys.stdout.write("\nReducing:\n") 120 sys.stdout.flush() 121 122 try: 123 while True: 124 assert m, "got a failure with no steps; broken clang binary?" 125 i = random.choice(list(range(len(m)))) 126 x = m[0:i] + m[i + 1 :] 127 m2 = CodeModel() 128 for d in x: 129 d(m2) 130 if m2.fails(): 131 m = x 132 model = m2 133 else: 134 sys.stdout.write(".") 135 sys.stdout.flush() 136 except KeyboardInterrupt: 137 # FIXME: Clean out output directory first. 138 model.fails() 139 return model 140 141 142def choose(options): 143 while True: 144 i = int(random.uniform(0, len(options) + none_opts)) 145 if i >= len(options): 146 break 147 yield options[i] 148 149 150def mutations(model): 151 options = [create_module, add_top_level_decl] 152 for opt in choose(options): 153 yield opt(model, options) 154 155 156def create_module(model, options): 157 n = model.make_name() 158 159 def go(model): 160 model.modules[n] = (model.source, model.decls) 161 (model.source, model.decls) = ("", {}) 162 163 options += [lambda model, options: add_import(model, options, n)] 164 return go 165 166 167def add_top_level_decl(model, options): 168 n = model.make_name() 169 d = random.choice([decl for decl in decls if decl.valid(model)]) 170 171 def go(model): 172 if not d.valid(model): 173 return 174 d.apply(model, n) 175 176 return go 177 178 179def add_import(model, options, module_name): 180 def go(model): 181 if module_name in model.modules: 182 model.source += '#include "%s.h"\n' % module_name 183 model.decls.update(model.modules[module_name][1]) 184 185 return go 186 187 188sys.stdout.write("Finding bug: ") 189while True: 190 if generate(): 191 break 192 sys.stdout.write(".") 193 sys.stdout.flush() 194