pretty_asserts.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. #!/usr/bin/env python3
  2. import re
  3. import sys
  4. # NOTE the use of macros here helps keep a consistent stack depth which
  5. # tools may rely on.
  6. #
  7. # If compilation errors are noisy consider using -ftrack-macro-expansion=0.
  8. #
  9. LIMIT = 16
  10. CMP = {
  11. '==': 'eq',
  12. '!=': 'ne',
  13. '<=': 'le',
  14. '>=': 'ge',
  15. '<': 'lt',
  16. '>': 'gt',
  17. }
  18. LEXEMES = {
  19. 'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'],
  20. 'assert': ['assert'],
  21. 'arrow': ['=>'],
  22. 'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"],
  23. 'paren': ['\(', '\)'],
  24. 'cmp': CMP.keys(),
  25. 'logic': ['\&\&', '\|\|'],
  26. 'sep': [':', ';', '\{', '\}', ','],
  27. }
  28. def openio(path, mode='r'):
  29. if path == '-':
  30. if 'r' in mode:
  31. return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
  32. else:
  33. return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
  34. else:
  35. return open(path, mode)
  36. def write_header(f, limit=LIMIT):
  37. f.writeln("// Generated by %s:" % sys.argv[0])
  38. f.writeln("//")
  39. f.writeln("// %s" % ' '.join(sys.argv))
  40. f.writeln("//")
  41. f.writeln()
  42. f.writeln("#include <stdbool.h>")
  43. f.writeln("#include <stdint.h>")
  44. f.writeln("#include <inttypes.h>")
  45. f.writeln("#include <stdio.h>")
  46. f.writeln("#include <string.h>")
  47. f.writeln("#include <signal.h>")
  48. # give source a chance to define feature macros
  49. f.writeln("#undef _FEATURES_H")
  50. f.writeln()
  51. # write print macros
  52. f.writeln("__attribute__((unused))")
  53. f.writeln("static void __pretty_assert_print_bool(")
  54. f.writeln(" const void *v, size_t size) {")
  55. f.writeln(" (void)size;")
  56. f.writeln(" printf(\"%s\", *(const bool*)v ? \"true\" : \"false\");")
  57. f.writeln("}")
  58. f.writeln()
  59. f.writeln("__attribute__((unused))")
  60. f.writeln("static void __pretty_assert_print_int(")
  61. f.writeln(" const void *v, size_t size) {")
  62. f.writeln(" (void)size;")
  63. f.writeln(" printf(\"%\"PRIiMAX, *(const intmax_t*)v);")
  64. f.writeln("}")
  65. f.writeln()
  66. f.writeln("__attribute__((unused))")
  67. f.writeln("static void __pretty_assert_print_mem(")
  68. f.writeln(" const void *v, size_t size) {")
  69. f.writeln(" const uint8_t *v_ = v;")
  70. f.writeln(" printf(\"\\\"\");")
  71. f.writeln(" for (size_t i = 0; i < size && i < %d; i++) {" % limit)
  72. f.writeln(" if (v_[i] >= ' ' && v_[i] <= '~') {")
  73. f.writeln(" printf(\"%c\", v_[i]);")
  74. f.writeln(" } else {")
  75. f.writeln(" printf(\"\\\\x%02x\", v_[i]);")
  76. f.writeln(" }")
  77. f.writeln(" }")
  78. f.writeln(" if (size > %d) {" % limit)
  79. f.writeln(" printf(\"...\");")
  80. f.writeln(" }")
  81. f.writeln(" printf(\"\\\"\");")
  82. f.writeln("}")
  83. f.writeln()
  84. f.writeln("__attribute__((unused))")
  85. f.writeln("static void __pretty_assert_print_str(")
  86. f.writeln(" const void *v, size_t size) {")
  87. f.writeln(" __pretty_assert_print_mem(v, size);")
  88. f.writeln("}")
  89. f.writeln()
  90. f.writeln("__attribute__((unused, noinline))")
  91. f.writeln("static void __pretty_assert_fail(")
  92. f.writeln(" const char *file, int line,")
  93. f.writeln(" void (*type_print_cb)(const void*, size_t),")
  94. f.writeln(" const char *cmp,")
  95. f.writeln(" const void *lh, size_t lsize,")
  96. f.writeln(" const void *rh, size_t rsize) {")
  97. f.writeln(" printf(\"%s:%d:assert: assert failed with \", file, line);")
  98. f.writeln(" type_print_cb(lh, lsize);")
  99. f.writeln(" printf(\", expected %s \", cmp);")
  100. f.writeln(" type_print_cb(rh, rsize);")
  101. f.writeln(" printf(\"\\n\");")
  102. f.writeln(" fflush(NULL);")
  103. f.writeln(" raise(SIGABRT);")
  104. f.writeln("}")
  105. f.writeln()
  106. # write assert macros
  107. for op, cmp in sorted(CMP.items()):
  108. f.writeln("#define __PRETTY_ASSERT_BOOL_%s(lh, rh) do { \\"
  109. % cmp.upper())
  110. f.writeln(" bool _lh = !!(lh); \\")
  111. f.writeln(" bool _rh = !!(rh); \\")
  112. f.writeln(" if (!(_lh %s _rh)) { \\" % op)
  113. f.writeln(" __pretty_assert_fail( \\")
  114. f.writeln(" __FILE__, __LINE__, \\")
  115. f.writeln(" __pretty_assert_print_bool, \"%s\", \\"
  116. % cmp)
  117. f.writeln(" &_lh, 0, \\")
  118. f.writeln(" &_rh, 0); \\")
  119. f.writeln(" } \\")
  120. f.writeln("} while (0)")
  121. for op, cmp in sorted(CMP.items()):
  122. f.writeln("#define __PRETTY_ASSERT_INT_%s(lh, rh) do { \\"
  123. % cmp.upper())
  124. f.writeln(" __typeof__(lh) _lh = lh; \\")
  125. f.writeln(" __typeof__(lh) _rh = rh; \\")
  126. f.writeln(" if (!(_lh %s _rh)) { \\" % op)
  127. f.writeln(" __pretty_assert_fail( \\")
  128. f.writeln(" __FILE__, __LINE__, \\")
  129. f.writeln(" __pretty_assert_print_int, \"%s\", \\"
  130. % cmp)
  131. f.writeln(" &(intmax_t){_lh}, 0, \\")
  132. f.writeln(" &(intmax_t){_rh}, 0); \\")
  133. f.writeln(" } \\")
  134. f.writeln("} while (0)")
  135. for op, cmp in sorted(CMP.items()):
  136. f.writeln("#define __PRETTY_ASSERT_MEM_%s(lh, rh, size) do { \\"
  137. % cmp.upper())
  138. f.writeln(" const void *_lh = lh; \\")
  139. f.writeln(" const void *_rh = rh; \\")
  140. f.writeln(" if (!(memcmp(_lh, _rh, size) %s 0)) { \\" % op)
  141. f.writeln(" __pretty_assert_fail( \\")
  142. f.writeln(" __FILE__, __LINE__, \\")
  143. f.writeln(" __pretty_assert_print_mem, \"%s\", \\"
  144. % cmp)
  145. f.writeln(" _lh, size, \\")
  146. f.writeln(" _rh, size); \\")
  147. f.writeln(" } \\")
  148. f.writeln("} while (0)")
  149. for op, cmp in sorted(CMP.items()):
  150. f.writeln("#define __PRETTY_ASSERT_STR_%s(lh, rh) do { \\"
  151. % cmp.upper())
  152. f.writeln(" const char *_lh = lh; \\")
  153. f.writeln(" const char *_rh = rh; \\")
  154. f.writeln(" if (!(strcmp(_lh, _rh) %s 0)) { \\" % op)
  155. f.writeln(" __pretty_assert_fail( \\")
  156. f.writeln(" __FILE__, __LINE__, \\")
  157. f.writeln(" __pretty_assert_print_str, \"%s\", \\"
  158. % cmp)
  159. f.writeln(" _lh, strlen(_lh), \\")
  160. f.writeln(" _rh, strlen(_rh)); \\")
  161. f.writeln(" } \\")
  162. f.writeln("} while (0)")
  163. f.writeln()
  164. f.writeln()
  165. def mkassert(type, cmp, lh, rh, size=None):
  166. if size is not None:
  167. return ("__PRETTY_ASSERT_%s_%s(%s, %s, %s)"
  168. % (type.upper(), cmp.upper(), lh, rh, size))
  169. else:
  170. return ("__PRETTY_ASSERT_%s_%s(%s, %s)"
  171. % (type.upper(), cmp.upper(), lh, rh))
  172. # simple recursive descent parser
  173. class ParseFailure(Exception):
  174. def __init__(self, expected, found):
  175. self.expected = expected
  176. self.found = found
  177. def __str__(self):
  178. return "expected %r, found %s..." % (
  179. self.expected, repr(self.found)[:70])
  180. class Parser:
  181. def __init__(self, in_f, lexemes=LEXEMES):
  182. p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l))
  183. for n, l in lexemes.items())
  184. p = re.compile(p, re.DOTALL)
  185. data = in_f.read()
  186. tokens = []
  187. line = 1
  188. col = 0
  189. while True:
  190. m = p.search(data)
  191. if m:
  192. if m.start() > 0:
  193. tokens.append((None, data[:m.start()], line, col))
  194. tokens.append((m.lastgroup, m.group(), line, col))
  195. data = data[m.end():]
  196. else:
  197. tokens.append((None, data, line, col))
  198. break
  199. self.tokens = tokens
  200. self.off = 0
  201. def lookahead(self, *pattern):
  202. if self.off < len(self.tokens):
  203. token = self.tokens[self.off]
  204. if token[0] in pattern or token[1] in pattern:
  205. self.m = token[1]
  206. return self.m
  207. self.m = None
  208. return self.m
  209. def accept(self, *patterns):
  210. m = self.lookahead(*patterns)
  211. if m is not None:
  212. self.off += 1
  213. return m
  214. def expect(self, *patterns):
  215. m = self.accept(*patterns)
  216. if not m:
  217. raise ParseFailure(patterns, self.tokens[self.off:])
  218. return m
  219. def push(self):
  220. return self.off
  221. def pop(self, state):
  222. self.off = state
  223. def p_assert(p):
  224. state = p.push()
  225. # assert(memcmp(a,b,size) cmp 0)?
  226. try:
  227. p.expect('assert') ; p.accept('ws')
  228. p.expect('(') ; p.accept('ws')
  229. p.expect('memcmp') ; p.accept('ws')
  230. p.expect('(') ; p.accept('ws')
  231. lh = p_expr(p) ; p.accept('ws')
  232. p.expect(',') ; p.accept('ws')
  233. rh = p_expr(p) ; p.accept('ws')
  234. p.expect(',') ; p.accept('ws')
  235. size = p_expr(p) ; p.accept('ws')
  236. p.expect(')') ; p.accept('ws')
  237. cmp = p.expect('cmp') ; p.accept('ws')
  238. p.expect('0') ; p.accept('ws')
  239. p.expect(')')
  240. return mkassert('mem', CMP[cmp], lh, rh, size)
  241. except ParseFailure:
  242. p.pop(state)
  243. # assert(strcmp(a,b) cmp 0)?
  244. try:
  245. p.expect('assert') ; p.accept('ws')
  246. p.expect('(') ; p.accept('ws')
  247. p.expect('strcmp') ; p.accept('ws')
  248. p.expect('(') ; p.accept('ws')
  249. lh = p_expr(p) ; p.accept('ws')
  250. p.expect(',') ; p.accept('ws')
  251. rh = p_expr(p) ; p.accept('ws')
  252. p.expect(')') ; p.accept('ws')
  253. cmp = p.expect('cmp') ; p.accept('ws')
  254. p.expect('0') ; p.accept('ws')
  255. p.expect(')')
  256. return mkassert('str', CMP[cmp], lh, rh)
  257. except ParseFailure:
  258. p.pop(state)
  259. # assert(a cmp b)?
  260. try:
  261. p.expect('assert') ; p.accept('ws')
  262. p.expect('(') ; p.accept('ws')
  263. lh = p_expr(p) ; p.accept('ws')
  264. cmp = p.expect('cmp') ; p.accept('ws')
  265. rh = p_expr(p) ; p.accept('ws')
  266. p.expect(')')
  267. return mkassert('int', CMP[cmp], lh, rh)
  268. except ParseFailure:
  269. p.pop(state)
  270. # assert(a)?
  271. p.expect('assert') ; p.accept('ws')
  272. p.expect('(') ; p.accept('ws')
  273. lh = p_exprs(p) ; p.accept('ws')
  274. p.expect(')')
  275. return mkassert('bool', 'eq', lh, 'true')
  276. def p_expr(p):
  277. res = []
  278. while True:
  279. if p.accept('('):
  280. res.append(p.m)
  281. while True:
  282. res.append(p_exprs(p))
  283. if p.accept('sep'):
  284. res.append(p.m)
  285. else:
  286. break
  287. res.append(p.expect(')'))
  288. elif p.lookahead('assert'):
  289. state = p.push()
  290. try:
  291. res.append(p_assert(p))
  292. except ParseFailure:
  293. p.pop(state)
  294. res.append(p.expect('assert'))
  295. elif p.accept('string', None, 'ws'):
  296. res.append(p.m)
  297. else:
  298. return ''.join(res)
  299. def p_exprs(p):
  300. res = []
  301. while True:
  302. res.append(p_expr(p))
  303. if p.accept('cmp', 'logic', ','):
  304. res.append(p.m)
  305. else:
  306. return ''.join(res)
  307. def p_stmt(p):
  308. ws = p.accept('ws') or ''
  309. # memcmp(lh,rh,size) => 0?
  310. if p.lookahead('memcmp'):
  311. state = p.push()
  312. try:
  313. p.expect('memcmp') ; p.accept('ws')
  314. p.expect('(') ; p.accept('ws')
  315. lh = p_expr(p) ; p.accept('ws')
  316. p.expect(',') ; p.accept('ws')
  317. rh = p_expr(p) ; p.accept('ws')
  318. p.expect(',') ; p.accept('ws')
  319. size = p_expr(p) ; p.accept('ws')
  320. p.expect(')') ; p.accept('ws')
  321. p.expect('=>') ; p.accept('ws')
  322. p.expect('0') ; p.accept('ws')
  323. return ws + mkassert('mem', 'eq', lh, rh, size)
  324. except ParseFailure:
  325. p.pop(state)
  326. # strcmp(lh,rh) => 0?
  327. if p.lookahead('strcmp'):
  328. state = p.push()
  329. try:
  330. p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
  331. lh = p_expr(p) ; p.accept('ws')
  332. p.expect(',') ; p.accept('ws')
  333. rh = p_expr(p) ; p.accept('ws')
  334. p.expect(')') ; p.accept('ws')
  335. p.expect('=>') ; p.accept('ws')
  336. p.expect('0') ; p.accept('ws')
  337. return ws + mkassert('str', 'eq', lh, rh)
  338. except ParseFailure:
  339. p.pop(state)
  340. # lh => rh?
  341. lh = p_exprs(p)
  342. if p.accept('=>'):
  343. rh = p_exprs(p)
  344. return ws + mkassert('int', 'eq', lh, rh)
  345. else:
  346. return ws + lh
  347. def main(input=None, output=None, pattern=[], limit=LIMIT):
  348. with openio(input or '-', 'r') as in_f:
  349. # create parser
  350. lexemes = LEXEMES.copy()
  351. lexemes['assert'] += pattern
  352. p = Parser(in_f, lexemes)
  353. with openio(output or '-', 'w') as f:
  354. def writeln(s=''):
  355. f.write(s)
  356. f.write('\n')
  357. f.writeln = writeln
  358. # write extra verbose asserts
  359. write_header(f, limit=limit)
  360. if input is not None:
  361. f.writeln("#line %d \"%s\"" % (1, input))
  362. # parse and write out stmt at a time
  363. try:
  364. while True:
  365. f.write(p_stmt(p))
  366. if p.accept('sep'):
  367. f.write(p.m)
  368. else:
  369. break
  370. except ParseFailure as f:
  371. pass
  372. for i in range(p.off, len(p.tokens)):
  373. f.write(p.tokens[i][1])
  374. if __name__ == "__main__":
  375. import argparse
  376. import sys
  377. parser = argparse.ArgumentParser(
  378. description="Preprocessor that makes asserts easy to debug.")
  379. parser.add_argument('input',
  380. help="Input C file.")
  381. parser.add_argument('-o', '--output', required=True,
  382. help="Output C file.")
  383. parser.add_argument('-p', '--pattern', action='append',
  384. help="Regex patterns to search for starting an assert statement. This"
  385. " implicitly includes \"assert\" and \"=>\".")
  386. parser.add_argument('-l', '--limit',
  387. default=LIMIT, type=lambda x: int(x, 0),
  388. help="Maximum number of characters to display in strcmp and memcmp.")
  389. sys.exit(main(**{k: v
  390. for k, v in vars(parser.parse_args()).items()
  391. if v is not None}))