Skip to main content

rustc_lint/
dangling.rs

1use rustc_ast::visit::{visit_opt, walk_list};
2use rustc_attr_ir::find_attr;
3use rustc_attr_ir::lang_items::LangItem;
4use rustc_hir::def::Res;
5use rustc_hir::def_id::LocalDefId;
6use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
7use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, TyKind};
8use rustc_lint_defs::{declare_lint, impl_lint_pass};
9use rustc_middle::ty::{self, Ty, TyCtxt};
10use rustc_span::{Span, sym};
11
12use crate::diagnostics::{DanglingPointersFromLocals, DanglingPointersFromTemporaries};
13use crate::{LateContext, LateLintPass};
14
15#[doc =
r" The `dangling_pointers_from_temporaries` lint detects getting a pointer to data"]
#[doc = r" of a temporary that will immediately get dropped."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" # #![allow(unused)]"]
#[doc = r" # unsafe fn use_data(ptr: *const u8) { }"]
#[doc = r" fn gather_and_use(bytes: impl Iterator<Item = u8>) {"]
#[doc = r"     let x: *const u8 = bytes.collect::<Vec<u8>>().as_ptr();"]
#[doc = r"     unsafe { use_data(x) }"]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Getting a pointer from a temporary value will not prolong its lifetime,"]
#[doc =
r" which means that the value can be dropped and the allocation freed"]
#[doc = r" while the pointer still exists, making the pointer dangling."]
#[doc = r" This is not an error (as far as the type system is concerned)"]
#[doc = r" but probably is not what the user intended either."]
#[doc = r""]
#[doc =
r" If you need stronger guarantees, consider using references instead,"]
#[doc =
r" as they are statically verified by the borrow-checker to never dangle."]
pub static DANGLING_POINTERS_FROM_TEMPORARIES: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "DANGLING_POINTERS_FROM_TEMPORARIES",
            default_level: ::rustc_lint_defs::Warn,
            desc: "detects getting a pointer from a temporary",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
16    /// The `dangling_pointers_from_temporaries` lint detects getting a pointer to data
17    /// of a temporary that will immediately get dropped.
18    ///
19    /// ### Example
20    ///
21    /// ```rust
22    /// # #![allow(unused)]
23    /// # unsafe fn use_data(ptr: *const u8) { }
24    /// fn gather_and_use(bytes: impl Iterator<Item = u8>) {
25    ///     let x: *const u8 = bytes.collect::<Vec<u8>>().as_ptr();
26    ///     unsafe { use_data(x) }
27    /// }
28    /// ```
29    ///
30    /// {{produces}}
31    ///
32    /// ### Explanation
33    ///
34    /// Getting a pointer from a temporary value will not prolong its lifetime,
35    /// which means that the value can be dropped and the allocation freed
36    /// while the pointer still exists, making the pointer dangling.
37    /// This is not an error (as far as the type system is concerned)
38    /// but probably is not what the user intended either.
39    ///
40    /// If you need stronger guarantees, consider using references instead,
41    /// as they are statically verified by the borrow-checker to never dangle.
42    pub DANGLING_POINTERS_FROM_TEMPORARIES,
43    Warn,
44    "detects getting a pointer from a temporary"
45}
46
47#[doc =
r" The `dangling_pointers_from_locals` lint detects getting a pointer to data"]
#[doc = r" of a local that will be dropped at the end of the function."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" fn f() -> *const u8 {"]
#[doc = r"     let x = 0;"]
#[doc = r"     &x // returns a dangling ptr to `x`"]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Returning a pointer from a local value will not prolong its lifetime,"]
#[doc =
r" which means that the value can be dropped and the allocation freed"]
#[doc = r" while the pointer still exists, making the pointer dangling."]
#[doc = r" This is not an error (as far as the type system is concerned)"]
#[doc = r" but probably is not what the user intended either."]
#[doc = r""]
#[doc =
r" If you need stronger guarantees, consider using references instead,"]
#[doc =
r" as they are statically verified by the borrow-checker to never dangle."]
pub static DANGLING_POINTERS_FROM_LOCALS: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "DANGLING_POINTERS_FROM_LOCALS",
            default_level: ::rustc_lint_defs::Warn,
            desc: "detects returning a pointer from a local variable",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
48    /// The `dangling_pointers_from_locals` lint detects getting a pointer to data
49    /// of a local that will be dropped at the end of the function.
50    ///
51    /// ### Example
52    ///
53    /// ```rust
54    /// fn f() -> *const u8 {
55    ///     let x = 0;
56    ///     &x // returns a dangling ptr to `x`
57    /// }
58    /// ```
59    ///
60    /// {{produces}}
61    ///
62    /// ### Explanation
63    ///
64    /// Returning a pointer from a local value will not prolong its lifetime,
65    /// which means that the value can be dropped and the allocation freed
66    /// while the pointer still exists, making the pointer dangling.
67    /// This is not an error (as far as the type system is concerned)
68    /// but probably is not what the user intended either.
69    ///
70    /// If you need stronger guarantees, consider using references instead,
71    /// as they are statically verified by the borrow-checker to never dangle.
72    pub DANGLING_POINTERS_FROM_LOCALS,
73    Warn,
74    "detects returning a pointer from a local variable"
75}
76
77/// FIXME: false negatives (i.e. the lint is not emitted when it should be)
78/// 1. Ways to get a temporary that are not recognized:
79///    - `owning_temporary.field`
80///    - `owning_temporary[index]`
81/// 2. No checks for ref-to-ptr conversions:
82///    - `&raw [mut] temporary`
83///    - `&temporary as *(const|mut) _`
84///    - `ptr::from_ref(&temporary)` and friends
85#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for DanglingPointers { }
#[automatically_derived]
impl ::core::clone::Clone for DanglingPointers {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for DanglingPointers { }Copy, #[automatically_derived]
impl ::core::default::Default for DanglingPointers {
    #[inline]
    fn default() -> Self { Self }
}Default)]
86pub(crate) struct DanglingPointers;
87
88impl ::rustc_lint_defs::LintPass for DanglingPointers {
    fn name(&self) -> &'static str { "DanglingPointers" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [DANGLING_POINTERS_FROM_TEMPORARIES,
                        DANGLING_POINTERS_FROM_LOCALS]))
    }
}
impl DanglingPointers {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [DANGLING_POINTERS_FROM_TEMPORARIES,
                        DANGLING_POINTERS_FROM_LOCALS]))
    }
}impl_lint_pass!(DanglingPointers => [DANGLING_POINTERS_FROM_TEMPORARIES, DANGLING_POINTERS_FROM_LOCALS]);
89
90// This skips over const blocks, but they cannot use or return a dangling pointer anyways.
91impl<'tcx> LateLintPass<'tcx> for DanglingPointers {
92    fn check_fn(
93        &mut self,
94        cx: &LateContext<'tcx>,
95        fn_kind: FnKind<'tcx>,
96        fn_decl: &'tcx FnDecl<'tcx>,
97        body: &'tcx Body<'tcx>,
98        _: Span,
99        def_id: LocalDefId,
100    ) {
101        DanglingPointerSearcher { cx, inside_call_args: false }.visit_body(body);
102
103        if let FnRetTy::Return(ret_ty) = &fn_decl.output
104            && let TyKind::Ptr(_) = ret_ty.kind
105        {
106            // get the return type of the function or closure
107            let ty = match cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip().kind() {
108                ty::FnDef(..) => cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(),
109                ty::Closure(_, args) => args.as_closure().sig(),
110                _ => return,
111            };
112            let ty = ty.output();
113
114            // this type is only used for layout computation and pretty-printing, neither of them rely on regions
115            let ty = cx.tcx.instantiate_bound_regions_with_erased(ty);
116
117            // verify that we have a pointer type
118            let inner_ty = match ty.kind() {
119                ty::RawPtr(inner_ty, _) => *inner_ty,
120                _ => return,
121            };
122
123            if cx
124                .tcx
125                .layout_of(cx.typing_env().as_query_input(inner_ty))
126                .is_ok_and(|layout| !layout.is_1zst())
127            {
128                let dcx = &DanglingPointerLocalContext {
129                    body: def_id,
130                    fn_ret: ty,
131                    fn_ret_span: ret_ty.span,
132                    fn_ret_inner: inner_ty,
133                    fn_kind: match fn_kind {
134                        FnKind::ItemFn(..) => "function",
135                        FnKind::Method(..) => "method",
136                        FnKind::Closure => "closure",
137                    },
138                };
139
140                // look for `return`s
141                DanglingPointerReturnSearcher { cx, dcx }.visit_body(body);
142
143                // analyze implicit return expression
144                if let ExprKind::Block(block, None) = &body.value.kind
145                    && let innermost_block = block.innermost_block()
146                    && let Some(expr) = innermost_block.expr
147                {
148                    lint_addr_of_local(cx, dcx, expr);
149                }
150            }
151        }
152    }
153}
154
155struct DanglingPointerLocalContext<'tcx> {
156    body: LocalDefId,
157    fn_ret: Ty<'tcx>,
158    fn_ret_span: Span,
159    fn_ret_inner: Ty<'tcx>,
160    fn_kind: &'static str,
161}
162
163struct DanglingPointerReturnSearcher<'lcx, 'tcx> {
164    cx: &'lcx LateContext<'tcx>,
165    dcx: &'lcx DanglingPointerLocalContext<'tcx>,
166}
167
168impl<'tcx> Visitor<'tcx> for DanglingPointerReturnSearcher<'_, 'tcx> {
169    fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result {
170        if let ExprKind::Ret(Some(expr)) = expr.kind {
171            lint_addr_of_local(self.cx, self.dcx, expr);
172        }
173        walk_expr(self, expr)
174    }
175}
176
177/// Look for `&<path_to_local_in_same_body>` pattern and emit lint for it
178fn lint_addr_of_local<'a>(
179    cx: &LateContext<'a>,
180    dcx: &DanglingPointerLocalContext<'a>,
181    expr: &'a Expr<'a>,
182) {
183    // peel casts as they do not interest us here, we want the inner expression.
184    let inner = super::utils::peel_casts(cx, expr);
185
186    if let ExprKind::AddrOf(_, _, inner_of) = inner.kind
187        && let ExprKind::Path(ref qpath) = inner_of.peel_blocks().kind
188        && let Res::Local(from) = cx.qpath_res(qpath, inner_of.hir_id)
189        && cx.tcx.hir_enclosing_body_owner(from) == dcx.body
190    {
191        cx.tcx.emit_node_span_lint(
192            DANGLING_POINTERS_FROM_LOCALS,
193            expr.hir_id,
194            expr.span,
195            DanglingPointersFromLocals {
196                ret_ty: dcx.fn_ret,
197                ret_ty_span: dcx.fn_ret_span,
198                fn_kind: dcx.fn_kind,
199                local_var: cx.tcx.hir_span(from),
200                local_var_name: cx.tcx.hir_ident(from),
201                local_var_ty: dcx.fn_ret_inner,
202                created_at: (expr.hir_id != inner.hir_id).then_some(inner.span),
203            },
204        );
205    }
206}
207
208/// This produces a dangling pointer:
209/// ```ignore (example)
210/// let ptr = CString::new("hello").unwrap().as_ptr();
211/// foo(ptr)
212/// ```
213///
214/// But this does not:
215/// ```ignore (example)
216/// foo(CString::new("hello").unwrap().as_ptr())
217/// ```
218///
219/// But this does:
220/// ```ignore (example)
221/// foo({ let ptr = CString::new("hello").unwrap().as_ptr(); ptr })
222/// ```
223///
224/// So we have to keep track of when we are inside of a function/method call argument.
225struct DanglingPointerSearcher<'lcx, 'tcx> {
226    cx: &'lcx LateContext<'tcx>,
227    /// Keeps track of whether we are inside of function/method call arguments,
228    /// where this lint should not be emitted.
229    ///
230    /// See [the main doc][`Self`] for examples.
231    inside_call_args: bool,
232}
233
234impl Visitor<'_> for DanglingPointerSearcher<'_, '_> {
235    fn visit_expr(&mut self, expr: &Expr<'_>) -> Self::Result {
236        if !self.inside_call_args {
237            lint_expr(self.cx, expr)
238        }
239        match expr.kind {
240            ExprKind::Call(lhs, args) | ExprKind::MethodCall(_, lhs, args, _) => {
241                self.visit_expr(lhs);
242                self.with_inside_call_args(true, |this| for elem in args {
    match ::rustc_ast_ir::visit::VisitorResult::branch(this.visit_expr(elem))
        {
        core::ops::ControlFlow::Continue(()) =>
            (),
            #[allow(unreachable_code)]
            core::ops::ControlFlow::Break(r) => {
            return ::rustc_ast_ir::visit::VisitorResult::from_residual(r);
        }
    };
}walk_list!(this, visit_expr, args))
243            }
244            ExprKind::Block(&Block { stmts, expr, .. }, _) => {
245                self.with_inside_call_args(false, |this| for elem in stmts {
    match ::rustc_ast_ir::visit::VisitorResult::branch(this.visit_stmt(elem))
        {
        core::ops::ControlFlow::Continue(()) =>
            (),
            #[allow(unreachable_code)]
            core::ops::ControlFlow::Break(r) => {
            return ::rustc_ast_ir::visit::VisitorResult::from_residual(r);
        }
    };
}walk_list!(this, visit_stmt, stmts));
246                if let Some(x) = expr {
    match ::rustc_ast_ir::visit::VisitorResult::branch(self.visit_expr(x)) {
        core::ops::ControlFlow::Continue(()) =>
            (),
            #[allow(unreachable_code)]
            core::ops::ControlFlow::Break(r) => {
            return ::rustc_ast_ir::visit::VisitorResult::from_residual(r);
        }
    };
}visit_opt!(self, visit_expr, expr)
247            }
248            _ => walk_expr(self, expr),
249        }
250    }
251}
252
253impl DanglingPointerSearcher<'_, '_> {
254    fn with_inside_call_args<R>(
255        &mut self,
256        inside_call_args: bool,
257        callback: impl FnOnce(&mut Self) -> R,
258    ) -> R {
259        let old = core::mem::replace(&mut self.inside_call_args, inside_call_args);
260        let result = callback(self);
261        self.inside_call_args = old;
262        result
263    }
264}
265
266fn lint_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
267    if let ExprKind::MethodCall(method, receiver, _args, _span) = expr.kind
268        && is_temporary_rvalue(receiver)
269        && let ty = cx.typeck_results().expr_ty(receiver)
270        && owns_allocation(cx.tcx, ty)
271        && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
272        && {
        {
            'done:
                {
                for i in ::rustc_attr_ir::HasAttrs::get_attrs(fn_id, &cx.tcx)
                    {
                    #[allow(unused_imports)]
                    use ::rustc_attr_ir::AttributeKind::*;
                    let i: &::rustc_attr_ir::Attribute = i;
                    match i {
                        ::rustc_attr_ir::Attribute::Parsed(RustcAsPtr) => {
                            break 'done Some(());
                        }
                        ::rustc_attr_ir::Attribute::Unparsed(..) =>
                            {}
                            #[deny(unreachable_patterns)]
                            _ => {}
                    }
                }
                None
            }
        }
    }.is_some()find_attr!(cx.tcx, fn_id, RustcAsPtr)
273    {
274        cx.tcx.emit_node_span_lint(
275            DANGLING_POINTERS_FROM_TEMPORARIES,
276            expr.hir_id,
277            method.ident.span,
278            DanglingPointersFromTemporaries {
279                callee: method.ident,
280                ty,
281                ptr_span: method.ident.span,
282                temporary_span: receiver.span,
283            },
284        )
285    }
286}
287
288fn is_temporary_rvalue(expr: &Expr<'_>) -> bool {
289    match expr.kind {
290        // Const is not temporary.
291        ExprKind::ConstBlock(..) | ExprKind::Repeat(..) | ExprKind::Lit(..) => false,
292
293        // This is literally lvalue.
294        ExprKind::Path(..) => false,
295
296        // Calls return rvalues.
297        ExprKind::Call(..)
298        | ExprKind::MethodCall(..)
299        | ExprKind::Use(..)
300        | ExprKind::Binary(..) => true,
301
302        // Inner blocks are rvalues.
303        ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::Block(..) => true,
304
305        // FIXME: these should probably recurse and typecheck along the way.
306        //        Some false negatives are possible for now.
307        ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(..) => false,
308
309        ExprKind::Struct(..) => true,
310
311        // FIXME: this has false negatives, but I do not want to deal with 'static/const promotion just yet.
312        ExprKind::Array(..) => false,
313
314        // These typecheck to `!`
315        ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Become(..) => {
316            false
317        }
318
319        // These typecheck to `()`
320        ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Yield(..) => false,
321
322        // Compiler-magic macros
323        ExprKind::AddrOf(..) | ExprKind::OffsetOf(..) | ExprKind::InlineAsm(..) => false,
324
325        // We are not interested in these
326        ExprKind::Cast(..)
327        | ExprKind::Closure(..)
328        | ExprKind::Tup(..)
329        | ExprKind::DropTemps(..)
330        | ExprKind::Let(..) => false,
331
332        ExprKind::UnsafeBinderCast(..) => false,
333
334        // Not applicable
335        ExprKind::Type(..) | ExprKind::Err(..) => false,
336    }
337}
338
339// Array, Vec, String, CString, MaybeUninit, Cell, Box<[_]>, Box<str>, Box<CStr>, UnsafeCell,
340// SyncUnsafeCell, or any of the above in arbitrary many nested Box'es.
341fn owns_allocation(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool {
342    if ty.is_array() {
343        true
344    } else if let Some(inner) = ty.boxed_ty() {
345        inner.is_slice()
346            || inner.is_str()
347            || inner.ty_adt_def().is_some_and(|def| tcx.is_lang_item(def.did(), LangItem::CStr))
348            || owns_allocation(tcx, inner)
349    } else if let Some(def) = ty.ty_adt_def() {
350        for lang_item in [LangItem::String, LangItem::MaybeUninit, LangItem::UnsafeCell] {
351            if tcx.is_lang_item(def.did(), lang_item) {
352                return true;
353            }
354        }
355        tcx.get_diagnostic_name(def.did()).is_some_and(|name| {
356            #[allow(non_exhaustive_omitted_patterns)] match name {
    sym::cstring_type | sym::Vec | sym::Cell | sym::SyncUnsafeCell => true,
    _ => false,
}matches!(name, sym::cstring_type | sym::Vec | sym::Cell | sym::SyncUnsafeCell)
357        })
358    } else {
359        false
360    }
361}