| #!/usr/bin/env python3 |
| __doc__ = ''' |
| With regex magic, try to pinpoint all LOG* macro calls that lack a final newline. |
| Also find those that have non-printable characters or extra newlines. |
| |
| Usage: |
| |
| ./verify_log_statements.py [-d|--debug] [dir] [file] [...] |
| |
| Without args, default to '.' |
| ''' |
| |
| import re |
| import sys |
| import codecs |
| import os.path |
| |
| # This regex matches the entire LOGxx(...) statement over multiple lines. |
| # It pinpoints the format string by looking for the first arg that contains quotes. |
| # It then matches any number of separate quoted strings, and accepts 0 or more args after that. |
| log_statement_re = re.compile(r'^[ \t]*LOG[_A-Z]+\(([^";,]*,)*[ \t\r\n]*(("[^"]*"[^";,]*)*)(,[^;]*|)\);', |
| re.MULTILINE | re.DOTALL) |
| fmt_re = re.compile(r'("[^"]*".*)*fmt') |
| osmo_stringify_re = re.compile("OSMO_STRINGIFY[_A-Z]*\([^)]*\)") |
| |
| debug = ('-d' in sys.argv) or ('--debug' in sys.argv) |
| |
| args = [x for x in sys.argv[1:] if not (x == '-d' or x == '--debug')] |
| if not args: |
| args = ['.'] |
| |
| class error_found: |
| def __init__(self, f, charpos, msg, text): |
| self.f = f |
| self.charpos = charpos |
| self.msg = msg |
| self.text = text |
| self.line = None |
| |
| def make_line_idx(file_content): |
| line_idx = [] |
| pos = 0 |
| line_nr = 1 |
| line_idx.append((pos, line_nr)) |
| for line in file_content.split('\n'): |
| pos += len(line) |
| line_nr += 1 |
| line_idx.append((pos, line_nr)) |
| pos += 1 # newline char |
| return line_idx |
| |
| def char_pos_2_line(line_idx, sorted_char_positions): |
| r = [] |
| line_i = 0 |
| next_line_i = 1 |
| for char_pos in sorted_char_positions: |
| while (line_i+1) < len(line_idx) and char_pos > line_idx[line_i+1][0]: |
| line_i += 1 |
| r.append(line_idx[line_i][1]) |
| return r |
| |
| def check_file(f): |
| if not (f.endswith('.h') or f.endswith('.c') or f.endswith('.cpp')): |
| return [] |
| |
| try: |
| errors_found = [] |
| |
| file_content = codecs.open(f, "r", "utf-8", errors='ignore').read() |
| |
| for log in log_statement_re.finditer(file_content): |
| quoted = log.group(2) |
| |
| # Skip 'LOG("bla" fmt )' strings that typically appear as #defines. |
| if fmt_re.match(quoted): |
| if debug: |
| errors_found.append(error_found(f, log.start(), 'Skipping define', log.group(0))) |
| continue |
| |
| # Drop PRI* parts of 'LOG("bla %"PRIu64" foo")' |
| for n in (16,32,64): |
| quoted = quoted.replace('PRIu' + str(n), '') |
| quoted = quoted.replace('PRId' + str(n), '') |
| quoted = ''.join(osmo_stringify_re.split(quoted)) |
| |
| # Use py eval to join separate string constants: drop any tabs/newlines |
| # that are not in quotes, between separate string constants. |
| try: |
| quoted = eval('(' + quoted + '\n)' ) |
| except: |
| # hopefully eval broke because of some '## args' macro def |
| if debug: |
| ignored.append(error_found(f, log.start(), 'Ignoring', log.group(0))) |
| continue |
| |
| # check for errors... |
| |
| # final newline |
| if not quoted.endswith('\n'): |
| errors_found.append(error_found(f, log.start(), 'Missing final newline', log.group(0))) |
| |
| # disallowed chars and extra newlines |
| for c in quoted[:-1]: |
| if not c.isprintable() and not c == '\t': |
| if c == '\n': |
| msg = 'Extraneous newline' |
| else: |
| msg = 'Illegal char' |
| errors_found.append(error_found(f, log.start(), msg + ' %r' % c, log.group(0))) |
| |
| if not error_found: |
| return [] |
| |
| line_idx = make_line_idx(file_content) |
| for r, line in zip(errors_found, char_pos_2_line(line_idx, [rr.charpos for rr in errors_found])): |
| r.line = line |
| |
| return errors_found |
| except: |
| print("ERROR WHILE PROCESSING %r" % f, file=sys.stderr) |
| raise |
| |
| all_errors_found = [] |
| for f in args: |
| if os.path.isdir(f): |
| for parent_path, subdirs, files in os.walk(f, None, None): |
| for ff in files: |
| all_errors_found.extend(check_file(os.path.join(parent_path, ff))) |
| else: |
| all_errors_found.extend(check_file(f)) |
| |
| def print_errors(errs): |
| for err in errs: |
| print('%s: %s:%d\n%s\n' % (err.msg, err.f, err.line or 0, err.text)) |
| |
| print_errors(all_errors_found) |
| |
| sys.exit(len(all_errors_found)) |
| |
| # vim: tabstop=2 shiftwidth=2 expandtab |