modfuzz.py 3.8 KB

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