Richard Smith | 6e2a64f | 2016-06-27 19:43:46 +0000 | [diff] [blame] | 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 | |
Serge Guelton | c0ebe77 | 2018-12-18 08:36:33 +0000 | [diff] [blame^] | 7 | from __future__ import print_function |
Richard Smith | 6e2a64f | 2016-06-27 19:43:46 +0000 | [diff] [blame] | 8 | import random |
| 9 | import subprocess |
| 10 | import sys |
| 11 | import os |
| 12 | |
| 13 | clang = sys.argv[1] |
| 14 | none_opts = 0.3 |
| 15 | |
Serge Guelton | 09616bd | 2018-12-03 12:12:48 +0000 | [diff] [blame] | 16 | class Decl(object): |
Richard Smith | 6e2a64f | 2016-06-27 19:43:46 +0000 | [diff] [blame] | 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 | |
Serge Guelton | 09616bd | 2018-12-03 12:12:48 +0000 | [diff] [blame] | 43 | class FS(object): |
Richard Smith | 6e2a64f | 2016-06-27 19:43:46 +0000 | [diff] [blame] | 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 | |
Serge Guelton | 09616bd | 2018-12-03 12:12:48 +0000 | [diff] [blame] | 66 | class CodeModel(object): |
Richard Smith | 6e2a64f | 2016-06-27 19:43:46 +0000 | [diff] [blame] | 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: |
Serge Guelton | c0ebe77 | 2018-12-18 08:36:33 +0000 | [diff] [blame^] | 101 | print() |
Richard Smith | 6e2a64f | 2016-06-27 19:43:46 +0000 | [diff] [blame] | 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?' |
Serge Guelton | c5d97e3 | 2018-12-18 08:24:06 +0000 | [diff] [blame] | 110 | i = random.choice(list(range(len(m)))) |
Richard Smith | 6e2a64f | 2016-06-27 19:43:46 +0000 | [diff] [blame] | 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() |