Skip to content

Commit 00b4f28

Browse files
committed
Implement unsafe_derive_deserialize lint
1 parent 6dcc8d5 commit 00b4f28

File tree

9 files changed

+263
-10
lines changed

9 files changed

+263
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,7 @@ Released 2018-09-13
15241524
[`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern
15251525
[`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable
15261526
[`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal
1527+
[`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize
15271528
[`unsafe_removed_from_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_removed_from_name
15281529
[`unsafe_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_vector_initialization
15291530
[`unseparated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#unseparated_literal_suffix

clippy_lints/src/derive.rs

Lines changed: 150 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use crate::utils::paths;
2-
use crate::utils::{is_automatically_derived, is_copy, match_path, span_lint_and_note, span_lint_and_then};
2+
use crate::utils::{
3+
is_automatically_derived, is_copy, match_path, span_lint_and_help, span_lint_and_note, span_lint_and_then,
4+
};
35
use if_chain::if_chain;
4-
use rustc_hir::{Item, ItemKind, TraitRef};
6+
use rustc_hir as hir;
7+
use rustc_hir::def_id::DefId;
8+
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, NestedVisitorMap, Visitor};
59
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_middle::hir::map::Map;
611
use rustc_middle::ty::{self, Ty};
712
use rustc_session::{declare_lint_pass, declare_tool_lint};
813
use rustc_span::source_map::Span;
@@ -62,11 +67,45 @@ declare_clippy_lint! {
6267
"implementing `Clone` explicitly on `Copy` types"
6368
}
6469

65-
declare_lint_pass!(Derive => [EXPL_IMPL_CLONE_ON_COPY, DERIVE_HASH_XOR_EQ]);
70+
declare_clippy_lint! {
71+
/// **What it does:** Checks for deriving `serde::Deserialize` on a type that
72+
/// has methods using `unsafe`.
73+
///
74+
/// **Why is this bad?** Deriving `serde::Deserialize` will create a constructor
75+
/// that may violate invariants hold by another constructor.
76+
///
77+
/// **Known problems:** None.
78+
///
79+
/// **Example:**
80+
///
81+
/// ```rust,ignore
82+
/// use serde::Deserialize;
83+
///
84+
/// #[derive(Deserialize)]
85+
/// pub struct Foo {
86+
/// // ..
87+
/// }
88+
///
89+
/// impl Foo {
90+
/// pub fn new() -> Self {
91+
/// // setup here ..
92+
/// }
93+
///
94+
/// pub unsafe fn parts() -> (&str, &str) {
95+
/// // assumes invariants hold
96+
/// }
97+
/// }
98+
/// ```
99+
pub UNSAFE_DERIVE_DESERIALIZE,
100+
correctness,
101+
"deriving `serde::Deserialize` on a type that has methods using `unsafe`"
102+
}
103+
104+
declare_lint_pass!(Derive => [EXPL_IMPL_CLONE_ON_COPY, DERIVE_HASH_XOR_EQ, UNSAFE_DERIVE_DESERIALIZE]);
66105

67106
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Derive {
68-
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) {
69-
if let ItemKind::Impl {
107+
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) {
108+
if let hir::ItemKind::Impl {
70109
of_trait: Some(ref trait_ref),
71110
..
72111
} = item.kind
@@ -76,7 +115,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Derive {
76115

77116
check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
78117

79-
if !is_automatically_derived {
118+
if is_automatically_derived {
119+
check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
120+
} else {
80121
check_copy_clone(cx, item, trait_ref, ty);
81122
}
82123
}
@@ -87,7 +128,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Derive {
87128
fn check_hash_peq<'a, 'tcx>(
88129
cx: &LateContext<'a, 'tcx>,
89130
span: Span,
90-
trait_ref: &TraitRef<'_>,
131+
trait_ref: &hir::TraitRef<'_>,
91132
ty: Ty<'tcx>,
92133
hash_is_automatically_derived: bool,
93134
) {
@@ -134,7 +175,12 @@ fn check_hash_peq<'a, 'tcx>(
134175
}
135176

136177
/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
137-
fn check_copy_clone<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
178+
fn check_copy_clone<'a, 'tcx>(
179+
cx: &LateContext<'a, 'tcx>,
180+
item: &hir::Item<'_>,
181+
trait_ref: &hir::TraitRef<'_>,
182+
ty: Ty<'tcx>,
183+
) {
138184
if match_path(&trait_ref.path, &paths::CLONE_TRAIT) {
139185
if !is_copy(cx, ty) {
140186
return;
@@ -173,3 +219,99 @@ fn check_copy_clone<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item: &Item<'_>, trait
173219
);
174220
}
175221
}
222+
223+
/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
224+
fn check_unsafe_derive_deserialize<'a, 'tcx>(
225+
cx: &LateContext<'a, 'tcx>,
226+
item: &hir::Item<'_>,
227+
trait_ref: &hir::TraitRef<'_>,
228+
ty: Ty<'tcx>,
229+
) {
230+
fn item_from_def_id<'tcx>(cx: &LateContext<'_, 'tcx>, def_id: DefId) -> &'tcx hir::Item<'tcx> {
231+
let hir_id = cx.tcx.hir().as_local_hir_id(def_id).unwrap();
232+
cx.tcx.hir().expect_item(hir_id)
233+
}
234+
235+
fn has_unsafe<'tcx>(cx: &LateContext<'_, 'tcx>, item: &'tcx hir::Item<'_>) -> bool {
236+
let mut visitor = UnsafeVisitor { cx, has_unsafe: false };
237+
walk_item(&mut visitor, item);
238+
visitor.has_unsafe
239+
}
240+
241+
if_chain! {
242+
if match_path(&trait_ref.path, &paths::SERDE_DESERIALIZE);
243+
if let ty::Adt(def, _) = ty.kind;
244+
if def.did.is_local();
245+
if cx.tcx.inherent_impls(def.did)
246+
.iter()
247+
.map(|imp_did| item_from_def_id(cx, *imp_did))
248+
.any(|imp| has_unsafe(cx, imp));
249+
then {
250+
span_lint_and_help(
251+
cx,
252+
UNSAFE_DERIVE_DESERIALIZE,
253+
item.span,
254+
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
255+
None,
256+
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
257+
);
258+
}
259+
}
260+
}
261+
262+
struct UnsafeVisitor<'a, 'tcx> {
263+
cx: &'a LateContext<'a, 'tcx>,
264+
has_unsafe: bool,
265+
}
266+
267+
impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
268+
type Map = Map<'tcx>;
269+
270+
fn visit_fn(
271+
&mut self,
272+
kind: FnKind<'tcx>,
273+
decl: &'tcx hir::FnDecl<'_>,
274+
body_id: hir::BodyId,
275+
span: Span,
276+
id: hir::HirId,
277+
) {
278+
if self.has_unsafe {
279+
return;
280+
}
281+
282+
if_chain! {
283+
if let Some(header) = kind.header();
284+
if let hir::Unsafety::Unsafe = header.unsafety;
285+
then {
286+
self.has_unsafe = true;
287+
}
288+
}
289+
290+
walk_fn(self, kind, decl, body_id, span, id);
291+
}
292+
293+
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
294+
if self.has_unsafe {
295+
return;
296+
}
297+
298+
if let hir::ExprKind::Block(block, _) = expr.kind {
299+
use hir::{BlockCheckMode, UnsafeSource};
300+
301+
match block.rules {
302+
BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
303+
| BlockCheckMode::PushUnsafeBlock(UnsafeSource::UserProvided)
304+
| BlockCheckMode::PopUnsafeBlock(UnsafeSource::UserProvided) => {
305+
self.has_unsafe = true;
306+
},
307+
_ => {},
308+
}
309+
}
310+
311+
walk_expr(self, expr);
312+
}
313+
314+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
315+
NestedVisitorMap::All(self.cx.tcx.hir())
316+
}
317+
}

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
520520
&dereference::EXPLICIT_DEREF_METHODS,
521521
&derive::DERIVE_HASH_XOR_EQ,
522522
&derive::EXPL_IMPL_CLONE_ON_COPY,
523+
&derive::UNSAFE_DERIVE_DESERIALIZE,
523524
&doc::DOC_MARKDOWN,
524525
&doc::MISSING_ERRORS_DOC,
525526
&doc::MISSING_SAFETY_DOC,
@@ -1196,6 +1197,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11961197
LintId::of(&copies::IFS_SAME_COND),
11971198
LintId::of(&copies::IF_SAME_THEN_ELSE),
11981199
LintId::of(&derive::DERIVE_HASH_XOR_EQ),
1200+
LintId::of(&derive::UNSAFE_DERIVE_DESERIALIZE),
11991201
LintId::of(&doc::MISSING_SAFETY_DOC),
12001202
LintId::of(&doc::NEEDLESS_DOCTEST_MAIN),
12011203
LintId::of(&double_comparison::DOUBLE_COMPARISONS),
@@ -1608,6 +1610,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16081610
LintId::of(&copies::IFS_SAME_COND),
16091611
LintId::of(&copies::IF_SAME_THEN_ELSE),
16101612
LintId::of(&derive::DERIVE_HASH_XOR_EQ),
1613+
LintId::of(&derive::UNSAFE_DERIVE_DESERIALIZE),
16111614
LintId::of(&drop_bounds::DROP_BOUNDS),
16121615
LintId::of(&drop_forget_ref::DROP_COPY),
16131616
LintId::of(&drop_forget_ref::DROP_REF),

clippy_lints/src/utils/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
4949
/// Use this if you want to provide some general help but
5050
/// can't provide a specific machine applicable suggestion.
5151
///
52-
/// The `help` message is not attached to any `Span`.
52+
/// The `help` message can be optionally attached to a `Span`.
5353
///
5454
/// # Example
5555
///

clippy_lints/src/utils/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
241241
///
242242
/// # Examples
243243
/// ```rust,ignore
244-
/// match_qpath(path, &["std", "rt", "begin_unwind"])
244+
/// match_path_ast(path, &["std", "rt", "begin_unwind"])
245245
/// ```
246246
pub fn match_path_ast(path: &ast::Path, segments: &[&str]) -> bool {
247247
path.segments

clippy_lints/src/utils/paths.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
108108
pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
109109
pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
110110
pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
111+
pub const SERDE_DESERIALIZE: [&str; 2] = ["_serde", "Deserialize"];
111112
pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
112113
pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
113114
pub const SLICE_ITER: [&str; 3] = ["core", "slice", "Iter"];

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2334,6 +2334,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
23342334
deprecation: None,
23352335
module: "literal_representation",
23362336
},
2337+
Lint {
2338+
name: "unsafe_derive_deserialize",
2339+
group: "correctness",
2340+
desc: "deriving `serde::Deserialize` on a type that has methods using `unsafe`",
2341+
deprecation: None,
2342+
module: "derive",
2343+
},
23372344
Lint {
23382345
name: "unsafe_removed_from_name",
23392346
group: "style",

tests/ui/unsafe_derive_deserialize.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#![warn(clippy::unsafe_derive_deserialize)]
2+
#![allow(unused, clippy::missing_safety_doc)]
3+
4+
extern crate serde;
5+
6+
use serde::Deserialize;
7+
8+
#[derive(Deserialize)]
9+
pub struct A {}
10+
impl A {
11+
pub unsafe fn new(_a: i32, _b: i32) -> Self {
12+
Self {}
13+
}
14+
}
15+
16+
#[derive(Deserialize)]
17+
pub struct B {}
18+
impl B {
19+
pub unsafe fn unsafe_method(&self) {}
20+
}
21+
22+
#[derive(Deserialize)]
23+
pub struct C {}
24+
impl C {
25+
pub fn unsafe_block(&self) {
26+
unsafe {}
27+
}
28+
}
29+
30+
#[derive(Deserialize)]
31+
pub struct D {}
32+
impl D {
33+
pub fn inner_unsafe_fn(&self) {
34+
unsafe fn inner() {}
35+
}
36+
}
37+
38+
// Does not derive `Deserialize`, should be ignored
39+
pub struct E {}
40+
impl E {
41+
pub unsafe fn new(_a: i32, _b: i32) -> Self {
42+
Self {}
43+
}
44+
45+
pub unsafe fn unsafe_method(&self) {}
46+
47+
pub fn unsafe_block(&self) {
48+
unsafe {}
49+
}
50+
51+
pub fn inner_unsafe_fn(&self) {
52+
unsafe fn inner() {}
53+
}
54+
}
55+
56+
// Does not have methods using `unsafe`, should be ignored
57+
#[derive(Deserialize)]
58+
pub struct F {}
59+
60+
fn main() {}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe`
2+
--> $DIR/unsafe_derive_deserialize.rs:8:10
3+
|
4+
LL | #[derive(Deserialize)]
5+
| ^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::unsafe-derive-deserialize` implied by `-D warnings`
8+
= help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html
9+
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe`
12+
--> $DIR/unsafe_derive_deserialize.rs:16:10
13+
|
14+
LL | #[derive(Deserialize)]
15+
| ^^^^^^^^^^^
16+
|
17+
= help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html
18+
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
19+
20+
error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe`
21+
--> $DIR/unsafe_derive_deserialize.rs:22:10
22+
|
23+
LL | #[derive(Deserialize)]
24+
| ^^^^^^^^^^^
25+
|
26+
= help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html
27+
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
28+
29+
error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe`
30+
--> $DIR/unsafe_derive_deserialize.rs:30:10
31+
|
32+
LL | #[derive(Deserialize)]
33+
| ^^^^^^^^^^^
34+
|
35+
= help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html
36+
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
37+
38+
error: aborting due to 4 previous errors
39+

0 commit comments

Comments
 (0)