forked from OSchip/llvm-project
Add simple, stupid, pattern-based fuzzer / reducer for modules bugs. I've
already used this to find and reduce quite a few bugs, and it works pretty well if you can find the right patterns. llvm-svn: 273913
This commit is contained in:
parent
82f41518ed
commit
6e2a64f289
|
@ -0,0 +1,166 @@
|
|||
#! /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()
|
Loading…
Reference in New Issue