rustc_lint/
autorefs.rs

1use rustc_ast::{BorrowKind, UnOp};
2use rustc_hir::{Expr, ExprKind, Mutability};
3use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, OverloadedDeref};
4use rustc_session::{declare_lint, declare_lint_pass};
5use rustc_span::sym;
6
7use crate::lints::{
8    ImplicitUnsafeAutorefsDiag, ImplicitUnsafeAutorefsMethodNote, ImplicitUnsafeAutorefsOrigin,
9    ImplicitUnsafeAutorefsSuggestion,
10};
11use crate::{LateContext, LateLintPass, LintContext};
12
13declare_lint! {
14    /// The `dangerous_implicit_autorefs` lint checks for implicitly taken references
15    /// to dereferences of raw pointers.
16    ///
17    /// ### Example
18    ///
19    /// ```rust
20    /// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] {
21    ///     unsafe { &raw mut (*ptr)[..16] }
22    ///     //                      ^^^^^^ this calls `IndexMut::index_mut(&mut ..., ..16)`,
23    ///     //                             implicitly creating a reference
24    /// }
25    /// ```
26    ///
27    /// {{produces}}
28    ///
29    /// ### Explanation
30    ///
31    /// When working with raw pointers it's usually undesirable to create references,
32    /// since they inflict additional safety requirements. Unfortunately, it's possible
33    /// to take a reference to the dereference of a raw pointer implicitly, which inflicts
34    /// the usual reference requirements.
35    ///
36    /// If you are sure that you can soundly take a reference, then you can take it explicitly:
37    ///
38    /// ```rust
39    /// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] {
40    ///     unsafe { &raw mut (&mut *ptr)[..16] }
41    /// }
42    /// ```
43    ///
44    /// Otherwise try to find an alternative way to achive your goals using only raw pointers:
45    ///
46    /// ```rust
47    /// use std::ptr;
48    ///
49    /// fn fun(ptr: *mut [u8]) -> *mut [u8] {
50    ///     ptr::slice_from_raw_parts_mut(ptr.cast(), 16)
51    /// }
52    /// ```
53    pub DANGEROUS_IMPLICIT_AUTOREFS,
54    Warn,
55    "implicit reference to a dereference of a raw pointer",
56    report_in_external_macro
57}
58
59declare_lint_pass!(ImplicitAutorefs => [DANGEROUS_IMPLICIT_AUTOREFS]);
60
61impl<'tcx> LateLintPass<'tcx> for ImplicitAutorefs {
62    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
63        // This logic has mostly been taken from
64        // <https://github.com/rust-lang/rust/pull/103735#issuecomment-1370420305>
65
66        // 5. Either of the following:
67        //   a. A deref followed by any non-deref place projection (that intermediate
68        //      deref will typically be auto-inserted).
69        //   b. A method call annotated with `#[rustc_no_implicit_refs]`.
70        //   c. A deref followed by a `&raw const` or `&raw mut`.
71        let mut is_coming_from_deref = false;
72        let inner = match expr.kind {
73            ExprKind::AddrOf(BorrowKind::Raw, _, inner) => match inner.kind {
74                ExprKind::Unary(UnOp::Deref, inner) => {
75                    is_coming_from_deref = true;
76                    inner
77                }
78                _ => return,
79            },
80            ExprKind::Index(base, _, _) => base,
81            ExprKind::MethodCall(_, inner, _, _) => {
82                // PERF: Checking of `#[rustc_no_implicit_refs]` is deferred below
83                // because checking for attribute is a bit costly.
84                inner
85            }
86            ExprKind::Field(inner, _) => inner,
87            _ => return,
88        };
89
90        let typeck = cx.typeck_results();
91        let adjustments_table = typeck.adjustments();
92
93        if let Some(adjustments) = adjustments_table.get(inner.hir_id)
94            // 4. Any number of automatically inserted deref/derefmut calls.
95            && let adjustments = peel_derefs_adjustments(&**adjustments)
96            // 3. An automatically inserted reference (might come from a deref).
97            && let [adjustment] = adjustments
98            && let Some((borrow_mutbl, through_overloaded_deref)) = has_implicit_borrow(adjustment)
99            && let ExprKind::Unary(UnOp::Deref, dereferenced) =
100                // 2. Any number of place projections.
101                peel_place_mappers(inner).kind
102            // 1. Deref of a raw pointer.
103            && typeck.expr_ty(dereferenced).is_raw_ptr()
104            && let method_did = match expr.kind {
105                // PERF: 5. b. A method call annotated with `#[rustc_no_implicit_refs]`
106                ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
107                _ => None,
108            }
109            && method_did.map(|did| cx.tcx.has_attr(did, sym::rustc_no_implicit_autorefs)).unwrap_or(true)
110        {
111            cx.emit_span_lint(
112                DANGEROUS_IMPLICIT_AUTOREFS,
113                expr.span.source_callsite(),
114                ImplicitUnsafeAutorefsDiag {
115                    raw_ptr_span: dereferenced.span,
116                    raw_ptr_ty: typeck.expr_ty(dereferenced),
117                    origin: if through_overloaded_deref {
118                        ImplicitUnsafeAutorefsOrigin::OverloadedDeref
119                    } else {
120                        ImplicitUnsafeAutorefsOrigin::Autoref {
121                            autoref_span: inner.span,
122                            autoref_ty: typeck.expr_ty_adjusted(inner),
123                        }
124                    },
125                    method: method_did.map(|did| ImplicitUnsafeAutorefsMethodNote {
126                        def_span: cx.tcx.def_span(did),
127                        method_name: cx.tcx.item_name(did),
128                    }),
129                    suggestion: ImplicitUnsafeAutorefsSuggestion {
130                        mutbl: borrow_mutbl.ref_prefix_str(),
131                        deref: if is_coming_from_deref { "*" } else { "" },
132                        start_span: inner.span.shrink_to_lo(),
133                        end_span: inner.span.shrink_to_hi(),
134                    },
135                },
136            )
137        }
138    }
139}
140
141/// Peels expressions from `expr` that can map a place.
142fn peel_place_mappers<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
143    loop {
144        match expr.kind {
145            ExprKind::Index(base, _idx, _) => expr = &base,
146            ExprKind::Field(e, _) => expr = &e,
147            _ => break expr,
148        }
149    }
150}
151
152/// Peel derefs adjustments until the last last element.
153fn peel_derefs_adjustments<'a>(mut adjs: &'a [Adjustment<'a>]) -> &'a [Adjustment<'a>] {
154    while let [Adjustment { kind: Adjust::Deref(_), .. }, end @ ..] = adjs
155        && !end.is_empty()
156    {
157        adjs = end;
158    }
159    adjs
160}
161
162/// Test if some adjustment has some implicit borrow.
163///
164/// Returns `Some((mutability, was_an_overloaded_deref))` if the argument adjustment is
165/// an implicit borrow (or has an implicit borrow via an overloaded deref).
166fn has_implicit_borrow(Adjustment { kind, .. }: &Adjustment<'_>) -> Option<(Mutability, bool)> {
167    match kind {
168        &Adjust::Deref(Some(OverloadedDeref { mutbl, .. })) => Some((mutbl, true)),
169        &Adjust::Borrow(AutoBorrow::Ref(mutbl)) => Some((mutbl.into(), false)),
170        Adjust::NeverToAny
171        | Adjust::Pointer(..)
172        | Adjust::ReborrowPin(..)
173        | Adjust::Deref(None)
174        | Adjust::Borrow(AutoBorrow::RawPtr(..)) => None,
175    }
176}