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