|
| 1 | +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note}; |
| 2 | +use clippy_utils::ty::{implements_trait, is_copy}; |
| 3 | +use rustc_ast::ImplPolarity; |
| 4 | +use rustc_hir::{Item, ItemKind}; |
| 5 | +use rustc_lint::{LateContext, LateLintPass}; |
| 6 | +use rustc_middle::ty::{self, subst::GenericArgKind, Ty}; |
| 7 | +use rustc_session::{declare_lint_pass, declare_tool_lint}; |
| 8 | +use rustc_span::sym; |
| 9 | + |
| 10 | +declare_clippy_lint! { |
| 11 | + /// ### What it does |
| 12 | + /// Warns about a field in a `Send` struct that is neither `Send` nor `Copy`. |
| 13 | + /// |
| 14 | + /// ### Why is this bad? |
| 15 | + /// Sending the struct to another thread and drops it there will also drop |
| 16 | + /// the field in the new thread. This effectively changes the ownership of |
| 17 | + /// the field type to the new thread and creates a soundness issue by |
| 18 | + /// breaking breaks the non-`Send` invariant. |
| 19 | + /// |
| 20 | + /// ### Known Problems |
| 21 | + /// Data structures that contain raw pointers may cause false positives. |
| 22 | + /// They are sometimes safe to be sent across threads but do not implement |
| 23 | + /// the `Send` trait. This lint has a heuristic to filter out basic cases |
| 24 | + /// such as `Vec<*const T>`, but it's not perfect. |
| 25 | + /// |
| 26 | + /// ### Example |
| 27 | + /// ```rust |
| 28 | + /// use std::sync::Arc; |
| 29 | + /// |
| 30 | + /// // There is no `RC: Send` bound here |
| 31 | + /// unsafe impl<RC, T: Send> Send for ArcGuard<RC, T> {} |
| 32 | + /// |
| 33 | + /// #[derive(Debug, Clone)] |
| 34 | + /// pub struct ArcGuard<RC, T> { |
| 35 | + /// inner: T, |
| 36 | + /// // Possibly drops `Arc<RC>` (and in turn `RC`) on a wrong thread |
| 37 | + /// head: Arc<RC> |
| 38 | + /// } |
| 39 | + /// ``` |
| 40 | + pub NON_SEND_FIELD_IN_SEND_TY, |
| 41 | + nursery, |
| 42 | + "a field in a `Send` struct does not implement `Send`" |
| 43 | +} |
| 44 | + |
| 45 | +declare_lint_pass!(NonSendFieldInSendTy => [NON_SEND_FIELD_IN_SEND_TY]); |
| 46 | + |
| 47 | +impl<'tcx> LateLintPass<'tcx> for NonSendFieldInSendTy { |
| 48 | + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { |
| 49 | + let send_trait = cx.tcx.get_diagnostic_item(sym::send_trait).unwrap(); |
| 50 | + |
| 51 | + // Check if we are in `Send` impl item |
| 52 | + if_chain! { |
| 53 | + if let ItemKind::Impl(hir_impl) = &item.kind; |
| 54 | + if let Some(trait_ref) = &hir_impl.of_trait; |
| 55 | + if let Some(trait_id) = trait_ref.trait_def_id(); |
| 56 | + if send_trait == trait_id; |
| 57 | + if let ImplPolarity::Positive = hir_impl.polarity; |
| 58 | + if let Some(ty_trait_ref) = cx.tcx.impl_trait_ref(item.def_id); |
| 59 | + if let self_ty = ty_trait_ref.self_ty(); |
| 60 | + if let ty::Adt(adt_def, impl_trait_substs) = self_ty.kind(); |
| 61 | + then { |
| 62 | + for variant in &adt_def.variants { |
| 63 | + for field in &variant.fields { |
| 64 | + let field_ty = field.ty(cx.tcx, impl_trait_substs); |
| 65 | + |
| 66 | + // TODO: substs rebase_onto |
| 67 | + |
| 68 | + if raw_pointer_in_ty_def(cx, field_ty) |
| 69 | + || implements_trait(cx, field_ty, send_trait, &[]) |
| 70 | + || is_copy(cx, field_ty) |
| 71 | + { |
| 72 | + continue; |
| 73 | + } |
| 74 | + |
| 75 | + if let Some(field_span) = cx.tcx.hir().span_if_local(field.did) { |
| 76 | + if is_ty_param(field_ty) { |
| 77 | + span_lint_and_help( |
| 78 | + cx, |
| 79 | + NON_SEND_FIELD_IN_SEND_TY, |
| 80 | + field_span, |
| 81 | + "a field in a `Send` struct does not implement `Send`", |
| 82 | + Some(item.span), |
| 83 | + &format!("add `{}: Send` in `Send` impl for `{}`", field_ty, self_ty), |
| 84 | + ) |
| 85 | + } else { |
| 86 | + span_lint_and_note( |
| 87 | + cx, |
| 88 | + NON_SEND_FIELD_IN_SEND_TY, |
| 89 | + field_span, |
| 90 | + "a field in a `Send` struct does not implement `Send`", |
| 91 | + Some(item.span), |
| 92 | + &format!( |
| 93 | + "type `{}` doesn't implement `Send` when `{}` is `Send`", |
| 94 | + field_ty, self_ty |
| 95 | + ), |
| 96 | + ) |
| 97 | + } |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + } |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +/// Returns `true` if the type itself is a raw pointer or has a raw pointer as a |
| 107 | +/// generic parameter, e.g., `Vec<*const u8>`. |
| 108 | +/// Note that it does not look into enum variants or struct fields. |
| 109 | +fn raw_pointer_in_ty_def<'tcx>(cx: &LateContext<'tcx>, target_ty: Ty<'tcx>) -> bool { |
| 110 | + for ty_node in target_ty.walk(cx.tcx) { |
| 111 | + if_chain! { |
| 112 | + if let GenericArgKind::Type(inner_ty) = ty_node.unpack(); |
| 113 | + if let ty::RawPtr(_) = inner_ty.kind(); |
| 114 | + then { |
| 115 | + return true; |
| 116 | + } |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + false |
| 121 | +} |
| 122 | + |
| 123 | +/// Returns `true` if the type is a type parameter such as `T`. |
| 124 | +fn is_ty_param<'tcx>(target_ty: Ty<'tcx>) -> bool { |
| 125 | + matches!(target_ty.kind(), ty::Param(_)) |
| 126 | +} |
0 commit comments