diff --git a/scripts/ci/wire_declines_audit.py b/scripts/ci/wire_declines_audit.py old mode 100755 new mode 100644 index a5efcfde..c782d1e6 --- a/scripts/ci/wire_declines_audit.py +++ b/scripts/ci/wire_declines_audit.py @@ -17,6 +17,10 @@ Sites reached through MGL_WIRE_DECLINE_AT satisfy both at once. A bare WireDeclineTally::Count(WireDeclineSite::X) is allowed ONLY when an MGLOG_ statement carrying the same reason already stands just above it. +Sources are matched on their text with comments and string/character literals blanked out +(newlines kept, so every report cites the real line): a site or a log that is only a comment, +or only a string, is not one. + usage: wire_declines_audit.py [repo-root] """ import os @@ -35,6 +39,61 @@ COUNT_SITE = re.compile(r"WireDeclineTally::Count\(\s*WireDeclineSite::([A-Za-z] # Warning or error only: an MGLOG_D / MGLOG_V is compiled away in the Release builds that ship, # so it is not a line anybody can read off a device. LOGGED = re.compile(r"MGLOG_[WE](_ONCE)?\s*\(") +RAW_PREFIX = re.compile(r"(?:^|[^A-Za-z0-9_])(?:u8|u|U|L)?R$") + + +def strip_code(text: str) -> str: + """Blank out //, /* */ comments and string / character literals, keeping every newline. + + What remains lines up with the source line for line, so a match's index is a real line + number. Raw strings R"d(...)d" and C++14 digit separators (1'000) are handled.""" + out = list(text) + n = len(text) + i = 0 + + def blank(a: int, b: int) -> None: + for k in range(a, min(b, n)): + if out[k] != "\n": + out[k] = " " + + while i < n: + c = text[i] + nxt = text[i + 1] if i + 1 < n else "" + if c == "/" and nxt == "/": + j = i + while j < n and text[j] != "\n": + # A backslash-newline continues a // comment onto the next line. + if text[j] == "\\" and j + 1 < n and text[j + 1] == "\n": + j += 2 + continue + j += 1 + blank(i, j) + i = j + elif c == "/" and nxt == "*": + j = text.find("*/", i + 2) + j = n if j < 0 else j + 2 + blank(i, j) + i = j + elif c == '"' and RAW_PREFIX.search(text[max(0, i - 3):i]): + open_paren = text.find("(", i + 1) + if open_paren < 0: + blank(i, n) + break + delim = text[i + 1:open_paren] + close = text.find(")" + delim + '"', open_paren + 1) + j = n if close < 0 else close + len(delim) + 2 + blank(i, j) + i = j + elif c == '"' or (c == "'" and not (i > 0 and (text[i - 1].isalnum() or text[i - 1] == "_"))): + j = i + 1 + while j < n and text[j] != c and text[j] != "\n": + j += 2 if text[j] == "\\" else 1 + j = min(j + 1, n) + blank(i, j) + i = j + else: + i += 1 + return "".join(out) def main() -> int: @@ -64,7 +123,8 @@ def main() -> int: if entry == "WireDeclineTally.h": continue path = os.path.join(src_dir, entry) - lines = open(path, encoding="utf-8", errors="replace").read().splitlines() + with open(path, encoding="utf-8", errors="replace") as f: + lines = strip_code(f.read()).splitlines() for i, line in enumerate(lines): for m in AT_SITE.finditer(line): counted.setdefault(m.group(1), []).append((entry, i + 1))