blob: 49ac522cd17e1bdc3a9df4d1bfa3ca22ee6a5472 [file] [log] [blame]
#!/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()