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