rustc_lint/
dangling.rs

1use rustc_ast::visit::{visit_opt, walk_list};
2use rustc_hir::def_id::LocalDefId;
3use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
4use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, LangItem};
5use rustc_middle::ty::{Ty, TyCtxt};
6use rustc_session::{declare_lint, impl_lint_pass};
7use rustc_span::{Span, sym};
8
9use crate::lints::DanglingPointersFromTemporaries;
10use crate::{LateContext, LateLintPass};
11
12declare_lint! {
13    /// The `dangling_pointers_from_temporaries` lint detects getting a pointer to data
14    /// of a temporary that will immediately get dropped.
15    ///
16    /// ### Example
17    ///
18    /// ```rust
19    /// # #![allow(unused)]
20    /// # unsafe fn use_data(ptr: *const u8) { }
21    /// fn gather_and_use(bytes: impl Iterator<Item = u8>) {
22    ///     let x: *const u8 = bytes.collect::<Vec<u8>>().as_ptr();
23    ///     unsafe { use_data(x) }
24    /// }
25    /// ```
26    ///
27    /// {{produces}}
28    ///
29    /// ### Explanation
30    ///
31    /// Getting a pointer from a temporary value will not prolong its lifetime,
32    /// which means that the value can be dropped and the allocation freed
33    /// while the pointer still exists, making the pointer dangling.
34    /// This is not an error (as far as the type system is concerned)
35    /// but probably is not what the user intended either.
36    ///
37    /// If you need stronger guarantees, consider using references instead,
38    /// as they are statically verified by the borrow-checker to never dangle.
39    pub DANGLING_POINTERS_FROM_TEMPORARIES,
40    Warn,
41    "detects getting a pointer from a temporary"
42}
43
44/// FIXME: false negatives (i.e. the lint is not emitted when it should be)
45/// 1. Ways to get a temporary that are not recognized:
46///    - `owning_temporary.field`
47///    - `owning_temporary[index]`
48/// 2. No checks for ref-to-ptr conversions:
49///    - `&raw [mut] temporary`
50///    - `&temporary as *(const|mut) _`
51///    - `ptr::from_ref(&temporary)` and friends
52#[derive(Clone, Copy, Default)]
53pub(crate) struct DanglingPointers;
54
55impl_lint_pass!(DanglingPointers => [DANGLING_POINTERS_FROM_TEMPORARIES]);
56
57// This skips over const blocks, but they cannot use or return a dangling pointer anyways.
58impl<'tcx> LateLintPass<'tcx> for DanglingPointers {
59    fn check_fn(
60        &mut self,
61        cx: &LateContext<'tcx>,
62        _: FnKind<'tcx>,
63        _: &'tcx FnDecl<'tcx>,
64        body: &'tcx Body<'tcx>,
65        _: Span,
66        _: LocalDefId,
67    ) {
68        DanglingPointerSearcher { cx, inside_call_args: false }.visit_body(body)
69    }
70}
71
72/// This produces a dangling pointer:
73/// ```ignore (example)
74/// let ptr = CString::new("hello").unwrap().as_ptr();
75/// foo(ptr)
76/// ```
77///
78/// But this does not:
79/// ```ignore (example)
80/// foo(CString::new("hello").unwrap().as_ptr())
81/// ```
82///
83/// But this does:
84/// ```ignore (example)
85/// foo({ let ptr = CString::new("hello").unwrap().as_ptr(); ptr })
86/// ```
87///
88/// So we have to keep track of when we are inside of a function/method call argument.
89struct DanglingPointerSearcher<'lcx, 'tcx> {
90    cx: &'lcx LateContext<'tcx>,
91    /// Keeps track of whether we are inside of function/method call arguments,
92    /// where this lint should not be emitted.
93    ///
94    /// See [the main doc][`Self`] for examples.
95    inside_call_args: bool,
96}
97
98impl Visitor<'_> for DanglingPointerSearcher<'_, '_> {
99    fn visit_expr(&mut self, expr: &Expr<'_>) -> Self::Result {
100        if !self.inside_call_args {
101            lint_expr(self.cx, expr)
102        }
103        match expr.kind {
104            ExprKind::Call(lhs, args) | ExprKind::MethodCall(_, lhs, args, _) => {
105                self.visit_expr(lhs);
106                self.with_inside_call_args(true, |this| walk_list!(this, visit_expr, args))
107            }
108            ExprKind::Block(&Block { stmts, expr, .. }, _) => {
109                self.with_inside_call_args(false, |this| walk_list!(this, visit_stmt, stmts));
110                visit_opt!(self, visit_expr, expr)
111            }
112            _ => walk_expr(self, expr),
113        }
114    }
115}
116
117impl DanglingPointerSearcher<'_, '_> {
118    fn with_inside_call_args<R>(
119        &mut self,
120        inside_call_args: bool,
121        callback: impl FnOnce(&mut Self) -> R,
122    ) -> R {
123        let old = core::mem::replace(&mut self.inside_call_args, inside_call_args);
124        let result = callback(self);
125        self.inside_call_args = old;
126        result
127    }
128}
129
130fn lint_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
131    if let ExprKind::MethodCall(method, receiver, _args, _span) = expr.kind
132        && is_temporary_rvalue(receiver)
133        && let ty = cx.typeck_results().expr_ty(receiver)
134        && owns_allocation(cx.tcx, ty)
135        && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
136        && cx.tcx.has_attr(fn_id, sym::rustc_as_ptr)
137    {
138        // FIXME: use `emit_node_lint` when `#[primary_span]` is added.
139        cx.tcx.emit_node_span_lint(
140            DANGLING_POINTERS_FROM_TEMPORARIES,
141            expr.hir_id,
142            method.ident.span,
143            DanglingPointersFromTemporaries {
144                callee: method.ident,
145                ty,
146                ptr_span: method.ident.span,
147                temporary_span: receiver.span,
148            },
149        )
150    }
151}
152
153fn is_temporary_rvalue(expr: &Expr<'_>) -> bool {
154    match expr.kind {
155        // Const is not temporary.
156        ExprKind::ConstBlock(..) | ExprKind::Repeat(..) | ExprKind::Lit(..) => false,
157
158        // This is literally lvalue.
159        ExprKind::Path(..) => false,
160
161        // Calls return rvalues.
162        ExprKind::Call(..)
163        | ExprKind::MethodCall(..)
164        | ExprKind::Use(..)
165        | ExprKind::Binary(..) => true,
166
167        // Inner blocks are rvalues.
168        ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::Block(..) => true,
169
170        // FIXME: these should probably recurse and typecheck along the way.
171        //        Some false negatives are possible for now.
172        ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(..) => false,
173
174        ExprKind::Struct(..) => true,
175
176        // FIXME: this has false negatives, but I do not want to deal with 'static/const promotion just yet.
177        ExprKind::Array(..) => false,
178
179        // These typecheck to `!`
180        ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Become(..) => {
181            false
182        }
183
184        // These typecheck to `()`
185        ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Yield(..) => false,
186
187        // Compiler-magic macros
188        ExprKind::AddrOf(..) | ExprKind::OffsetOf(..) | ExprKind::InlineAsm(..) => false,
189
190        // We are not interested in these
191        ExprKind::Cast(..)
192        | ExprKind::Closure(..)
193        | ExprKind::Tup(..)
194        | ExprKind::DropTemps(..)
195        | ExprKind::Let(..) => false,
196
197        ExprKind::UnsafeBinderCast(..) => false,
198
199        // Not applicable
200        ExprKind::Type(..) | ExprKind::Err(..) => false,
201    }
202}
203
204// Array, Vec, String, CString, MaybeUninit, Cell, Box<[_]>, Box<str>, Box<CStr>, UnsafeCell,
205// SyncUnsafeCell, or any of the above in arbitrary many nested Box'es.
206fn owns_allocation(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool {
207    if ty.is_array() {
208        true
209    } else if let Some(inner) = ty.boxed_ty() {
210        inner.is_slice()
211            || inner.is_str()
212            || inner.ty_adt_def().is_some_and(|def| tcx.is_lang_item(def.did(), LangItem::CStr))
213            || owns_allocation(tcx, inner)
214    } else if let Some(def) = ty.ty_adt_def() {
215        for lang_item in [LangItem::String, LangItem::MaybeUninit, LangItem::UnsafeCell] {
216            if tcx.is_lang_item(def.did(), lang_item) {
217                return true;
218            }
219        }
220        tcx.get_diagnostic_name(def.did()).is_some_and(|name| {
221            matches!(name, sym::cstring_type | sym::Vec | sym::Cell | sym::SyncUnsafeCell)
222        })
223    } else {
224        false
225    }
226}