1use rustc_ast::visit::{visit_opt, walk_list};
2use rustc_hir::attrs::AttributeKind;
3use rustc_hir::def::Res;
4use rustc_hir::def_id::LocalDefId;
5use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
6use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, LangItem, TyKind, find_attr};
7use rustc_middle::ty::{self, Ty, TyCtxt};
8use rustc_session::{declare_lint, impl_lint_pass};
9use rustc_span::{Span, sym};
1011use crate::lints::{DanglingPointersFromLocals, DanglingPointersFromTemporaries};
12use crate::{LateContext, LateLintPass};
1314#[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! {
15/// The `dangling_pointers_from_temporaries` lint detects getting a pointer to data
16 /// of a temporary that will immediately get dropped.
17 ///
18 /// ### Example
19 ///
20 /// ```rust
21 /// # #![allow(unused)]
22 /// # unsafe fn use_data(ptr: *const u8) { }
23 /// fn gather_and_use(bytes: impl Iterator<Item = u8>) {
24 /// let x: *const u8 = bytes.collect::<Vec<u8>>().as_ptr();
25 /// unsafe { use_data(x) }
26 /// }
27 /// ```
28 ///
29 /// {{produces}}
30 ///
31 /// ### Explanation
32 ///
33 /// Getting a pointer from a temporary value will not prolong its lifetime,
34 /// which means that the value can be dropped and the allocation freed
35 /// while the pointer still exists, making the pointer dangling.
36 /// This is not an error (as far as the type system is concerned)
37 /// but probably is not what the user intended either.
38 ///
39 /// If you need stronger guarantees, consider using references instead,
40 /// as they are statically verified by the borrow-checker to never dangle.
41pub DANGLING_POINTERS_FROM_TEMPORARIES,
42 Warn,
43"detects getting a pointer from a temporary"
44}4546#[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! {
47/// The `dangling_pointers_from_locals` lint detects getting a pointer to data
48 /// of a local that will be dropped at the end of the function.
49 ///
50 /// ### Example
51 ///
52 /// ```rust
53 /// fn f() -> *const u8 {
54 /// let x = 0;
55 /// &x // returns a dangling ptr to `x`
56 /// }
57 /// ```
58 ///
59 /// {{produces}}
60 ///
61 /// ### Explanation
62 ///
63 /// Returning a pointer from a local value will not prolong its lifetime,
64 /// which means that the value can be dropped and the allocation freed
65 /// while the pointer still exists, making the pointer dangling.
66 /// This is not an error (as far as the type system is concerned)
67 /// but probably is not what the user intended either.
68 ///
69 /// If you need stronger guarantees, consider using references instead,
70 /// as they are statically verified by the borrow-checker to never dangle.
71pub DANGLING_POINTERS_FROM_LOCALS,
72 Warn,
73"detects returning a pointer from a local variable"
74}7576/// FIXME: false negatives (i.e. the lint is not emitted when it should be)
77/// 1. Ways to get a temporary that are not recognized:
78/// - `owning_temporary.field`
79/// - `owning_temporary[index]`
80/// 2. No checks for ref-to-ptr conversions:
81/// - `&raw [mut] temporary`
82/// - `&temporary as *(const|mut) _`
83/// - `ptr::from_ref(&temporary)` and friends
84#[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)]
85pub(crate) struct DanglingPointers;
8687impl ::rustc_lint_defs::LintPass for DanglingPointers {
fn name(&self) -> &'static str { "DanglingPointers" }
fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
<[_]>::into_vec(::alloc::boxed::box_new([DANGLING_POINTERS_FROM_TEMPORARIES,
DANGLING_POINTERS_FROM_LOCALS]))
}
}
impl DanglingPointers {
#[allow(unused)]
pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
<[_]>::into_vec(::alloc::boxed::box_new([DANGLING_POINTERS_FROM_TEMPORARIES,
DANGLING_POINTERS_FROM_LOCALS]))
}
}impl_lint_pass!(DanglingPointers => [DANGLING_POINTERS_FROM_TEMPORARIES, DANGLING_POINTERS_FROM_LOCALS]);
8889// This skips over const blocks, but they cannot use or return a dangling pointer anyways.
90impl<'tcx> LateLintPass<'tcx> for DanglingPointers {
91fn check_fn(
92&mut self,
93 cx: &LateContext<'tcx>,
94 fn_kind: FnKind<'tcx>,
95 fn_decl: &'tcx FnDecl<'tcx>,
96 body: &'tcx Body<'tcx>,
97_: Span,
98 def_id: LocalDefId,
99 ) {
100DanglingPointerSearcher { cx, inside_call_args: false }.visit_body(body);
101102if let FnRetTy::Return(ret_ty) = &fn_decl.output
103 && let TyKind::Ptr(_) = ret_ty.kind
104 {
105// get the return type of the function or closure
106let ty = match cx.tcx.type_of(def_id).instantiate_identity().kind() {
107 ty::FnDef(..) => cx.tcx.fn_sig(def_id).instantiate_identity(),
108 ty::Closure(_, args) => args.as_closure().sig(),
109_ => return,
110 };
111let ty = ty.output();
112113// this type is only used for layout computation and pretty-printing, neither of them rely on regions
114let ty = cx.tcx.instantiate_bound_regions_with_erased(ty);
115116// verify that we have a pointer type
117let inner_ty = match ty.kind() {
118 ty::RawPtr(inner_ty, _) => *inner_ty,
119_ => return,
120 };
121122if cx123 .tcx
124 .layout_of(cx.typing_env().as_query_input(inner_ty))
125 .is_ok_and(|layout| !layout.is_1zst())
126 {
127let dcx = &DanglingPointerLocalContext {
128 body: def_id,
129 fn_ret: ty,
130 fn_ret_span: ret_ty.span,
131 fn_ret_inner: inner_ty,
132 fn_kind: match fn_kind {
133 FnKind::ItemFn(..) => "function",
134 FnKind::Method(..) => "method",
135 FnKind::Closure => "closure",
136 },
137 };
138139// look for `return`s
140DanglingPointerReturnSearcher { cx, dcx }.visit_body(body);
141142// analyze implicit return expression
143if let ExprKind::Block(block, None) = &body.value.kind
144 && let innermost_block = block.innermost_block()
145 && let Some(expr) = innermost_block.expr
146 {
147lint_addr_of_local(cx, dcx, expr);
148 }
149 }
150 }
151 }
152}
153154struct DanglingPointerLocalContext<'tcx> {
155 body: LocalDefId,
156 fn_ret: Ty<'tcx>,
157 fn_ret_span: Span,
158 fn_ret_inner: Ty<'tcx>,
159 fn_kind: &'static str,
160}
161162struct DanglingPointerReturnSearcher<'lcx, 'tcx> {
163 cx: &'lcx LateContext<'tcx>,
164 dcx: &'lcx DanglingPointerLocalContext<'tcx>,
165}
166167impl<'tcx> Visitor<'tcx> for DanglingPointerReturnSearcher<'_, 'tcx> {
168fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result {
169if let ExprKind::Ret(Some(expr)) = expr.kind {
170lint_addr_of_local(self.cx, self.dcx, expr);
171 }
172walk_expr(self, expr)
173 }
174}
175176/// Look for `&<path_to_local_in_same_body>` pattern and emit lint for it
177fn lint_addr_of_local<'a>(
178 cx: &LateContext<'a>,
179 dcx: &DanglingPointerLocalContext<'a>,
180 expr: &'a Expr<'a>,
181) {
182// peel casts as they do not interest us here, we want the inner expression.
183let (inner, _) = super::utils::peel_casts(cx, expr);
184185if let ExprKind::AddrOf(_, _, inner_of) = inner.kind
186 && let ExprKind::Path(ref qpath) = inner_of.peel_blocks().kind
187 && let Res::Local(from) = cx.qpath_res(qpath, inner_of.hir_id)
188 && cx.tcx.hir_enclosing_body_owner(from) == dcx.body
189 {
190cx.tcx.emit_node_span_lint(
191DANGLING_POINTERS_FROM_LOCALS,
192expr.hir_id,
193expr.span,
194DanglingPointersFromLocals {
195 ret_ty: dcx.fn_ret,
196 ret_ty_span: dcx.fn_ret_span,
197 fn_kind: dcx.fn_kind,
198 local_var: cx.tcx.hir_span(from),
199 local_var_name: cx.tcx.hir_ident(from),
200 local_var_ty: dcx.fn_ret_inner,
201 created_at: (expr.hir_id != inner.hir_id).then_some(inner.span),
202 },
203 );
204 }
205}
206207/// This produces a dangling pointer:
208/// ```ignore (example)
209/// let ptr = CString::new("hello").unwrap().as_ptr();
210/// foo(ptr)
211/// ```
212///
213/// But this does not:
214/// ```ignore (example)
215/// foo(CString::new("hello").unwrap().as_ptr())
216/// ```
217///
218/// But this does:
219/// ```ignore (example)
220/// foo({ let ptr = CString::new("hello").unwrap().as_ptr(); ptr })
221/// ```
222///
223/// So we have to keep track of when we are inside of a function/method call argument.
224struct DanglingPointerSearcher<'lcx, 'tcx> {
225 cx: &'lcx LateContext<'tcx>,
226/// Keeps track of whether we are inside of function/method call arguments,
227 /// where this lint should not be emitted.
228 ///
229 /// See [the main doc][`Self`] for examples.
230inside_call_args: bool,
231}
232233impl Visitor<'_> for DanglingPointerSearcher<'_, '_> {
234fn visit_expr(&mut self, expr: &Expr<'_>) -> Self::Result {
235if !self.inside_call_args {
236lint_expr(self.cx, expr)
237 }
238match expr.kind {
239 ExprKind::Call(lhs, args) | ExprKind::MethodCall(_, lhs, args, _) => {
240self.visit_expr(lhs);
241self.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))
242 }
243 ExprKind::Block(&Block { stmts, expr, .. }, _) => {
244self.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));
245if 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)246 }
247_ => walk_expr(self, expr),
248 }
249 }
250}
251252impl DanglingPointerSearcher<'_, '_> {
253fn with_inside_call_args<R>(
254&mut self,
255 inside_call_args: bool,
256 callback: impl FnOnce(&mut Self) -> R,
257 ) -> R {
258let old = core::mem::replace(&mut self.inside_call_args, inside_call_args);
259let result = callback(self);
260self.inside_call_args = old;
261result262 }
263}
264265fn lint_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
266if let ExprKind::MethodCall(method, receiver, _args, _span) = expr.kind
267 && is_temporary_rvalue(receiver)
268 && let ty = cx.typeck_results().expr_ty(receiver)
269 && owns_allocation(cx.tcx, ty)
270 && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
271 && {
{
'done:
{
for i in cx.tcx.get_all_attrs(fn_id) {
let i: &rustc_hir::Attribute = i;
match i {
rustc_hir::Attribute::Parsed(AttributeKind::RustcAsPtr(_))
=> {
break 'done Some(());
}
_ => {}
}
}
None
}
}.is_some()
}find_attr!(cx.tcx.get_all_attrs(fn_id), AttributeKind::RustcAsPtr(_))272 {
273// FIXME: use `emit_node_lint` when `#[primary_span]` is added.
274cx.tcx.emit_node_span_lint(
275DANGLING_POINTERS_FROM_TEMPORARIES,
276expr.hir_id,
277method.ident.span,
278DanglingPointersFromTemporaries {
279 callee: method.ident,
280ty,
281 ptr_span: method.ident.span,
282 temporary_span: receiver.span,
283 },
284 )
285 }
286}
287288fn is_temporary_rvalue(expr: &Expr<'_>) -> bool {
289match expr.kind {
290// Const is not temporary.
291ExprKind::ConstBlock(..) | ExprKind::Repeat(..) | ExprKind::Lit(..) => false,
292293// This is literally lvalue.
294ExprKind::Path(..) => false,
295296// Calls return rvalues.
297ExprKind::Call(..)
298 | ExprKind::MethodCall(..)
299 | ExprKind::Use(..)
300 | ExprKind::Binary(..) => true,
301302// Inner blocks are rvalues.
303ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::Block(..) => true,
304305// FIXME: these should probably recurse and typecheck along the way.
306 // Some false negatives are possible for now.
307ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(..) => false,
308309 ExprKind::Struct(..) => true,
310311// FIXME: this has false negatives, but I do not want to deal with 'static/const promotion just yet.
312ExprKind::Array(..) => false,
313314// These typecheck to `!`
315ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Become(..) => {
316false
317}
318319// These typecheck to `()`
320ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Yield(..) => false,
321322// Compiler-magic macros
323ExprKind::AddrOf(..) | ExprKind::OffsetOf(..) | ExprKind::InlineAsm(..) => false,
324325// We are not interested in these
326ExprKind::Cast(..)
327 | ExprKind::Closure(..)
328 | ExprKind::Tup(..)
329 | ExprKind::DropTemps(..)
330 | ExprKind::Let(..) => false,
331332 ExprKind::UnsafeBinderCast(..) => false,
333334// Not applicable
335ExprKind::Type(..) | ExprKind::Err(..) => false,
336 }
337}
338339// 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 {
342if ty.is_array() {
343true
344} else if let Some(inner) = ty.boxed_ty() {
345inner.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() {
350for lang_item in [LangItem::String, LangItem::MaybeUninit, LangItem::UnsafeCell] {
351if tcx.is_lang_item(def.did(), lang_item) {
352return true;
353 }
354 }
355tcx.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 {
359false
360}
361}