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}