-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #6394 - nico-abram:unsafe_sizeof_count_copies, r=ebroto
Add lint size_of_in_element_count Fixes #6381 changelog: Add lint to check for using size_of::<T> or size_of_val::<T> in the count parameter to ptr::copy or ptr::copy_nonoverlapping, which take a count of Ts (And not a count of bytes) - \[X] Followed [lint naming conventions][lint_naming] - \[X] Added passing UI tests (including committed `.stderr` file) - \[ ] `cargo test` passes locally - \[X] Executed `cargo dev update_lints` - \[X] Added lint documentation - \[X] Run `cargo dev fmt` [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints Running `cargo test` locally fails with this error: ``` running 1 test test fmt ... FAILED failures: ---- fmt stdout ---- status: exit code: 1 stdout: stderr: error: unable to unlink old fallback exe error: caused by: Access is denied. (os error 5) thread 'fmt' panicked at 'Formatting check failed. Run `cargo dev fmt` to update formatting.', tests\fmt.rs:32:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: fmt test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out ``` But I did run `cargo dev fmt`
- Loading branch information
Showing
6 changed files
with
418 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
LINT_MSG, | ||
None, | ||
HELP_MSG | ||
); | ||
} | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) }; | ||
} |
Oops, something went wrong.