Skip to content

fix(rules): expect_in_hot_path must not fire inside one-time initialisers - #659

Merged
hyperpolymath merged 2 commits into
mainfrom
fix/expect-in-hot-path-precision
Jul 29, 2026
Merged

fix(rules): expect_in_hot_path must not fire inside one-time initialisers#659
hyperpolymath merged 2 commits into
mainfrom
fix/expect-in-hot-path-precision

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

The rule is named "in hot path" but had no concept of one — pattern: ~r/\.expect\(/, a bare substring match over the whole file. So it fires on:

static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(..).expect(..));

which compiles exactly once on first access.

Measured on gitbot-fleet, 2026-07-29: of 28 expect_in_hot_path findings, 11 were this shape. The remediation those findings invite — "cache it with once_cell" — describes code that already exists, so the finding could never be cleared by following its own advice. That is worse than a miss: it spends reviewer trust, and it is the kind of noise that drives people to baseline a whole gate.

Adds an opt-in context: :runtime_path field. Rules carrying it match against a copy with lazy-initialiser bodies elided; every other rule is untouched. Follows the existing strip_inline_test_blocks/2 precedent rather than inventing a second mechanism.

Verified against the real files, both directions:

file before after
deps.rs — every expect inside LazyLock flagged []
contrast.rs — genuine per-call Regex::new in a fn flagged 6 occurrences still detected

The rule keeps its teeth and loses its false alarms.

Follow-on from #658. Two sibling imprecisions found in the same triage and not fixed here: actions_expression_injection fires when untrusted context is correctly bound via env: and never interpolated into run:; secret_action_without_presence_gate fires when a presence gate is already present.

🤖 Generated with Claude Code

…sers

The rule is named 'in hot path' but had no concept of one:

    pattern: ~r/\.expect\(/

a bare substring match over the whole file. It therefore fires on

    static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(..).expect(..));

which compiles exactly once on first access.

Measured on gitbot-fleet 2026-07-29: of 28 expect_in_hot_path findings, 11 were
this shape. The remediation those findings invite -- 'cache it with once_cell' --
describes code that already exists, so the finding could never be cleared by
following its own advice. That is worse than a miss: it spends reviewer trust.

Adds an opt-in  field. Rules carrying it are matched
against a copy with lazy-initialiser bodies elided; every other rule is
unaffected. This follows the existing strip_inline_test_blocks/2 precedent
rather than inventing a second mechanism.

Verified against the real files, both directions:
  deps.rs      (all expects inside LazyLock)  -> []        (was: flagged)
  contrast.rs  (per-call Regex::new in a fn)  -> 6 occurrences still detected

So the rule keeps its teeth and loses its false alarms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread lib/rules/code_safety.ex
Comment thread lib/rules/code_safety.ex
@gitar-bot

gitar-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

Note

Automatic reviews are paused because your trial's included automatic processing has been used for this period. Upgrade now, or comment "Gitar review" to run a review anytime.
Learn more

Code Review ✅ Approved 2 resolved / 2 findings

Refines the expect_in_hot_path rule to ignore one-time initialisers using a runtime_path context. However, the regex-based parenthesis balancer fails on paren characters inside string literals, and automated test coverage for the new feature is missing.

✅ 2 resolved
Edge Case: Paren chars in string literals defeat lazy-initialiser stripping

📄 lib/rules/code_safety.ex:820-826
strip_lazy_initialisers relies on a regex to balance parentheses, but a regex cannot track string/char literal context. When the closure body contains a literal paren — extremely common in the motivating case, e.g. LazyLock::new(|| Regex::new(r"\)").expect(..)) — the ) inside r"\)" is mis-paired with Regex::new(, so the match ends early and elides only ...Regex::new(r"\)"), leaving .expect(..) in the scanned content. The expect_in_hot_path false positive this PR aims to remove is then reintroduced for exactly the regex-heavy initialisers it targets. Additionally, the pattern only balances up to two nested paren levels, so bodies like Regex::new(r"((a))") fail to strip at all. Consider scanning from the ::new(|| opener in Elixir with a manual balanced-paren walk that skips "..."/r"..."/'...' literals, rather than a fixed-depth regex.

Quality: No tests for strip_lazy_initialisers / runtime_path context

📄 lib/rules/code_safety.ex:782-796
The new strip_lazy_initialisers behavior and the context: :runtime_path scanning path have no automated coverage in test/code_safety_test.exs (no reference to Lazy/OnceLock/runtime_path). Given the subtlety of the paren-balancing regex, add fixtures for both directions: an expect inside LazyLock::new(|| ...) should not be flagged, and a genuine per-call Regex::new(..).expect(..) in a normal fn should still be flagged — plus a case where the regex literal contains a paren.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@hyperpolymath
hyperpolymath merged commit c0bf6e0 into main Jul 29, 2026
61 of 73 checks passed
@hyperpolymath
hyperpolymath deleted the fix/expect-in-hot-path-precision branch July 29, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant