error_flag_lint.rs — new abstract stack simulation pass that tracks…#388
error_flag_lint.rs — new abstract stack simulation pass that tracks…#388
Conversation
…Bool values from fallible operations through: - Stack ops: swap, rot, over, dup, nip, tuck, 2dup - Aux stack: >aux / aux> - Combinators: dip, keep, bi (conservatively clear tracked flags) - Control flow: if (marks as checked), match, branch joining Integration: - seqc lint — runs after resource analysis (Phase 2b) - LSP — runs after pattern linting, shows inline diagnostics in editor Deduplication: Skips drops within 2 lines of the fallible operation (handled by pattern linter with better precision). Only fires for genuinely non-adjacent drops — the cases patterns can't catch. Test coverage: 7 unit tests covering adjacent drop (suppressed), non-adjacent nip, if-checking, aux round-trip, division, and escape analysis. False positives on examples/: zero.
PR Review:
|
| Test | Path Covered |
|---|---|
test_adjacent_drop_not_flagged |
Suppression within 2-line window |
test_non_adjacent_drop_flagged |
Core warning path via nip |
test_checked_by_if |
Statement::If Bool consumption |
test_aux_round_trip_drop |
>aux/aux> tracking |
test_division_checked |
i./ with if-check |
test_nip_drops_flag |
nip keeps Bool on top (no warn) |
test_swap_nip_drops_flag |
swap + nip drops Bool |
Missing test cases worth adding:
matcharm handling (theVariantvsVariantWithBindingspath)dip/keep/bicombinator behavior (flags cleared conservatively)callwith an anonymous quotationseq:allow(unchecked-error-flag)suppression attribute- Multiple fallible operations in sequence (does the second flag suppress the first?)
2drop/3droppaths
Security Considerations
No direct security concerns. The analyzer operates purely on AST data; there is no I/O, no unsafe code, and no external input parsing.
The fallible_op_info table covers crypto operations (crypto.aes-gcm-decrypt, etc.), which is good — silently ignored decryption failures can be security-critical. Ensuring those are flagged is a meaningful security-adjacent benefit.
Minor / Nits
- The
analyze_wordmethod callsself.diagnostics.clear()and thenstd::mem::take(&mut self.diagnostics). SinceErrorFlagAnalyzerholdsdiagnosticsas a field but the public API isanalyze_programreturning aVec, consider makingdiagnosticsa local variable inanalyze_wordinstead of member state — it would remove the need forclear()andtake()and make the borrowing cleaner. emit_warningcallsfallible_op_info(&flag.operation)again to get the description string. SinceErrorFlagalready storesoperation, andfallible_op_infodoes a fullmatchscan on every call, it would be slightly cleaner to store the description inErrorFlagat creation time (avoiding the re-lookup).- The
LintDiagnosticfieldsstart_index: 0andend_index: 0are set to zero. If downstream consumers use these for source-range highlighting, they'll get incorrect positions. Theline/end_linefields are populated, but the index fields may need attention depending on how the LSP and CLI renderers use them.
Summary
This is a solid, well-structured addition to the lint pipeline. The main actionable items before merge are:
- Fix the stale/confusing doc comment in the module-level docstring about
nip. - Reconcile
fallible_op_infoandconsume_inputsinto a single table to eliminate the sync drift and dead entries. - Clarify or implement the
Variantpattern binding push in thematcharm handler. - Consider removing
ErrorFlag::idif it has no current use.
The deduplication boundary (within-2-lines → pattern linter, beyond → this pass) is a smart design choice. The zero false positives on the examples/ directory is a good validation signal.
Reviewed by Claude (claude-sonnet-4-6)
┌─────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Issue │ Fix │ ├─────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Stale nip doc comment │ Rewrote to accurate examples (swap nip, aux round-trip) │ ├─────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ fallible_op_info / consume_inputs sync │ Merged into single FallibleOpInfo struct with inputs, values_before_bool, description. Eliminated │ │ drift │ consume_inputs entirely │ ├─────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Dead entries in old consume_inputs │ Gone — single table now, no dead code │ ├─────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ErrorFlag::id unused │ Removed field entirely │ ├─────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ emit_warning re-lookup of description │ Description now stored in ErrorFlag at creation time │ ├─────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Variant pattern binding gap │ Documented as known limitation (matches resource_lint behavior) │ └─────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 970 tests pass, zero warnings, zero false positives on examples.
PR Review:
|
┌────────────────────────────────────────┬───────────────────────────────────────────────────────────────────┐ │ Issue │ Fix │ ├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ test_nip_drops_flag contradictory name │ Renamed to test_nip_preserves_flag_on_top, cleaned comment │ ├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ Redundant let popped / let _ = popped │ Simplified to state.pop() │ ├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ cond clears entire stack │ Now pops one value (count arg) instead of clearing; added comment │ ├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ Span-missing suppression │ Added comment documenting the edge case │ ├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ Missing allowed_lints test │ Added test_allow_suppresses_warning │ ├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ Missing multiple-flags test │ Added test_multiple_flags_both_dropped (two flags, both warned) │ ├────────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤ │ Missing dip false-positive test │ Added test_dip_clears_flags_no_false_positive │ └────────────────────────────────────────┴───────────────────────────────────────────────────────────────────┘ 10 tests now (was 7), 973 total.
Code Review:
|
… Bool values from fallible operations through:
Integration:
Deduplication: Skips drops within 2 lines of the fallible operation (handled by pattern linter with better precision). Only fires for genuinely
non-adjacent drops — the cases patterns can't catch.
Test coverage: 7 unit tests covering adjacent drop (suppressed), non-adjacent nip, if-checking, aux round-trip, division, and escape analysis.
False positives on examples/: zero.