Skip to content

Commit 2e9ee90

Browse files
committed
implement lint for suspicious auto trait impls
1 parent 2bae730 commit 2e9ee90

File tree

8 files changed

+359
-7
lines changed

8 files changed

+359
-7
lines changed

compiler/rustc_index/src/bit_set.rs

+6
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,12 @@ pub struct GrowableBitSet<T: Idx> {
938938
bit_set: BitSet<T>,
939939
}
940940

941+
impl<T: Idx> Default for GrowableBitSet<T> {
942+
fn default() -> Self {
943+
GrowableBitSet::new_empty()
944+
}
945+
}
946+
941947
impl<T: Idx> GrowableBitSet<T> {
942948
/// Ensure that the set can hold at least `min_domain_size` elements.
943949
pub fn ensure(&mut self, min_domain_size: usize) {

compiler/rustc_lint_defs/src/builtin.rs

+35
Original file line numberDiff line numberDiff line change
@@ -3050,6 +3050,7 @@ declare_lint_pass! {
30503050
DEREF_INTO_DYN_SUPERTRAIT,
30513051
DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
30523052
DUPLICATE_MACRO_ATTRIBUTES,
3053+
SUSPICIOUS_AUTO_TRAIT_IMPLS,
30533054
]
30543055
}
30553056

@@ -3626,3 +3627,37 @@ declare_lint! {
36263627
Warn,
36273628
"duplicated attribute"
36283629
}
3630+
3631+
declare_lint! {
3632+
/// The `suspicious_auto_trait_impls` lint checks for potentially incorrect
3633+
/// implementations of auto traits.
3634+
///
3635+
/// ### Example
3636+
///
3637+
/// ```rust
3638+
/// struct Foo<T>(T);
3639+
///
3640+
/// unsafe impl<T> Send for Foo<*const T> {}
3641+
/// ```
3642+
///
3643+
/// {{produces}}
3644+
///
3645+
/// ### Explanation
3646+
///
3647+
/// A type can implement auto traits, e.g. `Send`, `Sync` and `Unpin`,
3648+
/// in two different ways: either by writing an explicit impl or if
3649+
/// all fields of the type implement that auto trait.
3650+
///
3651+
/// The compiler disables the automatic implementation if an explicit one
3652+
/// exists for given type constructor. The exact rules governing this
3653+
/// are currently unsound and quite subtle and and will be modified in the future.
3654+
/// This change will cause the automatic implementation to be disabled in more
3655+
/// cases, potentially breaking some code.
3656+
pub SUSPICIOUS_AUTO_TRAIT_IMPLS,
3657+
Warn,
3658+
"the rules governing auto traits will change in the future",
3659+
@future_incompatible = FutureIncompatibleInfo {
3660+
reason: FutureIncompatibilityReason::FutureReleaseSemanticsChange,
3661+
reference: "issue #93367 <https://github.com/rust-lang/rust/issues/93367>",
3662+
};
3663+
}

compiler/rustc_middle/src/ty/trait_def.rs

+17
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,23 @@ impl<'tcx> TyCtxt<'tcx> {
144144
});
145145
}
146146

147+
pub fn non_blanket_impls_for_ty(
148+
self,
149+
def_id: DefId,
150+
self_ty: Ty<'tcx>,
151+
) -> impl Iterator<Item = DefId> + 'tcx {
152+
let impls = self.trait_impls_of(def_id);
153+
if let Some(simp) =
154+
fast_reject::simplify_type(self, self_ty, SimplifyParams::No, StripReferences::No)
155+
{
156+
if let Some(impls) = impls.non_blanket_impls.get(&simp) {
157+
return impls.iter().copied();
158+
}
159+
}
160+
161+
[].iter().copied()
162+
}
163+
147164
/// Applies function to every impl that could possibly match the self type `self_ty` and returns
148165
/// the first non-none value.
149166
pub fn find_map_relevant_impl<T, F: FnMut(DefId) -> Option<T>>(

compiler/rustc_typeck/src/coherence/orphan.rs

+210-3
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
11
//! Orphan checker: every impl either implements a trait defined in this
22
//! crate or pertains to a type defined in this crate.
33
4+
use rustc_data_structures::fx::FxHashSet;
45
use rustc_errors::struct_span_err;
56
use rustc_errors::ErrorReported;
67
use rustc_hir as hir;
8+
use rustc_index::bit_set::GrowableBitSet;
79
use rustc_infer::infer::TyCtxtInferExt;
8-
use rustc_middle::ty::{self, TyCtxt};
9-
use rustc_span::def_id::LocalDefId;
10+
use rustc_middle::ty::subst::{GenericArg, InternalSubsts};
11+
use rustc_middle::ty::{self, ImplPolarity, Ty, TyCtxt, TypeFoldable, TypeVisitor};
12+
use rustc_session::lint;
13+
use rustc_span::def_id::{DefId, LocalDefId};
1014
use rustc_span::Span;
1115
use rustc_trait_selection::traits;
16+
use std::ops::ControlFlow;
1217

1318
pub(super) fn orphan_check_crate(tcx: TyCtxt<'_>, (): ()) -> &[LocalDefId] {
1419
let mut errors = Vec::new();
15-
for (_trait, impls_of_trait) in tcx.all_local_trait_impls(()) {
20+
for (&trait_def_id, impls_of_trait) in tcx.all_local_trait_impls(()) {
1621
for &impl_of_trait in impls_of_trait {
1722
match orphan_check_impl(tcx, impl_of_trait) {
1823
Ok(()) => {}
1924
Err(ErrorReported) => errors.push(impl_of_trait),
2025
}
2126
}
27+
28+
if tcx.trait_is_auto(trait_def_id) {
29+
lint_auto_trait_impls(tcx, trait_def_id, impls_of_trait);
30+
}
2231
}
2332
tcx.arena.alloc_slice(&errors)
2433
}
@@ -265,3 +274,201 @@ fn emit_orphan_check_error<'tcx>(
265274

266275
Err(ErrorReported)
267276
}
277+
278+
#[derive(Default)]
279+
struct AreUniqueParamsVisitor {
280+
seen: GrowableBitSet<u32>,
281+
}
282+
283+
#[derive(Copy, Clone)]
284+
enum NotUniqueParam<'tcx> {
285+
DuplicateParam(GenericArg<'tcx>),
286+
NotParam(GenericArg<'tcx>),
287+
}
288+
289+
impl<'tcx> TypeVisitor<'tcx> for AreUniqueParamsVisitor {
290+
type BreakTy = NotUniqueParam<'tcx>;
291+
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
292+
match t.kind() {
293+
ty::Param(p) => {
294+
if self.seen.insert(p.index) {
295+
ControlFlow::CONTINUE
296+
} else {
297+
ControlFlow::Break(NotUniqueParam::DuplicateParam(t.into()))
298+
}
299+
}
300+
_ => ControlFlow::Break(NotUniqueParam::NotParam(t.into())),
301+
}
302+
}
303+
fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
304+
match r {
305+
ty::ReEarlyBound(p) => {
306+
if self.seen.insert(p.index) {
307+
ControlFlow::CONTINUE
308+
} else {
309+
ControlFlow::Break(NotUniqueParam::DuplicateParam(r.into()))
310+
}
311+
}
312+
_ => ControlFlow::Break(NotUniqueParam::NotParam(r.into())),
313+
}
314+
}
315+
fn visit_const(&mut self, c: &'tcx ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
316+
match c.val {
317+
ty::ConstKind::Param(p) => {
318+
if self.seen.insert(p.index) {
319+
ControlFlow::CONTINUE
320+
} else {
321+
ControlFlow::Break(NotUniqueParam::DuplicateParam(c.into()))
322+
}
323+
}
324+
_ => ControlFlow::Break(NotUniqueParam::NotParam(c.into())),
325+
}
326+
}
327+
}
328+
329+
/// Lint impls of auto traits if they are likely to have
330+
/// unsound or surprising effects on auto impls.
331+
fn lint_auto_trait_impls(tcx: TyCtxt<'_>, trait_def_id: DefId, impls: &[LocalDefId]) {
332+
let mut non_covering_impls = Vec::new();
333+
for &impl_def_id in impls {
334+
let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap();
335+
if trait_ref.references_error() {
336+
return;
337+
}
338+
339+
if tcx.impl_polarity(impl_def_id) != ImplPolarity::Positive {
340+
return;
341+
}
342+
343+
assert_eq!(trait_ref.substs.len(), 1);
344+
let self_ty = trait_ref.self_ty();
345+
let (self_type_did, substs) = match self_ty.kind() {
346+
ty::Adt(def, substs) => (def.did, substs),
347+
_ => {
348+
// FIXME: should also lint for stuff like `&i32` but
349+
// considering that auto traits are unstable, that
350+
// isn't too important for now as this only affects
351+
// crates using `nightly`, and std.
352+
continue;
353+
}
354+
};
355+
356+
// Impls which completely cover a given root type are fine as they
357+
// disable auto impls entirely. So only lint if the substs
358+
// are not a permutation of the identity substs.
359+
match substs.visit_with(&mut AreUniqueParamsVisitor::default()) {
360+
ControlFlow::Continue(()) => {} // ok
361+
ControlFlow::Break(arg) => {
362+
// Ideally:
363+
//
364+
// - compute the requirements for the auto impl candidate
365+
// - check whether these are implied by the non covering impls
366+
// - if not, emit the lint
367+
//
368+
// What we do here is a bit simpler:
369+
//
370+
// - badly check if an auto impl candidate definitely does not apply
371+
// for the given simplified type
372+
// - if so, do not lint
373+
if fast_reject_auto_impl(tcx, trait_def_id, self_ty) {
374+
// ok
375+
} else {
376+
non_covering_impls.push((impl_def_id, self_type_did, arg));
377+
}
378+
}
379+
}
380+
}
381+
382+
for &(impl_def_id, self_type_did, arg) in &non_covering_impls {
383+
tcx.struct_span_lint_hir(
384+
lint::builtin::SUSPICIOUS_AUTO_TRAIT_IMPLS,
385+
tcx.hir().local_def_id_to_hir_id(impl_def_id),
386+
tcx.def_span(impl_def_id),
387+
|err| {
388+
let mut err = err.build(&format!(
389+
"cross-crate traits with a default impl, like `{}`, \
390+
should not be specialized",
391+
tcx.def_path_str(trait_def_id),
392+
));
393+
let item_span = tcx.def_span(self_type_did);
394+
let self_descr = tcx.def_kind(self_type_did).descr(self_type_did);
395+
err.span_note(
396+
item_span,
397+
&format!(
398+
"try using the same sequence of generic parameters as the {} definition",
399+
self_descr,
400+
),
401+
);
402+
match arg {
403+
NotUniqueParam::DuplicateParam(arg) => {
404+
err.note(&format!("`{}` is mentioned multiple times", arg));
405+
}
406+
NotUniqueParam::NotParam(arg) => {
407+
err.note(&format!("`{}` is not a generic parameter", arg));
408+
}
409+
}
410+
err.emit();
411+
},
412+
);
413+
}
414+
}
415+
416+
fn fast_reject_auto_impl<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId, self_ty: Ty<'tcx>) -> bool {
417+
struct DisableAutoTraitVisitor<'tcx> {
418+
tcx: TyCtxt<'tcx>,
419+
trait_def_id: DefId,
420+
self_ty_root: Ty<'tcx>,
421+
seen: FxHashSet<DefId>,
422+
}
423+
424+
impl<'tcx> TypeVisitor<'tcx> for DisableAutoTraitVisitor<'tcx> {
425+
type BreakTy = ();
426+
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
427+
let tcx = self.tcx;
428+
if t != self.self_ty_root {
429+
for impl_def_id in tcx.non_blanket_impls_for_ty(self.trait_def_id, t) {
430+
match tcx.impl_polarity(impl_def_id) {
431+
ImplPolarity::Negative => return ControlFlow::BREAK,
432+
ImplPolarity::Reservation => {}
433+
// FIXME(@lcnr): That's probably not good enough, idk
434+
//
435+
// We might just want to take the rustdoc code and somehow avoid
436+
// explicit impls for `Self`.
437+
ImplPolarity::Positive => return ControlFlow::CONTINUE,
438+
}
439+
}
440+
}
441+
442+
match t.kind() {
443+
ty::Adt(def, substs) => {
444+
// @lcnr: This is the only place where cycles can happen. We avoid this
445+
// by only visiting each `DefId` once.
446+
//
447+
// This will be is incorrect in subtle cases, but I don't care :)
448+
if self.seen.insert(def.did) {
449+
for ty in def.all_fields().map(|field| field.ty(tcx, substs)) {
450+
ty.visit_with(self)?;
451+
}
452+
}
453+
454+
ControlFlow::CONTINUE
455+
}
456+
_ => t.super_visit_with(self),
457+
}
458+
}
459+
}
460+
461+
let self_ty_root = match self_ty.kind() {
462+
ty::Adt(def, _) => tcx.mk_adt(def, InternalSubsts::identity_for_item(tcx, def.did)),
463+
_ => unimplemented!("unexpected self ty {:?}", self_ty),
464+
};
465+
466+
self_ty_root
467+
.visit_with(&mut DisableAutoTraitVisitor {
468+
tcx,
469+
self_ty_root,
470+
trait_def_id,
471+
seen: FxHashSet::default(),
472+
})
473+
.is_break()
474+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![deny(suspicious_auto_trait_impls)]
2+
3+
struct MayImplementSendOk<T>(T);
4+
unsafe impl<T: Send> Send for MayImplementSendOk<T> {} // ok
5+
6+
struct MayImplementSendErr<T>(T);
7+
unsafe impl<T: Send> Send for MayImplementSendErr<&T> {}
8+
//~^ ERROR
9+
//~| WARNING this will change its meaning
10+
11+
struct ContainsNonSendDirect<T>(*const T);
12+
unsafe impl<T: Send> Send for ContainsNonSendDirect<&T> {} // ok
13+
14+
struct ContainsPtr<T>(*const T);
15+
struct ContainsIndirectNonSend<T>(ContainsPtr<T>);
16+
unsafe impl<T: Send> Send for ContainsIndirectNonSend<&T> {} // ok
17+
18+
struct ContainsVec<T>(Vec<T>);
19+
unsafe impl Send for ContainsVec<i32> {}
20+
//~^ ERROR
21+
//~| WARNING this will change its meaning
22+
23+
struct TwoParams<T, U>(T, U);
24+
unsafe impl<T: Send, U: Send> Send for TwoParams<T, U> {} // ok
25+
26+
struct TwoParamsFlipped<T, U>(T, U);
27+
unsafe impl<T: Send, U: Send> Send for TwoParamsFlipped<U, T> {} // ok
28+
29+
struct TwoParamsSame<T, U>(T, U);
30+
unsafe impl<T: Send> Send for TwoParamsSame<T, T> {}
31+
//~^ ERROR
32+
//~| WARNING this will change its meaning
33+
34+
fn main() {}

0 commit comments

Comments
 (0)