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