3"""The annotation vocabulary, and the proof that it matches what the macros emit.
5This module is small on purpose. It holds the set of rule keys the checker
6dispatches on, and one cross-check -- :func:`check_rule_keys` -- that compares
7that set against the strings ``ra8_attributes.h`` actually writes.
9That cross-check is the reason the vocabulary is not just spelled inline where
10it is used. A rule keyed on a spelling no macro produces matches zero symbols
11and reports zero violations forever, which is indistinguishable from a clean
12tree. Four rules in this checker were in exactly that state at once. Keeping
13the keys and their proof in one file makes the two impossible to edit apart.
16from __future__
import annotations
20from annot_model
import Violation
21from annot_scope
import repo_root
27ATTRIBUTES_HEADER = repo_root() /
"libs" /
"ra8_core" /
"inc" /
"ra8_attributes.h"
32INFORMATIONAL_RULES = {
"ra8_latency_budget_ns",
"ra8_reviewed_by",
"ra8_register_bank"}
34ANNOTATION_PREFIXES = (
46 "ra8_hw_register_access",
48 "ra8_mcdc_deactivated",
53 "ra8_latency_budget_ns",
58 "ra8_releases_resource",
64LINKAGE_ANNOTATIONS = frozenset({
"ra8_priv",
"ra8_internal",
"ra8_test_helper"})
67def parse_annotation(ann: str) -> tuple[str, str]:
68 """Split ``ra8_max_stack:512`` -> (``ra8_max_stack``, ``512``)."""
70 rule, _, arg = ann.partition(
":")
71 return rule.strip(), arg.strip()
72 return ann.strip(),
""
75def emitted_annotation_keys() -> set[str]:
76 """Return every annotation string ``ra8_attributes.h`` can emit.
78 Read straight out of the header rather than restated here, because a
79 restatement is what goes stale. Each macro expands through
80 ``RA8_INTERNAL_ANNOTATE("ra8_<rule>...")``, or through the shared
81 ``RA8_INTERNAL_ANNOTATE_ARG("ra8_<rule>:", arg)`` helper the macros
82 that carry a value use; the rule key is the text up to the first colon.
85 text = ATTRIBUTES_HEADER.read_text(errors=
"ignore")
88 pattern =
r'RA8_INTERNAL_ANNOTATE(?:_ARG)?\(\s*"(ra8_[a-z0-9_]+)'
89 return {m.group(1)
for m
in re.finditer(pattern, text)}
92def check_rule_keys() -> list[Violation]:
93 """Fail when a rule keys on a string no annotation macro emits.
95 This is the failure mode that looks exactly like success. A rule
96 keyed on a spelling nothing produces matches zero symbols and reports
97 zero violations for as long as nobody checks, and four rules in this
98 file were in that state at once: RA8_HW_REGISTER_ACCESS emits
99 "ra8_hw_register_access" but rule 6 looked for "ra8_hw_mmio", and the
100 NASA-rule-3, stack-budget and latency-budget rules each looked for a
101 key their macro never wrote. Cross-checking both directions against
102 the header makes the whole class impossible to reintroduce silently.
104 emitted = emitted_annotation_keys()
109 str(ATTRIBUTES_HEADER),
111 "no RA8_INTERNAL_ANNOTATE() strings found -- the annotation "
112 "header moved or changed shape, so every rule key is unverified",
115 known = set(ANNOTATION_PREFIXES)
116 out: list[Violation] = []
120 str(ATTRIBUTES_HEADER),
122 f
"rule key '{key}' is not emitted by any macro in ra8_attributes.h; "
123 f
"the rule keyed on it can never match",
125 for key
in sorted(known - emitted)
130 str(ATTRIBUTES_HEADER),
132 f
"annotation '{key}' is emitted by a macro but no rule recognises it; "
133 f
"every use of that macro is silently ignored",
135 for key
in sorted(emitted - known)