ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
check_entry_points.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""Gate: every first-party entry point uses its own build domain's contract.
5
6Two domains, two contracts, one enforced boundary (#707):
7
8* **Hosted** -- ``tests/`` and ``tools/`` run under an OS that reads an exit
9 status, so ISO C applies: ``int main(void)`` or ``int main(int, char**)``.
10 Both host compilers enforce this themselves; this gate exists so the
11 spelling cannot drift back to ``int32_t`` where nothing would notice.
12* **Freestanding** -- ``examples/`` and ``port/`` are bare metal
13 reached from ``Reset_Handler``. There is no process and no exit status, so
14 the entry point is ``void main(void)``, declared once in
15 ``libs/ra8_core/inc/ra8_boot_entry.h``.
16
17``apps/`` -- the products tier -- is the one root that carries BOTH, so it
18cannot be classified by its name: see ``FIRMWARE_APPS`` below.
19
20WHY A GATE RATHER THAN TRUSTING THE COMPILER
21
22The compiler catches a *disagreement it can see*. It could not see this one.
23``int32_t`` is ``long int`` on arm-none-eabi and ``int`` on the host, so
24``int32_t main(void)`` meant a different function type on each side; on the
25chip that is ``-Werror=main``, which 208 example files silenced with a local
26``#pragma GCC diagnostic ignored "-Wmain"``. Worse, the declaration lived in
27sixteen copy-pasted ``extern int32_t main(void);`` lines in vector tables --
28a different translation unit from the definition, so roughly thirty apps had
29drifted into declaring one type and defining another with nothing to notice.
30
31The single declaration in ``ra8_boot_entry.h`` is what makes the compiler
32able to check; requiring firmware entry points to INCLUDE it is what makes
33the check actually happen, because GCC exempts ``main`` from
34``-Wmissing-prototypes``. This gate enforces the include, the spelling, and
35the absence of any renewed suppression.
36
37WHAT IS DELIBERATELY NOT ENFORCED
38
39Type *agreement* between declaration and definition -- that is the
40compiler's job once the include is present, and it does it better. This gate
41is textual on purpose (see ``funcsize_c.py`` for the same reasoning): it must
42cover translation units that never reach a ``compile_commands.json``,
43including apps excluded from the unified cross-configure.
44
45``LLVMFuzzerTestOneInput`` harnesses have no ``main`` at all, and the
46Cortex-M33 ``cpu1_main`` is ``static`` and never named ``main``; neither is
47an entry point by this gate's definition and neither is scanned.
48
49Run::
50
51 python3 scripts/checks/check_entry_points.py # scan the tree
52 python3 scripts/checks/check_entry_points.py --selftest # both directions
53 python3 scripts/checks/check_entry_points.py --list # inventory
54 python3 scripts/checks/check_entry_points.py FILE... # pre-commit mode
55
56Exit 0 if clean, 1 on findings or a failing selftest, 2 on a broken scan.
57"""
58
59from __future__ import annotations
60
61import re
62import sys
63from pathlib import Path
64
65sys.path.insert(0, str(Path(__file__).resolve().parent))
66from lint_targets import ( # needs the sys.path line above
67 firmware_app_dirs,
68 first_party_paths,
69)
70from selftest_assert import expect, report # needs the sys.path line above
71
72REPO_ROOT = Path(__file__).resolve().parents[2]
73
74EXIT_OK = 0
75EXIT_FAIL = 1
76EXIT_CONFIG = 2
77
78BOOT_HEADER = "ra8_boot_entry.h"
79BOOT_HEADER_REL = "libs/ra8_core/inc/ra8_boot_entry.h"
80
81# Roots whose entry points are reached from Reset_Handler rather than from a C
82# runtime. Derived from where the build actually cross-compiles: the app
83# discovery in the top-level CMakeLists globs examples/, and port/ is RTOS glue
84# compiled into firmware images. The Ring 5 secure substrate that used to sit
85# under src/ is now libs/ra8_secure_app -- pure library code with no entry
86# point, so libs/ needs no row here (#724).
87FIRMWARE_ROOTS = ("examples/", "port/")
88HOSTED_ROOTS = ("tests/", "tools/", "apps/")
89
90# ...and the one root where the name settles nothing. `apps/` is the PRODUCTS
91# tier and it carries both domains: the mdl CLI is a host program the C
92# runtime starts and whose exit status something reads, while the e-reader is a
93# two-image TrustZone composition reached from Reset_Handler. Classifying the
94# whole root either way is wrong for half of it -- calling the e-reader hosted
95# demands `int main(void)` of a freestanding image and drops the
96# `ra8_boot_entry.h` include that makes the cross-TU check happen at all, which
97# is precisely the #707 hole.
98#
99# So the firmware products are NAMED here, and `check_firmware_apps()` re-derives
100# the same set from the tree on every whole-tree run. The declaration is what
101# keeps classification textual (this gate must reach translation units that
102# never appear in a compile database); the derivation is what stops the
103# declaration from drifting -- a new firmware product nobody listed FAILS, and a
104# listed path that stopped being one FAILS too. Neither half is load-bearing
105# alone.
106FIRMWARE_APPS = ("apps/board/stand_alone/ereader/",)
107
108# Measured 2026-08-15 on dev @ ad515de20: 234 firmware entry points, 637
109# hosted ones. A tree this size cannot legitimately fall to a handful; an
110# enumeration that collapses reports a clean tree because it looked at
111# almost nothing. Lower these deliberately, with a reason.
112FIRMWARE_FLOOR = 150
113HOSTED_FLOOR = 400
114
115# Named paths the scan must still reach. A floor catches a total collapse; this
116# catches a subtler one where a root stops being enumerated but the others keep
117# the count above the floor.
118MUST_DISCOVER = (
119 "apps/board/stand_alone/ereader/src/main.c",
120 "libs/ra8_core/inc/ra8_boot_entry.h",
121)
122
123SUFFIXES = (".c", ".cpp", ".h")
124
125# `void main(void)` / `int main(void)` / `int main(int argc, char **argv)`.
126# Anchored at column 0: an entry point is never nested or indented, and the
127# anchor keeps the pattern off `static int main_loop(...)` and friends.
128MAIN_DEF_RE = re.compile(r"^(?P<ret>[A-Za-z_][A-Za-z0-9_ ]*?)\s+main\s*\‍((?P<args>[^)]*)\‍)\s*$")
129MAIN_DECL_RE = re.compile(r"^\s*(?:extern\s+)?[A-Za-z_][A-Za-z0-9_ ]*?\s+main\s*\‍([^)]*\‍)\s*;\s*$")
130INCLUDE_RE = re.compile(r'^\s*#\s*include\s+"([^"]+)"')
131RETURN_VALUE_RE = re.compile(r"^\s*return\s+[^;]+;")
132SUPPRESSION_RE = re.compile(
133 r'#\s*pragma\s+(?:GCC|clang)\s+diagnostic\s+ignored\s+"-Wmain(?:-return-type)?"'
134)
135HOSTED_ARGS_OK = (
136 "void",
137 "int argc, char** argv",
138 "int argc, char **argv",
139 "int argc, char *argv[]",
140 "int argc, char* argv[]",
141 "",
142)
143
144
145class ScanError(RuntimeError):
146 """The scan stopped being a measurement."""
147
148
149def domain_of(rel: str, firmware_apps: tuple[str, ...] = FIRMWARE_APPS) -> str | None:
150 """Which build domain owns this path, or None if it is neither.
151
152 Nested test build units are hosted even when they verify a firmware
153 product. A named firmware product is then tested before the general
154 ``apps/`` hosted root.
155 """
156 if "tests" in rel.split("/"):
157 return "hosted"
158 if rel.startswith(firmware_apps):
159 return "firmware"
160 if rel.startswith(HOSTED_ROOTS):
161 return "hosted"
162 if rel.startswith(FIRMWARE_ROOTS):
163 return "firmware"
164 return None
165
166
167def find_main(lines: list[str]) -> tuple[int, str, str] | None:
168 """Locate a ``main`` DEFINITION: signature line, return type, arguments."""
169 for i, line in enumerate(lines):
170 match = MAIN_DEF_RE.match(line.rstrip())
171 if not match:
172 continue
173 ret = " ".join(match.group("ret").split())
174 if ret in {"return", "case", "else"} or ret.startswith("//"):
175 continue
176 # A definition is followed by `{`; a declaration ends in `;` and so
177 # never matches the pattern above, but a K&R-era prototype might.
178 nxt = next(
179 (lines[j].strip() for j in range(i + 1, min(i + 3, len(lines))) if lines[j].strip()), ""
180 )
181 if not (line.rstrip().endswith("{") or nxt.startswith("{")):
182 continue
183 return i, ret, " ".join(match.group("args").split())
184 return None
185
186
187def body_returns_a_value(lines: list[str], sig: int) -> int | None:
188 """1-based line of the first value-returning `return` in main's body."""
189 depth = 0
190 started = False
191 for i in range(sig, len(lines)):
192 depth += lines[i].count("{") - lines[i].count("}")
193 if lines[i].count("{"):
194 started = True
195 if started and depth <= 0:
196 return None
197 if started and depth >= 1 and RETURN_VALUE_RE.match(lines[i]):
198 return i + 1
199 return None
200
201
202def copied_main_declarations(rel: str, lines: list[str]) -> list[str]:
203 """Reject declarations copied outside the one authoritative header."""
204 if rel == BOOT_HEADER_REL:
205 return []
206 return [
207 f"{rel}:{i + 1}: duplicates the main() declaration outside "
208 f'{BOOT_HEADER_REL}. Include "{BOOT_HEADER}" instead so every '
209 f"firmware definition is checked against one authoritative type."
210 for i, line in enumerate(lines)
211 if MAIN_DECL_RE.match(line)
212 ]
213
214
215def check_file(
216 rel: str, text: str, firmware_apps: tuple[str, ...] = FIRMWARE_APPS
217) -> tuple[list[str], str | None]:
218 """Findings for one file, plus the domain of any entry point it defines."""
219 findings: list[str] = []
220 lines = text.splitlines()
221 findings.extend(copied_main_declarations(rel, lines))
222
223 for i, line in enumerate(lines):
224 if SUPPRESSION_RE.search(line):
225 findings.append(
226 f"{rel}:{i + 1}: -Wmain suppression. The firmware lane is "
227 f"-ffreestanding, so the diagnostic does not apply; a "
228 f"suppression here is hiding something else."
229 )
230
231 found = find_main(lines)
232 if found is None:
233 return findings, None
234 sig, ret, args = found
235 domain = domain_of(rel, firmware_apps)
236
237 if domain is None:
238 findings.append(
239 f"{rel}:{sig + 1}: defines main() but is under neither a hosted "
240 f"root {HOSTED_ROOTS} nor a firmware root {FIRMWARE_ROOTS}, so no "
241 f"entry-point contract applies to it. Classify it by moving it, "
242 f"or extend the roots in this checker."
243 )
244 return findings, None
245
246 if domain == "firmware":
247 if ret != "void" or args != "void":
248 findings.append(
249 f"{rel}:{sig + 1}: firmware entry point is "
250 f"`{ret} main({args})`; the freestanding contract is "
251 f"`void main(void)` (see {BOOT_HEADER})."
252 )
253 included = {m.group(1) for m in (INCLUDE_RE.match(line) for line in lines) if m}
254 if BOOT_HEADER not in included:
255 findings.append(
256 f"{rel}:{sig + 1}: firmware entry point does not include "
257 f'"{BOOT_HEADER}". Without it the compiler never compares this '
258 f"definition against the shared declaration, which is exactly "
259 f"how ~30 of these drifted out of agreement."
260 )
261 bad = body_returns_a_value(lines, sig)
262 if bad is not None:
263 findings.append(
264 f"{rel}:{bad}: `return <value>;` inside a `void main`. "
265 f"A freestanding entry point has nothing to return to."
266 )
267 elif ret != "int" or args not in HOSTED_ARGS_OK:
268 findings.append(
269 f"{rel}:{sig + 1}: hosted entry point is `{ret} main({args})`; "
270 f"ISO C requires `int main(void)` or `int main(int, char**)`."
271 )
272
273 return findings, domain
274
275
276def scan(paths: list[str]) -> tuple[list[str], dict[str, int]]:
277 """Scan every path, returning findings and a per-domain count."""
278 findings: list[str] = []
279 counts = {"hosted": 0, "firmware": 0}
280 for rel in paths:
281 try:
282 text = (REPO_ROOT / rel).read_text(encoding="utf-8")
283 except (OSError, UnicodeDecodeError):
284 continue
285 file_findings, domain = check_file(rel, text)
286 findings.extend(file_findings)
287 if domain:
288 counts[domain] += 1
289 return findings, counts
290
291
292def check_shared_declaration() -> list[str]:
293 """The one declaration must still exist, and stay freestanding-guarded.
294
295 Everything else in this gate is downstream of this file. Delete the
296 declaration and every firmware entry point silently loses the
297 cross-translation-unit check that is the whole point of #707; drop the
298 guard and every hosted TU that includes the header stops compiling with
299 `conflicting types for 'main'`.
300 """
301 rel = BOOT_HEADER_REL
302 try:
303 text = (REPO_ROOT / rel).read_text(encoding="utf-8")
304 except OSError:
305 return [f"{rel}: missing. It holds the one declaration of main()."]
306
307 findings = []
308 if not re.search(r"^void main\‍(void\‍);$", text, re.MULTILINE):
309 findings.append(
310 f"{rel}: no `void main(void);` declaration. Firmware entry points "
311 f"include this header so the compiler can compare their definition "
312 f"against it; without the declaration nothing is checked."
313 )
314 if "__STDC_HOSTED__" not in text:
315 findings.append(
316 f"{rel}: the main() declaration is not guarded by "
317 f"`__STDC_HOSTED__ == 0`. Unguarded, every hosted TU including "
318 f"this header fails with `conflicting types for 'main'`."
319 )
320 return findings
321
322
323def check_firmware_apps(paths: list[str] | None = None) -> list[str]:
324 """``FIRMWARE_APPS`` must still name exactly the firmware products present.
325
326 ``lint_targets.firmware_app_dirs()`` derives the set from what the build
327 does with a directory -- an app dir under ``apps/`` holding both a linker
328 script and a ``vector_table.c`` is linked into an image, not started by a C
329 runtime. Both directions of a disagreement are silent failures, which is
330 why this is a hard error rather than a warning:
331
332 * a firmware product missing from the tuple is classified HOSTED, so the
333 gate demands ISO ``int main`` of a freestanding image and stops requiring
334 the ``ra8_boot_entry.h`` include;
335 * a stale entry classifies nothing at all, and goes on reporting success
336 forever.
337
338 ``paths`` defaults to the whole tracked tree deliberately: this gate's own
339 scan carries only C/C++ sources, and a linker script is half the evidence.
340 Pass a list only from a selftest fixture.
341 """
342 derived = tuple(f"{d}/" for d in firmware_app_dirs(paths))
343 if derived == FIRMWARE_APPS:
344 return []
345 missing = [
346 f"{d}: a firmware product (linker script + vector_table.c) that "
347 f"FIRMWARE_APPS does not name, so this gate classifies it as a hosted "
348 f"program and applies the wrong entry-point contract to it. Add it to "
349 f"FIRMWARE_APPS in check_entry_points.py."
350 for d in derived
351 if d not in FIRMWARE_APPS
352 ]
353 stale = [
354 f"{d}: named in FIRMWARE_APPS but no longer a firmware product (no "
355 f"linker script + vector_table.c pair). A stale entry classifies "
356 f"nothing and reports success forever; drop it."
357 for d in FIRMWARE_APPS
358 if d not in derived
359 ]
360 return missing + stale
361
362
363def enforce_floors(counts: dict[str, int], paths: list[str]) -> None:
364 """Raise unless the sweep still reached enough to be a measurement."""
365 if counts["firmware"] < FIRMWARE_FLOOR:
366 msg = (
367 f"{counts['firmware']} firmware entry point(s) found, floor is "
368 f"{FIRMWARE_FLOOR}. The scan stopped reaching them; a clean tree "
369 f"is NOT the explanation."
370 )
371 raise ScanError(msg)
372 if counts["hosted"] < HOSTED_FLOOR:
373 msg = (
374 f"{counts['hosted']} hosted entry point(s) found, floor is "
375 f"{HOSTED_FLOOR}. The scan stopped reaching them."
376 )
377 raise ScanError(msg)
378 missing = [p for p in MUST_DISCOVER if p not in set(paths)]
379 if missing:
380 msg = f"the scan no longer reaches: {', '.join(missing)}"
381 raise ScanError(msg)
382
383
384def _selftest_quiet(failures: list[str]) -> None:
385 """MUST STAY QUIET: conforming entry points produce no finding."""
386 good_fw = '#include "ra8_boot_entry.h"\nvoid main(void)\n{\n for (;;) {\n }\n}\n'
387 quiet, domain = check_file("examples/x/src/main.c", good_fw)
388 expect(not quiet, f"a conforming firmware entry point is silent (got {quiet})", failures)
389 expect(domain == "firmware", f"classified firmware (got {domain})", failures)
390
391 good_hosted = "int main(void)\n{\n return 0;\n}\n"
392 quiet, domain = check_file("tests/misc/src/test_x.c", good_hosted)
393 expect(not quiet, f"a conforming hosted entry point is silent (got {quiet})", failures)
394 expect(domain == "hosted", f"classified hosted (got {domain})", failures)
395
396 good_product_fw = '#include "ra8_boot_entry.h"\nvoid main(void)\n{\n for (;;) {\n }\n}\n'
397 quiet, domain = check_file("apps/board/stand_alone/ereader/src/main.c", good_product_fw)
398 expect(not quiet, f"a conforming firmware PRODUCT is silent (got {quiet})", failures)
399 expect(
400 domain == "firmware",
401 f"a named firmware product classifies firmware (got {domain})",
402 failures,
403 )
404
405 quiet, domain = check_file("apps/host/mdl/src/main.c", "int main(void)\n{\n return 0;\n}\n")
406 expect(not quiet, f"a conforming hosted PRODUCT is silent (got {quiet})", failures)
407 expect(domain == "hosted", f"an unnamed product stays hosted (got {domain})", failures)
408
409 quiet, domain = check_file("apps/board/stand_alone/ereader/tests/src/test_main.c", good_hosted)
410 expect(not quiet, f"a firmware product's hosted test stays silent (got {quiet})", failures)
411 expect(domain == "hosted", f"a nested product test is hosted (got {domain})", failures)
412
413 argv_main = "int main(int argc, char** argv)\n{\n return 0;\n}\n"
414 expect(
415 not check_file("tools/t/src/main.c", argv_main)[0],
416 "hosted int main(int, char**) is silent",
417 failures,
418 )
419
420 expect(
421 not check_file("libs/ra8_core/src/x.c", "static int main_loop(void)\n{\n}\n")[0],
422 "a function merely NAMED like main is not an entry point",
423 failures,
424 )
425 expect(
426 not check_file(BOOT_HEADER_REL, "void main(void);\n")[0],
427 "the one declaration in the shared header is silent",
428 failures,
429 )
430
431
432def _selftest_fires(failures: list[str]) -> None:
433 """MUST FIRE: every rule catches its own violation."""
434 fires = {
435 "firmware entry point returning int": (
436 "examples/x/src/main.c",
437 '#include "ra8_boot_entry.h"\nint main(void)\n{\n return 0;\n}\n',
438 ),
439 "firmware entry point missing the shared header": (
440 "examples/x/src/main.c",
441 "void main(void)\n{\n}\n",
442 ),
443 "value-returning return inside void main": (
444 "examples/x/src/main.c",
445 '#include "ra8_boot_entry.h"\nvoid main(void)\n{\n return 1;\n}\n',
446 ),
447 "hosted entry point spelled int32_t": (
448 "tests/misc/src/test_x.c",
449 "int32_t main(void)\n{\n return 0;\n}\n",
450 ),
451 "a renewed -Wmain suppression": (
452 "examples/x/src/main.c",
453 '#include "ra8_boot_entry.h"\n'
454 '#pragma GCC diagnostic ignored "-Wmain"\n'
455 "void main(void)\n{\n}\n",
456 ),
457 "an entry point in an unclassifiable root": (
458 "libs/ra8_core/src/x.c",
459 "int main(void)\n{\n return 0;\n}\n",
460 ),
461 "a copied main declaration outside the shared header": (
462 "examples/x/src/vector_table.c",
463 "extern int32_t main(void);\n",
464 ),
465 "a firmware PRODUCT written to the hosted contract": (
466 "apps/board/stand_alone/ereader/src/main.c",
467 '#include "ra8_boot_entry.h"\nint main(void)\n{\n return 0;\n}\n',
468 ),
469 "a hosted PRODUCT written to the freestanding contract": (
470 "apps/host/mdl/src/main.c",
471 "void main(void)\n{\n}\n",
472 ),
473 }
474 for label, (rel, text) in fires.items():
475 expect(bool(check_file(rel, text)[0]), f"MUST FIRE: {label}", failures)
476
477
478def _selftest_scope(failures: list[str]) -> None:
479 """The scan still reaches what it must, and still excludes what it must."""
480 live = discover()
481 expect(
482 any(p.startswith("examples/") for p in live), "the live scan reaches examples/", failures
483 )
484 expect(any(p.startswith("tests/") for p in live), "the live scan reaches tests/", failures)
485 expect(
486 all(not p.startswith(("libs/third_party/", "apps/shared_libs/third_party/")) for p in live),
487 "the live scan excludes vendored SOUP",
488 failures,
489 )
490
491 # The floor is a testable predicate, not just an inline branch.
492 collapsed_rejected = False
493 try:
494 enforce_floors({"hosted": 0, "firmware": 0}, list(MUST_DISCOVER))
495 except ScanError:
496 collapsed_rejected = True
497 expect(collapsed_rejected, "MUST FIRE: a collapsed scan is rejected", failures)
498
499 expect(
500 not check_shared_declaration(),
501 "the live shared declaration is present and guarded",
502 failures,
503 )
504
505 # The products tier carries both domains, and the live tree must still say
506 # so -- if apps/ ever held only hosted products this gate's extra rule
507 # would be dead weight nobody would notice.
508 expect(
509 not check_firmware_apps(),
510 "FIRMWARE_APPS agrees with the products actually in the tree",
511 failures,
512 )
513 expect(
514 bool(check_firmware_apps(["libs/ra8_core/src/x.c"])),
515 "MUST FIRE: a stale FIRMWARE_APPS entry is rejected",
516 failures,
517 )
518 expect(
519 bool(
520 check_firmware_apps(
521 [
522 "apps/board/stand_alone/ereader/src/vector_table.c",
523 "apps/board/stand_alone/ereader/linker_script.ld",
524 "apps/board/stand_alone/invented/src/vector_table.c",
525 "apps/board/stand_alone/invented/invented.ld",
526 ]
527 )
528 ),
529 "MUST FIRE: an unlisted firmware product is rejected",
530 failures,
531 )
532 expect(
533 domain_of("apps/board/stand_alone/ereader/src/main.c", ()) == "hosted",
534 "without its FIRMWARE_APPS entry the e-reader would be misclassified",
535 failures,
536 )
537
538
539def selftest() -> int:
540 """Assert both directions: the rules fire, and they stay quiet."""
541 print("check_entry_points.py --selftest")
542 failures: list[str] = []
543 _selftest_quiet(failures)
544 _selftest_fires(failures)
545 _selftest_scope(failures)
546 return report(failures)
547
548
549def discover() -> list[str]:
550 """Every first-party C/C++ path, from git ls-files."""
551 return first_party_paths(SUFFIXES)
552
553
554def main(argv: list[str]) -> int:
555 """CLI entry point; see the module docstring for the modes."""
556 args = argv[1:]
557 if "--selftest" in args:
558 return selftest()
559
560 explicit = [a for a in args if not a.startswith("-")]
561 paths = explicit or discover()
562
563 findings, counts = scan(paths)
564 if not explicit:
565 findings = check_shared_declaration() + check_firmware_apps() + findings
566
567 if "--list" in args:
568 for rel in paths:
569 text = (REPO_ROOT / rel).read_text(encoding="utf-8", errors="ignore")
570 found = find_main(text.splitlines())
571 if found:
572 print(f"{domain_of(rel) or 'UNCLASSIFIED':<13}{rel}")
573 return EXIT_OK
574
575 if not explicit:
576 # The floors describe a whole-tree sweep; an explicit file list from
577 # the pre-commit hook legitimately narrows to one file.
578 try:
579 enforce_floors(counts, paths)
580 except ScanError as exc:
581 print(f"check_entry_points.py: FATAL -- {exc}", file=sys.stderr)
582 return EXIT_CONFIG
583
584 if findings:
585 print(
586 f"check_entry_points.py: {len(findings)} entry-point violation(s):\n",
587 file=sys.stderr,
588 )
589 for finding in findings:
590 print(f" {finding}", file=sys.stderr)
591 print(
592 "\nFirmware entry points are `void main(void)` and include "
593 f'"{BOOT_HEADER}"; hosted ones use ISO `int main(...)`. '
594 "See docs/STYLE_GUIDE.md.",
595 file=sys.stderr,
596 )
597 return EXIT_FAIL
598
599 print(
600 f"check_entry_points.py: {counts['firmware']} firmware + "
601 f"{counts['hosted']} hosted entry point(s), no findings."
602 )
603 return EXIT_OK
604
605
606if __name__ == "__main__":
607 sys.exit(main(sys.argv))
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298
#define min(x, y)
Untyped minimum shim used by the SOUP's buffer clamping.
Definition xz_config.h:157