prettyasserts.py 16 KB

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