Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lint size_of_in_element_count #6394

Merged
merged 6 commits into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,7 @@ Released 2018-09-13
[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
Expand Down
5 changes: 5 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ mod self_assignment;
mod serde_api;
mod shadow;
mod single_component_path_imports;
mod size_of_in_element_count;
mod slow_vector_initialization;
mod stable_sort_primitive;
mod strings;
Expand Down Expand Up @@ -847,6 +848,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&shadow::SHADOW_SAME,
&shadow::SHADOW_UNRELATED,
&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
&size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT,
&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
&stable_sort_primitive::STABLE_SORT_PRIMITIVE,
&strings::STRING_ADD,
Expand Down Expand Up @@ -998,6 +1000,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || box matches::Matches::new(msrv));
store.register_early_pass(move || box manual_non_exhaustive::ManualNonExhaustive::new(msrv));
store.register_late_pass(move || box manual_strip::ManualStrip::new(msrv));
store.register_late_pass(|| box size_of_in_element_count::SizeOfInElementCount);
store.register_late_pass(|| box map_clone::MapClone);
store.register_late_pass(|| box map_err_ignore::MapErrIgnore);
store.register_late_pass(|| box shadow::Shadow);
Expand Down Expand Up @@ -1559,6 +1562,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&self_assignment::SELF_ASSIGNMENT),
LintId::of(&serde_api::SERDE_API_MISUSE),
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(&size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
LintId::of(&stable_sort_primitive::STABLE_SORT_PRIMITIVE),
LintId::of(&strings::STRING_FROM_UTF8_AS_BYTES),
Expand Down Expand Up @@ -1868,6 +1872,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&regex::INVALID_REGEX),
LintId::of(&self_assignment::SELF_ASSIGNMENT),
LintId::of(&serde_api::SERDE_API_MISUSE),
LintId::of(&size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
LintId::of(&swap::ALMOST_SWAPPED),
Expand Down
146 changes: 146 additions & 0 deletions clippy_lints/src/size_of_in_element_count.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//! Lint on use of `size_of` or `size_of_val` of T in an expression
//! expecting a count of T

use crate::utils::{match_def_path, paths, span_lint_and_help};
use if_chain::if_chain;
use rustc_hir::BinOpKind;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty, TyS, TypeAndMut};
use rustc_session::{declare_lint_pass, declare_tool_lint};

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
///
/// **Known problems:** None.
///
/// **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) };
/// ```
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(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Ty<'tcx>> {
match expr.kind {
ExprKind::Call(count_func, _func_args) => {
if_chain! {
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 match_def_path(cx, def_id, &paths::MEM_SIZE_OF)
|| match_def_path(cx, def_id, &paths::MEM_SIZE_OF_VAL);
then {
cx.typeck_results().node_substs(count_func.hir_id).types().next()
} else {
None
}
}
},
ExprKind::Binary(op, left, right) if BinOpKind::Mul == op.node || BinOpKind::Div == op.node => {
get_size_of_ty(cx, left).or_else(|| get_size_of_ty(cx, right))
},
ExprKind::Cast(expr, _) => get_size_of_ty(cx, expr),
_ => None,
}
}

fn get_pointee_ty_and_count_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(Ty<'tcx>, &'tcx Expr<'tcx>)> {
const FUNCTIONS: [&[&str]; 8] = [
&paths::COPY_NONOVERLAPPING,
&paths::COPY,
&paths::WRITE_BYTES,
&paths::PTR_SWAP_NONOVERLAPPING,
&paths::PTR_SLICE_FROM_RAW_PARTS,
&paths::PTR_SLICE_FROM_RAW_PARTS_MUT,
&paths::SLICE_FROM_RAW_PARTS,
&paths::SLICE_FROM_RAW_PARTS_MUT,
];
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 FUNCTIONS.iter().any(|func_path| match_def_path(cx, def_id, func_path));

// Get the pointee type
if let Some(pointee_ty) = cx.typeck_results().node_substs(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);
if TyS::same_type(pointee_ty, ty_used_for_size_of);
then {
span_lint_and_help(
cx,
SIZE_OF_IN_ELEMENT_COUNT,
count_expr.span,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

LINT_MSG,
None,
HELP_MSG
);
}
};
}
}
10 changes: 10 additions & 0 deletions clippy_lints/src/utils/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub const CLONE_TRAIT: [&str; 3] = ["core", "clone", "Clone"];
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
pub const COPY: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
pub const COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy"];
pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
Expand Down Expand Up @@ -73,6 +75,8 @@ pub const MEM_MANUALLY_DROP: [&str; 4] = ["core", "mem", "manually_drop", "Manua
pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"];
pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"];
pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"];
pub const MEM_SIZE_OF: [&str; 3] = ["core", "mem", "size_of"];
pub const MEM_SIZE_OF_VAL: [&str; 3] = ["core", "mem", "size_of_val"];
pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
Expand Down Expand Up @@ -100,6 +104,9 @@ pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"];
pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"];
pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
pub const RC: [&str; 3] = ["alloc", "rc", "Rc"];
Expand All @@ -121,6 +128,8 @@ pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGu
pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
Expand Down Expand Up @@ -154,3 +163,4 @@ pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
pub const WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
61 changes: 61 additions & 0 deletions tests/ui/size_of_in_element_count.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#![warn(clippy::size_of_in_element_count)]
#![allow(clippy::ptr_offset_with_cast)]

use std::mem::{size_of, size_of_val};
use std::ptr::{
copy, copy_nonoverlapping, slice_from_raw_parts, slice_from_raw_parts_mut, swap_nonoverlapping, write_bytes,
};
use std::slice::{from_raw_parts, from_raw_parts_mut};

fn main() {
const SIZE: usize = 128;
const HALF_SIZE: usize = SIZE / 2;
const DOUBLE_SIZE: usize = SIZE * 2;
let mut x = [2u8; SIZE];
let mut y = [2u8; SIZE];

// Count is size_of (Should trigger the lint)
unsafe { copy_nonoverlapping::<u8>(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>()) };
unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) };

unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::<u8>()) };
unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::<u8>()) };
unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::<u8>()) };
unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::<u8>()) };

unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>()) };
unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) };

unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::<u8>() * SIZE) };
unsafe { write_bytes(y.as_mut_ptr(), 0u8, size_of::<u8>() * SIZE) };

unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::<u8>() * SIZE) };

slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::<u8>() * SIZE);
slice_from_raw_parts(y.as_ptr(), size_of::<u8>() * SIZE);

unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::<u8>() * SIZE) };
unsafe { from_raw_parts(y.as_ptr(), size_of::<u8>() * SIZE) };

unsafe { y.as_mut_ptr().sub(size_of::<u8>()) };
y.as_ptr().wrapping_sub(size_of::<u8>());
unsafe { y.as_ptr().add(size_of::<u8>()) };
y.as_mut_ptr().wrapping_add(size_of::<u8>());
unsafe { y.as_ptr().offset(size_of::<u8>() as isize) };
y.as_mut_ptr().wrapping_offset(size_of::<u8>() as isize);

// Count expression involving multiplication of size_of (Should trigger the lint)
unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>() * SIZE) };

// Count expression involving nested multiplications of size_of (Should trigger the lint)
unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), HALF_SIZE * size_of_val(&x[0]) * 2) };

// Count expression involving divisions of size_of (Should trigger the lint)
unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::<u8>() / 2) };

// No size_of calls (Should not trigger the lint)
unsafe { copy(x.as_ptr(), y.as_mut_ptr(), SIZE) };

// Different types for pointee and size_of (Should not trigger the lint)
unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::<u16>() / 2 * SIZE) };
}
Loading