Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit ee74574

Browse files
committed
Emit one report for all fields in the same ADT
1 parent a81a5ad commit ee74574

File tree

3 files changed

+203
-123
lines changed

3 files changed

+203
-123
lines changed

clippy_lints/src/non_send_field_in_send_ty.rs

Lines changed: 85 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
use clippy_utils::diagnostics::span_lint_hir_and_then;
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::is_lint_allowed;
23
use clippy_utils::ty::{implements_trait, is_copy};
34
use rustc_ast::ImplPolarity;
5+
use rustc_hir::def_id::DefId;
46
use rustc_hir::{Item, ItemKind};
57
use rustc_lint::{LateContext, LateLintPass};
68
use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
79
use rustc_session::{declare_lint_pass, declare_tool_lint};
8-
use rustc_span::sym;
10+
use rustc_span::symbol::Symbol;
11+
use rustc_span::{sym, Span};
912

1013
declare_clippy_lint! {
1114
/// ### What it does
@@ -64,51 +67,98 @@ impl<'tcx> LateLintPass<'tcx> for NonSendFieldInSendTy {
6467
if let self_ty = ty_trait_ref.self_ty();
6568
if let ty::Adt(adt_def, impl_trait_substs) = self_ty.kind();
6669
then {
70+
let mut non_send_fields = Vec::new();
71+
72+
let hir_map = cx.tcx.hir();
6773
for variant in &adt_def.variants {
6874
for field in &variant.fields {
69-
let field_ty = field.ty(cx.tcx, impl_trait_substs);
70-
71-
if raw_pointer_in_ty_def(cx, field_ty)
72-
|| implements_trait(cx, field_ty, send_trait, &[])
73-
|| is_copy(cx, field_ty)
74-
{
75-
continue;
75+
if_chain! {
76+
if let Some(field_hir_id) = field
77+
.did
78+
.as_local()
79+
.map(|local_def_id| hir_map.local_def_id_to_hir_id(local_def_id));
80+
if !is_lint_allowed(cx, NON_SEND_FIELD_IN_SEND_TY, field_hir_id);
81+
if let field_ty = field.ty(cx.tcx, impl_trait_substs);
82+
if !ty_allowed_in_send(cx, field_ty, send_trait);
83+
if let Some(field_span) = hir_map.span_if_local(field.did);
84+
then {
85+
non_send_fields.push(NonSendField {
86+
name: hir_map.name(field_hir_id),
87+
span: field_span,
88+
ty: field_ty,
89+
generic_params: collect_generic_params(cx, field_ty),
90+
})
91+
}
7692
}
93+
}
94+
}
7795

78-
if let Some(field_hir_id) = field
79-
.did
80-
.as_local()
81-
.map(|local_def_id| cx.tcx.hir().local_def_id_to_hir_id(local_def_id))
82-
{
83-
if let Some(field_span) = cx.tcx.hir().span_if_local(field.did) {
84-
span_lint_hir_and_then(
85-
cx,
86-
NON_SEND_FIELD_IN_SEND_TY,
87-
field_hir_id,
88-
field_span,
89-
"non-`Send` field found in a `Send` struct",
90-
|diag| {
91-
diag.span_note(
92-
item.span,
93-
&format!(
94-
"type `{}` doesn't implement `Send` when `{}` is `Send`",
95-
field_ty, self_ty
96-
),
97-
);
98-
if is_ty_param(field_ty) {
99-
diag.help(&format!("add `{}: Send` bound", field_ty));
100-
}
101-
},
96+
if !non_send_fields.is_empty() {
97+
span_lint_and_then(
98+
cx,
99+
NON_SEND_FIELD_IN_SEND_TY,
100+
item.span,
101+
&format!(
102+
"this implementation is unsound, as some fields in `{}` are `!Send`",
103+
self_ty
104+
),
105+
|diag| {
106+
for field in non_send_fields {
107+
diag.span_note(
108+
field.span,
109+
&format!("the field `{}` has type `{}` which is not `Send`", field.name, field.ty),
102110
);
111+
112+
match field.generic_params.len() {
113+
0 => diag.help("use a thread-safe type that implements `Send`"),
114+
1 if is_ty_param(field.ty) => diag.help(&format!("add `{}: Send` bound in `Send` impl", field.ty)),
115+
_ => diag.help(&format!(
116+
"add bounds on type parameter{} `{}` that satisfy `{}: Send`",
117+
if field.generic_params.len() > 1 { "s" } else { "" },
118+
field.generic_params_string(),
119+
field.ty
120+
)),
121+
};
103122
}
104-
}
105-
}
123+
},
124+
)
106125
}
107126
}
108127
}
109128
}
110129
}
111130

131+
struct NonSendField<'tcx> {
132+
name: Symbol,
133+
span: Span,
134+
ty: Ty<'tcx>,
135+
generic_params: Vec<Ty<'tcx>>,
136+
}
137+
138+
impl<'tcx> NonSendField<'tcx> {
139+
fn generic_params_string(&self) -> String {
140+
self.generic_params
141+
.iter()
142+
.map(ToString::to_string)
143+
.collect::<Vec<_>>()
144+
.join(", ")
145+
}
146+
}
147+
148+
fn collect_generic_params<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Vec<Ty<'tcx>> {
149+
ty.walk(cx.tcx)
150+
.filter_map(|inner| match inner.unpack() {
151+
GenericArgKind::Type(inner_ty) => Some(inner_ty),
152+
_ => None,
153+
})
154+
.filter(|&inner_ty| is_ty_param(inner_ty))
155+
.collect()
156+
}
157+
158+
fn ty_allowed_in_send<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, send_trait: DefId) -> bool {
159+
raw_pointer_in_ty_def(cx, ty) || implements_trait(cx, ty, send_trait, &[]) || is_copy(cx, ty)
160+
}
161+
112162
/// Returns `true` if the type itself is a raw pointer or has a raw pointer as a
113163
/// generic parameter, e.g., `Vec<*const u8>`.
114164
/// Note that it does not look into enum variants or struct fields.

tests/ui/non_send_field_in_send_ty.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use std::cell::UnsafeCell;
55
use std::ptr::NonNull;
6+
use std::rc::Rc;
67
use std::sync::{Arc, Mutex, MutexGuard};
78

89
// disrustor / RUSTSEC-2020-0150
@@ -47,6 +48,12 @@ pub struct DeviceHandle<T: UsbContext> {
4748
unsafe impl<T: UsbContext> Send for DeviceHandle<T> {}
4849

4950
// Other basic tests
51+
pub struct NoGeneric {
52+
rc_is_not_send: Rc<String>,
53+
}
54+
55+
unsafe impl Send for NoGeneric {}
56+
5057
pub struct MultiField<T> {
5158
field1: T,
5259
field2: T,
@@ -62,6 +69,13 @@ pub enum MyOption<T> {
6269

6370
unsafe impl<T> Send for MyOption<T> {}
6471

72+
// Multiple type parameters
73+
pub struct MultiParam<A, B> {
74+
vec: Vec<(A, B)>,
75+
}
76+
77+
unsafe impl<A, B> Send for MultiParam<A, B> {}
78+
6579
// Raw pointers are allowed
6680
extern "C" {
6781
type SomeFfiType;

0 commit comments

Comments
 (0)