about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEduardo Broto <ebroto@tutanota.com>2020-04-19 23:11:30 +0200
committerEduardo Broto <ebroto@tutanota.com>2020-04-19 23:26:17 +0200
commit00b4f2819fa4786bb54bd2c4091767f64268d528 (patch)
tree25da9d3bdba6a85fc40c0d9b00d5f6fb2ee0b34b
parent6dcc8d50381b561a87cdfb926c829434857e635f (diff)
downloadrust-00b4f2819fa4786bb54bd2c4091767f64268d528.tar.gz
rust-00b4f2819fa4786bb54bd2c4091767f64268d528.zip
Implement unsafe_derive_deserialize lint
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/derive.rs158
-rw-r--r--clippy_lints/src/lib.rs3
-rw-r--r--clippy_lints/src/utils/diagnostics.rs2
-rw-r--r--clippy_lints/src/utils/mod.rs2
-rw-r--r--clippy_lints/src/utils/paths.rs1
-rw-r--r--src/lintlist/mod.rs7
-rw-r--r--tests/ui/unsafe_derive_deserialize.rs60
-rw-r--r--tests/ui/unsafe_derive_deserialize.stderr39
9 files changed, 263 insertions, 10 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e513787a53a..b97452873a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1524,6 +1524,7 @@ Released 2018-09-13
 [`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern
 [`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable
 [`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal
+[`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize
 [`unsafe_removed_from_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_removed_from_name
 [`unsafe_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_vector_initialization
 [`unseparated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#unseparated_literal_suffix
diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs
index f5a358d424f..2f7d30674e1 100644
--- a/clippy_lints/src/derive.rs
+++ b/clippy_lints/src/derive.rs
@@ -1,8 +1,13 @@
 use crate::utils::paths;
-use crate::utils::{is_automatically_derived, is_copy, match_path, span_lint_and_note, span_lint_and_then};
+use crate::utils::{
+    is_automatically_derived, is_copy, match_path, span_lint_and_help, span_lint_and_note, span_lint_and_then,
+};
 use if_chain::if_chain;
-use rustc_hir::{Item, ItemKind, TraitRef};
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, NestedVisitorMap, Visitor};
 use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
 use rustc_middle::ty::{self, Ty};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::source_map::Span;
@@ -62,11 +67,45 @@ declare_clippy_lint! {
     "implementing `Clone` explicitly on `Copy` types"
 }
 
-declare_lint_pass!(Derive => [EXPL_IMPL_CLONE_ON_COPY, DERIVE_HASH_XOR_EQ]);
+declare_clippy_lint! {
+    /// **What it does:** Checks for deriving `serde::Deserialize` on a type that
+    /// has methods using `unsafe`.
+    ///
+    /// **Why is this bad?** Deriving `serde::Deserialize` will create a constructor
+    /// that may violate invariants hold by another constructor.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:**
+    ///
+    /// ```rust,ignore
+    /// use serde::Deserialize;
+    ///
+    /// #[derive(Deserialize)]
+    /// pub struct Foo {
+    ///     // ..
+    /// }
+    ///
+    /// impl Foo {
+    ///     pub fn new() -> Self {
+    ///         // setup here ..
+    ///     }
+    ///
+    ///     pub unsafe fn parts() -> (&str, &str) {
+    ///         // assumes invariants hold
+    ///     }
+    /// }
+    /// ```
+    pub UNSAFE_DERIVE_DESERIALIZE,
+    correctness,
+    "deriving `serde::Deserialize` on a type that has methods using `unsafe`"
+}
+
+declare_lint_pass!(Derive => [EXPL_IMPL_CLONE_ON_COPY, DERIVE_HASH_XOR_EQ, UNSAFE_DERIVE_DESERIALIZE]);
 
 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Derive {
-    fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) {
-        if let ItemKind::Impl {
+    fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) {
+        if let hir::ItemKind::Impl {
             of_trait: Some(ref trait_ref),
             ..
         } = item.kind
@@ -76,7 +115,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Derive {
 
             check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
 
-            if !is_automatically_derived {
+            if is_automatically_derived {
+                check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
+            } else {
                 check_copy_clone(cx, item, trait_ref, ty);
             }
         }
@@ -87,7 +128,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Derive {
 fn check_hash_peq<'a, 'tcx>(
     cx: &LateContext<'a, 'tcx>,
     span: Span,
-    trait_ref: &TraitRef<'_>,
+    trait_ref: &hir::TraitRef<'_>,
     ty: Ty<'tcx>,
     hash_is_automatically_derived: bool,
 ) {
@@ -134,7 +175,12 @@ fn check_hash_peq<'a, 'tcx>(
 }
 
 /// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
-fn check_copy_clone<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
+fn check_copy_clone<'a, 'tcx>(
+    cx: &LateContext<'a, 'tcx>,
+    item: &hir::Item<'_>,
+    trait_ref: &hir::TraitRef<'_>,
+    ty: Ty<'tcx>,
+) {
     if match_path(&trait_ref.path, &paths::CLONE_TRAIT) {
         if !is_copy(cx, ty) {
             return;
@@ -173,3 +219,99 @@ fn check_copy_clone<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item: &Item<'_>, trait
         );
     }
 }
+
+/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
+fn check_unsafe_derive_deserialize<'a, 'tcx>(
+    cx: &LateContext<'a, 'tcx>,
+    item: &hir::Item<'_>,
+    trait_ref: &hir::TraitRef<'_>,
+    ty: Ty<'tcx>,
+) {
+    fn item_from_def_id<'tcx>(cx: &LateContext<'_, 'tcx>, def_id: DefId) -> &'tcx hir::Item<'tcx> {
+        let hir_id = cx.tcx.hir().as_local_hir_id(def_id).unwrap();
+        cx.tcx.hir().expect_item(hir_id)
+    }
+
+    fn has_unsafe<'tcx>(cx: &LateContext<'_, 'tcx>, item: &'tcx hir::Item<'_>) -> bool {
+        let mut visitor = UnsafeVisitor { cx, has_unsafe: false };
+        walk_item(&mut visitor, item);
+        visitor.has_unsafe
+    }
+
+    if_chain! {
+        if match_path(&trait_ref.path, &paths::SERDE_DESERIALIZE);
+        if let ty::Adt(def, _) = ty.kind;
+        if def.did.is_local();
+        if cx.tcx.inherent_impls(def.did)
+            .iter()
+            .map(|imp_did| item_from_def_id(cx, *imp_did))
+            .any(|imp| has_unsafe(cx, imp));
+        then {
+            span_lint_and_help(
+                cx,
+                UNSAFE_DERIVE_DESERIALIZE,
+                item.span,
+                "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
+                None,
+                "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
+            );
+        }
+    }
+}
+
+struct UnsafeVisitor<'a, 'tcx> {
+    cx: &'a LateContext<'a, 'tcx>,
+    has_unsafe: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
+    type Map = Map<'tcx>;
+
+    fn visit_fn(
+        &mut self,
+        kind: FnKind<'tcx>,
+        decl: &'tcx hir::FnDecl<'_>,
+        body_id: hir::BodyId,
+        span: Span,
+        id: hir::HirId,
+    ) {
+        if self.has_unsafe {
+            return;
+        }
+
+        if_chain! {
+            if let Some(header) = kind.header();
+            if let hir::Unsafety::Unsafe = header.unsafety;
+            then {
+                self.has_unsafe = true;
+            }
+        }
+
+        walk_fn(self, kind, decl, body_id, span, id);
+    }
+
+    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+        if self.has_unsafe {
+            return;
+        }
+
+        if let hir::ExprKind::Block(block, _) = expr.kind {
+            use hir::{BlockCheckMode, UnsafeSource};
+
+            match block.rules {
+                BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
+                | BlockCheckMode::PushUnsafeBlock(UnsafeSource::UserProvided)
+                | BlockCheckMode::PopUnsafeBlock(UnsafeSource::UserProvided) => {
+                    self.has_unsafe = true;
+                },
+                _ => {},
+            }
+        }
+
+        walk_expr(self, expr);
+    }
+
+    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+        NestedVisitorMap::All(self.cx.tcx.hir())
+    }
+}
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 19c46476263..d105afe5931 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -520,6 +520,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &dereference::EXPLICIT_DEREF_METHODS,
         &derive::DERIVE_HASH_XOR_EQ,
         &derive::EXPL_IMPL_CLONE_ON_COPY,
+        &derive::UNSAFE_DERIVE_DESERIALIZE,
         &doc::DOC_MARKDOWN,
         &doc::MISSING_ERRORS_DOC,
         &doc::MISSING_SAFETY_DOC,
@@ -1196,6 +1197,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&copies::IFS_SAME_COND),
         LintId::of(&copies::IF_SAME_THEN_ELSE),
         LintId::of(&derive::DERIVE_HASH_XOR_EQ),
+        LintId::of(&derive::UNSAFE_DERIVE_DESERIALIZE),
         LintId::of(&doc::MISSING_SAFETY_DOC),
         LintId::of(&doc::NEEDLESS_DOCTEST_MAIN),
         LintId::of(&double_comparison::DOUBLE_COMPARISONS),
@@ -1608,6 +1610,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&copies::IFS_SAME_COND),
         LintId::of(&copies::IF_SAME_THEN_ELSE),
         LintId::of(&derive::DERIVE_HASH_XOR_EQ),
+        LintId::of(&derive::UNSAFE_DERIVE_DESERIALIZE),
         LintId::of(&drop_bounds::DROP_BOUNDS),
         LintId::of(&drop_forget_ref::DROP_COPY),
         LintId::of(&drop_forget_ref::DROP_REF),
diff --git a/clippy_lints/src/utils/diagnostics.rs b/clippy_lints/src/utils/diagnostics.rs
index 093ef319108..24a1bdf1883 100644
--- a/clippy_lints/src/utils/diagnostics.rs
+++ b/clippy_lints/src/utils/diagnostics.rs
@@ -49,7 +49,7 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
 /// Use this if you want to provide some general help but
 /// can't provide a specific machine applicable suggestion.
 ///
-/// The `help` message is not attached to any `Span`.
+/// The `help` message can be optionally attached to a `Span`.
 ///
 /// # Example
 ///
diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs
index f7a91fcdd21..a0543a8dcf9 100644
--- a/clippy_lints/src/utils/mod.rs
+++ b/clippy_lints/src/utils/mod.rs
@@ -241,7 +241,7 @@ pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
 ///
 /// # Examples
 /// ```rust,ignore
-/// match_qpath(path, &["std", "rt", "begin_unwind"])
+/// match_path_ast(path, &["std", "rt", "begin_unwind"])
 /// ```
 pub fn match_path_ast(path: &ast::Path, segments: &[&str]) -> bool {
     path.segments
diff --git a/clippy_lints/src/utils/paths.rs b/clippy_lints/src/utils/paths.rs
index d93f8a1e560..f85845be56d 100644
--- a/clippy_lints/src/utils/paths.rs
+++ b/clippy_lints/src/utils/paths.rs
@@ -108,6 +108,7 @@ pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
 pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
 pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
 pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
+pub const SERDE_DESERIALIZE: [&str; 2] = ["_serde", "Deserialize"];
 pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
 pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
 pub const SLICE_ITER: [&str; 3] = ["core", "slice", "Iter"];
diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs
index 213d054e403..c7c851a8032 100644
--- a/src/lintlist/mod.rs
+++ b/src/lintlist/mod.rs
@@ -2335,6 +2335,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
         module: "literal_representation",
     },
     Lint {
+        name: "unsafe_derive_deserialize",
+        group: "correctness",
+        desc: "deriving `serde::Deserialize` on a type that has methods using `unsafe`",
+        deprecation: None,
+        module: "derive",
+    },
+    Lint {
         name: "unsafe_removed_from_name",
         group: "style",
         desc: "`unsafe` removed from API names on import",
diff --git a/tests/ui/unsafe_derive_deserialize.rs b/tests/ui/unsafe_derive_deserialize.rs
new file mode 100644
index 00000000000..7bee9c499e1
--- /dev/null
+++ b/tests/ui/unsafe_derive_deserialize.rs
@@ -0,0 +1,60 @@
+#![warn(clippy::unsafe_derive_deserialize)]
+#![allow(unused, clippy::missing_safety_doc)]
+
+extern crate serde;
+
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+pub struct A {}
+impl A {
+    pub unsafe fn new(_a: i32, _b: i32) -> Self {
+        Self {}
+    }
+}
+
+#[derive(Deserialize)]
+pub struct B {}
+impl B {
+    pub unsafe fn unsafe_method(&self) {}
+}
+
+#[derive(Deserialize)]
+pub struct C {}
+impl C {
+    pub fn unsafe_block(&self) {
+        unsafe {}
+    }
+}
+
+#[derive(Deserialize)]
+pub struct D {}
+impl D {
+    pub fn inner_unsafe_fn(&self) {
+        unsafe fn inner() {}
+    }
+}
+
+// Does not derive `Deserialize`, should be ignored
+pub struct E {}
+impl E {
+    pub unsafe fn new(_a: i32, _b: i32) -> Self {
+        Self {}
+    }
+
+    pub unsafe fn unsafe_method(&self) {}
+
+    pub fn unsafe_block(&self) {
+        unsafe {}
+    }
+
+    pub fn inner_unsafe_fn(&self) {
+        unsafe fn inner() {}
+    }
+}
+
+// Does not have methods using `unsafe`, should be ignored
+#[derive(Deserialize)]
+pub struct F {}
+
+fn main() {}
diff --git a/tests/ui/unsafe_derive_deserialize.stderr b/tests/ui/unsafe_derive_deserialize.stderr
new file mode 100644
index 00000000000..1978bd95a67
--- /dev/null
+++ b/tests/ui/unsafe_derive_deserialize.stderr
@@ -0,0 +1,39 @@
+error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe`
+  --> $DIR/unsafe_derive_deserialize.rs:8:10
+   |
+LL | #[derive(Deserialize)]
+   |          ^^^^^^^^^^^
+   |
+   = note: `-D clippy::unsafe-derive-deserialize` implied by `-D warnings`
+   = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html
+   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe`
+  --> $DIR/unsafe_derive_deserialize.rs:16:10
+   |
+LL | #[derive(Deserialize)]
+   |          ^^^^^^^^^^^
+   |
+   = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html
+   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe`
+  --> $DIR/unsafe_derive_deserialize.rs:22:10
+   |
+LL | #[derive(Deserialize)]
+   |          ^^^^^^^^^^^
+   |
+   = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html
+   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe`
+  --> $DIR/unsafe_derive_deserialize.rs:30:10
+   |
+LL | #[derive(Deserialize)]
+   |          ^^^^^^^^^^^
+   |
+   = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html
+   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 4 previous errors
+