blob: 4dc25e846963b7911b5f43d08e5e356bf7ff9b80 [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
Serge Gueltonc0ebe772018-12-18 08:36:33 +00007from __future__ import print_function
Richard Smith6e2a64f2016-06-27 19:43:46 +00008import random
9import subprocess
10import sys
11import os
12
13clang = sys.argv[1]
14none_opts = 0.3
15
Serge Guelton09616bd2018-12-03 12:12:48 +000016class Decl(object):
Richard Smith6e2a64f2016-06-27 19:43:46 +000017 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
Serge Guelton09616bd2018-12-03 12:12:48 +000043class FS(object):
Richard Smith6e2a64f2016-06-27 19:43:46 +000044 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
Serge Guelton09616bd2018-12-03 12:12:48 +000066class CodeModel(object):
Richard Smith6e2a64f2016-06-27 19:43:46 +000067 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:
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000101 print()
Richard Smith6e2a64f2016-06-27 19:43:46 +0000102 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?'
Serge Gueltonc5d97e32018-12-18 08:24:06 +0000110 i = random.choice(list(range(len(m))))
Richard Smith6e2a64f2016-06-27 19:43:46 +0000111 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()