#!/usr/bin/env python3 import re import sys PATTERN = ['LFS_ASSERT', 'assert'] PREFIX = 'LFS' MAXWIDTH = 16 ASSERT = "__{PREFIX}_ASSERT_{TYPE}_{COMP}" FAIL = """ __attribute__((unused)) static void __{prefix}_assert_fail_{type}( const char *file, int line, const char *comp, {ctype} lh, size_t lsize, {ctype} rh, size_t rsize) {{ printf("%s:%d:assert: assert failed with ", file, line); __{prefix}_assert_print_{type}(lh, lsize); printf(", expected %s ", comp); __{prefix}_assert_print_{type}(rh, rsize); printf("\\n"); fflush(NULL); raise(SIGABRT); }} """ COMP = { '==': 'eq', '!=': 'ne', '<=': 'le', '>=': 'ge', '<': 'lt', '>': 'gt', } TYPE = { 'int': { 'ctype': 'intmax_t', 'fail': FAIL, 'print': """ __attribute__((unused)) static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ (void)size; printf("%"PRIiMAX, v); }} """, 'assert': """ #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) do {{ __typeof__(lh) _lh = lh; __typeof__(lh) _rh = (__typeof__(lh))rh; if (!(_lh {op} _rh)) {{ __{prefix}_assert_fail_{type}(file, line, "{comp}", (intmax_t)_lh, 0, (intmax_t)_rh, 0); }} }} while (0) """ }, 'bool': { 'ctype': 'bool', 'fail': FAIL, 'print': """ __attribute__((unused)) static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ (void)size; printf("%s", v ? "true" : "false"); }} """, 'assert': """ #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) do {{ bool _lh = !!(lh); bool _rh = !!(rh); if (!(_lh {op} _rh)) {{ __{prefix}_assert_fail_{type}(file, line, "{comp}", _lh, 0, _rh, 0); }} }} while (0) """ }, 'mem': { 'ctype': 'const void *', 'fail': FAIL, 'print': """ __attribute__((unused)) static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ const uint8_t *s = v; printf("\\\""); for (size_t i = 0; i < size && i < {maxwidth}; i++) {{ if (s[i] >= ' ' && s[i] <= '~') {{ printf("%c", s[i]); }} else {{ printf("\\\\x%02x", s[i]); }} }} if (size > {maxwidth}) {{ printf("..."); }} printf("\\\""); }} """, 'assert': """ #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh, size) do {{ const void *_lh = lh; const void *_rh = rh; if (!(memcmp(_lh, _rh, size) {op} 0)) {{ __{prefix}_assert_fail_{type}(file, line, "{comp}", _lh, size, _rh, size); }} }} while (0) """ }, 'str': { 'ctype': 'const char *', 'fail': FAIL, 'print': """ __attribute__((unused)) static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ __{prefix}_assert_print_mem(v, size); }} """, 'assert': """ #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) do {{ const char *_lh = lh; const char *_rh = rh; if (!(strcmp(_lh, _rh) {op} 0)) {{ __{prefix}_assert_fail_{type}(file, line, "{comp}", _lh, strlen(_lh), _rh, strlen(_rh)); }} }} while (0) """ } } def mkdecls(outf, maxwidth=16): outf.write("#include \n") outf.write("#include \n") outf.write("#include \n") outf.write("#include \n") outf.write("#include \n") for type, desc in sorted(TYPE.items()): format = { 'type': type.lower(), 'TYPE': type.upper(), 'ctype': desc['ctype'], 'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(), 'maxwidth': maxwidth, } outf.write(re.sub('\s+', ' ', desc['print'].strip().format(**format))+'\n') outf.write(re.sub('\s+', ' ', desc['fail'].strip().format(**format))+'\n') for op, comp in sorted(COMP.items()): format.update({ 'comp': comp.lower(), 'COMP': comp.upper(), 'op': op, }) outf.write(re.sub('\s+', ' ', desc['assert'].strip().format(**format))+'\n') def mkassert(type, comp, lh, rh, size=None): format = { 'type': type.lower(), 'TYPE': type.upper(), 'comp': comp.lower(), 'COMP': comp.upper(), 'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(), 'lh': lh.strip(' '), 'rh': rh.strip(' '), 'size': size, } if size: return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh}, {size})') .format(**format)) else: return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh})') .format(**format)) # simple recursive descent parser LEX = { 'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'], 'assert': PATTERN, 'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"], 'arrow': ['=>'], 'paren': ['\(', '\)'], 'op': ['strcmp', 'memcmp', '->'], 'comp': ['==', '!=', '<=', '>=', '<', '>'], 'logic': ['\&\&', '\|\|'], 'sep': [':', ';', '\{', '\}', ','], } 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 Parse: def __init__(self, inf, lexemes): p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l)) for n, l in lexemes.items()) p = re.compile(p, re.DOTALL) data = inf.read() tokens = [] while True: m = p.search(data) if m: if m.start() > 0: tokens.append((None, data[:m.start()])) tokens.append((m.lastgroup, m.group())) data = data[m.end():] else: tokens.append((None, data)) 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 passert(p): def pastr(p): p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') lh = pexpr(p) ; p.accept('ws') p.expect(',') ; p.accept('ws') rh = pexpr(p) ; p.accept('ws') p.expect(')') ; p.accept('ws') comp = p.expect('comp') ; p.accept('ws') p.expect('0') ; p.accept('ws') p.expect(')') return mkassert('str', COMP[comp], lh, rh) def pamem(p): p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') p.expect('memcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') lh = pexpr(p) ; p.accept('ws') p.expect(',') ; p.accept('ws') rh = pexpr(p) ; p.accept('ws') p.expect(',') ; p.accept('ws') size = pexpr(p) ; p.accept('ws') p.expect(')') ; p.accept('ws') comp = p.expect('comp') ; p.accept('ws') p.expect('0') ; p.accept('ws') p.expect(')') return mkassert('mem', COMP[comp], lh, rh, size) def paint(p): p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') lh = pexpr(p) ; p.accept('ws') comp = p.expect('comp') ; p.accept('ws') rh = pexpr(p) ; p.accept('ws') p.expect(')') return mkassert('int', COMP[comp], lh, rh) def pabool(p): p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') lh = pexprs(p) ; p.accept('ws') p.expect(')') return mkassert('bool', 'eq', lh, 'true') def pa(p): return p.expect('assert') state = p.push() lastf = None for pa in [pastr, pamem, paint, pabool, pa]: try: return pa(p) except ParseFailure as f: p.pop(state) lastf = f else: raise lastf def pexpr(p): res = [] while True: if p.accept('('): res.append(p.m) while True: res.append(pexprs(p)) if p.accept('sep'): res.append(p.m) else: break res.append(p.expect(')')) elif p.lookahead('assert'): res.append(passert(p)) elif p.accept('assert', 'ws', 'string', 'op', None): res.append(p.m) else: return ''.join(res) def pexprs(p): res = [] while True: res.append(pexpr(p)) if p.accept('comp', 'logic', ','): res.append(p.m) else: return ''.join(res) def pstmt(p): ws = p.accept('ws') or '' lh = pexprs(p) if p.accept('=>'): rh = pexprs(p) return ws + mkassert('int', 'eq', lh, rh) else: return ws + lh def main(args): inf = open(args.input, 'r') if args.input else sys.stdin outf = open(args.output, 'w') if args.output else sys.stdout lexemes = LEX.copy() if args.pattern: lexemes['assert'] = args.pattern p = Parse(inf, lexemes) # write extra verbose asserts mkdecls(outf, maxwidth=args.maxwidth) if args.input: outf.write("#line %d \"%s\"\n" % (1, args.input)) # parse and write out stmt at a time try: while True: outf.write(pstmt(p)) if p.accept('sep'): outf.write(p.m) else: break except ParseFailure as f: pass for i in range(p.off, len(p.tokens)): outf.write(p.tokens[i][1]) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser( description="Cpp step that increases assert verbosity") parser.add_argument('input', nargs='?', help="Input C file after cpp.") parser.add_argument('-o', '--output', required=True, help="Output C file.") parser.add_argument('-p', '--pattern', action='append', help="Patterns to search for starting an assert statement.") parser.add_argument('--maxwidth', default=MAXWIDTH, type=int, help="Maximum number of characters to display for strcmp and memcmp.") main(parser.parse_args())