| use rustc_ast::{BorrowKind, UnOp}; | |
| use rustc_hir::{Expr, ExprKind, Mutability, find_attr}; | |
| use rustc_middle::ty::adjustment::{ | |
| Adjust, Adjustment, AutoBorrow, DerefAdjustKind, OverloadedDeref, | |
| }; | |
| use rustc_session::{declare_lint, declare_lint_pass}; | |
| use crate::lints::{ | |
| ImplicitUnsafeAutorefsDiag, ImplicitUnsafeAutorefsMethodNote, ImplicitUnsafeAutorefsOrigin, | |
| ImplicitUnsafeAutorefsSuggestion, | |
| }; | |
| use crate::{LateContext, LateLintPass, LintContext}; | |
| declare_lint! { | |
| /// The `dangerous_implicit_autorefs` lint checks for implicitly taken references | |
| /// to dereferences of raw pointers. | |
| /// | |
| /// ### Example | |
| /// | |
| /// ```rust,compile_fail | |
| /// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] { | |
| /// unsafe { &raw mut (*ptr)[..16] } | |
| /// // ^^^^^^ this calls `IndexMut::index_mut(&mut ..., ..16)`, | |
| /// // implicitly creating a reference | |
| /// } | |
| /// ``` | |
| /// | |
| /// {{produces}} | |
| /// | |
| /// ### Explanation | |
| /// | |
| /// When working with raw pointers it's usually undesirable to create references, | |
| /// since they inflict additional safety requirements. Unfortunately, it's possible | |
| /// to take a reference to the dereference of a raw pointer implicitly, which inflicts | |
| /// the usual reference requirements. | |
| /// | |
| /// If you are sure that you can soundly take a reference, then you can take it explicitly: | |
| /// | |
| /// ```rust | |
| /// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] { | |
| /// unsafe { &raw mut (&mut *ptr)[..16] } | |
| /// } | |
| /// ``` | |
| /// | |
| /// Otherwise try to find an alternative way to achieve your goals using only raw pointers: | |
| /// | |
| /// ```rust | |
| /// use std::ptr; | |
| /// | |
| /// fn fun(ptr: *mut [u8]) -> *mut [u8] { | |
| /// ptr::slice_from_raw_parts_mut(ptr.cast(), 16) | |
| /// } | |
| /// ``` | |
| pub DANGEROUS_IMPLICIT_AUTOREFS, | |
| Deny, | |
| "implicit reference to a dereference of a raw pointer", | |
| report_in_external_macro | |
| } | |
| declare_lint_pass!(ImplicitAutorefs => [DANGEROUS_IMPLICIT_AUTOREFS]); | |
| impl<'tcx> LateLintPass<'tcx> for ImplicitAutorefs { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
| // This logic has mostly been taken from | |
| // <https://github.com/rust-lang/rust/pull/103735#issuecomment-1370420305> | |
| // 5. Either of the following: | |
| // a. A deref followed by any non-deref place projection (that intermediate | |
| // deref will typically be auto-inserted). | |
| // b. A method call annotated with `#[rustc_no_implicit_refs]`. | |
| // c. A deref followed by a `&raw const` or `&raw mut`. | |
| let mut is_coming_from_deref = false; | |
| let inner = match expr.kind { | |
| ExprKind::AddrOf(BorrowKind::Raw, _, inner) => match inner.kind { | |
| ExprKind::Unary(UnOp::Deref, inner) => { | |
| is_coming_from_deref = true; | |
| inner | |
| } | |
| _ => return, | |
| }, | |
| ExprKind::Index(base, _, _) => base, | |
| ExprKind::MethodCall(_, inner, _, _) => { | |
| // PERF: Checking of `#[rustc_no_implicit_refs]` is deferred below | |
| // because checking for attribute is a bit costly. | |
| inner | |
| } | |
| ExprKind::Field(inner, _) => inner, | |
| _ => return, | |
| }; | |
| let typeck = cx.typeck_results(); | |
| let adjustments_table = typeck.adjustments(); | |
| if let Some(adjustments) = adjustments_table.get(inner.hir_id) | |
| // 4. Any number of automatically inserted deref/derefmut calls. | |
| && let adjustments = peel_derefs_adjustments(&**adjustments) | |
| // 3. An automatically inserted reference (might come from a deref). | |
| && let [adjustment] = adjustments | |
| && let Some((borrow_mutbl, through_overloaded_deref)) = has_implicit_borrow(adjustment) | |
| && let ExprKind::Unary(UnOp::Deref, dereferenced) = | |
| // 2. Any number of place projections. | |
| peel_place_mappers(inner).kind | |
| // 1. Deref of a raw pointer. | |
| && typeck.expr_ty(dereferenced).is_raw_ptr() | |
| && let method_did = match expr.kind { | |
| // PERF: 5. b. A method call annotated with `#[rustc_no_implicit_refs]` | |
| ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), | |
| _ => None, | |
| } | |
| && method_did.map(|did| find_attr!(cx.tcx, did, RustcNoImplicitAutorefs)).unwrap_or(true) | |
| { | |
| cx.emit_span_lint( | |
| DANGEROUS_IMPLICIT_AUTOREFS, | |
| expr.span.source_callsite(), | |
| ImplicitUnsafeAutorefsDiag { | |
| raw_ptr_span: dereferenced.span, | |
| raw_ptr_ty: typeck.expr_ty(dereferenced), | |
| origin: if through_overloaded_deref { | |
| ImplicitUnsafeAutorefsOrigin::OverloadedDeref | |
| } else { | |
| ImplicitUnsafeAutorefsOrigin::Autoref { | |
| autoref_span: inner.span, | |
| autoref_ty: typeck.expr_ty_adjusted(inner), | |
| } | |
| }, | |
| method: method_did.map(|did| ImplicitUnsafeAutorefsMethodNote { | |
| def_span: cx.tcx.def_span(did), | |
| method_name: cx.tcx.item_name(did), | |
| }), | |
| suggestion: ImplicitUnsafeAutorefsSuggestion { | |
| mutbl: borrow_mutbl.ref_prefix_str(), | |
| deref: if is_coming_from_deref { "*" } else { "" }, | |
| start_span: inner.span.shrink_to_lo(), | |
| end_span: inner.span.shrink_to_hi(), | |
| }, | |
| }, | |
| ) | |
| } | |
| } | |
| } | |
| /// Peels expressions from `expr` that can map a place. | |
| fn peel_place_mappers<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { | |
| loop { | |
| match expr.kind { | |
| ExprKind::Index(base, _idx, _) => expr = &base, | |
| ExprKind::Field(e, _) => expr = &e, | |
| _ => break expr, | |
| } | |
| } | |
| } | |
| /// Peel derefs adjustments until the last last element. | |
| fn peel_derefs_adjustments<'a>(mut adjs: &'a [Adjustment<'a>]) -> &'a [Adjustment<'a>] { | |
| while let [Adjustment { kind: Adjust::Deref(_), .. }, end @ ..] = adjs | |
| && !end.is_empty() | |
| { | |
| adjs = end; | |
| } | |
| adjs | |
| } | |
| /// Test if some adjustment has some implicit borrow. | |
| /// | |
| /// Returns `Some((mutability, was_an_overloaded_deref))` if the argument adjustment is | |
| /// an implicit borrow (or has an implicit borrow via an overloaded deref). | |
| fn has_implicit_borrow(Adjustment { kind, .. }: &Adjustment<'_>) -> Option<(Mutability, bool)> { | |
| match kind { | |
| &Adjust::Deref(DerefAdjustKind::Overloaded(OverloadedDeref { mutbl, .. })) => { | |
| Some((mutbl, true)) | |
| } | |
| &Adjust::Borrow(AutoBorrow::Ref(mutbl)) => Some((mutbl.into(), false)), | |
| Adjust::NeverToAny | |
| | Adjust::Pointer(..) | |
| | Adjust::Deref(DerefAdjustKind::Builtin | DerefAdjustKind::Pin) | |
| | Adjust::GenericReborrow(..) | |
| | Adjust::Borrow(AutoBorrow::RawPtr(..) | AutoBorrow::Pin(..)) => None, | |
| } | |
| } |