Skip to content

Commit e8703a0

Browse files
committed
Auto merge of #10098 - lukaslueg:size_of_ref, r=Jarcho
Add size_of_ref lint This addresses #9995, which is likely raising a valid point about `std::mem::size_of_val()`: It's [very easy to use double-references as the argument](apache/datafusion#4371 (comment)), which the function will happily accept and give back the size of _the reference_, not the size of the value _behind_ the reference. In the worst case, if the value matches the programmer's expectation, this seems to work, while in fact, everything will go horribly wrong e.g. on a different platform. The size of a `&T` is independent of what `T` is, and people might want to use `std::mem::size_of_val()` to actually get the size of _any_ reference (e.g. via `&&()`). I would rather suggest that this is always bad behavior, though ([instead](https://doc.rust-lang.org/reference/type-layout.html#pointers-and-references-layout), [and](https://doc.rust-lang.org/stable/std/primitive.usize.html#associatedconstant.BITS)). I, therefore, put this lint into `correctness`. Since the problem is usually easily fixed by removing extra `&`, I went light on suggesting code. --- changelog: New lint: [`size_of_ref`] [#10098](#10098) <!-- changelog_checked -->
2 parents e2a687d + d7b9e19 commit e8703a0

File tree

6 files changed

+131
-0
lines changed

6 files changed

+131
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4547,6 +4547,7 @@ Released 2018-09-13
45474547
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
45484548
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
45494549
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
4550+
[`size_of_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_ref
45504551
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
45514552
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
45524553
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
537537
crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO,
538538
crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO,
539539
crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO,
540+
crate::size_of_ref::SIZE_OF_REF_INFO,
540541
crate::slow_vector_initialization::SLOW_VECTOR_INITIALIZATION_INFO,
541542
crate::std_instead_of_core::ALLOC_INSTEAD_OF_CORE_INFO,
542543
crate::std_instead_of_core::STD_INSTEAD_OF_ALLOC_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ mod shadow;
265265
mod single_char_lifetime_names;
266266
mod single_component_path_imports;
267267
mod size_of_in_element_count;
268+
mod size_of_ref;
268269
mod slow_vector_initialization;
269270
mod std_instead_of_core;
270271
mod strings;
@@ -906,6 +907,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
906907
store.register_late_pass(|_| Box::new(semicolon_block::SemicolonBlock));
907908
store.register_late_pass(|_| Box::new(fn_null_check::FnNullCheck));
908909
store.register_late_pass(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse));
910+
store.register_late_pass(|_| Box::new(size_of_ref::SizeOfRef));
909911
// add lints here, do not remove this comment, it's used in `new_lint`
910912
}
911913

clippy_lints/src/size_of_ref.rs

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use clippy_utils::{diagnostics::span_lint_and_help, path_def_id, ty::peel_mid_ty_refs};
2+
use rustc_hir::{Expr, ExprKind};
3+
use rustc_lint::{LateContext, LateLintPass};
4+
use rustc_session::{declare_lint_pass, declare_tool_lint};
5+
use rustc_span::sym;
6+
7+
declare_clippy_lint! {
8+
/// ### What it does
9+
///
10+
/// Checks for calls to `std::mem::size_of_val()` where the argument is
11+
/// a reference to a reference.
12+
///
13+
/// ### Why is this bad?
14+
///
15+
/// Calling `size_of_val()` with a reference to a reference as the argument
16+
/// yields the size of the reference-type, not the size of the value behind
17+
/// the reference.
18+
///
19+
/// ### Example
20+
/// ```rust
21+
/// struct Foo {
22+
/// buffer: [u8],
23+
/// }
24+
///
25+
/// impl Foo {
26+
/// fn size(&self) -> usize {
27+
/// // Note that `&self` as an argument is a `&&Foo`: Because `self`
28+
/// // is already a reference, `&self` is a double-reference.
29+
/// // The return value of `size_of_val()` therefor is the
30+
/// // size of the reference-type, not the size of `self`.
31+
/// std::mem::size_of_val(&self)
32+
/// }
33+
/// }
34+
/// ```
35+
/// Use instead:
36+
/// ```rust
37+
/// struct Foo {
38+
/// buffer: [u8],
39+
/// }
40+
///
41+
/// impl Foo {
42+
/// fn size(&self) -> usize {
43+
/// // Correct
44+
/// std::mem::size_of_val(self)
45+
/// }
46+
/// }
47+
/// ```
48+
#[clippy::version = "1.67.0"]
49+
pub SIZE_OF_REF,
50+
suspicious,
51+
"Argument to `std::mem::size_of_val()` is a double-reference, which is almost certainly unintended"
52+
}
53+
declare_lint_pass!(SizeOfRef => [SIZE_OF_REF]);
54+
55+
impl LateLintPass<'_> for SizeOfRef {
56+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
57+
if let ExprKind::Call(path, [arg]) = expr.kind
58+
&& let Some(def_id) = path_def_id(cx, path)
59+
&& cx.tcx.is_diagnostic_item(sym::mem_size_of_val, def_id)
60+
&& let arg_ty = cx.typeck_results().expr_ty(arg)
61+
&& peel_mid_ty_refs(arg_ty).1 > 1
62+
{
63+
span_lint_and_help(
64+
cx,
65+
SIZE_OF_REF,
66+
expr.span,
67+
"argument to `std::mem::size_of_val()` is a reference to a reference",
68+
None,
69+
"dereference the argument to `std::mem::size_of_val()` to get the size of the value instead of the size of the reference-type",
70+
);
71+
}
72+
}
73+
}

tests/ui/size_of_ref.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#![allow(unused)]
2+
#![warn(clippy::size_of_ref)]
3+
4+
use std::mem::size_of_val;
5+
6+
fn main() {
7+
let x = 5;
8+
let y = &x;
9+
10+
size_of_val(&x); // no lint
11+
size_of_val(y); // no lint
12+
13+
size_of_val(&&x);
14+
size_of_val(&y);
15+
}
16+
17+
struct S {
18+
field: u32,
19+
data: Vec<u8>,
20+
}
21+
22+
impl S {
23+
/// Get size of object including `self`, in bytes.
24+
pub fn size(&self) -> usize {
25+
std::mem::size_of_val(&self) + (std::mem::size_of::<u8>() * self.data.capacity())
26+
}
27+
}

tests/ui/size_of_ref.stderr

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
error: argument to `std::mem::size_of_val()` is a reference to a reference
2+
--> $DIR/size_of_ref.rs:13:5
3+
|
4+
LL | size_of_val(&&x);
5+
| ^^^^^^^^^^^^^^^^
6+
|
7+
= help: dereference the argument to `std::mem::size_of_val()` to get the size of the value instead of the size of the reference-type
8+
= note: `-D clippy::size-of-ref` implied by `-D warnings`
9+
10+
error: argument to `std::mem::size_of_val()` is a reference to a reference
11+
--> $DIR/size_of_ref.rs:14:5
12+
|
13+
LL | size_of_val(&y);
14+
| ^^^^^^^^^^^^^^^
15+
|
16+
= help: dereference the argument to `std::mem::size_of_val()` to get the size of the value instead of the size of the reference-type
17+
18+
error: argument to `std::mem::size_of_val()` is a reference to a reference
19+
--> $DIR/size_of_ref.rs:25:9
20+
|
21+
LL | std::mem::size_of_val(&self) + (std::mem::size_of::<u8>() * self.data.capacity())
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23+
|
24+
= help: dereference the argument to `std::mem::size_of_val()` to get the size of the value instead of the size of the reference-type
25+
26+
error: aborting due to 3 previous errors
27+

0 commit comments

Comments
 (0)