1use rustc_ast::visit::{visit_opt, walk_list};
2use rustc_attr_data_structures::{AttributeKind, find_attr};
3use rustc_hir::def_id::LocalDefId;
4use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
5use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, LangItem};
6use rustc_middle::ty::{Ty, TyCtxt};
7use rustc_session::{declare_lint, impl_lint_pass};
8use rustc_span::{Span, sym};
9
10use crate::lints::DanglingPointersFromTemporaries;
11use crate::{LateContext, LateLintPass};
12
13declare_lint! {
14 pub DANGLING_POINTERS_FROM_TEMPORARIES,
41 Warn,
42 "detects getting a pointer from a temporary"
43}
44
45#[derive(Clone, Copy, Default)]
54pub(crate) struct DanglingPointers;
55
56impl_lint_pass!(DanglingPointers => [DANGLING_POINTERS_FROM_TEMPORARIES]);
57
58impl<'tcx> LateLintPass<'tcx> for DanglingPointers {
60 fn check_fn(
61 &mut self,
62 cx: &LateContext<'tcx>,
63 _: FnKind<'tcx>,
64 _: &'tcx FnDecl<'tcx>,
65 body: &'tcx Body<'tcx>,
66 _: Span,
67 _: LocalDefId,
68 ) {
69 DanglingPointerSearcher { cx, inside_call_args: false }.visit_body(body)
70 }
71}
72
73struct DanglingPointerSearcher<'lcx, 'tcx> {
91 cx: &'lcx LateContext<'tcx>,
92 inside_call_args: bool,
97}
98
99impl Visitor<'_> for DanglingPointerSearcher<'_, '_> {
100 fn visit_expr(&mut self, expr: &Expr<'_>) -> Self::Result {
101 if !self.inside_call_args {
102 lint_expr(self.cx, expr)
103 }
104 match expr.kind {
105 ExprKind::Call(lhs, args) | ExprKind::MethodCall(_, lhs, args, _) => {
106 self.visit_expr(lhs);
107 self.with_inside_call_args(true, |this| walk_list!(this, visit_expr, args))
108 }
109 ExprKind::Block(&Block { stmts, expr, .. }, _) => {
110 self.with_inside_call_args(false, |this| walk_list!(this, visit_stmt, stmts));
111 visit_opt!(self, visit_expr, expr)
112 }
113 _ => walk_expr(self, expr),
114 }
115 }
116}
117
118impl DanglingPointerSearcher<'_, '_> {
119 fn with_inside_call_args<R>(
120 &mut self,
121 inside_call_args: bool,
122 callback: impl FnOnce(&mut Self) -> R,
123 ) -> R {
124 let old = core::mem::replace(&mut self.inside_call_args, inside_call_args);
125 let result = callback(self);
126 self.inside_call_args = old;
127 result
128 }
129}
130
131fn lint_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
132 if let ExprKind::MethodCall(method, receiver, _args, _span) = expr.kind
133 && is_temporary_rvalue(receiver)
134 && let ty = cx.typeck_results().expr_ty(receiver)
135 && owns_allocation(cx.tcx, ty)
136 && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
137 && find_attr!(cx.tcx.get_all_attrs(fn_id), AttributeKind::AsPtr(_))
138 {
139 cx.tcx.emit_node_span_lint(
141 DANGLING_POINTERS_FROM_TEMPORARIES,
142 expr.hir_id,
143 method.ident.span,
144 DanglingPointersFromTemporaries {
145 callee: method.ident,
146 ty,
147 ptr_span: method.ident.span,
148 temporary_span: receiver.span,
149 },
150 )
151 }
152}
153
154fn is_temporary_rvalue(expr: &Expr<'_>) -> bool {
155 match expr.kind {
156 ExprKind::ConstBlock(..) | ExprKind::Repeat(..) | ExprKind::Lit(..) => false,
158
159 ExprKind::Path(..) => false,
161
162 ExprKind::Call(..)
164 | ExprKind::MethodCall(..)
165 | ExprKind::Use(..)
166 | ExprKind::Binary(..) => true,
167
168 ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::Block(..) => true,
170
171 ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(..) => false,
174
175 ExprKind::Struct(..) => true,
176
177 ExprKind::Array(..) => false,
179
180 ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Become(..) => {
182 false
183 }
184
185 ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Yield(..) => false,
187
188 ExprKind::AddrOf(..) | ExprKind::OffsetOf(..) | ExprKind::InlineAsm(..) => false,
190
191 ExprKind::Cast(..)
193 | ExprKind::Closure(..)
194 | ExprKind::Tup(..)
195 | ExprKind::DropTemps(..)
196 | ExprKind::Let(..) => false,
197
198 ExprKind::UnsafeBinderCast(..) => false,
199
200 ExprKind::Type(..) | ExprKind::Err(..) => false,
202 }
203}
204
205fn owns_allocation(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool {
208 if ty.is_array() {
209 true
210 } else if let Some(inner) = ty.boxed_ty() {
211 inner.is_slice()
212 || inner.is_str()
213 || inner.ty_adt_def().is_some_and(|def| tcx.is_lang_item(def.did(), LangItem::CStr))
214 || owns_allocation(tcx, inner)
215 } else if let Some(def) = ty.ty_adt_def() {
216 for lang_item in [LangItem::String, LangItem::MaybeUninit, LangItem::UnsafeCell] {
217 if tcx.is_lang_item(def.did(), lang_item) {
218 return true;
219 }
220 }
221 tcx.get_diagnostic_name(def.did()).is_some_and(|name| {
222 matches!(name, sym::cstring_type | sym::Vec | sym::Cell | sym::SyncUnsafeCell)
223 })
224 } else {
225 false
226 }
227}