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 | |
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 | |
16 | class 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 | |
37 | decls = [ |
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 | |
43 | class 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 | |
64 | fs = FS() |
65 | |
66 | class 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 | |
90 | def 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 | |
126 | def 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 | |
133 | def mutations(model): |
134 | options = [create_module, add_top_level_decl] |
135 | for opt in choose(options): |
136 | yield opt(model, options) |
137 | |
138 | def 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 | |
146 | def 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 | |
155 | def 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 | |
162 | sys.stdout.write('Finding bug: ') |
163 | while True: |
164 | if generate(): |
165 | break |
166 | sys.stdout.write('.') |
167 | sys.stdout.flush() |
168 | |