Skip to main content

rustc_lint/
dangling.rs

1use rustc_ast::visit::{visit_opt, walk_list};
2use rustc_hir::def::Res;
3use rustc_hir::def_id::LocalDefId;
4use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
5use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, LangItem, TyKind, find_attr};
6use rustc_middle::ty::{self, Ty, TyCtxt};
7use rustc_session::{declare_lint, impl_lint_pass};
8use rustc_span::{Span, sym};
9
10use crate::lints::{DanglingPointersFromLocals, DanglingPointersFromTemporaries};
11use crate::{LateContext, LateLintPass};
12
13#[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! {
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#[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! {
46    /// The `dangling_pointers_from_locals` lint detects getting a pointer to data
47    /// of a local that will be dropped at the end of the function.
48    ///
49    /// ### Example
50    ///
51    /// ```rust
52    /// fn f() -> *const u8 {
53    ///     let x = 0;
54    ///     &x // returns a dangling ptr to `x`
55    /// }
56    /// ```
57    ///
58    /// {{produces}}
59    ///
60    /// ### Explanation
61    ///
62    /// Returning a pointer from a local value will not prolong its lifetime,
63    /// which means that the value can be dropped and the allocation freed
64    /// while the pointer still exists, making the pointer dangling.
65    /// This is not an error (as far as the type system is concerned)
66    /// but probably is not what the user intended either.
67    ///
68    /// If you need stronger guarantees, consider using references instead,
69    /// as they are statically verified by the borrow-checker to never dangle.
70    pub DANGLING_POINTERS_FROM_LOCALS,
71    Warn,
72    "detects returning a pointer from a local variable"
73}
74
75/// FIXME: false negatives (i.e. the lint is not emitted when it should be)
76/// 1. Ways to get a temporary that are not recognized:
77///    - `owning_temporary.field`
78///    - `owning_temporary[index]`
79/// 2. No checks for ref-to-ptr conversions:
80///    - `&raw [mut] temporary`
81///    - `&temporary as *(const|mut) _`
82///    - `ptr::from_ref(&temporary)` and friends
83#[derive(#[automatically_derived]
impl ::core::clone::Clone for DanglingPointers {
    #[inline]
    fn clone(&self) -> DanglingPointers { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for DanglingPointers { }Copy, #[automatically_derived]
impl ::core::default::Default for DanglingPointers {
    #[inline]
    fn default() -> DanglingPointers { DanglingPointers {} }
}Default)]
84pub(crate) struct DanglingPointers;
85
86impl ::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]);
87
88// This skips over const blocks, but they cannot use or return a dangling pointer anyways.
89impl<'tcx> LateLintPass<'tcx> for DanglingPointers {
90    fn check_fn(
91        &mut self,
92        cx: &LateContext<'tcx>,
93        fn_kind: FnKind<'tcx>,
94        fn_decl: &'tcx FnDecl<'tcx>,
95        body: &'tcx Body<'tcx>,
96        _: Span,
97        def_id: LocalDefId,
98    ) {
99        DanglingPointerSearcher { cx, inside_call_args: false }.visit_body(body);
100
101        if let FnRetTy::Return(ret_ty) = &fn_decl.output
102            && let TyKind::Ptr(_) = ret_ty.kind
103        {
104            // get the return type of the function or closure
105            let ty = match cx.tcx.type_of(def_id).instantiate_identity().kind() {
106                ty::FnDef(..) => cx.tcx.fn_sig(def_id).instantiate_identity(),
107                ty::Closure(_, args) => args.as_closure().sig(),
108                _ => return,
109            };
110            let ty = ty.output();
111
112            // this type is only used for layout computation and pretty-printing, neither of them rely on regions
113            let ty = cx.tcx.instantiate_bound_regions_with_erased(ty);
114
115            // verify that we have a pointer type
116            let inner_ty = match ty.kind() {
117                ty::RawPtr(inner_ty, _) => *inner_ty,
118                _ => return,
119            };
120
121            if cx
122                .tcx
123                .layout_of(cx.typing_env().as_query_input(inner_ty))
124                .is_ok_and(|layout| !layout.is_1zst())
125            {
126                let dcx = &DanglingPointerLocalContext {
127                    body: def_id,
128                    fn_ret: ty,
129                    fn_ret_span: ret_ty.span,
130                    fn_ret_inner: inner_ty,
131                    fn_kind: match fn_kind {
132                        FnKind::ItemFn(..) => "function",
133                        FnKind::Method(..) => "method",
134                        FnKind::Closure => "closure",
135                    },
136                };
137
138                // look for `return`s
139                DanglingPointerReturnSearcher { cx, dcx }.visit_body(body);
140
141                // analyze implicit return expression
142                if let ExprKind::Block(block, None) = &body.value.kind
143                    && let innermost_block = block.innermost_block()
144                    && let Some(expr) = innermost_block.expr
145                {
146                    lint_addr_of_local(cx, dcx, expr);
147                }
148            }
149        }
150    }
151}
152
153struct DanglingPointerLocalContext<'tcx> {
154    body: LocalDefId,
155    fn_ret: Ty<'tcx>,
156    fn_ret_span: Span,
157    fn_ret_inner: Ty<'tcx>,
158    fn_kind: &'static str,
159}
160
161struct DanglingPointerReturnSearcher<'lcx, 'tcx> {
162    cx: &'lcx LateContext<'tcx>,
163    dcx: &'lcx DanglingPointerLocalContext<'tcx>,
164}
165
166impl<'tcx> Visitor<'tcx> for DanglingPointerReturnSearcher<'_, 'tcx> {
167    fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result {
168        if let ExprKind::Ret(Some(expr)) = expr.kind {
169            lint_addr_of_local(self.cx, self.dcx, expr);
170        }
171        walk_expr(self, expr)
172    }
173}
174
175/// Look for `&<path_to_local_in_same_body>` pattern and emit lint for it
176fn lint_addr_of_local<'a>(
177    cx: &LateContext<'a>,
178    dcx: &DanglingPointerLocalContext<'a>,
179    expr: &'a Expr<'a>,
180) {
181    // peel casts as they do not interest us here, we want the inner expression.
182    let (inner, _) = super::utils::peel_casts(cx, expr);
183
184    if let ExprKind::AddrOf(_, _, inner_of) = inner.kind
185        && let ExprKind::Path(ref qpath) = inner_of.peel_blocks().kind
186        && let Res::Local(from) = cx.qpath_res(qpath, inner_of.hir_id)
187        && cx.tcx.hir_enclosing_body_owner(from) == dcx.body
188    {
189        cx.tcx.emit_node_span_lint(
190            DANGLING_POINTERS_FROM_LOCALS,
191            expr.hir_id,
192            expr.span,
193            DanglingPointersFromLocals {
194                ret_ty: dcx.fn_ret,
195                ret_ty_span: dcx.fn_ret_span,
196                fn_kind: dcx.fn_kind,
197                local_var: cx.tcx.hir_span(from),
198                local_var_name: cx.tcx.hir_ident(from),
199                local_var_ty: dcx.fn_ret_inner,
200                created_at: (expr.hir_id != inner.hir_id).then_some(inner.span),
201            },
202        );
203    }
204}
205
206/// This produces a dangling pointer:
207/// ```ignore (example)
208/// let ptr = CString::new("hello").unwrap().as_ptr();
209/// foo(ptr)
210/// ```
211///
212/// But this does not:
213/// ```ignore (example)
214/// foo(CString::new("hello").unwrap().as_ptr())
215/// ```
216///
217/// But this does:
218/// ```ignore (example)
219/// foo({ let ptr = CString::new("hello").unwrap().as_ptr(); ptr })
220/// ```
221///
222/// So we have to keep track of when we are inside of a function/method call argument.
223struct DanglingPointerSearcher<'lcx, 'tcx> {
224    cx: &'lcx LateContext<'tcx>,
225    /// Keeps track of whether we are inside of function/method call arguments,
226    /// where this lint should not be emitted.
227    ///
228    /// See [the main doc][`Self`] for examples.
229    inside_call_args: bool,
230}
231
232impl Visitor<'_> for DanglingPointerSearcher<'_, '_> {
233    fn visit_expr(&mut self, expr: &Expr<'_>) -> Self::Result {
234        if !self.inside_call_args {
235            lint_expr(self.cx, expr)
236        }
237        match expr.kind {
238            ExprKind::Call(lhs, args) | ExprKind::MethodCall(_, lhs, args, _) => {
239                self.visit_expr(lhs);
240                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))
241            }
242            ExprKind::Block(&Block { stmts, expr, .. }, _) => {
243                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));
244                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)
245            }
246            _ => walk_expr(self, expr),
247        }
248    }
249}
250
251impl DanglingPointerSearcher<'_, '_> {
252    fn with_inside_call_args<R>(
253        &mut self,
254        inside_call_args: bool,
255        callback: impl FnOnce(&mut Self) -> R,
256    ) -> R {
257        let old = core::mem::replace(&mut self.inside_call_args, inside_call_args);
258        let result = callback(self);
259        self.inside_call_args = old;
260        result
261    }
262}
263
264fn lint_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
265    if let ExprKind::MethodCall(method, receiver, _args, _span) = expr.kind
266        && is_temporary_rvalue(receiver)
267        && let ty = cx.typeck_results().expr_ty(receiver)
268        && owns_allocation(cx.tcx, ty)
269        && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
270        && {

        #[allow(deprecated)]
        {
            {
                'done:
                    {
                    for i in cx.tcx.get_all_attrs(fn_id) {
                        #[allow(unused_imports)]
                        use rustc_hir::attrs::AttributeKind::*;
                        let i: &rustc_hir::Attribute = i;
                        match i {
                            rustc_hir::Attribute::Parsed(RustcAsPtr(_)) => {
                                break 'done Some(());
                            }
                            rustc_hir::Attribute::Unparsed(..) =>
                                {}
                                #[deny(unreachable_patterns)]
                                _ => {}
                        }
                    }
                    None
                }
            }
        }
    }.is_some()find_attr!(cx.tcx, fn_id, RustcAsPtr(_))
271    {
272        cx.tcx.emit_node_span_lint(
273            DANGLING_POINTERS_FROM_TEMPORARIES,
274            expr.hir_id,
275            method.ident.span,
276            DanglingPointersFromTemporaries {
277                callee: method.ident,
278                ty,
279                ptr_span: method.ident.span,
280                temporary_span: receiver.span,
281            },
282        )
283    }
284}
285
286fn is_temporary_rvalue(expr: &Expr<'_>) -> bool {
287    match expr.kind {
288        // Const is not temporary.
289        ExprKind::ConstBlock(..) | ExprKind::Repeat(..) | ExprKind::Lit(..) => false,
290
291        // This is literally lvalue.
292        ExprKind::Path(..) => false,
293
294        // Calls return rvalues.
295        ExprKind::Call(..)
296        | ExprKind::MethodCall(..)
297        | ExprKind::Use(..)
298        | ExprKind::Binary(..) => true,
299
300        // Inner blocks are rvalues.
301        ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::Block(..) => true,
302
303        // FIXME: these should probably recurse and typecheck along the way.
304        //        Some false negatives are possible for now.
305        ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(..) => false,
306
307        ExprKind::Struct(..) => true,
308
309        // FIXME: this has false negatives, but I do not want to deal with 'static/const promotion just yet.
310        ExprKind::Array(..) => false,
311
312        // These typecheck to `!`
313        ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Become(..) => {
314            false
315        }
316
317        // These typecheck to `()`
318        ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Yield(..) => false,
319
320        // Compiler-magic macros
321        ExprKind::AddrOf(..) | ExprKind::OffsetOf(..) | ExprKind::InlineAsm(..) => false,
322
323        // We are not interested in these
324        ExprKind::Cast(..)
325        | ExprKind::Closure(..)
326        | ExprKind::Tup(..)
327        | ExprKind::DropTemps(..)
328        | ExprKind::Let(..) => false,
329
330        ExprKind::UnsafeBinderCast(..) => false,
331
332        // Not applicable
333        ExprKind::Type(..) | ExprKind::Err(..) => false,
334    }
335}
336
337// Array, Vec, String, CString, MaybeUninit, Cell, Box<[_]>, Box<str>, Box<CStr>, UnsafeCell,
338// SyncUnsafeCell, or any of the above in arbitrary many nested Box'es.
339fn owns_allocation(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool {
340    if ty.is_array() {
341        true
342    } else if let Some(inner) = ty.boxed_ty() {
343        inner.is_slice()
344            || inner.is_str()
345            || inner.ty_adt_def().is_some_and(|def| tcx.is_lang_item(def.did(), LangItem::CStr))
346            || owns_allocation(tcx, inner)
347    } else if let Some(def) = ty.ty_adt_def() {
348        for lang_item in [LangItem::String, LangItem::MaybeUninit, LangItem::UnsafeCell] {
349            if tcx.is_lang_item(def.did(), lang_item) {
350                return true;
351            }
352        }
353        tcx.get_diagnostic_name(def.did()).is_some_and(|name| {
354            #[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)
355        })
356    } else {
357        false
358    }
359}