1#!/usr/bin/env python
2
3# To use:
4#  1) Update the 'decls' list below with your fuzzing configuration.
5#  2) Run with the clang binary as the command-line argument.
6
7from __future__ import absolute_import, division, print_function
8import random
9import subprocess
10import sys
11import os
12
13clang = sys.argv[1]
14none_opts = 0.3
15
16class Decl(object):
17  def __init__(self, text, depends=[], provides=[], conflicts=[]):
18    self.text = text
19    self.depends = depends
20    self.provides = provides
21    self.conflicts = conflicts
22
23  def valid(self, model):
24    for i in self.depends:
25      if i not in model.decls:
26        return False
27    for i in self.conflicts:
28      if i in model.decls:
29        return False
30    return True
31
32  def apply(self, model, name):
33    for i in self.provides:
34      model.decls[i] = True
35    model.source += self.text % {'name': name}
36
37decls = [
38  Decl('struct X { int n; };\n', provides=['X'], conflicts=['X']),
39  Decl('static_assert(X{.n=1}.n == 1, "");\n', depends=['X']),
40  Decl('X %(name)s;\n', depends=['X']),
41]
42
43class FS(object):
44  def __init__(self):
45    self.fs = {}
46    self.prevfs = {}
47
48  def write(self, path, contents):
49    self.fs[path] = contents
50
51  def done(self):
52    for f, s in self.fs.items():
53      if self.prevfs.get(f) != s:
54        f = file(f, 'w')
55        f.write(s)
56        f.close()
57
58    for f in self.prevfs:
59      if f not in self.fs:
60        os.remove(f)
61
62    self.prevfs, self.fs = self.fs, {}
63
64fs = FS()
65
66class CodeModel(object):
67  def __init__(self):
68    self.source = ''
69    self.modules = {}
70    self.decls = {}
71    self.i = 0
72
73  def make_name(self):
74    self.i += 1
75    return 'n' + str(self.i)
76
77  def fails(self):
78    fs.write('module.modulemap',
79          ''.join('module %s { header "%s.h" export * }\n' % (m, m)
80                  for m in self.modules.keys()))
81
82    for m, (s, _) in self.modules.items():
83      fs.write('%s.h' % m, s)
84
85    fs.write('main.cc', self.source)
86    fs.done()
87
88    return subprocess.call([clang, '-std=c++11', '-c', '-fmodules', 'main.cc', '-o', '/dev/null']) != 0
89
90def generate():
91  model = CodeModel()
92  m = []
93
94  try:
95    for d in mutations(model):
96      d(model)
97      m.append(d)
98    if not model.fails():
99      return
100  except KeyboardInterrupt:
101    print()
102    return True
103
104  sys.stdout.write('\nReducing:\n')
105  sys.stdout.flush()
106
107  try:
108    while True:
109      assert m, 'got a failure with no steps; broken clang binary?'
110      i = random.choice(list(range(len(m))))
111      x = m[0:i] + m[i+1:]
112      m2 = CodeModel()
113      for d in x:
114        d(m2)
115      if m2.fails():
116        m = x
117        model = m2
118      else:
119        sys.stdout.write('.')
120        sys.stdout.flush()
121  except KeyboardInterrupt:
122    # FIXME: Clean out output directory first.
123    model.fails()
124    return model
125
126def choose(options):
127  while True:
128    i = int(random.uniform(0, len(options) + none_opts))
129    if i >= len(options):
130      break
131    yield options[i]
132
133def mutations(model):
134  options = [create_module, add_top_level_decl]
135  for opt in choose(options):
136    yield opt(model, options)
137
138def create_module(model, options):
139  n = model.make_name()
140  def go(model):
141    model.modules[n] = (model.source, model.decls)
142    (model.source, model.decls) = ('', {})
143  options += [lambda model, options: add_import(model, options, n)]
144  return go
145
146def add_top_level_decl(model, options):
147  n = model.make_name()
148  d = random.choice([decl for decl in decls if decl.valid(model)])
149  def go(model):
150    if not d.valid(model):
151      return
152    d.apply(model, n)
153  return go
154
155def add_import(model, options, module_name):
156  def go(model):
157    if module_name in model.modules:
158      model.source += '#include "%s.h"\n' % module_name
159      model.decls.update(model.modules[module_name][1])
160  return go
161
162sys.stdout.write('Finding bug: ')
163while True:
164  if generate():
165    break
166  sys.stdout.write('.')
167  sys.stdout.flush()
168