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