| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- #!/usr/bin/env python3
- #
- # Preprocessor that makes asserts easier to debug.
- #
- # Example:
- # ./scripts/prettyasserts.py -p LFS_ASSERT lfs.c -o lfs.a.c
- #
- # Copyright (c) 2022, The littlefs authors.
- # Copyright (c) 2020, Arm Limited. All rights reserved.
- # SPDX-License-Identifier: BSD-3-Clause
- #
- import re
- import sys
- # NOTE the use of macros here helps keep a consistent stack depth which
- # tools may rely on.
- #
- # If compilation errors are noisy consider using -ftrack-macro-expansion=0.
- #
- LIMIT = 16
- CMP = {
- '==': 'eq',
- '!=': 'ne',
- '<=': 'le',
- '>=': 'ge',
- '<': 'lt',
- '>': 'gt',
- }
- LEXEMES = {
- 'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'],
- 'assert': ['assert'],
- 'arrow': ['=>'],
- 'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"],
- 'paren': [r'\(', r'\)'],
- 'cmp': CMP.keys(),
- 'logic': [r'\&\&', r'\|\|'],
- 'sep': [':', ';', r'\{', r'\}', ','],
- 'op': ['->'], # specifically ops that conflict with cmp
- }
- def openio(path, mode='r', buffering=-1):
- # allow '-' for stdin/stdout
- if path == '-':
- if mode == 'r':
- return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
- else:
- return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering)
- else:
- return open(path, mode, buffering)
- def write_header(f, limit=LIMIT):
- f.writeln("// Generated by %s:" % sys.argv[0])
- f.writeln("//")
- f.writeln("// %s" % ' '.join(sys.argv))
- f.writeln("//")
- f.writeln()
- f.writeln("#include <stdbool.h>")
- f.writeln("#include <stdint.h>")
- f.writeln("#include <inttypes.h>")
- f.writeln("#include <stdio.h>")
- f.writeln("#include <string.h>")
- f.writeln("#include <signal.h>")
- # give source a chance to define feature macros
- f.writeln("#undef _FEATURES_H")
- f.writeln()
- # write print macros
- f.writeln("__attribute__((unused))")
- f.writeln("static void __pretty_assert_print_bool(")
- f.writeln(" const void *v, size_t size) {")
- f.writeln(" (void)size;")
- f.writeln(" printf(\"%s\", *(const bool*)v ? \"true\" : \"false\");")
- f.writeln("}")
- f.writeln()
- f.writeln("__attribute__((unused))")
- f.writeln("static void __pretty_assert_print_int(")
- f.writeln(" const void *v, size_t size) {")
- f.writeln(" (void)size;")
- f.writeln(" printf(\"%\"PRIiMAX, *(const intmax_t*)v);")
- f.writeln("}")
- f.writeln()
- f.writeln("__attribute__((unused))")
- f.writeln("static void __pretty_assert_print_ptr(")
- f.writeln(" const void *v, size_t size) {")
- f.writeln(" (void)size;")
- f.writeln(" printf(\"%p\", v);")
- f.writeln("}")
- f.writeln()
- f.writeln("__attribute__((unused))")
- f.writeln("static void __pretty_assert_print_mem(")
- f.writeln(" const void *v, size_t size) {")
- f.writeln(" const uint8_t *v_ = v;")
- f.writeln(" printf(\"\\\"\");")
- f.writeln(" for (size_t i = 0; i < size && i < %d; i++) {" % limit)
- f.writeln(" if (v_[i] >= ' ' && v_[i] <= '~') {")
- f.writeln(" printf(\"%c\", v_[i]);")
- f.writeln(" } else {")
- f.writeln(" printf(\"\\\\x%02x\", v_[i]);")
- f.writeln(" }")
- f.writeln(" }")
- f.writeln(" if (size > %d) {" % limit)
- f.writeln(" printf(\"...\");")
- f.writeln(" }")
- f.writeln(" printf(\"\\\"\");")
- f.writeln("}")
- f.writeln()
- f.writeln("__attribute__((unused))")
- f.writeln("static void __pretty_assert_print_str(")
- f.writeln(" const void *v, size_t size) {")
- f.writeln(" __pretty_assert_print_mem(v, size);")
- f.writeln("}")
- f.writeln()
- f.writeln("__attribute__((unused, noinline))")
- f.writeln("static void __pretty_assert_fail(")
- f.writeln(" const char *file, int line,")
- f.writeln(" void (*type_print_cb)(const void*, size_t),")
- f.writeln(" const char *cmp,")
- f.writeln(" const void *lh, size_t lsize,")
- f.writeln(" const void *rh, size_t rsize) {")
- f.writeln(" printf(\"%s:%d:assert: assert failed with \", file, line);")
- f.writeln(" type_print_cb(lh, lsize);")
- f.writeln(" printf(\", expected %s \", cmp);")
- f.writeln(" type_print_cb(rh, rsize);")
- f.writeln(" printf(\"\\n\");")
- f.writeln(" fflush(NULL);")
- f.writeln(" raise(SIGABRT);")
- f.writeln("}")
- f.writeln()
- # write assert macros
- for op, cmp in sorted(CMP.items()):
- f.writeln("#define __PRETTY_ASSERT_BOOL_%s(lh, rh) do { \\"
- % cmp.upper())
- f.writeln(" bool _lh = !!(lh); \\")
- f.writeln(" bool _rh = !!(rh); \\")
- f.writeln(" if (!(_lh %s _rh)) { \\" % op)
- f.writeln(" __pretty_assert_fail( \\")
- f.writeln(" __FILE__, __LINE__, \\")
- f.writeln(" __pretty_assert_print_bool, \"%s\", \\"
- % cmp)
- f.writeln(" &_lh, 0, \\")
- f.writeln(" &_rh, 0); \\")
- f.writeln(" } \\")
- f.writeln("} while (0)")
- for op, cmp in sorted(CMP.items()):
- f.writeln("#define __PRETTY_ASSERT_INT_%s(lh, rh) do { \\"
- % cmp.upper())
- f.writeln(" __typeof__(lh) _lh = lh; \\")
- f.writeln(" __typeof__(lh) _rh = rh; \\")
- f.writeln(" if (!(_lh %s _rh)) { \\" % op)
- f.writeln(" __pretty_assert_fail( \\")
- f.writeln(" __FILE__, __LINE__, \\")
- f.writeln(" __pretty_assert_print_int, \"%s\", \\"
- % cmp)
- f.writeln(" &(intmax_t){_lh}, 0, \\")
- f.writeln(" &(intmax_t){_rh}, 0); \\")
- f.writeln(" } \\")
- f.writeln("} while (0)")
- for op, cmp in sorted(CMP.items()):
- f.writeln("#define __PRETTY_ASSERT_MEM_%s(lh, rh, size) do { \\"
- % cmp.upper())
- f.writeln(" const void *_lh = lh; \\")
- f.writeln(" const void *_rh = rh; \\")
- f.writeln(" if (!(memcmp(_lh, _rh, size) %s 0)) { \\" % op)
- f.writeln(" __pretty_assert_fail( \\")
- f.writeln(" __FILE__, __LINE__, \\")
- f.writeln(" __pretty_assert_print_mem, \"%s\", \\"
- % cmp)
- f.writeln(" _lh, size, \\")
- f.writeln(" _rh, size); \\")
- f.writeln(" } \\")
- f.writeln("} while (0)")
- for op, cmp in sorted(CMP.items()):
- f.writeln("#define __PRETTY_ASSERT_STR_%s(lh, rh) do { \\"
- % cmp.upper())
- f.writeln(" const char *_lh = lh; \\")
- f.writeln(" const char *_rh = rh; \\")
- f.writeln(" if (!(strcmp(_lh, _rh) %s 0)) { \\" % op)
- f.writeln(" __pretty_assert_fail( \\")
- f.writeln(" __FILE__, __LINE__, \\")
- f.writeln(" __pretty_assert_print_str, \"%s\", \\"
- % cmp)
- f.writeln(" _lh, strlen(_lh), \\")
- f.writeln(" _rh, strlen(_rh)); \\")
- f.writeln(" } \\")
- f.writeln("} while (0)")
- for op, cmp in sorted(CMP.items()):
- # Only EQ and NE are supported when compared to NULL.
- if cmp not in ['eq', 'ne']:
- continue
- f.writeln("#define __PRETTY_ASSERT_PTR_%s(lh, rh) do { \\"
- % cmp.upper())
- f.writeln(" const void *_lh = (const void*)(uintptr_t)lh; \\")
- f.writeln(" const void *_rh = (const void*)(uintptr_t)rh; \\")
- f.writeln(" if (!(_lh %s _rh)) { \\" % op)
- f.writeln(" __pretty_assert_fail( \\")
- f.writeln(" __FILE__, __LINE__, \\")
- f.writeln(" __pretty_assert_print_ptr, \"%s\", \\"
- % cmp)
- f.writeln(" (const void*){_lh}, 0, \\")
- f.writeln(" (const void*){_rh}, 0); \\")
- f.writeln(" } \\")
- f.writeln("} while (0)")
- f.writeln()
- f.writeln()
- def mkassert(type, cmp, lh, rh, size=None):
- if size is not None:
- return ("__PRETTY_ASSERT_%s_%s(%s, %s, %s)"
- % (type.upper(), cmp.upper(), lh, rh, size))
- else:
- return ("__PRETTY_ASSERT_%s_%s(%s, %s)"
- % (type.upper(), cmp.upper(), lh, rh))
- # simple recursive descent parser
- class ParseFailure(Exception):
- def __init__(self, expected, found):
- self.expected = expected
- self.found = found
- def __str__(self):
- return "expected %r, found %s..." % (
- self.expected, repr(self.found)[:70])
- class Parser:
- def __init__(self, in_f, lexemes=LEXEMES):
- p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l))
- for n, l in lexemes.items())
- p = re.compile(p, re.DOTALL)
- data = in_f.read()
- tokens = []
- line = 1
- col = 0
- while True:
- m = p.search(data)
- if m:
- if m.start() > 0:
- tokens.append((None, data[:m.start()], line, col))
- tokens.append((m.lastgroup, m.group(), line, col))
- data = data[m.end():]
- else:
- tokens.append((None, data, line, col))
- break
- self.tokens = tokens
- self.off = 0
- def lookahead(self, *pattern):
- if self.off < len(self.tokens):
- token = self.tokens[self.off]
- if token[0] in pattern or token[1] in pattern:
- self.m = token[1]
- return self.m
- self.m = None
- return self.m
- def accept(self, *patterns):
- m = self.lookahead(*patterns)
- if m is not None:
- self.off += 1
- return m
- def expect(self, *patterns):
- m = self.accept(*patterns)
- if not m:
- raise ParseFailure(patterns, self.tokens[self.off:])
- return m
- def push(self):
- return self.off
- def pop(self, state):
- self.off = state
- def p_assert(p):
- state = p.push()
- # assert(memcmp(a,b,size) cmp 0)?
- try:
- p.expect('assert') ; p.accept('ws')
- p.expect('(') ; p.accept('ws')
- p.expect('memcmp') ; p.accept('ws')
- p.expect('(') ; p.accept('ws')
- lh = p_expr(p) ; p.accept('ws')
- p.expect(',') ; p.accept('ws')
- rh = p_expr(p) ; p.accept('ws')
- p.expect(',') ; p.accept('ws')
- size = p_expr(p) ; p.accept('ws')
- p.expect(')') ; p.accept('ws')
- cmp = p.expect('cmp') ; p.accept('ws')
- p.expect('0') ; p.accept('ws')
- p.expect(')')
- return mkassert('mem', CMP[cmp], lh, rh, size)
- except ParseFailure:
- p.pop(state)
- # assert(strcmp(a,b) cmp 0)?
- try:
- p.expect('assert') ; p.accept('ws')
- p.expect('(') ; p.accept('ws')
- p.expect('strcmp') ; p.accept('ws')
- p.expect('(') ; p.accept('ws')
- lh = p_expr(p) ; p.accept('ws')
- p.expect(',') ; p.accept('ws')
- rh = p_expr(p) ; p.accept('ws')
- p.expect(')') ; p.accept('ws')
- cmp = p.expect('cmp') ; p.accept('ws')
- p.expect('0') ; p.accept('ws')
- p.expect(')')
- return mkassert('str', CMP[cmp], lh, rh)
- except ParseFailure:
- p.pop(state)
- # assert(a cmp b)?
- try:
- p.expect('assert') ; p.accept('ws')
- p.expect('(') ; p.accept('ws')
- lh = p_expr(p) ; p.accept('ws')
- cmp = p.expect('cmp') ; p.accept('ws')
- rh = p_expr(p) ; p.accept('ws')
- p.expect(')')
- if rh == 'NULL' or lh == 'NULL':
- return mkassert('ptr', CMP[cmp], lh, rh)
- return mkassert('int', CMP[cmp], lh, rh)
- except ParseFailure:
- p.pop(state)
- # assert(a)?
- p.expect('assert') ; p.accept('ws')
- p.expect('(') ; p.accept('ws')
- lh = p_exprs(p) ; p.accept('ws')
- p.expect(')')
- return mkassert('bool', 'eq', lh, 'true')
- def p_expr(p):
- res = []
- while True:
- if p.accept('('):
- res.append(p.m)
- while True:
- res.append(p_exprs(p))
- if p.accept('sep'):
- res.append(p.m)
- else:
- break
- res.append(p.expect(')'))
- elif p.lookahead('assert'):
- state = p.push()
- try:
- res.append(p_assert(p))
- except ParseFailure:
- p.pop(state)
- res.append(p.expect('assert'))
- elif p.accept('string', 'op', 'ws', None):
- res.append(p.m)
- else:
- return ''.join(res)
- def p_exprs(p):
- res = []
- while True:
- res.append(p_expr(p))
- if p.accept('cmp', 'logic', ','):
- res.append(p.m)
- else:
- return ''.join(res)
- def p_stmt(p):
- ws = p.accept('ws') or ''
- # memcmp(lh,rh,size) => 0?
- if p.lookahead('memcmp'):
- state = p.push()
- try:
- p.expect('memcmp') ; p.accept('ws')
- p.expect('(') ; p.accept('ws')
- lh = p_expr(p) ; p.accept('ws')
- p.expect(',') ; p.accept('ws')
- rh = p_expr(p) ; p.accept('ws')
- p.expect(',') ; p.accept('ws')
- size = p_expr(p) ; p.accept('ws')
- p.expect(')') ; p.accept('ws')
- p.expect('=>') ; p.accept('ws')
- p.expect('0') ; p.accept('ws')
- return ws + mkassert('mem', 'eq', lh, rh, size)
- except ParseFailure:
- p.pop(state)
- # strcmp(lh,rh) => 0?
- if p.lookahead('strcmp'):
- state = p.push()
- try:
- p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
- lh = p_expr(p) ; p.accept('ws')
- p.expect(',') ; p.accept('ws')
- rh = p_expr(p) ; p.accept('ws')
- p.expect(')') ; p.accept('ws')
- p.expect('=>') ; p.accept('ws')
- p.expect('0') ; p.accept('ws')
- return ws + mkassert('str', 'eq', lh, rh)
- except ParseFailure:
- p.pop(state)
- # lh => rh?
- lh = p_exprs(p)
- if p.accept('=>'):
- rh = p_exprs(p)
- return ws + mkassert('int', 'eq', lh, rh)
- else:
- return ws + lh
- def main(input=None, output=None, pattern=[], limit=LIMIT):
- with openio(input or '-', 'r') as in_f:
- # create parser
- lexemes = LEXEMES.copy()
- lexemes['assert'] += pattern
- p = Parser(in_f, lexemes)
- with openio(output or '-', 'w') as f:
- def writeln(s=''):
- f.write(s)
- f.write('\n')
- f.writeln = writeln
- # write extra verbose asserts
- write_header(f, limit=limit)
- if input is not None:
- f.writeln("#line %d \"%s\"" % (1, input))
- # parse and write out stmt at a time
- try:
- while True:
- f.write(p_stmt(p))
- if p.accept('sep'):
- f.write(p.m)
- else:
- break
- except ParseFailure as e:
- print('warning: %s' % e)
- pass
- for i in range(p.off, len(p.tokens)):
- f.write(p.tokens[i][1])
- if __name__ == "__main__":
- import argparse
- import sys
- parser = argparse.ArgumentParser(
- description="Preprocessor that makes asserts easier to debug.",
- allow_abbrev=False)
- parser.add_argument(
- 'input',
- help="Input C file.")
- parser.add_argument(
- '-o', '--output',
- required=True,
- help="Output C file.")
- parser.add_argument(
- '-p', '--pattern',
- action='append',
- help="Regex patterns to search for starting an assert statement. This"
- " implicitly includes \"assert\" and \"=>\".")
- parser.add_argument(
- '-l', '--limit',
- type=lambda x: int(x, 0),
- default=LIMIT,
- help="Maximum number of characters to display in strcmp and memcmp. "
- "Defaults to %r." % LIMIT)
- sys.exit(main(**{k: v
- for k, v in vars(parser.parse_intermixed_args()).items()
- if v is not None}))
|