384 lines
11 KiB
Python
384 lines
11 KiB
Python
|
#!/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 <stdio.h>\n")
|
||
|
outf.write("#include <stdbool.h>\n")
|
||
|
outf.write("#include <stdint.h>\n")
|
||
|
outf.write("#include <inttypes.h>\n")
|
||
|
outf.write("#include <signal.h>\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())
|