| #!/usr/bin/env python | |
| # To use: | |
| # 1) Update the 'decls' list below with your fuzzing configuration. | |
| # 2) Run with the clang binary as the command-line argument. | |
| from __future__ import absolute_import, division, print_function | |
| import random | |
| import subprocess | |
| import sys | |
| import os | |
| clang = sys.argv[1] | |
| none_opts = 0.3 | |
| class Decl(object): | |
| def __init__(self, text, depends=[], provides=[], conflicts=[]): | |
| self.text = text | |
| self.depends = depends | |
| self.provides = provides | |
| self.conflicts = conflicts | |
| def valid(self, model): | |
| for i in self.depends: | |
| if i not in model.decls: | |
| return False | |
| for i in self.conflicts: | |
| if i in model.decls: | |
| return False | |
| return True | |
| def apply(self, model, name): | |
| for i in self.provides: | |
| model.decls[i] = True | |
| model.source += self.text % {"name": name} | |
| decls = [ | |
| Decl("struct X { int n; };\n", provides=["X"], conflicts=["X"]), | |
| Decl('static_assert(X{.n=1}.n == 1, "");\n', depends=["X"]), | |
| Decl("X %(name)s;\n", depends=["X"]), | |
| ] | |
| class FS(object): | |
| def __init__(self): | |
| self.fs = {} | |
| self.prevfs = {} | |
| def write(self, path, contents): | |
| self.fs[path] = contents | |
| def done(self): | |
| for f, s in self.fs.items(): | |
| if self.prevfs.get(f) != s: | |
| f = file(f, "w") | |
| f.write(s) | |
| f.close() | |
| for f in self.prevfs: | |
| if f not in self.fs: | |
| os.remove(f) | |
| self.prevfs, self.fs = self.fs, {} | |
| fs = FS() | |
| class CodeModel(object): | |
| def __init__(self): | |
| self.source = "" | |
| self.modules = {} | |
| self.decls = {} | |
| self.i = 0 | |
| def make_name(self): | |
| self.i += 1 | |
| return "n" + str(self.i) | |
| def fails(self): | |
| fs.write( | |
| "module.modulemap", | |
| "".join( | |
| 'module %s { header "%s.h" export * }\n' % (m, m) | |
| for m in self.modules.keys() | |
| ), | |
| ) | |
| for m, (s, _) in self.modules.items(): | |
| fs.write("%s.h" % m, s) | |
| fs.write("main.cc", self.source) | |
| fs.done() | |
| return ( | |
| subprocess.call( | |
| [clang, "-std=c++11", "-c", "-fmodules", "main.cc", "-o", "/dev/null"] | |
| ) | |
| != 0 | |
| ) | |
| def generate(): | |
| model = CodeModel() | |
| m = [] | |
| try: | |
| for d in mutations(model): | |
| d(model) | |
| m.append(d) | |
| if not model.fails(): | |
| return | |
| except KeyboardInterrupt: | |
| print() | |
| return True | |
| sys.stdout.write("\nReducing:\n") | |
| sys.stdout.flush() | |
| try: | |
| while True: | |
| assert m, "got a failure with no steps; broken clang binary?" | |
| i = random.choice(list(range(len(m)))) | |
| x = m[0:i] + m[i + 1 :] | |
| m2 = CodeModel() | |
| for d in x: | |
| d(m2) | |
| if m2.fails(): | |
| m = x | |
| model = m2 | |
| else: | |
| sys.stdout.write(".") | |
| sys.stdout.flush() | |
| except KeyboardInterrupt: | |
| # FIXME: Clean out output directory first. | |
| model.fails() | |
| return model | |
| def choose(options): | |
| while True: | |
| i = int(random.uniform(0, len(options) + none_opts)) | |
| if i >= len(options): | |
| break | |
| yield options[i] | |
| def mutations(model): | |
| options = [create_module, add_top_level_decl] | |
| for opt in choose(options): | |
| yield opt(model, options) | |
| def create_module(model, options): | |
| n = model.make_name() | |
| def go(model): | |
| model.modules[n] = (model.source, model.decls) | |
| (model.source, model.decls) = ("", {}) | |
| options += [lambda model, options: add_import(model, options, n)] | |
| return go | |
| def add_top_level_decl(model, options): | |
| n = model.make_name() | |
| d = random.choice([decl for decl in decls if decl.valid(model)]) | |
| def go(model): | |
| if not d.valid(model): | |
| return | |
| d.apply(model, n) | |
| return go | |
| def add_import(model, options, module_name): | |
| def go(model): | |
| if module_name in model.modules: | |
| model.source += '#include "%s.h"\n' % module_name | |
| model.decls.update(model.modules[module_name][1]) | |
| return go | |
| sys.stdout.write("Finding bug: ") | |
| while True: | |
| if generate(): | |
| break | |
| sys.stdout.write(".") | |
| sys.stdout.flush() |