lib/clippy: assert() for non-optional args

This is mostly to help static analysis; since we know from the command
string which args are optional and which aren't, we can add assert()
statements on them.

Fixes: #3270
Signed-off-by: David Lamparter <equinox@diac24.net>
This commit is contained in:
David Lamparter 2019-06-04 20:27:05 +02:00
parent 3779776a39
commit 4381a59be0

View File

@ -37,6 +37,7 @@ class RenderHandler(object):
deref = '' deref = ''
drop_str = False drop_str = False
canfail = True canfail = True
canassert = False
class StringHandler(RenderHandler): class StringHandler(RenderHandler):
argtype = 'const char *' argtype = 'const char *'
@ -44,6 +45,7 @@ class StringHandler(RenderHandler):
code = Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;') code = Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;')
drop_str = True drop_str = True
canfail = False canfail = False
canassert = True
class LongHandler(RenderHandler): class LongHandler(RenderHandler):
argtype = 'long' argtype = 'long'
@ -111,6 +113,7 @@ if (argv[_i]->text[0] == 'X') {
_fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr); _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
$varname = &s__$varname; $varname = &s__$varname;
}''') }''')
canassert = True
def mix_handlers(handlers): def mix_handlers(handlers):
def combine(a, b): def combine(a, b):
@ -171,6 +174,7 @@ $argblocks
return CMD_WARNING; return CMD_WARNING;
#endif #endif
#endif #endif
$argassert
return ${fnname}_magic(self, vty, argc, argv$arglist); return ${fnname}_magic(self, vty, argc, argv$arglist);
} }
@ -182,6 +186,21 @@ argblock = Template('''
$code $code
}''') }''')
def get_always_args(token, always_args, args = [], stack = []):
if token in stack:
return
if token.type == 'END_TKN':
for arg in list(always_args):
if arg not in args:
always_args.remove(arg)
return
stack = stack + [token]
if token.type in handlers and token.varname is not None:
args = args + [token.varname]
for nexttkn in token.next():
get_always_args(nexttkn, always_args, args, stack)
def process_file(fn, ofd, dumpfd, all_defun): def process_file(fn, ofd, dumpfd, all_defun):
errors = 0 errors = 0
filedata = clippy.parse(fn) filedata = clippy.parse(fn)
@ -206,6 +225,7 @@ def process_file(fn, ofd, dumpfd, all_defun):
graph = clippy.Graph(cmddef) graph = clippy.Graph(cmddef)
args = OrderedDict() args = OrderedDict()
always_args = set()
for token, depth in clippy.graph_iterate(graph): for token, depth in clippy.graph_iterate(graph):
if token.type not in handlers: if token.type not in handlers:
continue continue
@ -213,6 +233,9 @@ def process_file(fn, ofd, dumpfd, all_defun):
continue continue
arg = args.setdefault(token.varname, []) arg = args.setdefault(token.varname, [])
arg.append(handlers[token.type](token)) arg.append(handlers[token.type](token))
always_args.add(token.varname)
get_always_args(graph.first(), always_args)
#print('-' * 76) #print('-' * 76)
#pprint(entry) #pprint(entry)
@ -224,30 +247,36 @@ def process_file(fn, ofd, dumpfd, all_defun):
argdecls = [] argdecls = []
arglist = [] arglist = []
argblocks = [] argblocks = []
argassert = []
doc = [] doc = []
canfail = 0 canfail = 0
def do_add(handler, varname, attr = ''): def do_add(handler, basename, varname, attr = ''):
argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr)) argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr))
argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t'))) argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t')))
arglist.append(', %s%s' % (handler.deref, varname)) arglist.append(', %s%s' % (handler.deref, varname))
if basename in always_args and handler.canassert:
argassert.append('''\tif (!%s) {
\t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s");
\t\treturn CMD_WARNING;
\t}\n''' % (varname, varname))
if attr == '': if attr == '':
at = handler.argtype at = handler.argtype
if not at.startswith('const '): if not at.startswith('const '):
at = '. . . ' + at at = '. . . ' + at
doc.append('\t%-26s %s' % (at, varname)) doc.append('\t%-26s %s %s' % (at, 'alw' if basename in always_args else 'opt', varname))
for varname in args.keys(): for varname in args.keys():
handler = mix_handlers(args[varname]) handler = mix_handlers(args[varname])
#print(varname, handler) #print(varname, handler)
if handler is None: continue if handler is None: continue
do_add(handler, varname) do_add(handler, varname, varname)
code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t') code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
if handler.canfail: if handler.canfail:
canfail = 1 canfail = 1
strblock = '' strblock = ''
if not handler.drop_str: if not handler.drop_str:
do_add(StringHandler(None), '%s_str' % (varname), ' __attribute__ ((unused))') do_add(StringHandler(None), varname, '%s_str' % (varname), ' __attribute__ ((unused))')
strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname) strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname)
argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code})) argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code}))
@ -263,6 +292,7 @@ def process_file(fn, ofd, dumpfd, all_defun):
params['argblocks'] = ''.join(argblocks) params['argblocks'] = ''.join(argblocks)
params['canfail'] = canfail params['canfail'] = canfail
params['nonempty'] = len(argblocks) params['nonempty'] = len(argblocks)
params['argassert'] = ''.join(argassert)
ofd.write(templ.substitute(params)) ofd.write(templ.substitute(params))
return errors return errors