194 lines · python
1#!/usr/bin/env python2 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_function8import random9import subprocess10import sys11import os12 13clang = sys.argv[1]14none_opts = 0.315 16 17class Decl(object):18 def __init__(self, text, depends=[], provides=[], conflicts=[]):19 self.text = text20 self.depends = depends21 self.provides = provides22 self.conflicts = conflicts23 24 def valid(self, model):25 for i in self.depends:26 if i not in model.decls:27 return False28 for i in self.conflicts:29 if i in model.decls:30 return False31 return True32 33 def apply(self, model, name):34 for i in self.provides:35 model.decls[i] = True36 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] = contents53 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 = 077 78 def make_name(self):79 self.i += 180 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 != 0102 )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 return115 except KeyboardInterrupt:116 print()117 return True118 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 = x132 model = m2133 else:134 sys.stdout.write(".")135 sys.stdout.flush()136 except KeyboardInterrupt:137 # FIXME: Clean out output directory first.138 model.fails()139 return model140 141 142def choose(options):143 while True:144 i = int(random.uniform(0, len(options) + none_opts))145 if i >= len(options):146 break147 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 go165 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 return174 d.apply(model, n)175 176 return go177 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_name183 model.decls.update(model.modules[module_name][1])184 185 return go186 187 188sys.stdout.write("Finding bug: ")189while True:190 if generate():191 break192 sys.stdout.write(".")193 sys.stdout.flush()194