about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/lib.mods.rs1
-rw-r--r--clippy_lints/src/lib.register_lints.rs1
-rw-r--r--clippy_lints/src/lib.register_nursery.rs1
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/non_send_fields_in_send_ty.rs238
-rw-r--r--clippy_lints/src/utils/conf.rs4
-rw-r--r--tests/ui-toml/strict_non_send_fields_in_send_ty/clippy.toml1
-rw-r--r--tests/ui-toml/strict_non_send_fields_in_send_ty/test.rs43
-rw-r--r--tests/ui-toml/strict_non_send_fields_in_send_ty/test.stderr91
-rw-r--r--tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr2
-rw-r--r--tests/ui/non_send_fields_in_send_ty.rs127
-rw-r--r--tests/ui/non_send_fields_in_send_ty.stderr171
13 files changed, 682 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58ea0f9ab9d..9a4424f5061 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2898,6 +2898,7 @@ Released 2018-09-13
 [`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
 [`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
 [`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
+[`non_send_fields_in_send_ty`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty
 [`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool
 [`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options
 [`nonstandard_macro_braces`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces
diff --git a/clippy_lints/src/lib.mods.rs b/clippy_lints/src/lib.mods.rs
index 2718604f905..7fd84783c22 100644
--- a/clippy_lints/src/lib.mods.rs
+++ b/clippy_lints/src/lib.mods.rs
@@ -149,6 +149,7 @@ mod no_effect;
 mod non_copy_const;
 mod non_expressive_names;
 mod non_octal_unix_permissions;
+mod non_send_fields_in_send_ty;
 mod nonstandard_macro_braces;
 mod open_options;
 mod option_env_unwrap;
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index 1ad27870b1a..28d54246fbb 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -366,6 +366,7 @@ store.register_lints(&[
     non_expressive_names::MANY_SINGLE_CHAR_NAMES,
     non_expressive_names::SIMILAR_NAMES,
     non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS,
+    non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
     nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
     open_options::NONSENSICAL_OPEN_OPTIONS,
     option_env_unwrap::OPTION_ENV_UNWRAP,
diff --git a/clippy_lints/src/lib.register_nursery.rs b/clippy_lints/src/lib.register_nursery.rs
index b082f577a52..32606e570d8 100644
--- a/clippy_lints/src/lib.register_nursery.rs
+++ b/clippy_lints/src/lib.register_nursery.rs
@@ -16,6 +16,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
     LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN),
     LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
     LintId::of(mutex_atomic::MUTEX_INTEGER),
+    LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY),
     LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES),
     LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
     LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE),
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 4e21b03217d..c3be6db6ffa 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -535,6 +535,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(move || Box::new(feature_name::FeatureName));
     store.register_late_pass(move || Box::new(iter_not_returning_iterator::IterNotReturningIterator));
     store.register_late_pass(move || Box::new(if_then_panic::IfThenPanic));
+    let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
+    store.register_late_pass(move || Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(enable_raw_pointer_heuristic_for_send)));
 }
 
 #[rustfmt::skip]
diff --git a/clippy_lints/src/non_send_fields_in_send_ty.rs b/clippy_lints/src/non_send_fields_in_send_ty.rs
new file mode 100644
index 00000000000..0dbf296c714
--- /dev/null
+++ b/clippy_lints/src/non_send_fields_in_send_ty.rs
@@ -0,0 +1,238 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::is_lint_allowed;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::{implements_trait, is_copy};
+use rustc_ast::ImplPolarity;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{FieldDef, Item, ItemKind, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Warns about fields in struct implementing `Send` that are neither `Send` nor `Copy`.
+    ///
+    /// ### Why is this bad?
+    /// Sending the struct to another thread will transfer the ownership to
+    /// the new thread by dropping in the current thread during the transfer.
+    /// This causes soundness issues for non-`Send` fields, as they are also
+    /// dropped and might not be set up to handle this.
+    ///
+    /// See:
+    /// * [*The Rustonomicon* about *Send and Sync*](https://doc.rust-lang.org/nomicon/send-and-sync.html)
+    /// * [The documentation of `Send`](https://doc.rust-lang.org/std/marker/trait.Send.html)
+    ///
+    /// ### Known Problems
+    /// Data structures that contain raw pointers may cause false positives.
+    /// They are sometimes safe to be sent across threads but do not implement
+    /// the `Send` trait. This lint has a heuristic to filter out basic cases
+    /// such as `Vec<*const T>`, but it's not perfect. Feel free to create an
+    /// issue if you have a suggestion on how this heuristic can be improved.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// struct ExampleStruct<T> {
+    ///     rc_is_not_send: Rc<String>,
+    ///     unbounded_generic_field: T,
+    /// }
+    ///
+    /// // This impl is unsound because it allows sending `!Send` types through `ExampleStruct`
+    /// unsafe impl<T> Send for ExampleStruct<T> {}
+    /// ```
+    /// Use thread-safe types like [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html)
+    /// or specify correct bounds on generic type parameters (`T: Send`).
+    pub NON_SEND_FIELDS_IN_SEND_TY,
+    nursery,
+    "there is field that does not implement `Send` in a `Send` struct"
+}
+
+#[derive(Copy, Clone)]
+pub struct NonSendFieldInSendTy {
+    enable_raw_pointer_heuristic: bool,
+}
+
+impl NonSendFieldInSendTy {
+    pub fn new(enable_raw_pointer_heuristic: bool) -> Self {
+        Self {
+            enable_raw_pointer_heuristic,
+        }
+    }
+}
+
+impl_lint_pass!(NonSendFieldInSendTy => [NON_SEND_FIELDS_IN_SEND_TY]);
+
+impl<'tcx> LateLintPass<'tcx> for NonSendFieldInSendTy {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+        let ty_allowed_in_send = if self.enable_raw_pointer_heuristic {
+            ty_allowed_with_raw_pointer_heuristic
+        } else {
+            ty_allowed_without_raw_pointer_heuristic
+        };
+
+        // Checks if we are in `Send` impl item.
+        // We start from `Send` impl instead of `check_field_def()` because
+        // single `AdtDef` may have multiple `Send` impls due to generic
+        // parameters, and the lint is much easier to implement in this way.
+        if_chain! {
+            if let Some(send_trait) = cx.tcx.get_diagnostic_item(sym::send_trait);
+            if let ItemKind::Impl(hir_impl) = &item.kind;
+            if let Some(trait_ref) = &hir_impl.of_trait;
+            if let Some(trait_id) = trait_ref.trait_def_id();
+            if send_trait == trait_id;
+            if let ImplPolarity::Positive = hir_impl.polarity;
+            if let Some(ty_trait_ref) = cx.tcx.impl_trait_ref(item.def_id);
+            if let self_ty = ty_trait_ref.self_ty();
+            if let ty::Adt(adt_def, impl_trait_substs) = self_ty.kind();
+            then {
+                let mut non_send_fields = Vec::new();
+
+                let hir_map = cx.tcx.hir();
+                for variant in &adt_def.variants {
+                    for field in &variant.fields {
+                        if_chain! {
+                            if let Some(field_hir_id) = field
+                                .did
+                                .as_local()
+                                .map(|local_def_id| hir_map.local_def_id_to_hir_id(local_def_id));
+                            if !is_lint_allowed(cx, NON_SEND_FIELDS_IN_SEND_TY, field_hir_id);
+                            if let field_ty = field.ty(cx.tcx, impl_trait_substs);
+                            if !ty_allowed_in_send(cx, field_ty, send_trait);
+                            if let Node::Field(field_def) = hir_map.get(field_hir_id);
+                            then {
+                                non_send_fields.push(NonSendField {
+                                    def: field_def,
+                                    ty: field_ty,
+                                    generic_params: collect_generic_params(cx, field_ty),
+                                })
+                            }
+                        }
+                    }
+                }
+
+                if !non_send_fields.is_empty() {
+                    span_lint_and_then(
+                        cx,
+                        NON_SEND_FIELDS_IN_SEND_TY,
+                        item.span,
+                        &format!(
+                            "this implementation is unsound, as some fields in `{}` are `!Send`",
+                            snippet(cx, hir_impl.self_ty.span, "Unknown")
+                        ),
+                        |diag| {
+                            for field in non_send_fields {
+                                diag.span_note(
+                                    field.def.span,
+                                    &format!("the type of field `{}` is `!Send`", field.def.ident.name),
+                                );
+
+                                match field.generic_params.len() {
+                                    0 => diag.help("use a thread-safe type that implements `Send`"),
+                                    1 if is_ty_param(field.ty) => diag.help(&format!("add `{}: Send` bound in `Send` impl", field.ty)),
+                                    _ => diag.help(&format!(
+                                        "add bounds on type parameter{} `{}` that satisfy `{}: Send`",
+                                        if field.generic_params.len() > 1 { "s" } else { "" },
+                                        field.generic_params_string(),
+                                        snippet(cx, field.def.ty.span, "Unknown"),
+                                    )),
+                                };
+                            }
+                        },
+                    );
+                }
+            }
+        }
+    }
+}
+
+struct NonSendField<'tcx> {
+    def: &'tcx FieldDef<'tcx>,
+    ty: Ty<'tcx>,
+    generic_params: Vec<Ty<'tcx>>,
+}
+
+impl<'tcx> NonSendField<'tcx> {
+    fn generic_params_string(&self) -> String {
+        self.generic_params
+            .iter()
+            .map(ToString::to_string)
+            .collect::<Vec<_>>()
+            .join(", ")
+    }
+}
+
+/// Given a type, collect all of its generic parameters.
+/// Example: `MyStruct<P, Box<Q, R>>` => `vec![P, Q, R]`
+fn collect_generic_params<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Vec<Ty<'tcx>> {
+    ty.walk(cx.tcx)
+        .filter_map(|inner| match inner.unpack() {
+            GenericArgKind::Type(inner_ty) => Some(inner_ty),
+            _ => None,
+        })
+        .filter(|&inner_ty| is_ty_param(inner_ty))
+        .collect()
+}
+
+/// Be more strict when the heuristic is disabled
+fn ty_allowed_without_raw_pointer_heuristic<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, send_trait: DefId) -> bool {
+    if implements_trait(cx, ty, send_trait, &[]) {
+        return true;
+    }
+
+    if is_copy(cx, ty) && !contains_raw_pointer(cx, ty) {
+        return true;
+    }
+
+    false
+}
+
+/// Heuristic to allow cases like `Vec<*const u8>`
+fn ty_allowed_with_raw_pointer_heuristic<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, send_trait: DefId) -> bool {
+    if implements_trait(cx, ty, send_trait, &[]) || is_copy(cx, ty) {
+        return true;
+    }
+
+    // The type is known to be `!Send` and `!Copy`
+    match ty.kind() {
+        ty::Tuple(_) => ty
+            .tuple_fields()
+            .all(|ty| ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait)),
+        ty::Array(ty, _) | ty::Slice(ty) => ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait),
+        ty::Adt(_, substs) => {
+            if contains_raw_pointer(cx, ty) {
+                // descends only if ADT contains any raw pointers
+                substs.iter().all(|generic_arg| match generic_arg.unpack() {
+                    GenericArgKind::Type(ty) => ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait),
+                    // Lifetimes and const generics are not solid part of ADT and ignored
+                    GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => true,
+                })
+            } else {
+                false
+            }
+        },
+        // Raw pointers are `!Send` but allowed by the heuristic
+        ty::RawPtr(_) => true,
+        _ => false,
+    }
+}
+
+/// Checks if the type contains any raw pointers in substs (including nested ones).
+fn contains_raw_pointer<'tcx>(cx: &LateContext<'tcx>, target_ty: Ty<'tcx>) -> bool {
+    for ty_node in target_ty.walk(cx.tcx) {
+        if_chain! {
+            if let GenericArgKind::Type(inner_ty) = ty_node.unpack();
+            if let ty::RawPtr(_) = inner_ty.kind();
+            then {
+                return true;
+            }
+        }
+    }
+
+    false
+}
+
+/// Returns `true` if the type is a type parameter such as `T`.
+fn is_ty_param(target_ty: Ty<'_>) -> bool {
+    matches!(target_ty.kind(), ty::Param(_))
+}
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs
index 1e0447239be..6cbada4c150 100644
--- a/clippy_lints/src/utils/conf.rs
+++ b/clippy_lints/src/utils/conf.rs
@@ -284,6 +284,10 @@ define_Conf! {
     ///
     /// The list of unicode scripts allowed to be used in the scope.
     (allowed_scripts: Vec<String> = vec!["Latin".to_string()]),
+    /// Lint: NON_SEND_FIELDS_IN_SEND_TY.
+    ///
+    /// Whether to apply the raw pointer heuristic to determine if a type is `Send`.
+    (enable_raw_pointer_heuristic_for_send: bool = true),
 }
 
 /// Search for the configuration file.
diff --git a/tests/ui-toml/strict_non_send_fields_in_send_ty/clippy.toml b/tests/ui-toml/strict_non_send_fields_in_send_ty/clippy.toml
new file mode 100644
index 00000000000..a942709d14a
--- /dev/null
+++ b/tests/ui-toml/strict_non_send_fields_in_send_ty/clippy.toml
@@ -0,0 +1 @@
+enable-raw-pointer-heuristic-for-send = false
diff --git a/tests/ui-toml/strict_non_send_fields_in_send_ty/test.rs b/tests/ui-toml/strict_non_send_fields_in_send_ty/test.rs
new file mode 100644
index 00000000000..90c2439dc34
--- /dev/null
+++ b/tests/ui-toml/strict_non_send_fields_in_send_ty/test.rs
@@ -0,0 +1,43 @@
+#![warn(clippy::non_send_fields_in_send_ty)]
+#![feature(extern_types)]
+
+use std::rc::Rc;
+
+// Basic tests should not be affected
+pub struct NoGeneric {
+    rc_is_not_send: Rc<String>,
+}
+
+unsafe impl Send for NoGeneric {}
+
+pub struct MultiField<T> {
+    field1: T,
+    field2: T,
+    field3: T,
+}
+
+unsafe impl<T> Send for MultiField<T> {}
+
+pub enum MyOption<T> {
+    MySome(T),
+    MyNone,
+}
+
+unsafe impl<T> Send for MyOption<T> {}
+
+// All fields are disallowed when raw pointer heuristic is off
+extern "C" {
+    type NonSend;
+}
+
+pub struct HeuristicTest {
+    field1: Vec<*const NonSend>,
+    field2: [*const NonSend; 3],
+    field3: (*const NonSend, *const NonSend, *const NonSend),
+    field4: (*const NonSend, Rc<u8>),
+    field5: Vec<Vec<*const NonSend>>,
+}
+
+unsafe impl Send for HeuristicTest {}
+
+fn main() {}
diff --git a/tests/ui-toml/strict_non_send_fields_in_send_ty/test.stderr b/tests/ui-toml/strict_non_send_fields_in_send_ty/test.stderr
new file mode 100644
index 00000000000..b07f9dd3df3
--- /dev/null
+++ b/tests/ui-toml/strict_non_send_fields_in_send_ty/test.stderr
@@ -0,0 +1,91 @@
+error: this implementation is unsound, as some fields in `NoGeneric` are `!Send`
+  --> $DIR/test.rs:11:1
+   |
+LL | unsafe impl Send for NoGeneric {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::non-send-fields-in-send-ty` implied by `-D warnings`
+note: the type of field `rc_is_not_send` is `!Send`
+  --> $DIR/test.rs:8:5
+   |
+LL |     rc_is_not_send: Rc<String>,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: use a thread-safe type that implements `Send`
+
+error: this implementation is unsound, as some fields in `MultiField<T>` are `!Send`
+  --> $DIR/test.rs:19:1
+   |
+LL | unsafe impl<T> Send for MultiField<T> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `field1` is `!Send`
+  --> $DIR/test.rs:14:5
+   |
+LL |     field1: T,
+   |     ^^^^^^^^^
+   = help: add `T: Send` bound in `Send` impl
+note: the type of field `field2` is `!Send`
+  --> $DIR/test.rs:15:5
+   |
+LL |     field2: T,
+   |     ^^^^^^^^^
+   = help: add `T: Send` bound in `Send` impl
+note: the type of field `field3` is `!Send`
+  --> $DIR/test.rs:16:5
+   |
+LL |     field3: T,
+   |     ^^^^^^^^^
+   = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `MyOption<T>` are `!Send`
+  --> $DIR/test.rs:26:1
+   |
+LL | unsafe impl<T> Send for MyOption<T> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `0` is `!Send`
+  --> $DIR/test.rs:22:12
+   |
+LL |     MySome(T),
+   |            ^
+   = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `HeuristicTest` are `!Send`
+  --> $DIR/test.rs:41:1
+   |
+LL | unsafe impl Send for HeuristicTest {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `field1` is `!Send`
+  --> $DIR/test.rs:34:5
+   |
+LL |     field1: Vec<*const NonSend>,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: use a thread-safe type that implements `Send`
+note: the type of field `field2` is `!Send`
+  --> $DIR/test.rs:35:5
+   |
+LL |     field2: [*const NonSend; 3],
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: use a thread-safe type that implements `Send`
+note: the type of field `field3` is `!Send`
+  --> $DIR/test.rs:36:5
+   |
+LL |     field3: (*const NonSend, *const NonSend, *const NonSend),
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: use a thread-safe type that implements `Send`
+note: the type of field `field4` is `!Send`
+  --> $DIR/test.rs:37:5
+   |
+LL |     field4: (*const NonSend, Rc<u8>),
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: use a thread-safe type that implements `Send`
+note: the type of field `field5` is `!Send`
+  --> $DIR/test.rs:38:5
+   |
+LL |     field5: Vec<Vec<*const NonSend>>,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: use a thread-safe type that implements `Send`
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
index e0029ebeb27..97bab1308aa 100644
--- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
+++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
@@ -1,4 +1,4 @@
-error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `avoid-breaking-exported-api`, `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `disallowed-types`, `unreadable-literal-lint-fractions`, `upper-case-acronyms-aggressive`, `cargo-ignore-publish`, `standard-macro-braces`, `enforced-import-renames`, `allowed-scripts`, `third-party` at line 5 column 1
+error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `avoid-breaking-exported-api`, `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `disallowed-types`, `unreadable-literal-lint-fractions`, `upper-case-acronyms-aggressive`, `cargo-ignore-publish`, `standard-macro-braces`, `enforced-import-renames`, `allowed-scripts`, `enable-raw-pointer-heuristic-for-send`, `third-party` at line 5 column 1
 
 error: aborting due to previous error
 
diff --git a/tests/ui/non_send_fields_in_send_ty.rs b/tests/ui/non_send_fields_in_send_ty.rs
new file mode 100644
index 00000000000..eca7f5e5655
--- /dev/null
+++ b/tests/ui/non_send_fields_in_send_ty.rs
@@ -0,0 +1,127 @@
+#![warn(clippy::non_send_fields_in_send_ty)]
+#![feature(extern_types)]
+
+use std::cell::UnsafeCell;
+use std::ptr::NonNull;
+use std::rc::Rc;
+use std::sync::{Arc, Mutex, MutexGuard};
+
+// disrustor / RUSTSEC-2020-0150
+pub struct RingBuffer<T> {
+    data: Vec<UnsafeCell<T>>,
+    capacity: usize,
+    mask: usize,
+}
+
+unsafe impl<T> Send for RingBuffer<T> {}
+
+// noise_search / RUSTSEC-2020-0141
+pub struct MvccRwLock<T> {
+    raw: *const T,
+    lock: Mutex<Box<T>>,
+}
+
+unsafe impl<T> Send for MvccRwLock<T> {}
+
+// async-coap / RUSTSEC-2020-0124
+pub struct ArcGuard<RC, T> {
+    inner: T,
+    head: Arc<RC>,
+}
+
+unsafe impl<RC, T: Send> Send for ArcGuard<RC, T> {}
+
+// rusb / RUSTSEC-2020-0098
+extern "C" {
+    type libusb_device_handle;
+}
+
+pub trait UsbContext {
+    // some user trait that does not guarantee `Send`
+}
+
+pub struct DeviceHandle<T: UsbContext> {
+    context: T,
+    handle: NonNull<libusb_device_handle>,
+}
+
+unsafe impl<T: UsbContext> Send for DeviceHandle<T> {}
+
+// Other basic tests
+pub struct NoGeneric {
+    rc_is_not_send: Rc<String>,
+}
+
+unsafe impl Send for NoGeneric {}
+
+pub struct MultiField<T> {
+    field1: T,
+    field2: T,
+    field3: T,
+}
+
+unsafe impl<T> Send for MultiField<T> {}
+
+pub enum MyOption<T> {
+    MySome(T),
+    MyNone,
+}
+
+unsafe impl<T> Send for MyOption<T> {}
+
+// Multiple type parameters
+pub struct MultiParam<A, B> {
+    vec: Vec<(A, B)>,
+}
+
+unsafe impl<A, B> Send for MultiParam<A, B> {}
+
+// Tests for raw pointer heuristic
+extern "C" {
+    type NonSend;
+}
+
+pub struct HeuristicTest {
+    // raw pointers are allowed
+    field1: Vec<*const NonSend>,
+    field2: [*const NonSend; 3],
+    field3: (*const NonSend, *const NonSend, *const NonSend),
+    // not allowed when it contains concrete `!Send` field
+    field4: (*const NonSend, Rc<u8>),
+    // nested raw pointer is also allowed
+    field5: Vec<Vec<*const NonSend>>,
+}
+
+unsafe impl Send for HeuristicTest {}
+
+// Test attributes
+#[allow(clippy::non_send_fields_in_send_ty)]
+pub struct AttrTest1<T>(T);
+
+pub struct AttrTest2<T> {
+    #[allow(clippy::non_send_fields_in_send_ty)]
+    field: T,
+}
+
+pub enum AttrTest3<T> {
+    #[allow(clippy::non_send_fields_in_send_ty)]
+    Enum1(T),
+    Enum2(T),
+}
+
+unsafe impl<T> Send for AttrTest1<T> {}
+unsafe impl<T> Send for AttrTest2<T> {}
+unsafe impl<T> Send for AttrTest3<T> {}
+
+// Multiple non-overlapping `Send` for a single type
+pub struct Complex<A, B> {
+    field1: A,
+    field2: B,
+}
+
+unsafe impl<P> Send for Complex<P, u32> {}
+
+// `MutexGuard` is non-Send
+unsafe impl<Q: Send> Send for Complex<Q, MutexGuard<'static, bool>> {}
+
+fn main() {}
diff --git a/tests/ui/non_send_fields_in_send_ty.stderr b/tests/ui/non_send_fields_in_send_ty.stderr
new file mode 100644
index 00000000000..8b8a1d16d9b
--- /dev/null
+++ b/tests/ui/non_send_fields_in_send_ty.stderr
@@ -0,0 +1,171 @@
+error: this implementation is unsound, as some fields in `RingBuffer<T>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:16:1
+   |
+LL | unsafe impl<T> Send for RingBuffer<T> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::non-send-fields-in-send-ty` implied by `-D warnings`
+note: the type of field `data` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:11:5
+   |
+LL |     data: Vec<UnsafeCell<T>>,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: add bounds on type parameter `T` that satisfy `Vec<UnsafeCell<T>>: Send`
+
+error: this implementation is unsound, as some fields in `MvccRwLock<T>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:24:1
+   |
+LL | unsafe impl<T> Send for MvccRwLock<T> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `lock` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:21:5
+   |
+LL |     lock: Mutex<Box<T>>,
+   |     ^^^^^^^^^^^^^^^^^^^
+   = help: add bounds on type parameter `T` that satisfy `Mutex<Box<T>>: Send`
+
+error: this implementation is unsound, as some fields in `ArcGuard<RC, T>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:32:1
+   |
+LL | unsafe impl<RC, T: Send> Send for ArcGuard<RC, T> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `head` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:29:5
+   |
+LL |     head: Arc<RC>,
+   |     ^^^^^^^^^^^^^
+   = help: add bounds on type parameter `RC` that satisfy `Arc<RC>: Send`
+
+error: this implementation is unsound, as some fields in `DeviceHandle<T>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:48:1
+   |
+LL | unsafe impl<T: UsbContext> Send for DeviceHandle<T> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `context` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:44:5
+   |
+LL |     context: T,
+   |     ^^^^^^^^^^
+   = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `NoGeneric` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:55:1
+   |
+LL | unsafe impl Send for NoGeneric {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `rc_is_not_send` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:52:5
+   |
+LL |     rc_is_not_send: Rc<String>,
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: use a thread-safe type that implements `Send`
+
+error: this implementation is unsound, as some fields in `MultiField<T>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:63:1
+   |
+LL | unsafe impl<T> Send for MultiField<T> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `field1` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:58:5
+   |
+LL |     field1: T,
+   |     ^^^^^^^^^
+   = help: add `T: Send` bound in `Send` impl
+note: the type of field `field2` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:59:5
+   |
+LL |     field2: T,
+   |     ^^^^^^^^^
+   = help: add `T: Send` bound in `Send` impl
+note: the type of field `field3` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:60:5
+   |
+LL |     field3: T,
+   |     ^^^^^^^^^
+   = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `MyOption<T>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:70:1
+   |
+LL | unsafe impl<T> Send for MyOption<T> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `0` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:66:12
+   |
+LL |     MySome(T),
+   |            ^
+   = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `MultiParam<A, B>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:77:1
+   |
+LL | unsafe impl<A, B> Send for MultiParam<A, B> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `vec` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:74:5
+   |
+LL |     vec: Vec<(A, B)>,
+   |     ^^^^^^^^^^^^^^^^
+   = help: add bounds on type parameters `A, B` that satisfy `Vec<(A, B)>: Send`
+
+error: this implementation is unsound, as some fields in `HeuristicTest` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:95:1
+   |
+LL | unsafe impl Send for HeuristicTest {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `field4` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:90:5
+   |
+LL |     field4: (*const NonSend, Rc<u8>),
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: use a thread-safe type that implements `Send`
+
+error: this implementation is unsound, as some fields in `AttrTest3<T>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:114:1
+   |
+LL | unsafe impl<T> Send for AttrTest3<T> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `0` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:109:11
+   |
+LL |     Enum2(T),
+   |           ^
+   = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `Complex<P, u32>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:122:1
+   |
+LL | unsafe impl<P> Send for Complex<P, u32> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `field1` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:118:5
+   |
+LL |     field1: A,
+   |     ^^^^^^^^^
+   = help: add `P: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `Complex<Q, MutexGuard<'static, bool>>` are `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:125:1
+   |
+LL | unsafe impl<Q: Send> Send for Complex<Q, MutexGuard<'static, bool>> {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the type of field `field2` is `!Send`
+  --> $DIR/non_send_fields_in_send_ty.rs:119:5
+   |
+LL |     field2: B,
+   |     ^^^^^^^^^
+   = help: use a thread-safe type that implements `Send`
+
+error: aborting due to 12 previous errors
+