| //! Lint on use of `size_of` or `size_of_val` of T in an expression |
| //! expecting a count of T |
| |
| use clippy_utils::diagnostics::span_lint_and_help; |
| use if_chain::if_chain; |
| use rustc_hir::{BinOpKind, Expr, ExprKind}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_middle::ty::{self, Ty, TypeAndMut}; |
| use rustc_session::{declare_lint_pass, declare_tool_lint}; |
| use rustc_span::sym; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Detects expressions where |
| /// `size_of::<T>` or `size_of_val::<T>` is used as a |
| /// count of elements of type `T` |
| /// |
| /// ### Why is this bad? |
| /// These functions expect a count |
| /// of `T` and not a number of bytes |
| /// |
| /// ### Example |
| /// ```rust,no_run |
| /// # use std::ptr::copy_nonoverlapping; |
| /// # use std::mem::size_of; |
| /// const SIZE: usize = 128; |
| /// let x = [2u8; SIZE]; |
| /// let mut y = [2u8; SIZE]; |
| /// unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>() * SIZE) }; |
| /// ``` |
| #[clippy::version = "1.50.0"] |
| pub SIZE_OF_IN_ELEMENT_COUNT, |
| correctness, |
| "using `size_of::<T>` or `size_of_val::<T>` where a count of elements of `T` is expected" |
| } |
| |
| declare_lint_pass!(SizeOfInElementCount => [SIZE_OF_IN_ELEMENT_COUNT]); |
| |
| fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, inverted: bool) -> Option<Ty<'tcx>> { |
| match expr.kind { |
| ExprKind::Call(count_func, _func_args) => { |
| if_chain! { |
| if !inverted; |
| if let ExprKind::Path(ref count_func_qpath) = count_func.kind; |
| if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id(); |
| if matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::mem_size_of | sym::mem_size_of_val)); |
| then { |
| cx.typeck_results().node_args(count_func.hir_id).types().next() |
| } else { |
| None |
| } |
| } |
| }, |
| ExprKind::Binary(op, left, right) if BinOpKind::Mul == op.node => { |
| get_size_of_ty(cx, left, inverted).or_else(|| get_size_of_ty(cx, right, inverted)) |
| }, |
| ExprKind::Binary(op, left, right) if BinOpKind::Div == op.node => { |
| get_size_of_ty(cx, left, inverted).or_else(|| get_size_of_ty(cx, right, !inverted)) |
| }, |
| ExprKind::Cast(expr, _) => get_size_of_ty(cx, expr, inverted), |
| _ => None, |
| } |
| } |
| |
| fn get_pointee_ty_and_count_expr<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'_>, |
| ) -> Option<(Ty<'tcx>, &'tcx Expr<'tcx>)> { |
| const METHODS: [&str; 11] = [ |
| "write_bytes", |
| "copy_to", |
| "copy_from", |
| "copy_to_nonoverlapping", |
| "copy_from_nonoverlapping", |
| "add", |
| "wrapping_add", |
| "sub", |
| "wrapping_sub", |
| "offset", |
| "wrapping_offset", |
| ]; |
| |
| if_chain! { |
| // Find calls to ptr::{copy, copy_nonoverlapping} |
| // and ptr::{swap_nonoverlapping, write_bytes}, |
| if let ExprKind::Call(func, [.., count]) = expr.kind; |
| if let ExprKind::Path(ref func_qpath) = func.kind; |
| if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); |
| if matches!(cx.tcx.get_diagnostic_name(def_id), Some( |
| sym::ptr_copy |
| | sym::ptr_copy_nonoverlapping |
| | sym::ptr_slice_from_raw_parts |
| | sym::ptr_slice_from_raw_parts_mut |
| | sym::ptr_swap_nonoverlapping |
| | sym::ptr_write_bytes |
| | sym::slice_from_raw_parts |
| | sym::slice_from_raw_parts_mut |
| )); |
| |
| // Get the pointee type |
| if let Some(pointee_ty) = cx.typeck_results().node_args(func.hir_id).types().next(); |
| then { |
| return Some((pointee_ty, count)); |
| } |
| }; |
| if_chain! { |
| // Find calls to copy_{from,to}{,_nonoverlapping} and write_bytes methods |
| if let ExprKind::MethodCall(method_path, ptr_self, [.., count], _) = expr.kind; |
| let method_ident = method_path.ident.as_str(); |
| if METHODS.iter().any(|m| *m == method_ident); |
| |
| // Get the pointee type |
| if let ty::RawPtr(TypeAndMut { ty: pointee_ty, .. }) = |
| cx.typeck_results().expr_ty(ptr_self).kind(); |
| then { |
| return Some((*pointee_ty, count)); |
| } |
| }; |
| None |
| } |
| |
| impl<'tcx> LateLintPass<'tcx> for SizeOfInElementCount { |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
| const HELP_MSG: &str = "use a count of elements instead of a count of bytes\ |
| , it already gets multiplied by the size of the type"; |
| |
| const LINT_MSG: &str = "found a count of bytes \ |
| instead of a count of elements of `T`"; |
| |
| if_chain! { |
| // Find calls to functions with an element count parameter and get |
| // the pointee type and count parameter expression |
| if let Some((pointee_ty, count_expr)) = get_pointee_ty_and_count_expr(cx, expr); |
| |
| // Find a size_of call in the count parameter expression and |
| // check that it's the same type |
| if let Some(ty_used_for_size_of) = get_size_of_ty(cx, count_expr, false); |
| if pointee_ty == ty_used_for_size_of; |
| then { |
| span_lint_and_help( |
| cx, |
| SIZE_OF_IN_ELEMENT_COUNT, |
| count_expr.span, |
| LINT_MSG, |
| None, |
| HELP_MSG |
| ); |
| } |
| }; |
| } |
| } |