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};
910use crate::lints::{DanglingPointersFromLocals, DanglingPointersFromTemporaries};
11use crate::{LateContext, LateLintPass};
1213#[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.
40pub DANGLING_POINTERS_FROM_TEMPORARIES,
41 Warn,
42"detects getting a pointer from a temporary"
43}4445#[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.
70pub DANGLING_POINTERS_FROM_LOCALS,
71 Warn,
72"detects returning a pointer from a local variable"
73}7475/// 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;
8586impl ::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]);
8788// This skips over const blocks, but they cannot use or return a dangling pointer anyways.
89impl<'tcx> LateLintPass<'tcx> for DanglingPointers {
90fn 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 ) {
99DanglingPointerSearcher { cx, inside_call_args: false }.visit_body(body);
100101if 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
105let 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 };
110let ty = ty.output();
111112// this type is only used for layout computation and pretty-printing, neither of them rely on regions
113let ty = cx.tcx.instantiate_bound_regions_with_erased(ty);
114115// verify that we have a pointer type
116let inner_ty = match ty.kind() {
117 ty::RawPtr(inner_ty, _) => *inner_ty,
118_ => return,
119 };
120121if cx122 .tcx
123 .layout_of(cx.typing_env().as_query_input(inner_ty))
124 .is_ok_and(|layout| !layout.is_1zst())
125 {
126let 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 };
137138// look for `return`s
139DanglingPointerReturnSearcher { cx, dcx }.visit_body(body);
140141// analyze implicit return expression
142if let ExprKind::Block(block, None) = &body.value.kind
143 && let innermost_block = block.innermost_block()
144 && let Some(expr) = innermost_block.expr
145 {
146lint_addr_of_local(cx, dcx, expr);
147 }
148 }
149 }
150 }
151}
152153struct 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}
160161struct DanglingPointerReturnSearcher<'lcx, 'tcx> {
162 cx: &'lcx LateContext<'tcx>,
163 dcx: &'lcx DanglingPointerLocalContext<'tcx>,
164}
165166impl<'tcx> Visitor<'tcx> for DanglingPointerReturnSearcher<'_, 'tcx> {
167fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result {
168if let ExprKind::Ret(Some(expr)) = expr.kind {
169lint_addr_of_local(self.cx, self.dcx, expr);
170 }
171walk_expr(self, expr)
172 }
173}
174175/// 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.
182let (inner, _) = super::utils::peel_casts(cx, expr);
183184if 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 {
189cx.tcx.emit_node_span_lint(
190DANGLING_POINTERS_FROM_LOCALS,
191expr.hir_id,
192expr.span,
193DanglingPointersFromLocals {
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}
205206/// 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.
229inside_call_args: bool,
230}
231232impl Visitor<'_> for DanglingPointerSearcher<'_, '_> {
233fn visit_expr(&mut self, expr: &Expr<'_>) -> Self::Result {
234if !self.inside_call_args {
235lint_expr(self.cx, expr)
236 }
237match expr.kind {
238 ExprKind::Call(lhs, args) | ExprKind::MethodCall(_, lhs, args, _) => {
239self.visit_expr(lhs);
240self.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, .. }, _) => {
243self.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));
244if 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}
250251impl DanglingPointerSearcher<'_, '_> {
252fn with_inside_call_args<R>(
253&mut self,
254 inside_call_args: bool,
255 callback: impl FnOnce(&mut Self) -> R,
256 ) -> R {
257let old = core::mem::replace(&mut self.inside_call_args, inside_call_args);
258let result = callback(self);
259self.inside_call_args = old;
260result261 }
262}
263264fn lint_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
265if 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 {
272cx.tcx.emit_node_span_lint(
273DANGLING_POINTERS_FROM_TEMPORARIES,
274expr.hir_id,
275method.ident.span,
276DanglingPointersFromTemporaries {
277 callee: method.ident,
278ty,
279 ptr_span: method.ident.span,
280 temporary_span: receiver.span,
281 },
282 )
283 }
284}
285286fn is_temporary_rvalue(expr: &Expr<'_>) -> bool {
287match expr.kind {
288// Const is not temporary.
289ExprKind::ConstBlock(..) | ExprKind::Repeat(..) | ExprKind::Lit(..) => false,
290291// This is literally lvalue.
292ExprKind::Path(..) => false,
293294// Calls return rvalues.
295ExprKind::Call(..)
296 | ExprKind::MethodCall(..)
297 | ExprKind::Use(..)
298 | ExprKind::Binary(..) => true,
299300// Inner blocks are rvalues.
301ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::Block(..) => true,
302303// FIXME: these should probably recurse and typecheck along the way.
304 // Some false negatives are possible for now.
305ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(..) => false,
306307 ExprKind::Struct(..) => true,
308309// FIXME: this has false negatives, but I do not want to deal with 'static/const promotion just yet.
310ExprKind::Array(..) => false,
311312// These typecheck to `!`
313ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Become(..) => {
314false
315}
316317// These typecheck to `()`
318ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Yield(..) => false,
319320// Compiler-magic macros
321ExprKind::AddrOf(..) | ExprKind::OffsetOf(..) | ExprKind::InlineAsm(..) => false,
322323// We are not interested in these
324ExprKind::Cast(..)
325 | ExprKind::Closure(..)
326 | ExprKind::Tup(..)
327 | ExprKind::DropTemps(..)
328 | ExprKind::Let(..) => false,
329330 ExprKind::UnsafeBinderCast(..) => false,
331332// Not applicable
333ExprKind::Type(..) | ExprKind::Err(..) => false,
334 }
335}
336337// 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 {
340if ty.is_array() {
341true
342} else if let Some(inner) = ty.boxed_ty() {
343inner.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() {
348for lang_item in [LangItem::String, LangItem::MaybeUninit, LangItem::UnsafeCell] {
349if tcx.is_lang_item(def.did(), lang_item) {
350return true;
351 }
352 }
353tcx.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 {
357false
358}
359}