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