ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
annot_source.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Reading source text back, for the checks a parse cannot answer.
4
5Most of this checker reasons over libclang's AST. Two things cannot be:
6
7* the ``section("...vectors")`` attribute, because ``CursorKind.SECTION_ATTR``
8 is absent from the libclang 18.1.x wheels the runners install, so an AST
9 lookup silently finds nothing; and
10* the NSC range-check idiom, because ``RA8_NSC_CHECK_NS_RANGE_R/_RW`` expands
11 to ``cmse_check_address_range()`` only under ``-mcmse``. This script parses
12 without it, so the macro becomes a ``((void)(p),(void)(n))`` no-op and leaves
13 no ``CallExpr`` at all.
14
15Both therefore read the pre-preprocessor text. That is a deliberate exception,
16not a general licence to grep instead of parse, and it is confined to this
17module so the exception stays countable.
18"""
19
20from __future__ import annotations
21
22import pathlib
23
24from annot_model import AnnotatedSymbol
25
26#: Cache of source files read back for textual (macro-level) checks.
27_SOURCE_CACHE: dict[str, list[str]] = {}
28
29
30def source_lines(path: str) -> list[str]:
31 """Return ``path``'s lines, cached. Empty list when unreadable."""
32 if path not in _SOURCE_CACHE:
33 try:
34 _SOURCE_CACHE[path] = pathlib.Path(path).read_text(errors="ignore").splitlines()
35 except OSError:
36 _SOURCE_CACHE[path] = []
37 return _SOURCE_CACHE[path]
38
39
40def definition_text(sym: AnnotatedSymbol) -> str:
41 """Return the source text of ``sym``'s definition.
42
43 Used for checks that must see the code *before* preprocessing, because
44 the construct being looked for is a macro that this script's parse
45 configuration expands away (see the NSC range-check rule).
46 """
47 lines = source_lines(sym.file)
48 if not lines or sym.line <= 0:
49 return ""
50 end = sym.end_line if sym.end_line >= sym.line else len(lines)
51 return "\n".join(lines[sym.line - 1 : end])