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