ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
annot_model.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""The data ``check_annotations.py`` builds from a parse and then reasons over.
4
5Four records, no behaviour: the symbol table entry, a call site, the evidence
6that the parse actually happened, and a finding. They live apart from both
7the walk that fills them and the rules that read them so that neither side can
8quietly redefine the other's shape -- and so the rules stay testable without
9libclang present.
10"""
11
12from __future__ import annotations
13
14from dataclasses import dataclass, field
15
16
17@dataclass
18class AnnotatedSymbol:
19 """One function, keyed by USR, with whatever ra8_* annotations it carries.
20
21 Every function declaration and definition the walk sees lands here,
22 annotated or not: the linkage rule has to reason about the functions
23 that carry *no* annotation, which is precisely the set an
24 annotations-only table cannot see.
25
26 The table is keyed by USR rather than by name. C lets every
27 translation unit have its own ``static`` helper with the same name,
28 and this repo does exactly that -- three unrelated
29 ``internal_zero_bytes`` live in net_pal, usb_hmsc and usb_pmsc. A
30 name-keyed table merges them into one entry whose annotations are the
31 union of all three and whose file is whichever definition happened to
32 be parsed last, so an annotation on one namesake is enforced against
33 the callers of another.
34 """
35
36 name: str
37 file: str
38 line: int
39 annotations: list[str] = field(default_factory=list)
40 #: True only when the declaration actually spells the ``static`` storage
41 #: class required by RA8_INTERNAL.
42 is_static: bool = False
43 #: True for every internal-linkage function, including C++ anonymous
44 #: namespaces. The external-linkage gate needs this broader language
45 #: property and must not confuse it with an actual ``static`` specifier.
46 has_internal_linkage: bool = False
47 has_inline: bool = False
48 section: str | None = None
49 return_type: str = ""
50 #: Last source line of the definition's extent (0 when only declared).
51 end_line: int = 0
52 #: True when at least one parameter is a pointer, i.e. the function
53 #: takes an address across the boundary and must range-check it.
54 has_pointer_param: bool = False
55 #: Clang Unified Symbol Resolution string of the canonical declaration.
56 #: This is the only reliable identity for a C function: two `static`
57 #: helpers in different TUs may share a name but never a USR.
58 usr: str = ""
59 #: True once a definition (not just a prototype) has been seen.
60 is_defined: bool = False
61 #: Every file holding a non-defining declaration of this function.
62 #: The linkage rule reads the published interface off this set: a
63 #: prototype in a header is an exported contract, a prototype in a
64 #: `.c` is a forward declaration and exports nothing.
65 decl_files: set[str] = field(default_factory=set)
66
67
68@dataclass
69class DataSymbol:
70 """One data definition relevant to the ``s_`` naming contract.
71
72 The naming rule distinguishes file-scope objects with internal linkage
73 from automatic locals and externally-linked globals. Libclang exposes
74 all three as ``VAR_DECL`` cursors, so preserving the scope and linkage
75 here avoids guessing from source text.
76 """
77
78 name: str
79 file: str
80 line: int
81 is_file_scope: bool
82 has_internal_linkage: bool
83
84
85@dataclass
86class CallSite:
87 """A direct CallExpr discovered during AST traversal."""
88
89 callee_name: str
90 caller_name: str
91 caller_file: str
92 caller_line: int
93 #: USR of the declaration this call actually resolved to. Compared
94 #: against AnnotatedSymbol.usr so an annotation on one function is
95 #: never attributed to a same-named `static` in another module.
96 callee_usr: str = ""
97 #: USR of the function the call sits inside, so caller-side lookups
98 #: separate namesake `static` helpers the same way callee lookups do.
99 caller_usr: str = ""
100 in_address_of: bool = False
101 parent_loop_label: str | None = None # for RA8_PROTECTED_WRITE detection
102 preceding_comment: str = ""
103
104
105@dataclass
106class ParseStats:
107 """Evidence that the parse saw the code it was asked to analyse."""
108
109 #: Every CallExpr encountered inside a function body.
110 calls_seen: int = 0
111 #: Those whose callee declaration libclang could resolve.
112 calls_resolved: int = 0
113 #: ``(including file, missing header)`` for each unresolved include.
114 missing_includes: set[tuple[str, str]] = field(default_factory=set)
115 #: Translation units libclang refused to parse at all.
116 unparsed: list[str] = field(default_factory=list)
117
118
119@dataclass
120class WalkState:
121 """Collections filled by one or more translation-unit AST walks."""
122
123 symbols: dict[str, AnnotatedSymbol] = field(default_factory=dict)
124 data_symbols: dict[str, DataSymbol] = field(default_factory=dict)
125 calls: list[CallSite] = field(default_factory=list)
126 vector_entries: set[str] = field(default_factory=set)
127 stats: ParseStats = field(default_factory=ParseStats)
128
129
130@dataclass
131class Violation:
132 """One finding. ``warn_only`` entries are reported but never fail the gate."""
133
134 rule: str
135 file: str
136 line: int
137 message: str
138 warn_only: bool = False