summaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
authorRichard Smith <richard-llvm@metafoo.co.uk>2016-06-27 19:43:46 +0000
committerRichard Smith <richard-llvm@metafoo.co.uk>2016-06-27 19:43:46 +0000
commite770b05ad598827b7521e599d4e360b5c2614ab6 (patch)
tree31e1aa4d69664970d00e1dbda35d2847bfbd86df /utils
parent8313f1a0e74a83fef3c2be2e401d5083324126ca (diff)
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. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@273913 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'utils')
-rw-r--r--utils/modfuzz.py166
1 files changed, 166 insertions, 0 deletions
diff --git a/utils/modfuzz.py b/utils/modfuzz.py
new file mode 100644
index 0000000000..a6aa1f1a25
--- /dev/null
+++ b/utils/modfuzz.py
@@ -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()