Skip to content

Commit 1336558

Browse files
committed
Auto merge of rust-lang#5493 - ebroto:unsafe_derive_deserialize, r=flip1995
Implement unsafe_derive_deserialize lint Added `unsafe_derive_deserialize` lint to check for cases when automatically deriving `serde::Deserialize` can be problematic, i.e. with types that have methods using `unsafe`. Closes rust-lang#5471 changelog: Add lint [`unsafe_derive_deserialize`]
2 parents 6dcc8d5 + b7f85e8 commit 1336558

File tree

9 files changed

+246
-6
lines changed

9 files changed

+246
-6
lines changed

CHANGELOG.md

+1
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

+134-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
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::def_id::DefId;
7+
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, NestedVisitorMap, Visitor};
8+
use rustc_hir::{
9+
BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, TraitRef, UnsafeSource, Unsafety,
10+
};
511
use rustc_lint::{LateContext, LateLintPass};
12+
use rustc_middle::hir::map::Map;
613
use rustc_middle::ty::{self, Ty};
714
use rustc_session::{declare_lint_pass, declare_tool_lint};
815
use rustc_span::source_map::Span;
@@ -62,7 +69,41 @@ declare_clippy_lint! {
6269
"implementing `Clone` explicitly on `Copy` types"
6370
}
6471

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

67108
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Derive {
68109
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) {
@@ -76,7 +117,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Derive {
76117

77118
check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
78119

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

clippy_lints/src/lib.rs

+2
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,
@@ -1105,6 +1106,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11051106
LintId::of(&default_trait_access::DEFAULT_TRAIT_ACCESS),
11061107
LintId::of(&dereference::EXPLICIT_DEREF_METHODS),
11071108
LintId::of(&derive::EXPL_IMPL_CLONE_ON_COPY),
1109+
LintId::of(&derive::UNSAFE_DERIVE_DESERIALIZE),
11081110
LintId::of(&doc::DOC_MARKDOWN),
11091111
LintId::of(&doc::MISSING_ERRORS_DOC),
11101112
LintId::of(&empty_enum::EMPTY_ENUM),

clippy_lints/src/utils/diagnostics.rs

+1-1
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

+1-1
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

+1
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

+7
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: "pedantic",
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

+60
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() {}
+39
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)