wowlab_tidy/languages/rust/rules/correctness/
unchecked_indexing.rs1use 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});