Skip to main content

wowlab_tidy/languages/rust/rules/correctness/
unchecked_indexing.rs

1use ra_ap_syntax::{ast, ast::LiteralKind};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7    Example {
8        label: "variable index",
9        code: "fn f(v: Vec<i32>, i: usize) { let _ = v[i]; }",
10        pass: false,
11    },
12    Example {
13        label: "literal index",
14        code: "fn f(v: Vec<i32>) { let _ = v[0]; }",
15        pass: true,
16    },
17    Example {
18        label: "indexing in test module",
19        code: "#[cfg(test)]\nmod tests {\n    fn f(v: Vec<i32>, i: usize) { let _ = v[i]; }\n}",
20        pass: true,
21    },
22    Example {
23        label: "with BOUNDS comment",
24        code: "fn f(v: Vec<i32>, i: usize) {\n    // BOUNDS: index validated by caller\n    let _ = v[i];\n}",
25        pass: true,
26    },
27];
28
29crate::ast_rule!(
30    unchecked_indexing,
31    "Flag `container[expr]` indexing with non-literal indices.",
32    "Indexing with a variable panics on out-of-bounds. Use .get() or add a // BOUNDS: comment explaining why the index is safe.",
33    Low,
34);
35
36fn check_unchecked_indexing(ctx: &AstCtx<'_>) -> Vec<Violation> {
37    ctx.nodes::<ast::IndexExpr>()
38        .filter(|index| !ctx.is_in_test(index))
39        .filter_map(|index| {
40            let expr = index.index()?;
41
42            if matches!(expr, ast::Expr::Literal(ref literal) if matches!(literal.kind(), LiteralKind::IntNumber(_))) {
43                return None;
44            }
45
46            let line = ctx.line_of(&expr);
47
48            (!crate::infra::helpers::has_preceding_comment(
49                ctx.file.lines,
50                line,
51                &["BOUNDS:"],
52            ))
53            .then(|| {
54                ctx.violation(
55                    &expr,
56                    "unchecked indexing — prefer `.get()` or add `// BOUNDS:` comment",
57                )
58            })
59        })
60        .collect()
61}
62
63crate::tidy_ast_test!(check_unchecked_indexing, {
64    crate::example_tests!(EXAMPLES, check_unchecked_indexing);
65});