3"""Recognize header-only non-inline static function declarations.
5The tidy router must distinguish ordinary standalone headers from textual
6include fragments. This scanner applies line splicing, removes preprocessing
7directives and comments, then tokenizes C-family source so declaration
8whitespace and line wrapping cannot change that ownership decision.
11from __future__
import annotations
18from pathlib
import Path
20_TOKEN_RE = re.compile(
22 (?P<block_comment>/\*.*?\*/)
23 | (?P<line_comment>//[^\n]*)
24 | (?P<string>"(?:\\.|[^"\\])*")
25 | (?P<char>'(?:\\.|[^'\\])*')
26 | (?P<identifier>[A-Za-z_][A-Za-z0-9_]*)
27 | (?P<punct>[{}()\[\];=,*])
31 re.DOTALL | re.VERBOSE,
33_IDENTIFIER_RE = re.compile(
r"[A-Za-z_][A-Za-z0-9_]*\Z")
36class ScanError(ValueError):
37 """The token stream is malformed, so ownership cannot be decided safely."""
40_GROUP_CLOSE = {
"(":
")",
"[":
"]",
"{":
"}"}
41_GROUP_OPEN = set(_GROUP_CLOSE)
42_GROUP_END = set(_GROUP_CLOSE.values())
43_POINTER_QUALIFIERS = {
"const",
"restrict",
"volatile",
"_Atomic"}
44_DECL_SPECIFIER_WORDS = {
82_INLINE_SPECIFIERS = frozenset({
"inline",
"__inline",
"__inline__",
"constexpr",
"consteval"})
83_TYPE_BEARING_SPECIFIERS = frozenset(
107_ELABORATED_SPECIFIERS = frozenset({
"class",
"enum",
"struct",
"union"})
108_PARAMETER_EXPRESSION_LEADS = frozenset({
"!",
"+",
"-",
"false",
"nullptr",
"true",
"~"})
109_MIN_USER_TYPE_DECL_TOKENS = 2
110_PREFIX_GROUP_WORDS = {
130 "single line": (
"RA8_INTERNAL static int helper(void);",
True),
131 "spliced keyword and name": (
"sta\\\ntic int hel\\\nper(void);",
True),
132 "spliced block comment": (
133 "/\\\n* static int hidden(void); */ int public_api(void);",
137 "#def\\\nine HIDDEN static int hidden(void)\nint public_api(void);",
140 "return/name split": (
"RA8_INTERNAL static int\nhelper(void);",
True),
141 "all specifiers split": (
"RA8_INTERNAL\nstatic\nint helper(void);",
True),
142 "trailing annotation declaration": (
"static int helper(void) RA8_UNUSED;",
True),
143 "exact RA8_UNUSED definition": (
144 "RA8_INTERNAL static int helper(void) RA8_UNUSED\n{ return 0; }",
147 "trailing annotation call": (
148 "static int helper(void) RA8_ANNOTATE(owner);",
151 "compiler attribute": (
152 "static int helper(void) __attribute__((unused));",
155 "parenthesized function name": (
"static int (helper)(void);",
True),
156 "returns function pointer": (
"static int (*helper(void))(int);",
True),
157 "mixed data then function": (
"static int value, helper(void);",
True),
158 "mixed function then data": (
"static int helper(void), value;",
True),
159 "pointer return": (
"static int *helper(void);",
True),
160 "inline then noninline": (
161 "static inline int accessor(void) { return 0; }\nstatic int helper(void);",
164 "static inline": (
"static inline int accessor(void) { return 0; }",
False),
165 "inline static": (
"inline static int accessor(void) { return 0; }",
False),
166 "static data": (
"static int value = 1;",
False),
167 "static function pointer data": (
"static int (*handler)(void);",
False),
168 "mixed data only": (
"static int first, *second;",
False),
169 "annotated data": (
"static int value RA8_UNUSED;",
False),
170 "function-local static": (
171 "int owner(void) { static int value = 1; return value; }",
174 "block declaration": (
175 "int owner(void) { int helper(void); static int value = 1; return value; }",
178 "comment and literal": (
179 '/* static int helper(void); */ const char *s = "static int helper(void);";',
182 "macro directive": (
"#define HELPER static int helper(void)\nint public_api(void);",
False),
183 "array initializer": (
"static int values[] = {1, 2};",
False),
184 "aggregate callback": (
185 "static struct record { int (*callback)(void); } value;",
190_CPP_SELFTEST_CASES = {
191 "extern C declaration": (
192 '#ifdef __cplusplus\nextern "C" {\n#endif\n'
193 "static int helper(void);\n"
194 "#ifdef __cplusplus\n}\n#endif\n",
197 "extern C definition": (
198 '#ifdef __cplusplus\nextern "C" {\n#endif\n'
199 "static int helper(void) { return 0; }\n"
200 "#ifdef __cplusplus\n}\n#endif\n",
203 "extern C nested scopes": (
204 '#ifdef __cplusplus\nextern "C" {\n#endif\n'
205 "struct Record { static int member(void); };\n"
206 "union Variant { int value; static int member(void); };\n"
207 "enum Kind { kind_one };\n"
208 "class Holder { static int member(void); };\n"
209 "int owner(void) { static int value = 1; return value; }\n"
210 "#ifdef __cplusplus\n}\n#endif\n",
214 "namespace detail { static int helper(void); }",
217 "inline namespace": (
218 "inline namespace abi { static int helper(void) { return 0; } }",
221 "anonymous namespace": (
222 "namespace { static int helper(void); }",
225 "nested namespace": (
226 "namespace outer::inner { static int helper(void); }",
229 "extern C++ wrapping extern C": (
230 'extern "C++" { extern "C" { static int helper(void); } }',
233 "direct initialized static object": (
234 "struct Value { explicit Value(int); }; static Value value(7);",
237 "identifier initialized static object": (
238 "struct Value { explicit Value(int); }; int existing = 7; static Value value(existing);",
241 "unary initialized static object": (
242 "struct Value { explicit Value(int); }; int existing = 7; static Value value(-existing);",
245 "call initialized static object": (
246 "struct Value { explicit Value(int); }; int factory(); static Value value(factory());",
249 "string initialized static object": (
250 'struct Value { explicit Value(const char *); }; static Value value("text");',
253 "character initialized static object": (
254 "struct Value { explicit Value(char); }; static Value value('x');",
257 "boolean initialized static object": (
258 "struct Value { explicit Value(bool); }; static Value value(true);",
261 "defaulted function parameter": (
262 "static int helper(int value = 7);",
265 "typedef named function parameter": (
266 "using Count = int; static int helper(Count value);",
269 "qualified unnamed function parameter": (
270 "namespace model { struct Value {}; } static int helper(model::Value);",
273 "namespace attribute": (
274 'namespace detail [[deprecated("fixture")]] { static int helper(void); }',
277 "nested inline namespace": (
278 "namespace outer::inline abi { static int helper(void); }",
281 "noexcept function": (
282 "static int helper(void) noexcept;",
285 "conditional noexcept function": (
286 "static int helper(void) noexcept(true);",
289 "trailing return function": (
290 "static auto helper(void) noexcept -> int;",
293 "templated trailing return": (
294 "#include <array>\nstatic auto helper(void) -> std::array<int, 2>;",
297 "nested templated trailing return": (
298 "#include <vector>\n"
299 "static auto helper(void) noexcept -> const std::vector<std::vector<int>>&;",
302 "global qualified templated trailing return": (
303 "#include <array>\nstatic auto helper(void) -> ::std::array<int, 2>;",
306 "templated direct initialized object": (
307 "#include <vector>\nint existing = 7; static std::vector<int> value(existing);",
310 "gnu inline spelling": (
311 "static __inline int helper(void) { return 0; }",
314 "gnu inline suffix spelling": (
315 "static __inline__ int helper(void) { return 0; }",
318 "constexpr implicit inline": (
319 "static constexpr int helper(void) { return 0; }",
322 "consteval implicit inline": (
323 "static consteval int helper(void) { return 0; }",
326 "namespace nested scopes stay opaque": (
327 "namespace detail {\n"
328 "struct Record { static int member(void); };\n"
329 "union Variant { int value; static int member(void); };\n"
330 "enum Kind { kind_one };\n"
331 "class Holder { static int member(void); };\n"
332 "int owner(void) { static int value = 1; return value; }\n"
339 "unterminated declarator": (
"static int broken(",
"c"),
340 "unterminated extern C wrapper": (
'extern "C" { static int helper(void);',
"c++"),
341 "malformed extern C declaration": (
'extern "C" { static int broken( }',
"c++"),
342 "unterminated namespace": (
"namespace detail { static int helper(void);",
"c++"),
343 "unterminated trailing template": (
344 "#include <vector>\nstatic auto helper(void) -> std::vector<int;",
350def _strip_preprocessor_directives(text: str) -> str:
351 """Replace directives with newlines while respecting continuations."""
354 for line
in text.splitlines(keepends=
True):
355 is_directive = continued
or line.lstrip().startswith(
"#")
356 continued = is_directive
and line.rstrip().endswith(
"\\")
358 kept.append(
"\n" if line.endswith(
"\n")
else "")
364def _splice_lines(text: str) -> str:
365 """Apply C translation-phase backslash-newline deletion."""
366 output: list[str] = []
368 while index < len(text):
369 if text.startswith(
"\\\r\n", index):
371 elif text.startswith(
"\\\n", index):
374 output.append(text[index])
376 return "".join(output)
379def _tokens(text: str) -> list[str]:
380 """Return semantic identifier and punctuation tokens."""
384 source = _strip_preprocessor_directives(_splice_lines(text))
387 ignored = {
"block_comment",
"line_comment",
"space"}
389 match.group(0)
for match
in _TOKEN_RE.finditer(source)
if match.lastgroup
not in ignored
393def _consume_group(tokens: list[str], start: int, end: int) -> int:
394 """Return the token after one balanced (), [], or {} group."""
395 opening = tokens[start]
396 if opening
not in _GROUP_OPEN:
397 message = f
"expected group opener, got {opening!r}"
398 raise ScanError(message)
399 stack = [_GROUP_CLOSE[opening]]
400 for index
in range(start + 1, end):
401 lexeme = tokens[index]
402 if lexeme
in _GROUP_OPEN:
403 stack.append(_GROUP_CLOSE[lexeme])
404 elif lexeme
in _GROUP_END
and (
not stack
or lexeme != stack.pop()):
405 message = f
"mismatched group terminator {lexeme!r}"
406 raise ScanError(message)
407 if lexeme
in _GROUP_END
and not stack:
409 message = f
"unterminated {opening!r} group"
410 raise ScanError(message)
413def _consume_double_bracket(tokens: list[str], start: int, end: int) -> int:
414 """Return the token after a balanced C23/C++ [[attribute]]."""
418 pair = tokens[index : index + 2]
419 if pair == [
"[",
"["]:
422 elif pair == [
"]",
"]"]:
429 message =
"unterminated '[[ attribute ]]' group"
430 raise ScanError(message)
433def _is_annotation_word(token: str) -> bool:
434 """Recognize project/compiler annotation macro spellings."""
436 token
in _ANNOTATION_WORDS
437 or token.startswith((
"RA8_",
"__"))
438 or (token.isupper()
and "_" in token)
442def _consume_annotation(tokens: list[str], start: int, end: int) -> int |
None:
443 """Consume one leading/trailing annotation, if present."""
444 if tokens[start : start + 2] == [
"[",
"["]:
445 return _consume_double_bracket(tokens, start, end)
446 token = tokens[start]
447 if not _IDENTIFIER_RE.fullmatch(token)
or not _is_annotation_word(token):
450 if after < end
and tokens[after] ==
"(":
451 return _consume_group(tokens, after, end)
455def _prefix_is_specifiers(prefix: list[str], *, require_static: bool) -> bool:
456 """Accept a declaration-specifier/attribute prefix, not declarator syntax."""
457 if require_static
and "static" not in prefix:
461 while index < len(prefix):
462 annotation_end = _consume_annotation(prefix, index, len(prefix))
463 if annotation_end
is not None:
464 index = annotation_end
466 lexeme = prefix[index]
467 if not _IDENTIFIER_RE.fullmatch(lexeme):
469 index = _consume_group(prefix, index, len(prefix))
472 if lexeme
not in _DECL_SPECIFIER_WORDS
and lexeme
not in _PREFIX_GROUP_WORDS:
474 if user_type_names > 1:
476 if index + 1 < len(prefix)
and prefix[index + 1] ==
"(":
477 if lexeme
not in _PREFIX_GROUP_WORDS:
479 index = _consume_group(prefix, index + 1, len(prefix))
482 return bool(prefix)
or not require_static
485def _parse_declarator(tokens: list[str], start: int, end: int) -> tuple[int, list[str]] |
None:
486 """Parse one C declarator and return its derived-type operators."""
488 pointers: list[str] = []
489 while index < end
and tokens[index] ==
"*":
490 pointers.append(
"pointer")
493 annotation_end = _consume_annotation(tokens, index, end)
494 if annotation_end
is not None:
495 index = annotation_end
496 elif tokens[index]
in _POINTER_QUALIFIERS:
502 and _IDENTIFIER_RE.fullmatch(tokens[index])
503 and tokens[index]
not in _DECL_SPECIFIER_WORDS
506 operators: list[str] = []
507 elif index < end
and tokens[index] ==
"(":
508 after_group = _consume_group(tokens, index, end)
509 inner = _parse_declarator(tokens, index + 1, after_group - 1)
510 if inner
is None or inner[0] != after_group - 1:
512 index, operators = after_group, inner[1]
515 while index < end
and tokens[index]
in {
"(",
"["}:
516 opening = tokens[index]
517 group_end = _consume_group(tokens, index, end)
518 if opening ==
"(" and not _is_parameter_declaration_clause(
519 tokens[index + 1 : group_end - 1]
523 operators.append(
"function" if opening ==
"(" else "array")
524 operators.extend(pointers)
525 return index, operators
528def _function_suffix_only(tokens: list[str], start: int, end: int) -> bool:
529 """Recognize conservative C++ function qualifiers and trailing returns."""
532 annotation_end = _consume_annotation(tokens, index, end)
533 if annotation_end
is not None:
534 index = annotation_end
536 if tokens[index]
in {
"&",
"const",
"volatile"}:
539 if tokens[index] ==
"noexcept":
541 if index < end
and tokens[index] ==
"(":
542 index = _consume_group(tokens, index, end)
544 if tokens[index : index + 2] == [
"-",
">"]:
545 trailing = tokens[index + 2 : end]
546 return bool(trailing)
and _is_parameter_declaration(trailing)
551def _top_level_parts(tokens: list[str], separator: str) -> list[list[str]]:
552 """Split at one separator while preserving balanced nested groups."""
553 parts: list[list[str]] = [[]]
554 stack: list[str] = []
555 for index, lexeme
in enumerate(tokens):
556 template_prefix = index > 0
and (
557 _IDENTIFIER_RE.fullmatch(tokens[index - 1])
is not None
558 or tokens[index - 1]
in {
">",
"]"}
560 if lexeme ==
"<" and template_prefix
and _template_argument_end(tokens, index)
is not None:
562 elif lexeme
in _GROUP_OPEN:
563 stack.append(_GROUP_CLOSE[lexeme])
564 elif lexeme ==
">" and stack[-1:] == [
">"]:
566 elif lexeme
in _GROUP_END
and (
not stack
or lexeme != stack.pop()):
567 message = f
"mismatched group terminator {lexeme!r}"
568 raise ScanError(message)
569 if lexeme == separator
and not stack:
572 parts[-1].append(lexeme)
574 message = f
"unterminated group, expected {stack[-1]!r}"
575 raise ScanError(message)
579def _without_annotations(tokens: list[str]) -> list[str]:
580 """Remove leading attributes while preserving the declaration body."""
581 return tokens[_annotations_end(tokens, 0) :]
584def _annotations_end(tokens: list[str], start: int) -> int:
585 """Return the first token after a consecutive attribute sequence."""
587 while index < len(tokens):
588 annotation_end = _consume_annotation(tokens, index, len(tokens))
589 if annotation_end
is None:
591 index = annotation_end
595def _template_argument_end(tokens: list[str], start: int) -> int |
None:
596 """Return the end of a balanced C++ template-argument list, if any."""
597 if tokens[start : start + 1] != [
"<"]:
601 while index < len(tokens):
602 lexeme = tokens[index]
603 if lexeme
in _GROUP_OPEN:
604 index = _consume_group(tokens, index, len(tokens))
616def _consume_template_arguments(tokens: list[str], start: int) -> int:
617 """Return the token after one balanced C++ template-argument list."""
618 end = _template_argument_end(tokens, start)
621 message =
"unterminated '<' template-argument group"
622 raise ScanError(message)
625def _has_qualified_type_prefix(tokens: list[str]) -> tuple[bool, int]:
626 """Return a positive qualified type-name prefix and its end offset."""
630 qualified = tokens[:2] == [
":",
":"]
633 if index >= len(tokens)
or not _IDENTIFIER_RE.fullmatch(tokens[index]):
636 if tokens[index : index + 1] == [
"<"]:
637 index = _consume_template_arguments(tokens, index)
638 while tokens[index : index + 2] == [
":",
":"]:
641 if index < len(tokens)
and tokens[index] ==
"template":
643 if index >= len(tokens)
or not _IDENTIFIER_RE.fullmatch(tokens[index]):
646 if tokens[index : index + 1] == [
"<"]:
647 index = _consume_template_arguments(tokens, index)
648 return qualified, index
651def _parameter_type_end(item: list[str]) -> int |
None:
652 """Return the end of a positive parameter type, or no type."""
654 type_is_positive =
False
655 while index < len(item):
656 annotation_end = _consume_annotation(item, index, len(item))
657 if annotation_end
is not None:
658 index = annotation_end
661 if token
in _DECL_SPECIFIER_WORDS:
662 type_is_positive = type_is_positive
or token
in _TYPE_BEARING_SPECIFIERS
664 if token
in _ELABORATED_SPECIFIERS:
665 if index >= len(item)
or not _IDENTIFIER_RE.fullmatch(item[index]):
668 type_is_positive =
True
670 if token
in _PREFIX_GROUP_WORDS
and item[index + 1 : index + 2] == [
"("]:
671 index = _consume_group(item, index + 1, len(item))
672 type_is_positive =
True
678 remainder = item[index:]
679 qualified, qualified_end = _has_qualified_type_prefix(remainder)
681 return index + qualified_end
682 user_type_has_declarator = len(remainder) >= _MIN_USER_TYPE_DECL_TOKENS
and (
683 remainder[1]
in {
"&",
"*"}
or _IDENTIFIER_RE.fullmatch(remainder[1])
685 qualified_by_specifier = index > 0
and len(remainder) == 1
688 and _IDENTIFIER_RE.fullmatch(remainder[0])
689 and (user_type_has_declarator
or qualified_by_specifier)
695def _parameter_declarator_is_valid(declarator: list[str]) -> bool:
696 """Return whether tokens form a conservative parameter declarator."""
699 if declarator[:1]
in ([
"&"], [
"*"]):
700 return all(token
in {
"&",
"*"}
or _IDENTIFIER_RE.fullmatch(token)
for token
in declarator)
701 if _IDENTIFIER_RE.fullmatch(declarator[0]):
703 _IDENTIFIER_RE.fullmatch(token)
or token
in {
"&",
"*",
"[",
"]",
"(",
")"}
704 for token
in declarator
706 if declarator[0] ==
"(":
708 return _consume_group(declarator, 0, len(declarator)) <= len(declarator)
714def _is_parameter_declaration(tokens: list[str]) -> bool:
715 """Recognize positive parameter-declaration grammar, never expressions."""
716 item = _without_annotations(_before_initializer(tokens))
719 if item == [
".",
".",
"."]:
722 if lead.startswith((
'"',
"'"))
or lead[:1].isdigit()
or lead
in _PARAMETER_EXPRESSION_LEADS:
724 type_end = _parameter_type_end(item)
725 return type_end
is not None and _parameter_declarator_is_valid(item[type_end:])
728def _is_parameter_declaration_clause(tokens: list[str]) -> bool:
729 """Require every top-level clause item to be a parameter declaration."""
732 parts = _top_level_parts(tokens,
",")
733 return bool(parts)
and all(_is_parameter_declaration(part)
for part
in parts)
736def _before_initializer(tokens: list[str]) -> list[str]:
737 """Discard a top-level initializer from one init-declarator."""
738 return _top_level_parts(tokens,
"=")[0]
741def _item_declares_function(tokens: list[str], *, require_static: bool) -> bool:
742 """Recognize a function declarator in one comma-separated item."""
743 item = _before_initializer(tokens)
744 stack: list[str] = []
745 for start
in range(len(item)):
746 at_top_level =
not stack
748 if lexeme
in _GROUP_OPEN:
749 stack.append(_GROUP_CLOSE[lexeme])
750 elif lexeme
in _GROUP_END
and (
not stack
or lexeme != stack.pop()):
751 message = f
"mismatched group terminator {lexeme!r}"
752 raise ScanError(message)
753 if not at_top_level
or not _prefix_is_specifiers(
754 item[:start], require_static=require_static
757 parsed = _parse_declarator(item, start, len(item))
760 after, operators = parsed
763 and operators[0] ==
"function"
764 and _function_suffix_only(item, after, len(item))
770def _statement_declares_function(
771 tokens: list[str], *, require_static: bool, exclude_inline: bool
773 """Recognize a function in a complete file-scope declaration header."""
774 if not tokens
or (require_static
and "static" not in tokens):
776 if exclude_inline
and _INLINE_SPECIFIERS.intersection(tokens):
778 for index, item
in enumerate(_top_level_parts(tokens,
",")):
779 if _item_declares_function(item, require_static=(require_static
and index == 0)):
784def _validate_groups(tokens: list[str]) ->
None:
785 """Reject malformed input rather than silently routing it as direct."""
786 stack: list[str] = []
787 for lexeme
in tokens:
788 if lexeme
in _GROUP_OPEN:
789 stack.append(_GROUP_CLOSE[lexeme])
790 elif lexeme
in _GROUP_END
and (
not stack
or lexeme != stack.pop()):
791 message = f
"mismatched group terminator {lexeme!r}"
792 raise ScanError(message)
794 message = f
"unterminated group, expected {stack[-1]!r}"
795 raise ScanError(message)
798def _is_language_linkage_wrapper(tokens: list[str]) -> bool:
799 """Recognize canonical C and C++ language-linkage block prefixes."""
800 return tuple(tokens)
in {(
"extern",
'"C"'), (
"extern",
'"C++"')}
803def _namespace_name_end(tokens: list[str], start: int) -> int |
None:
804 """Consume one optionally-inline namespace name and its attributes."""
806 if tokens[index : index + 1] == [
"inline"]:
808 if index >= len(tokens)
or not _IDENTIFIER_RE.fullmatch(tokens[index]):
810 return _annotations_end(tokens, index + 1)
813def _is_namespace_wrapper(tokens: list[str]) -> bool:
814 """Recognize named, anonymous, inline, and nested namespace prefixes."""
816 if tokens[:1] == [
"inline"]:
818 if tokens[index : index + 1] != [
"namespace"]:
820 index = _annotations_end(tokens, index + 1)
821 if index == len(tokens):
823 while index < len(tokens):
824 name_end = _namespace_name_end(tokens, index)
828 if index == len(tokens):
830 if tokens[index : index + 2] != [
":",
":"]:
836def _is_transparent_cpp_wrapper(tokens: list[str]) -> bool:
837 """Return whether a C++ wrapper preserves namespace/file-scope linkage."""
838 return _is_language_linkage_wrapper(tokens)
or _is_namespace_wrapper(tokens)
841def _scope_has_noninline_static(tokens: list[str], start: int, end: int) -> bool:
842 """Scan one real or transparent file scope for a matching declaration."""
843 statement: list[str] = []
846 lexeme = tokens[index]
848 after_group = _consume_group(tokens, index, end)
849 if _is_transparent_cpp_wrapper(statement):
850 if _scope_has_noninline_static(tokens, index + 1, after_group - 1):
855 if _statement_declares_function(statement, require_static=
True, exclude_inline=
True):
857 if _statement_declares_function(statement, require_static=
False, exclude_inline=
False):
861 statement.extend(tokens[index:after_group])
865 if _statement_declares_function(statement, require_static=
True, exclude_inline=
True):
869 statement.append(lexeme)
874def has_noninline_static_decl(text: str) -> bool:
875 """Return whether text has a file-scope non-inline static function."""
876 tokens = _tokens(text)
877 _validate_groups(tokens)
878 return _scope_has_noninline_static(tokens, 0, len(tokens))
881def _clang_accepts(name: str, source: str, language: str) -> bool:
882 """Prove the fixture is accepted C/C++, not parser-only invented syntax."""
883 compiler_name =
"clang++-18" if language ==
"c++" else "clang-18"
884 clang = shutil.which(compiler_name)
886 print(f
"static_decl_scan.py selftest: {compiler_name} not found", file=sys.stderr)
889 "#define RA8_INTERNAL\n"
890 "#define RA8_UNUSED __attribute__((unused))\n"
891 "#define RA8_ANNOTATE(x) __attribute__((annotate(#x)))\n"
894 result = subprocess.run(
897 "-std=c++20" if language ==
"c++" else "-std=c17",
904 input=preamble + source,
909 if result.returncode != 0:
911 f
"static_decl_scan.py selftest: clang rejected {name}:\n{result.stderr}",
918def _run_selftest_case(name: str, source: str, expected: bool, language: str) -> int:
919 """Run one detection direction and its real-compiler syntax proof."""
921 actual = has_noninline_static_decl(source)
922 except ScanError
as exc:
923 print(f
"static_decl_scan.py selftest: {name}: {exc}", file=sys.stderr)
925 mismatch = actual != expected
928 f
"static_decl_scan.py selftest: {name}: got {actual}, expected {expected}",
931 return int(mismatch) + int(
not _clang_accepts(name, source, language))
934def _run_selftest() -> int:
936 _run_selftest_case(name, source, expected, language)
937 for language, cases
in ((
"c", _SELFTEST_CASES), (
"c++", _CPP_SELFTEST_CASES))
938 for name, (source, expected)
in cases.items()
940 for name, (source, _language)
in _MALFORMED_CASES.items():
942 has_noninline_static_decl(source)
945 print(f
"static_decl_scan.py selftest: {name} did not fail closed", file=sys.stderr)
949 "static_decl_scan.py --selftest: "
950 f
"PASS ({len(_SELFTEST_CASES)} clang-18 + "
951 f
"{len(_CPP_SELFTEST_CASES)} clang++-18 cases)"
956def _parse_args() -> argparse.Namespace:
957 parser = argparse.ArgumentParser(description=__doc__)
958 parser.add_argument(
"--selftest", action=
"store_true", help=
"run the token scanner selftest")
959 parser.add_argument(
"path", nargs=
"?", type=Path, help=
"header to inspect")
960 return parser.parse_args()
964 """Run the file scanner or its selftest."""
967 return _run_selftest()
968 if args.path
is None:
969 print(
"static_decl_scan.py: a path is required", file=sys.stderr)
972 text = args.path.read_text(encoding=
"utf-8")
973 matched = has_noninline_static_decl(text)
974 except (OSError, UnicodeError, ScanError)
as exc:
975 print(f
"static_decl_scan.py: {exc}", file=sys.stderr)
977 return 0
if matched
else 1
980if __name__ ==
"__main__":
981 raise SystemExit(
main())
void main(void)
The application entry point Reset_Handler hands control to.