ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
suppression_coverage_scan.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Parse active gcovr result-masking controls and their local provenance."""
4
5from __future__ import annotations
6
7import re
8from dataclasses import dataclass
9from pathlib import Path
10
11from suppression_catalog import is_build_control, ownership
12from suppression_hash_lex import HashLexLine, hash_lines
13from suppression_model import Finding, Suppression
14
15PARSE_ERROR_VALUES = frozenset({"all", "negative_hits.warn", "negative_hits.warn_once_per_file"})
16FLAG_RE = re.compile(
17 r"(?P<parse>--gcov-ignore-parse-errors(?:=|\s+)(?P<value>[^\s)]+))"
18 r"|(?P<unreachable>--exclude-unreachable-branches)"
19 r"|(?P<throw>--exclude-throw-branches)"
20)
21CONFIG_KEYS = frozenset(
22 {"gcov-ignore-parse-errors", "exclude-unreachable-branches", "exclude-throw-branches"}
23)
24
25
26def _reason(line: HashLexLine) -> str:
27 """Return the same-line rationale for one active coverage mask."""
28 return line.comment.strip()
29
30
31@dataclass(frozen=True)
32class CoverageControl:
33 """Normalized gcovr coverage-mask fields."""
34
35 column: int
36 rule: str
37 reason: str
38 broad: bool
39
40
41def _record(path: str, line: HashLexLine, control: CoverageControl) -> Suppression:
42 """Build one gcovr coverage-control inventory row."""
43 concerns: list[str] = []
44 if not control.reason:
45 concerns.append("blank-reason")
46 if control.broad:
47 concerns.append("broad-coverage-mask")
48 return Suppression(
49 path,
50 line.line,
51 control.column,
52 "coverage-mask",
53 "gcovr",
54 control.rule,
55 control.rule,
56 "coverage-report",
57 control.reason,
58 "coverage-config",
59 ownership(path),
60 tuple(concerns),
61 evidence=("producer:gcovr-7.0",),
62 recommendation="retain-only-with-producer-evidence",
63 )
64
65
66def _is_data_only(prefix: str) -> bool:
67 """Reject quoted examples and assignments that do not invoke gcovr."""
68 stripped = prefix.strip()
69 if re.match(r"^(?:echo|printf|message)\b", stripped):
70 return True
71 return re.match(r"^[A-Za-z_][A-Za-z0-9_]*=", stripped) is not None
72
73
74def _scan_flags(path: str, text: str) -> tuple[list[Suppression], list[Finding]]:
75 """Parse active shell/CMake gcovr option tokens."""
76 first_line = text.partition("\n")[0]
77 if not is_build_control(path, first_line):
78 return [], []
79 lines, lex_findings = hash_lines(path, text)
80 records: list[Suppression] = []
81 findings = [Finding(item.code, item.message, path, item.line) for item in lex_findings]
82 for line in lines:
83 for match in FLAG_RE.finditer(line.code):
84 if _is_data_only(line.code[: match.start()]):
85 continue
86 reason = _reason(line)
87 if match.group("parse"):
88 value = match.group("value").strip("'\"")
89 if value not in PARSE_ERROR_VALUES:
90 message = f"unsupported gcovr 7.0 parse-error class {value!r}"
91 findings.append(Finding("malformed-coverage-mask", message, path, line.line))
92 continue
93 rule = f"gcov-ignore-parse-errors={value}"
94 records.append(
95 _record(
96 path,
97 line,
98 CoverageControl(match.start() + 1, rule, reason, value == "all"),
99 )
100 )
101 elif match.group("unreachable"):
102 records.append(
103 _record(
104 path,
105 line,
106 CoverageControl(
107 match.start() + 1,
108 "exclude-unreachable-branches",
109 reason,
110 broad=False,
111 ),
112 )
113 )
114 else:
115 records.append(
116 _record(
117 path,
118 line,
119 CoverageControl(
120 match.start() + 1,
121 "exclude-throw-branches",
122 reason,
123 broad=False,
124 ),
125 )
126 )
127 return records, findings
128
129
130def _config_bool(value: str) -> bool | None:
131 """Parse gcovr's accepted boolean spellings."""
132 lowered = value.lower()
133 if lowered in {"yes", "true", "1"}:
134 return True
135 if lowered in {"no", "false", "0"}:
136 return False
137 return None
138
139
140def _scan_config(path: str, text: str) -> tuple[list[Suppression], list[Finding]]:
141 """Parse an authoritative gcovr.cfg without accepting unknown mask syntax."""
142 if Path(path).name != "gcovr.cfg":
143 return [], []
144 records: list[Suppression] = []
145 findings: list[Finding] = []
146 seen: dict[str, int] = {}
147 for line_no, raw in enumerate(text.splitlines(), start=1):
148 stripped = raw.strip()
149 if not stripped or stripped.startswith("#") or "=" not in raw:
150 continue
151 key, value_and_comment = (part.strip() for part in raw.split("=", 1))
152 value, _separator, comment = value_and_comment.partition("#")
153 value = value.strip()
154 if key not in CONFIG_KEYS:
155 continue
156 if key in seen:
157 message = f"{key} duplicates line {seen[key]}"
158 findings.append(Finding("duplicate-coverage-mask", message, path, line_no))
159 continue
160 seen[key] = line_no
161 if key == "gcov-ignore-parse-errors":
162 if value not in PARSE_ERROR_VALUES:
163 findings.append(Finding("malformed-coverage-mask", f"{key}={value}", path, line_no))
164 continue
165 rule = f"{key}={value}"
166 broad = value == "all"
167 else:
168 active = _config_bool(value)
169 if active is None:
170 findings.append(Finding("malformed-coverage-mask", f"{key}={value}", path, line_no))
171 continue
172 if not active:
173 continue
174 rule = key
175 broad = False
176 line = HashLexLine(line_no, raw, comment.strip(), raw.find("#") + 1)
177 records.append(_record(path, line, CoverageControl(1, rule, comment.strip(), broad)))
178 return records, findings
179
180
181def scan_coverage_masks(path: str, text: str) -> tuple[list[Suppression], list[Finding]]:
182 """Inventory active gcovr masks from command lines and central config."""
183 records, findings = _scan_flags(path, text)
184 config_records, config_findings = _scan_config(path, text)
185 records.extend(config_records)
186 findings.extend(config_findings)
187 return records, findings