168 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Python
		
	
	
	
| #!/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()
 |