Run a list of `(regex, kind, message)` triples over each line of a function body. First-match wins per line so a single problematic expression doesn't fire every pattern. Lines skipped (all driven by AST walks for multi-line statements): - `constexpr` / `static const` declarations
(body, src: bytes, fname: str, fenced, patterns)
| 668 | |
| 669 | |
| 670 | def _scan_body_lines(body, src: bytes, fname: str, fenced, patterns) -> list: |
| 671 | """Run a list of `(regex, kind, message)` triples over each line of a |
| 672 | function body. First-match wins per line so a single problematic |
| 673 | expression doesn't fire every pattern. |
| 674 | |
| 675 | Lines skipped (all driven by AST walks for multi-line statements): |
| 676 | - `constexpr` / `static const` declarations -- init code, not per-call |
| 677 | - `[[unlikely]]`-attributed substatement bodies -- cold path |
| 678 | - `catch_clause` bodies -- error / exception path, not steady-state |
| 679 | |
| 680 | Lines skipped per-pattern: reciprocal-cache declarations |
| 681 | (`const T inv = 1.0 / x;`) bypass the divide rule -- they ARE the |
| 682 | recommended fix for runtime-divisor cost.""" |
| 683 | if body is None: |
| 684 | return [] |
| 685 | findings: list = [] |
| 686 | body_text = _node_text(body, src) |
| 687 | body_start = body.start_point[0] + 1 |
| 688 | exempt_lines = _init_only_decl_line_span(body, src) | _cold_branch_line_span( |
| 689 | body, src |
| 690 | ) |
| 691 | constexpr_names = _compile_time_constants_in_scope(body, src) |
| 692 | for j, line in enumerate(body_text.split("\n")): |
| 693 | abs_line = body_start + j |
| 694 | if fenced(abs_line) or abs_line in exempt_lines: |
| 695 | continue |
| 696 | scrubbed = _strip_strings_and_line_comments(line) |
| 697 | is_recip_cache = _is_reciprocal_cache_line(scrubbed) |
| 698 | # If every divisor / modulo on this line resolves to a constexpr |
| 699 | # name we know is in scope, the compiler folds them. Skip the |
| 700 | # divide/modulo runtime rules for this line. |
| 701 | divisors = _DIV_OR_MOD_DIVISOR_RE.findall(scrubbed) |
| 702 | all_compile_time = bool(divisors) and all( |
| 703 | d in constexpr_names for d in divisors |
| 704 | ) |
| 705 | for pat, kind, msg in patterns: |
| 706 | if is_recip_cache and kind == "perf-divide-runtime-divisor": |
| 707 | continue |
| 708 | if all_compile_time and kind in ( |
| 709 | "perf-divide-runtime-divisor", |
| 710 | "perf-modulo-runtime-divisor", |
| 711 | ): |
| 712 | continue |
| 713 | if pat.search(scrubbed): |
| 714 | findings.append(Finding(abs_line, kind, msg)) |
| 715 | break |
| 716 | return findings |
| 717 | |
| 718 | |
| 719 | def _recursion_findings(func_node, fname: str, body, src: bytes, fenced) -> list: |
no test coverage detected