xref: /llvm-project/clang/utils/modfuzz.py (revision dd3c26a045c081620375a878159f536758baba6e)
16e2a64f2SRichard Smith#!/usr/bin/env python
26e2a64f2SRichard Smith
36e2a64f2SRichard Smith# To use:
46e2a64f2SRichard Smith#  1) Update the 'decls' list below with your fuzzing configuration.
56e2a64f2SRichard Smith#  2) Run with the clang binary as the command-line argument.
66e2a64f2SRichard Smith
7b748c0e6SSerge Gueltonfrom __future__ import absolute_import, division, print_function
86e2a64f2SRichard Smithimport random
96e2a64f2SRichard Smithimport subprocess
106e2a64f2SRichard Smithimport sys
116e2a64f2SRichard Smithimport os
126e2a64f2SRichard Smith
136e2a64f2SRichard Smithclang = sys.argv[1]
146e2a64f2SRichard Smithnone_opts = 0.3
156e2a64f2SRichard Smith
16*dd3c26a0STobias Hieta
1709616bdbSSerge Gueltonclass Decl(object):
186e2a64f2SRichard Smith    def __init__(self, text, depends=[], provides=[], conflicts=[]):
196e2a64f2SRichard Smith        self.text = text
206e2a64f2SRichard Smith        self.depends = depends
216e2a64f2SRichard Smith        self.provides = provides
226e2a64f2SRichard Smith        self.conflicts = conflicts
236e2a64f2SRichard Smith
246e2a64f2SRichard Smith    def valid(self, model):
256e2a64f2SRichard Smith        for i in self.depends:
266e2a64f2SRichard Smith            if i not in model.decls:
276e2a64f2SRichard Smith                return False
286e2a64f2SRichard Smith        for i in self.conflicts:
296e2a64f2SRichard Smith            if i in model.decls:
306e2a64f2SRichard Smith                return False
316e2a64f2SRichard Smith        return True
326e2a64f2SRichard Smith
336e2a64f2SRichard Smith    def apply(self, model, name):
346e2a64f2SRichard Smith        for i in self.provides:
356e2a64f2SRichard Smith            model.decls[i] = True
36*dd3c26a0STobias Hieta        model.source += self.text % {"name": name}
37*dd3c26a0STobias Hieta
386e2a64f2SRichard Smith
396e2a64f2SRichard Smithdecls = [
40*dd3c26a0STobias Hieta    Decl("struct X { int n; };\n", provides=["X"], conflicts=["X"]),
41*dd3c26a0STobias Hieta    Decl('static_assert(X{.n=1}.n == 1, "");\n', depends=["X"]),
42*dd3c26a0STobias Hieta    Decl("X %(name)s;\n", depends=["X"]),
436e2a64f2SRichard Smith]
446e2a64f2SRichard Smith
45*dd3c26a0STobias Hieta
4609616bdbSSerge Gueltonclass FS(object):
476e2a64f2SRichard Smith    def __init__(self):
486e2a64f2SRichard Smith        self.fs = {}
496e2a64f2SRichard Smith        self.prevfs = {}
506e2a64f2SRichard Smith
516e2a64f2SRichard Smith    def write(self, path, contents):
526e2a64f2SRichard Smith        self.fs[path] = contents
536e2a64f2SRichard Smith
546e2a64f2SRichard Smith    def done(self):
556e2a64f2SRichard Smith        for f, s in self.fs.items():
566e2a64f2SRichard Smith            if self.prevfs.get(f) != s:
57*dd3c26a0STobias Hieta                f = file(f, "w")
586e2a64f2SRichard Smith                f.write(s)
596e2a64f2SRichard Smith                f.close()
606e2a64f2SRichard Smith
616e2a64f2SRichard Smith        for f in self.prevfs:
626e2a64f2SRichard Smith            if f not in self.fs:
636e2a64f2SRichard Smith                os.remove(f)
646e2a64f2SRichard Smith
656e2a64f2SRichard Smith        self.prevfs, self.fs = self.fs, {}
666e2a64f2SRichard Smith
67*dd3c26a0STobias Hieta
686e2a64f2SRichard Smithfs = FS()
696e2a64f2SRichard Smith
70*dd3c26a0STobias Hieta
7109616bdbSSerge Gueltonclass CodeModel(object):
726e2a64f2SRichard Smith    def __init__(self):
73*dd3c26a0STobias Hieta        self.source = ""
746e2a64f2SRichard Smith        self.modules = {}
756e2a64f2SRichard Smith        self.decls = {}
766e2a64f2SRichard Smith        self.i = 0
776e2a64f2SRichard Smith
786e2a64f2SRichard Smith    def make_name(self):
796e2a64f2SRichard Smith        self.i += 1
80*dd3c26a0STobias Hieta        return "n" + str(self.i)
816e2a64f2SRichard Smith
826e2a64f2SRichard Smith    def fails(self):
83*dd3c26a0STobias Hieta        fs.write(
84*dd3c26a0STobias Hieta            "module.modulemap",
85*dd3c26a0STobias Hieta            "".join(
86*dd3c26a0STobias Hieta                'module %s { header "%s.h" export * }\n' % (m, m)
87*dd3c26a0STobias Hieta                for m in self.modules.keys()
88*dd3c26a0STobias Hieta            ),
89*dd3c26a0STobias Hieta        )
906e2a64f2SRichard Smith
916e2a64f2SRichard Smith        for m, (s, _) in self.modules.items():
92*dd3c26a0STobias Hieta            fs.write("%s.h" % m, s)
936e2a64f2SRichard Smith
94*dd3c26a0STobias Hieta        fs.write("main.cc", self.source)
956e2a64f2SRichard Smith        fs.done()
966e2a64f2SRichard Smith
97*dd3c26a0STobias Hieta        return (
98*dd3c26a0STobias Hieta            subprocess.call(
99*dd3c26a0STobias Hieta                [clang, "-std=c++11", "-c", "-fmodules", "main.cc", "-o", "/dev/null"]
100*dd3c26a0STobias Hieta            )
101*dd3c26a0STobias Hieta            != 0
102*dd3c26a0STobias Hieta        )
103*dd3c26a0STobias Hieta
1046e2a64f2SRichard Smith
1056e2a64f2SRichard Smithdef generate():
1066e2a64f2SRichard Smith    model = CodeModel()
1076e2a64f2SRichard Smith    m = []
1086e2a64f2SRichard Smith
1096e2a64f2SRichard Smith    try:
1106e2a64f2SRichard Smith        for d in mutations(model):
1116e2a64f2SRichard Smith            d(model)
1126e2a64f2SRichard Smith            m.append(d)
1136e2a64f2SRichard Smith        if not model.fails():
1146e2a64f2SRichard Smith            return
1156e2a64f2SRichard Smith    except KeyboardInterrupt:
116c0ebe773SSerge Guelton        print()
1176e2a64f2SRichard Smith        return True
1186e2a64f2SRichard Smith
119*dd3c26a0STobias Hieta    sys.stdout.write("\nReducing:\n")
1206e2a64f2SRichard Smith    sys.stdout.flush()
1216e2a64f2SRichard Smith
1226e2a64f2SRichard Smith    try:
1236e2a64f2SRichard Smith        while True:
124*dd3c26a0STobias Hieta            assert m, "got a failure with no steps; broken clang binary?"
125c5d97e3eSSerge Guelton            i = random.choice(list(range(len(m))))
1266e2a64f2SRichard Smith            x = m[0:i] + m[i + 1 :]
1276e2a64f2SRichard Smith            m2 = CodeModel()
1286e2a64f2SRichard Smith            for d in x:
1296e2a64f2SRichard Smith                d(m2)
1306e2a64f2SRichard Smith            if m2.fails():
1316e2a64f2SRichard Smith                m = x
1326e2a64f2SRichard Smith                model = m2
1336e2a64f2SRichard Smith            else:
134*dd3c26a0STobias Hieta                sys.stdout.write(".")
1356e2a64f2SRichard Smith                sys.stdout.flush()
1366e2a64f2SRichard Smith    except KeyboardInterrupt:
1376e2a64f2SRichard Smith        # FIXME: Clean out output directory first.
1386e2a64f2SRichard Smith        model.fails()
1396e2a64f2SRichard Smith        return model
1406e2a64f2SRichard Smith
141*dd3c26a0STobias Hieta
1426e2a64f2SRichard Smithdef choose(options):
1436e2a64f2SRichard Smith    while True:
1446e2a64f2SRichard Smith        i = int(random.uniform(0, len(options) + none_opts))
1456e2a64f2SRichard Smith        if i >= len(options):
1466e2a64f2SRichard Smith            break
1476e2a64f2SRichard Smith        yield options[i]
1486e2a64f2SRichard Smith
149*dd3c26a0STobias Hieta
1506e2a64f2SRichard Smithdef mutations(model):
1516e2a64f2SRichard Smith    options = [create_module, add_top_level_decl]
1526e2a64f2SRichard Smith    for opt in choose(options):
1536e2a64f2SRichard Smith        yield opt(model, options)
1546e2a64f2SRichard Smith
155*dd3c26a0STobias Hieta
1566e2a64f2SRichard Smithdef create_module(model, options):
1576e2a64f2SRichard Smith    n = model.make_name()
158*dd3c26a0STobias Hieta
1596e2a64f2SRichard Smith    def go(model):
1606e2a64f2SRichard Smith        model.modules[n] = (model.source, model.decls)
161*dd3c26a0STobias Hieta        (model.source, model.decls) = ("", {})
162*dd3c26a0STobias Hieta
1636e2a64f2SRichard Smith    options += [lambda model, options: add_import(model, options, n)]
1646e2a64f2SRichard Smith    return go
1656e2a64f2SRichard Smith
166*dd3c26a0STobias Hieta
1676e2a64f2SRichard Smithdef add_top_level_decl(model, options):
1686e2a64f2SRichard Smith    n = model.make_name()
1696e2a64f2SRichard Smith    d = random.choice([decl for decl in decls if decl.valid(model)])
170*dd3c26a0STobias Hieta
1716e2a64f2SRichard Smith    def go(model):
1726e2a64f2SRichard Smith        if not d.valid(model):
1736e2a64f2SRichard Smith            return
1746e2a64f2SRichard Smith        d.apply(model, n)
175*dd3c26a0STobias Hieta
1766e2a64f2SRichard Smith    return go
1776e2a64f2SRichard Smith
178*dd3c26a0STobias Hieta
1796e2a64f2SRichard Smithdef add_import(model, options, module_name):
1806e2a64f2SRichard Smith    def go(model):
1816e2a64f2SRichard Smith        if module_name in model.modules:
1826e2a64f2SRichard Smith            model.source += '#include "%s.h"\n' % module_name
1836e2a64f2SRichard Smith            model.decls.update(model.modules[module_name][1])
184*dd3c26a0STobias Hieta
1856e2a64f2SRichard Smith    return go
1866e2a64f2SRichard Smith
187*dd3c26a0STobias Hieta
188*dd3c26a0STobias Hietasys.stdout.write("Finding bug: ")
1896e2a64f2SRichard Smithwhile True:
1906e2a64f2SRichard Smith    if generate():
1916e2a64f2SRichard Smith        break
192*dd3c26a0STobias Hieta    sys.stdout.write(".")
1936e2a64f2SRichard Smith    sys.stdout.flush()
194