| #!/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() |