|
#! /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. |
|
|
|
import random |
|
import subprocess |
|
import sys |
|
import os |
|
|
|
clang = sys.argv[1] |
|
none_opts = 0.3 |
|
|
|
class Decl: |
|
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: |
|
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: |
|
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(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() |