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};
10
11use crate::lints::{DanglingPointersFromLocals, DanglingPointersFromTemporaries};
12use crate::{LateContext, LateLintPass};
13
14declare_lint! {
15 pub DANGLING_POINTERS_FROM_TEMPORARIES,
42 Warn,
43 "detects getting a pointer from a temporary"
44}
45
46declare_lint! {
47 pub DANGLING_POINTERS_FROM_LOCALS,
72 Warn,
73 "detects returning a pointer from a local variable"
74}
75
76#[derive(Clone, Copy, Default)]
85pub(crate) struct DanglingPointers;
86
87impl_lint_pass!(DanglingPointers => [DANGLING_POINTERS_FROM_TEMPORARIES, DANGLING_POINTERS_FROM_LOCALS]);
88
89impl<'tcx> LateLintPass<'tcx> for DanglingPointers {
91 fn 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 ) {
100 DanglingPointerSearcher { cx, inside_call_args: false }.visit_body(body);
101
102 if let FnRetTy::Return(ret_ty) = &fn_decl.output
103 && let TyKind::Ptr(_) = ret_ty.kind
104 {
105 let 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 };
111 let ty = ty.output();
112
113 let ty = cx.tcx.instantiate_bound_regions_with_erased(ty);
115
116 let inner_ty = match ty.kind() {
118 ty::RawPtr(inner_ty, _) => *inner_ty,
119 _ => return,
120 };
121
122 if cx
123 .tcx
124 .layout_of(cx.typing_env().as_query_input(inner_ty))
125 .is_ok_and(|layout| !layout.is_1zst())
126 {
127 let 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 };
138
139 DanglingPointerReturnSearcher { cx, dcx }.visit_body(body);
141
142 if let ExprKind::Block(block, None) = &body.value.kind
144 && let innermost_block = block.innermost_block()
145 && let Some(expr) = innermost_block.expr
146 {
147 lint_addr_of_local(cx, dcx, expr);
148 }
149 }
150 }
151 }
152}
153
154struct 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}
161
162struct DanglingPointerReturnSearcher<'lcx, 'tcx> {
163 cx: &'lcx LateContext<'tcx>,
164 dcx: &'lcx DanglingPointerLocalContext<'tcx>,
165}
166
167impl<'tcx> Visitor<'tcx> for DanglingPointerReturnSearcher<'_, 'tcx> {
168 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result {
169 if let ExprKind::Ret(Some(expr)) = expr.kind {
170 lint_addr_of_local(self.cx, self.dcx, expr);
171 }
172 walk_expr(self, expr)
173 }
174}
175
176fn lint_addr_of_local<'a>(
178 cx: &LateContext<'a>,
179 dcx: &DanglingPointerLocalContext<'a>,
180 expr: &'a Expr<'a>,
181) {
182 let (inner, _) = super::utils::peel_casts(cx, expr);
184
185 if 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 {
190 cx.tcx.emit_node_span_lint(
191 DANGLING_POINTERS_FROM_LOCALS,
192 expr.hir_id,
193 expr.span,
194 DanglingPointersFromLocals {
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}
206
207struct DanglingPointerSearcher<'lcx, 'tcx> {
225 cx: &'lcx LateContext<'tcx>,
226 inside_call_args: bool,
231}
232
233impl Visitor<'_> for DanglingPointerSearcher<'_, '_> {
234 fn visit_expr(&mut self, expr: &Expr<'_>) -> Self::Result {
235 if !self.inside_call_args {
236 lint_expr(self.cx, expr)
237 }
238 match expr.kind {
239 ExprKind::Call(lhs, args) | ExprKind::MethodCall(_, lhs, args, _) => {
240 self.visit_expr(lhs);
241 self.with_inside_call_args(true, |this| walk_list!(this, visit_expr, args))
242 }
243 ExprKind::Block(&Block { stmts, expr, .. }, _) => {
244 self.with_inside_call_args(false, |this| walk_list!(this, visit_stmt, stmts));
245 visit_opt!(self, visit_expr, expr)
246 }
247 _ => walk_expr(self, expr),
248 }
249 }
250}
251
252impl DanglingPointerSearcher<'_, '_> {
253 fn with_inside_call_args<R>(
254 &mut self,
255 inside_call_args: bool,
256 callback: impl FnOnce(&mut Self) -> R,
257 ) -> R {
258 let old = core::mem::replace(&mut self.inside_call_args, inside_call_args);
259 let result = callback(self);
260 self.inside_call_args = old;
261 result
262 }
263}
264
265fn lint_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
266 if 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 && find_attr!(cx.tcx.get_all_attrs(fn_id), AttributeKind::AsPtr(_))
272 {
273 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 ExprKind::ConstBlock(..) | ExprKind::Repeat(..) | ExprKind::Lit(..) => false,
292
293 ExprKind::Path(..) => false,
295
296 ExprKind::Call(..)
298 | ExprKind::MethodCall(..)
299 | ExprKind::Use(..)
300 | ExprKind::Binary(..) => true,
301
302 ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::Block(..) => true,
304
305 ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(..) => false,
308
309 ExprKind::Struct(..) => true,
310
311 ExprKind::Array(..) => false,
313
314 ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Become(..) => {
316 false
317 }
318
319 ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Yield(..) => false,
321
322 ExprKind::AddrOf(..) | ExprKind::OffsetOf(..) | ExprKind::InlineAsm(..) => false,
324
325 ExprKind::Cast(..)
327 | ExprKind::Closure(..)
328 | ExprKind::Tup(..)
329 | ExprKind::DropTemps(..)
330 | ExprKind::Let(..) => false,
331
332 ExprKind::UnsafeBinderCast(..) => false,
333
334 ExprKind::Type(..) | ExprKind::Err(..) => false,
336 }
337}
338
339fn 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 matches!(name, sym::cstring_type | sym::Vec | sym::Cell | sym::SyncUnsafeCell)
357 })
358 } else {
359 false
360 }
361}