-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
drop_forget_ref.rs
153 lines (146 loc) · 5.95 KB
/
drop_forget_ref.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_must_use_func_call;
use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item};
use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use std::borrow::Cow;
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `std::mem::drop` with a value that does not implement `Drop`.
///
/// ### Why is this bad?
/// Calling `std::mem::drop` is no different than dropping such a type. A different value may
/// have been intended.
///
/// ### Example
/// ```no_run
/// struct Foo;
/// let x = Foo;
/// std::mem::drop(x);
/// ```
#[clippy::version = "1.62.0"]
pub DROP_NON_DROP,
suspicious,
"call to `std::mem::drop` with a value which does not implement `Drop`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `std::mem::forget` with a value that does not implement `Drop`.
///
/// ### Why is this bad?
/// Calling `std::mem::forget` is no different than dropping such a type. A different value may
/// have been intended.
///
/// ### Example
/// ```no_run
/// struct Foo;
/// let x = Foo;
/// std::mem::forget(x);
/// ```
#[clippy::version = "1.62.0"]
pub FORGET_NON_DROP,
suspicious,
"call to `std::mem::forget` with a value which does not implement `Drop`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `std::mem::forget(t)` where `t` is
/// `Drop` or has a field that implements `Drop`.
///
/// ### Why restrict this?
/// `std::mem::forget(t)` prevents `t` from running its destructor, possibly causing leaks.
/// It is not possible to detect all means of creating leaks, but it may be desirable to
/// prohibit the simple ones.
///
/// ### Example
/// ```no_run
/// # use std::mem;
/// # use std::rc::Rc;
/// mem::forget(Rc::new(55))
/// ```
#[clippy::version = "pre 1.29.0"]
pub MEM_FORGET,
restriction,
"`mem::forget` usage on `Drop` types, likely to cause memory leaks"
}
const DROP_NON_DROP_SUMMARY: &str = "call to `std::mem::drop` with a value that does not implement `Drop`. \
Dropping such a type only extends its contained lifetimes";
const FORGET_NON_DROP_SUMMARY: &str = "call to `std::mem::forget` with a value that does not implement `Drop`. \
Forgetting such a type is the same as dropping it";
declare_lint_pass!(DropForgetRef => [
DROP_NON_DROP,
FORGET_NON_DROP,
MEM_FORGET,
]);
impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Call(path, [arg]) = expr.kind
&& let ExprKind::Path(ref qpath) = path.kind
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
&& let Some(fn_name) = cx.tcx.get_diagnostic_name(def_id)
{
let arg_ty = cx.typeck_results().expr_ty(arg);
let is_copy = is_copy(cx, arg_ty);
let drop_is_single_call_in_arm = is_single_call_in_arm(cx, arg, expr);
let (lint, msg, note_span) = match fn_name {
// early return for uplifted lints: dropping_references, dropping_copy_types, forgetting_references,
// forgetting_copy_types
sym::mem_drop if arg_ty.is_ref() && !drop_is_single_call_in_arm => return,
sym::mem_forget if arg_ty.is_ref() => return,
sym::mem_drop if is_copy && !drop_is_single_call_in_arm => return,
sym::mem_forget if is_copy => return,
sym::mem_drop if is_type_lang_item(cx, arg_ty, LangItem::ManuallyDrop) => return,
sym::mem_drop
if !(arg_ty.needs_drop(cx.tcx, cx.typing_env())
|| is_must_use_func_call(cx, arg)
|| is_must_use_ty(cx, arg_ty)
|| drop_is_single_call_in_arm) =>
{
(DROP_NON_DROP, DROP_NON_DROP_SUMMARY.into(), Some(arg.span))
},
sym::mem_forget => {
if arg_ty.needs_drop(cx.tcx, cx.typing_env()) {
(
MEM_FORGET,
Cow::Owned(format!(
"usage of `mem::forget` on {}",
if arg_ty.ty_adt_def().is_some_and(|def| def.has_dtor(cx.tcx)) {
"`Drop` type"
} else {
"type with `Drop` fields"
}
)),
None,
)
} else {
(FORGET_NON_DROP, FORGET_NON_DROP_SUMMARY.into(), Some(arg.span))
}
},
_ => return,
};
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
let note = format!("argument has type `{arg_ty}`");
if let Some(span) = note_span {
diag.span_note(span, note);
} else {
diag.note(note);
}
});
}
}
}
// dropping returned value of a function like in the following snippet is considered idiomatic, see
// #9482 for examples match <var> {
// <pat> => drop(fn_with_side_effect_and_returning_some_value()),
// ..
// }
fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool {
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) {
if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) {
return body.hir_id == drop_expr.hir_id;
}
}
false
}