summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_lint/src/traits.rs
blob: df1587c5948f58d4cf984e48e852f2cb65f3bc22 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::LateContext;
use crate::LateLintPass;
use crate::LintContext;
use rustc_errors::fluent;
use rustc_hir as hir;
use rustc_span::symbol::sym;

declare_lint! {
    /// The `drop_bounds` lint checks for generics with `std::ops::Drop` as
    /// bounds.
    ///
    /// ### Example
    ///
    /// ```rust
    /// fn foo<T: Drop>() {}
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// A generic trait bound of the form `T: Drop` is most likely misleading
    /// and not what the programmer intended (they probably should have used
    /// `std::mem::needs_drop` instead).
    ///
    /// `Drop` bounds do not actually indicate whether a type can be trivially
    /// dropped or not, because a composite type containing `Drop` types does
    /// not necessarily implement `Drop` itself. Naïvely, one might be tempted
    /// to write an implementation that assumes that a type can be trivially
    /// dropped while also supplying a specialization for `T: Drop` that
    /// actually calls the destructor. However, this breaks down e.g. when `T`
    /// is `String`, which does not implement `Drop` itself but contains a
    /// `Vec`, which does implement `Drop`, so assuming `T` can be trivially
    /// dropped would lead to a memory leak here.
    ///
    /// Furthermore, the `Drop` trait only contains one method, `Drop::drop`,
    /// which may not be called explicitly in user code (`E0040`), so there is
    /// really no use case for using `Drop` in trait bounds, save perhaps for
    /// some obscure corner cases, which can use `#[allow(drop_bounds)]`.
    pub DROP_BOUNDS,
    Warn,
    "bounds of the form `T: Drop` are most likely incorrect"
}

declare_lint! {
    /// The `dyn_drop` lint checks for trait objects with `std::ops::Drop`.
    ///
    /// ### Example
    ///
    /// ```rust
    /// fn foo(_x: Box<dyn Drop>) {}
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// A trait object bound of the form `dyn Drop` is most likely misleading
    /// and not what the programmer intended.
    ///
    /// `Drop` bounds do not actually indicate whether a type can be trivially
    /// dropped or not, because a composite type containing `Drop` types does
    /// not necessarily implement `Drop` itself. Naïvely, one might be tempted
    /// to write a deferred drop system, to pull cleaning up memory out of a
    /// latency-sensitive code path, using `dyn Drop` trait objects. However,
    /// this breaks down e.g. when `T` is `String`, which does not implement
    /// `Drop`, but should probably be accepted.
    ///
    /// To write a trait object bound that accepts anything, use a placeholder
    /// trait with a blanket implementation.
    ///
    /// ```rust
    /// trait Placeholder {}
    /// impl<T> Placeholder for T {}
    /// fn foo(_x: Box<dyn Placeholder>) {}
    /// ```
    pub DYN_DROP,
    Warn,
    "trait objects of the form `dyn Drop` are useless"
}

declare_lint_pass!(
    /// Lint for bounds of the form `T: Drop`, which usually
    /// indicate an attempt to emulate `std::mem::needs_drop`.
    DropTraitConstraints => [DROP_BOUNDS, DYN_DROP]
);

impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
        use rustc_middle::ty::PredicateKind::*;

        let predicates = cx.tcx.explicit_predicates_of(item.def_id);
        for &(predicate, span) in predicates.predicates {
            let Trait(trait_predicate) = predicate.kind().skip_binder() else {
                continue
            };
            let def_id = trait_predicate.trait_ref.def_id;
            if cx.tcx.lang_items().drop_trait() == Some(def_id) {
                // Explicitly allow `impl Drop`, a drop-guards-as-Voldemort-type pattern.
                if trait_predicate.trait_ref.self_ty().is_impl_trait() {
                    continue;
                }
                cx.struct_span_lint(DROP_BOUNDS, span, |lint| {
                    let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else {
                        return
                    };
                    lint.build(fluent::lint::drop_trait_constraints)
                        .set_arg("predicate", predicate)
                        .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
                        .emit();
                });
            }
        }
    }

    fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx>) {
        let hir::TyKind::TraitObject(bounds, _lifetime, _syntax) = &ty.kind else {
            return
        };
        for bound in &bounds[..] {
            let def_id = bound.trait_ref.trait_def_id();
            if cx.tcx.lang_items().drop_trait() == def_id {
                cx.struct_span_lint(DYN_DROP, bound.span, |lint| {
                    let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else {
                        return
                    };
                    lint.build(fluent::lint::drop_glue)
                        .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
                        .emit();
                });
            }
        }
    }
}