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