]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/uninit_vec.rs
Rollup merge of #89642 - devnexen:macos_getenv_chng, r=m-ou-se
[rust.git] / src / tools / clippy / clippy_lints / src / uninit_vec.rs
1 use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
2 use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
3 use clippy_utils::ty::{is_type_diagnostic_item, is_uninit_value_valid_for_ty};
4 use clippy_utils::{is_lint_allowed, path_to_local_id, peel_hir_expr_while, SpanlessEq};
5 use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind};
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_middle::lint::in_external_macro;
8 use rustc_middle::ty;
9 use rustc_session::{declare_lint_pass, declare_tool_lint};
10 use rustc_span::{sym, Span};
11
12 // TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
13 declare_clippy_lint! {
14     /// ### What it does
15     /// Checks for `set_len()` call that creates `Vec` with uninitialized elements.
16     /// This is commonly caused by calling `set_len()` right after allocating or
17     /// reserving a buffer with `new()`, `default()`, `with_capacity()`, or `reserve()`.
18     ///
19     /// ### Why is this bad?
20     /// It creates a `Vec` with uninitialized data, which leads to
21     /// undefined behavior with most safe operations. Notably, uninitialized
22     /// `Vec<u8>` must not be used with generic `Read`.
23     ///
24     /// Moreover, calling `set_len()` on a `Vec` created with `new()` or `default()`
25     /// creates out-of-bound values that lead to heap memory corruption when used.
26     ///
27     /// ### Known Problems
28     /// This lint only checks directly adjacent statements.
29     ///
30     /// ### Example
31     /// ```rust,ignore
32     /// let mut vec: Vec<u8> = Vec::with_capacity(1000);
33     /// unsafe { vec.set_len(1000); }
34     /// reader.read(&mut vec); // undefined behavior!
35     /// ```
36     ///
37     /// ### How to fix?
38     /// 1. Use an initialized buffer:
39     ///    ```rust,ignore
40     ///    let mut vec: Vec<u8> = vec![0; 1000];
41     ///    reader.read(&mut vec);
42     ///    ```
43     /// 2. Wrap the content in `MaybeUninit`:
44     ///    ```rust,ignore
45     ///    let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
46     ///    vec.set_len(1000);  // `MaybeUninit` can be uninitialized
47     ///    ```
48     /// 3. If you are on nightly, `Vec::spare_capacity_mut()` is available:
49     ///    ```rust,ignore
50     ///    let mut vec: Vec<u8> = Vec::with_capacity(1000);
51     ///    let remaining = vec.spare_capacity_mut();  // `&mut [MaybeUninit<u8>]`
52     ///    // perform initialization with `remaining`
53     ///    vec.set_len(...);  // Safe to call `set_len()` on initialized part
54     ///    ```
55     pub UNINIT_VEC,
56     correctness,
57     "Vec with uninitialized data"
58 }
59
60 declare_lint_pass!(UninitVec => [UNINIT_VEC]);
61
62 // FIXME: update to a visitor-based implementation.
63 // Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368
64 impl<'tcx> LateLintPass<'tcx> for UninitVec {
65     fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
66         if !in_external_macro(cx.tcx.sess, block.span) {
67             for w in block.stmts.windows(2) {
68                 if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind {
69                     handle_uninit_vec_pair(cx, &w[0], expr);
70                 }
71             }
72
73             if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) {
74                 handle_uninit_vec_pair(cx, stmt, expr);
75             }
76         }
77     }
78 }
79
80 fn handle_uninit_vec_pair(
81     cx: &LateContext<'tcx>,
82     maybe_init_or_reserve: &'tcx Stmt<'tcx>,
83     maybe_set_len: &'tcx Expr<'tcx>,
84 ) {
85     if_chain! {
86         if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve);
87         if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len);
88         if vec.location.eq_expr(cx, set_len_self);
89         if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind();
90         if let ty::Adt(_, substs) = vec_ty.kind();
91         // `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()`
92         if !is_lint_allowed(cx, UNINIT_VEC, maybe_set_len.hir_id);
93         then {
94             if vec.has_capacity() {
95                 // with_capacity / reserve -> set_len
96
97                 // Check T of Vec<T>
98                 if !is_uninit_value_valid_for_ty(cx, substs.type_at(0)) {
99                     // FIXME: #7698, false positive of the internal lints
100                     #[allow(clippy::collapsible_span_lint_calls)]
101                     span_lint_and_then(
102                         cx,
103                         UNINIT_VEC,
104                         vec![call_span, maybe_init_or_reserve.span],
105                         "calling `set_len()` immediately after reserving a buffer creates uninitialized values",
106                         |diag| {
107                             diag.help("initialize the buffer or wrap the content in `MaybeUninit`");
108                         },
109                     );
110                 }
111             } else {
112                 // new / default -> set_len
113                 span_lint(
114                     cx,
115                     UNINIT_VEC,
116                     vec![call_span, maybe_init_or_reserve.span],
117                     "calling `set_len()` on empty `Vec` creates out-of-bound values",
118                 );
119             }
120         }
121     }
122 }
123
124 /// The target `Vec` that is initialized or reserved
125 #[derive(Clone, Copy)]
126 struct TargetVec<'tcx> {
127     location: VecLocation<'tcx>,
128     /// `None` if `reserve()`
129     init_kind: Option<VecInitKind>,
130 }
131
132 impl TargetVec<'_> {
133     pub fn has_capacity(self) -> bool {
134         !matches!(self.init_kind, Some(VecInitKind::New | VecInitKind::Default))
135     }
136 }
137
138 #[derive(Clone, Copy)]
139 enum VecLocation<'tcx> {
140     Local(HirId),
141     Expr(&'tcx Expr<'tcx>),
142 }
143
144 impl<'tcx> VecLocation<'tcx> {
145     pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
146         match self {
147             VecLocation::Local(hir_id) => path_to_local_id(expr, hir_id),
148             VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr),
149         }
150     }
151 }
152
153 /// Finds the target location where the result of `Vec` initialization is stored
154 /// or `self` expression for `Vec::reserve()`.
155 fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<TargetVec<'tcx>> {
156     match stmt.kind {
157         StmtKind::Local(local) => {
158             if_chain! {
159                 if let Some(init_expr) = local.init;
160                 if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind;
161                 if let Some(init_kind) = get_vec_init_kind(cx, init_expr);
162                 then {
163                     return Some(TargetVec {
164                         location: VecLocation::Local(hir_id),
165                         init_kind: Some(init_kind),
166                     })
167                 }
168             }
169         },
170         StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
171             ExprKind::Assign(lhs, rhs, _span) => {
172                 if let Some(init_kind) = get_vec_init_kind(cx, rhs) {
173                     return Some(TargetVec {
174                         location: VecLocation::Expr(lhs),
175                         init_kind: Some(init_kind),
176                     });
177                 }
178             },
179             ExprKind::MethodCall(path, _, [self_expr, _], _) if is_reserve(cx, path, self_expr) => {
180                 return Some(TargetVec {
181                     location: VecLocation::Expr(self_expr),
182                     init_kind: None,
183                 });
184             },
185             _ => (),
186         },
187         StmtKind::Item(_) => (),
188     }
189     None
190 }
191
192 fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool {
193     is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec)
194         && path.ident.name.as_str() == "reserve"
195 }
196
197 /// Returns self if the expression is `Vec::set_len()`
198 fn extract_set_len_self(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&'tcx Expr<'tcx>, Span)> {
199     // peel unsafe blocks in `unsafe { vec.set_len() }`
200     let expr = peel_hir_expr_while(expr, |e| {
201         if let ExprKind::Block(block, _) = e.kind {
202             // Extract the first statement/expression
203             match (block.stmts.get(0).map(|stmt| &stmt.kind), block.expr) {
204                 (None, Some(expr)) => Some(expr),
205                 (Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr),
206                 _ => None,
207             }
208         } else {
209             None
210         }
211     });
212     match expr.kind {
213         ExprKind::MethodCall(path, _, [self_expr, _], _) => {
214             let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs();
215             if is_type_diagnostic_item(cx, self_type, sym::Vec) && path.ident.name.as_str() == "set_len" {
216                 Some((self_expr, expr.span))
217             } else {
218                 None
219             }
220         },
221         _ => None,
222     }
223 }