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