rustc_lint/
dangling.rs

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