explode_asserts.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #!/usr/bin/env python3
  2. import parsy as p
  3. import re
  4. import io
  5. import sys
  6. ASSERT_PATTERN = p.string('LFS_ASSERT') | p.string('assert')
  7. ASSERT_CHARS = 'La'
  8. ASSERT_TARGET = '__LFS_ASSERT_{TYPE}_{COMP}'
  9. ASSERT_TESTS = {
  10. 'int': """
  11. __typeof__({lh}) _lh = {lh};
  12. __typeof__({lh}) _rh = (__typeof__({lh})){rh};
  13. if (!(_lh {op} _rh)) {{
  14. printf("%s:%d:assert: "
  15. "assert failed with %"PRIiMAX", expected {comp} %"PRIiMAX"\\n",
  16. {file}, {line}, (intmax_t)_lh, (intmax_t)_rh);
  17. fflush(NULL);
  18. raise(SIGABRT);
  19. }}
  20. """,
  21. 'str': """
  22. const char *_lh = {lh};
  23. const char *_rh = {rh};
  24. if (!(strcmp(_lh, _rh) {op} 0)) {{
  25. printf("%s:%d:assert: "
  26. "assert failed with \\\"%s\\\", expected {comp} \\\"%s\\\"\\n",
  27. {file}, {line}, _lh, _rh);
  28. fflush(NULL);
  29. raise(SIGABRT);
  30. }}
  31. """,
  32. 'bool': """
  33. bool _lh = !!({lh});
  34. bool _rh = !!({rh});
  35. if (!(_lh {op} _rh)) {{
  36. printf("%s:%d:assert: "
  37. "assert failed with %s, expected {comp} %s\\n",
  38. {file}, {line}, _lh ? "true" : "false", _rh ? "true" : "false");
  39. fflush(NULL);
  40. raise(SIGABRT);
  41. }}
  42. """,
  43. }
  44. def mkassert(lh, rh='true', type='bool', comp='eq'):
  45. return ((ASSERT_TARGET + "({lh}, {rh}, __FILE__, __LINE__, __func__)")
  46. .format(
  47. type=type, TYPE=type.upper(),
  48. comp=comp, COMP=comp.upper(),
  49. lh=lh.strip(' '),
  50. rh=rh.strip(' ')))
  51. def mkdecl(type, comp, op):
  52. return ((
  53. "#define "+ASSERT_TARGET+"(lh, rh, file, line, func)"
  54. " do {{"+re.sub('\s+', ' ', ASSERT_TESTS[type])+"}} while (0)\n")
  55. .format(
  56. type=type, TYPE=type.upper(),
  57. comp=comp, COMP=comp.upper(),
  58. lh='lh', rh='rh', op=op,
  59. file='file', line='line', func='func'))
  60. # add custom until combinator
  61. def until(self, end):
  62. return end.should_fail('should fail').then(self).many()
  63. p.Parser.until = until
  64. pcomp = (
  65. p.string('==').tag('eq') |
  66. p.string('!=').tag('ne') |
  67. p.string('<=').tag('le') |
  68. p.string('>=').tag('ge') |
  69. p.string('<').tag('lt') |
  70. p.string('>').tag('gt'));
  71. plogic = p.string('&&') | p.string('||')
  72. @p.generate
  73. def pstrassert():
  74. yield ASSERT_PATTERN + p.regex('\s*') + p.string('(') + p.regex('\s*')
  75. yield p.string('strcmp') + p.regex('\s*') + p.string('(') + p.regex('\s*')
  76. lh = yield pexpr.until(p.string(',') | p.string(')') | plogic)
  77. yield p.string(',') + p.regex('\s*')
  78. rh = yield pexpr.until(p.string(')') | plogic)
  79. yield p.string(')') + p.regex('\s*')
  80. op = yield pcomp
  81. yield p.regex('\s*') + p.string('0') + p.regex('\s*') + p.string(')')
  82. return mkassert(''.join(lh), ''.join(rh), 'str', op[0])
  83. @p.generate
  84. def pintassert():
  85. yield ASSERT_PATTERN + p.regex('\s*') + p.string('(') + p.regex('\s*')
  86. lh = yield pexpr.until(pcomp | p.string(')') | plogic)
  87. op = yield pcomp
  88. rh = yield pexpr.until(p.string(')') | plogic)
  89. yield p.string(')')
  90. return mkassert(''.join(lh), ''.join(rh), 'int', op[0])
  91. @p.generate
  92. def pboolassert():
  93. yield ASSERT_PATTERN + p.regex('\s*') + p.string('(') + p.regex('\s*')
  94. expr = yield pexpr.until(p.string(')'))
  95. yield p.string(')')
  96. return mkassert(''.join(expr), 'true', 'bool', 'eq')
  97. passert = p.peek(ASSERT_PATTERN) >> (pstrassert | pintassert | pboolassert)
  98. @p.generate
  99. def pcomment1():
  100. yield p.string('//')
  101. s = yield p.regex('[^\\n]*')
  102. yield p.string('\n')
  103. return '//' + s + '\n'
  104. @p.generate
  105. def pcomment2():
  106. yield p.string('/*')
  107. s = yield p.regex('((?!\*/).)*')
  108. yield p.string('*/')
  109. return '/*' + ''.join(s) + '*/'
  110. @p.generate
  111. def pcomment3():
  112. yield p.string('#')
  113. s = yield p.regex('[^\\n]*')
  114. yield p.string('\n')
  115. return '#' + s + '\n'
  116. pws = p.regex('\s+') | pcomment1 | pcomment2 | pcomment3
  117. @p.generate
  118. def pstring():
  119. q = yield p.regex('["\']')
  120. s = yield (p.string('\\%s' % q) | p.regex('[^%s]' % q)).many()
  121. yield p.string(q)
  122. return q + ''.join(s) + q
  123. @p.generate
  124. def pnested():
  125. l = yield p.string('(')
  126. n = yield pexpr.until(p.string(')'))
  127. r = yield p.string(')')
  128. return l + ''.join(n) + r
  129. pexpr = (
  130. # shortcut for a bit better performance
  131. p.regex('[^%s/#\'"():;{}=><,&|-]+' % ASSERT_CHARS) |
  132. pws |
  133. passert |
  134. pstring |
  135. pnested |
  136. p.string('->') |
  137. p.regex('.', re.DOTALL))
  138. @p.generate
  139. def pstmt():
  140. ws = yield pws.many()
  141. lh = yield pexpr.until(p.string('=>') | p.regex('[:;{}]'))
  142. op = yield p.string('=>').optional()
  143. if op == '=>':
  144. rh = yield pstmt
  145. return ''.join(ws) + mkassert(''.join(lh), rh, 'int', 'eq')
  146. else:
  147. return ''.join(ws) + ''.join(lh)
  148. @p.generate
  149. def pstmts():
  150. a = yield pstmt
  151. b = yield (p.regex('[:;{}]') + pstmt).many()
  152. return [a] + b
  153. def main(args):
  154. inf = open(args.input, 'r') if args.input else sys.stdin
  155. outf = open(args.output, 'w') if args.output else sys.stdout
  156. # parse C code
  157. input = inf.read()
  158. stmts = pstmts.parse(input)
  159. # write extra verbose asserts
  160. outf.write("#include <stdbool.h>\n")
  161. outf.write("#include <stdint.h>\n")
  162. outf.write("#include <inttypes.h>\n")
  163. outf.write("#include <signal.h>\n")
  164. outf.write(mkdecl('int', 'eq', '=='))
  165. outf.write(mkdecl('int', 'ne', '!='))
  166. outf.write(mkdecl('int', 'lt', '<'))
  167. outf.write(mkdecl('int', 'gt', '>'))
  168. outf.write(mkdecl('int', 'le', '<='))
  169. outf.write(mkdecl('int', 'ge', '>='))
  170. outf.write(mkdecl('str', 'eq', '=='))
  171. outf.write(mkdecl('str', 'ne', '!='))
  172. outf.write(mkdecl('str', 'lt', '<'))
  173. outf.write(mkdecl('str', 'gt', '>'))
  174. outf.write(mkdecl('str', 'le', '<='))
  175. outf.write(mkdecl('str', 'ge', '>='))
  176. outf.write(mkdecl('bool', 'eq', '=='))
  177. if args.input:
  178. outf.write("#line %d \"%s\"\n" % (1, args.input))
  179. # write parsed statements
  180. for stmt in stmts:
  181. outf.write(stmt)
  182. if __name__ == "__main__":
  183. import argparse
  184. parser = argparse.ArgumentParser(
  185. description="Cpp step that increases assert verbosity")
  186. parser.add_argument('input', nargs='?',
  187. help="Input C file after cpp.")
  188. parser.add_argument('-o', '--output',
  189. help="Output C file.")
  190. main(parser.parse_args())