Files
littlefs/scripts/prettyasserts.py
Christopher Haster 096f968cbb Dropped prettyasserts.py --no-arrows shorthand form
This probably doesn't deserve a shorthand form as you can usually just
ignore the arrow parsing. This frees up -A for potential future use.
2024-06-20 13:04:12 -05:00

550 lines
18 KiB
Python
Executable File

#!/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
LIMIT = 16
CMP = {
'==': 'eq',
'!=': 'ne',
'<=': 'le',
'>=': 'ge',
'<': 'lt',
'>': 'gt',
}
LEXEMES = {
'ws': [r'(?:\s|\n|#.*?(?<!\\)\n|//.*?(?<!\\)\n|/\*.*?\*/)+'],
'assert': [r'\bassert\b', r'\b__builtin_assert\b'],
'unreachable': [r'\bunreachable\b', r'\b__builtin_unreachable\b'],
'memcmp': [r'\bmemcmp\b', r'\b__builtin_memcmp\b'],
'strcmp': [r'\bstrcmp\b', r'\b__builtin_strcmp\b'],
'arrow': ['=>'],
'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"],
'paren': ['\(', '\)'],
'cmp': list(CMP.keys()),
'logic': ['\&\&', '\|\|'],
'sep': ['\?', ':', ','],
'term': [';', '\{', '\}'],
# specifically ops that conflict with cmp
'op': ['->', '>>', '<<'],
}
def openio(path, mode='r', buffering=-1):
# allow '-' for stdin/stdout
if path == '-':
if 'r' in mode:
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>")
# 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_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_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_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_str(")
f.writeln(" const void *v, size_t size) {")
f.writeln(" __pretty_assert_mem(v, size);")
f.writeln("}")
f.writeln()
f.writeln("__attribute__((unused))")
f.writeln("static void __pretty_assert_print(")
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("}")
f.writeln()
f.writeln("__attribute__((unused))")
f.writeln("static void __pretty_assert_print_unreachable(")
f.writeln(" const char *file, int line) {")
f.writeln(" printf(\"%s:%d:unreachable: \"")
f.writeln(" \"unreachable statement reached\\n\", file, line);")
f.writeln(" fflush(NULL);")
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_print( \\")
f.writeln(" __FILE__, __LINE__, \\")
f.writeln(" __pretty_assert_bool, \"%s\", \\"
% cmp)
f.writeln(" &_lh, 0, \\")
f.writeln(" &_rh, 0); \\")
f.writeln(" __builtin_trap(); \\")
f.writeln(" } \\")
f.writeln("} while (0)")
f.writeln()
for op, cmp in sorted(CMP.items()):
f.writeln("#define __PRETTY_ASSERT_INT_%s(lh, rh) do { \\"
% cmp.upper())
f.writeln(" __typeof__(rh) _lh = lh; \\")
f.writeln(" __typeof__(rh) _rh = rh; \\")
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
f.writeln(" __pretty_assert_print( \\")
f.writeln(" __FILE__, __LINE__, \\")
f.writeln(" __pretty_assert_int, \"%s\", \\"
% cmp)
f.writeln(" &(intmax_t){(intmax_t)_lh}, 0, \\")
f.writeln(" &(intmax_t){(intmax_t)_rh}, 0); \\")
f.writeln(" __builtin_trap(); \\")
f.writeln(" } \\")
f.writeln("} while (0)")
f.writeln()
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_print( \\")
f.writeln(" __FILE__, __LINE__, \\")
f.writeln(" __pretty_assert_mem, \"%s\", \\"
% cmp)
f.writeln(" _lh, size, \\")
f.writeln(" _rh, size); \\")
f.writeln(" __builtin_trap(); \\")
f.writeln(" } \\")
f.writeln("} while (0)")
f.writeln()
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_print( \\")
f.writeln(" __FILE__, __LINE__, \\")
f.writeln(" __pretty_assert_str, \"%s\", \\"
% cmp)
f.writeln(" _lh, strlen(_lh), \\")
f.writeln(" _rh, strlen(_rh)); \\")
f.writeln(" __builtin_trap(); \\")
f.writeln(" } \\")
f.writeln("} while (0)")
f.writeln()
f.writeln("#define __PRETTY_ASSERT_UNREACHABLE() do { \\")
f.writeln(" __pretty_assert_print_unreachable( \\")
f.writeln(" __FILE__, __LINE__); \\")
f.writeln(" __builtin_trap(); \\")
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))
def mkunreachable():
return "__PRETTY_ASSERT_UNREACHABLE()"
# 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(')')
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_unreachable(p):
# unreachable()?
p.expect('unreachable') ; p.accept('ws')
p.expect('(') ; p.accept('ws')
p.expect(')')
return mkunreachable()
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', 'term'):
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.lookahead('unreachable'):
state = p.push()
try:
res.append(p_unreachable(p))
except ParseFailure:
p.pop(state)
res.append(p.expect('unreachable'))
elif p.accept('memcmp', 'strcmp', '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', 'sep'):
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, *,
prefix=[],
prefix_insensitive=[],
assert_=[],
unreachable=[],
memcmp=[],
strcmp=[],
no_defaults=False,
no_upper=False,
no_arrows=False,
limit=LIMIT):
with openio(input or '-', 'r') as in_f:
# create parser
lexemes = {n: l.copy() for n, l in LEXEMES.items()}
if no_defaults:
lexemes['assert'] = []
lexemes['unreachable'] = []
lexemes['memcmp'] = []
lexemes['strcmp'] = []
if no_arrows:
lexemes['arrow'] = []
for p in prefix + prefix_insensitive:
lexemes['assert'].append(r'\b%sassert\b' % p)
lexemes['unreachable'].append(r'\b%sunreachable\b' % p)
lexemes['memcmp'].append(r'\b%smemcmp\b' % p)
lexemes['strcmp'].append(r'\b%sstrcmp\b' % p)
for p in prefix_insensitive:
lexemes['assert'].append(r'\b%sassert\b' % p.lower())
lexemes['unreachable'].append(r'\b%sunreachable\b' % p.lower())
lexemes['memcmp'].append(r'\b%smemcmp\b' % p.lower())
lexemes['strcmp'].append(r'\b%sstrcmp\b' % p.lower())
lexemes['assert'].append(r'\b%sASSERT\b' % p.upper())
lexemes['unreachable'].append(r'\b%sUNREACHABLE\b' % p.upper())
lexemes['memcmp'].append(r'\b%sMEMCMP\b' % p.upper())
lexemes['strcmp'].append(r'\b%sSTRCMP\b' % p.upper())
lexemes['assert'].extend(r'\b%s\b' % r for r in assert_)
lexemes['unreachable'].extend(r'\b%s\b' % r for r in unreachable)
lexemes['memcmp'].extend(r'\b%s\b' % r for r in memcmp)
lexemes['strcmp'].extend(r'\b%s\b' % r for r in strcmp)
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('term'):
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', '--prefix',
action='append',
help="Additional prefixes for symbols.")
parser.add_argument(
'-P', '--prefix-insensitive',
action='append',
help="Additional prefixes for lower/upper case symbol variants.")
parser.add_argument(
'--assert',
dest='assert_',
action='append',
help="Additional symbols for assert statements.")
parser.add_argument(
'--unreachable',
action='append',
help="Additional symbols for unreachable statements.")
parser.add_argument(
'--memcmp',
action='append',
help="Additional symbols for memcmp expressions.")
parser.add_argument(
'--strcmp',
action='append',
help="Additional symbols for strcmp expressions.")
parser.add_argument(
'-n', '--no-defaults',
action='store_true',
help="Disable default symbols.")
parser.add_argument(
'--no-arrows',
action='store_true',
help="Disable arrow (=>) expressions.")
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}))