11use rustc_ast:: visit:: { visit_opt, walk_list} ;
22use rustc_attr_data_structures:: { AttributeKind , find_attr} ;
3+ use rustc_hir:: def:: Res ;
34use rustc_hir:: def_id:: LocalDefId ;
45use rustc_hir:: intravisit:: { FnKind , Visitor , walk_expr} ;
5- use rustc_hir:: { Block , Body , Expr , ExprKind , FnDecl , LangItem } ;
6- use rustc_middle:: ty:: { Ty , TyCtxt } ;
6+ use rustc_hir:: { Block , Body , Expr , ExprKind , FnDecl , FnRetTy , LangItem , TyKind } ;
7+ use rustc_middle:: ty:: { self , Ty , TyCtxt , TypeVisitableExt } ;
78use rustc_session:: { declare_lint, impl_lint_pass} ;
89use rustc_span:: { Span , sym} ;
910
10- use crate :: lints:: DanglingPointersFromTemporaries ;
11+ use crate :: lints:: { DanglingPointersFromLocals , DanglingPointersFromTemporaries } ;
12+ use crate :: utils:: FollowInit ;
1113use crate :: { LateContext , LateLintPass } ;
1214
1315declare_lint ! {
@@ -42,6 +44,36 @@ declare_lint! {
4244 "detects getting a pointer from a temporary"
4345}
4446
47+ declare_lint ! {
48+ /// The `dangling_pointers_from_locals` lint detects getting a pointer to data
49+ /// of a local that will be dropped at the end of the function.
50+ ///
51+ /// ### Example
52+ ///
53+ /// ```rust
54+ /// fn f() -> *const u8 {
55+ /// let x = 0;
56+ /// &x // returns a dangling ptr to `x`
57+ /// }
58+ /// ```
59+ ///
60+ /// {{produces}}
61+ ///
62+ /// ### Explanation
63+ ///
64+ /// Returning a pointer from a local value will not prolong its lifetime,
65+ /// which means that the value can be dropped and the allocation freed
66+ /// while the pointer still exists, making the pointer dangling.
67+ /// This is not an error (as far as the type system is concerned)
68+ /// but probably is not what the user intended either.
69+ ///
70+ /// If you need stronger guarantees, consider using references instead,
71+ /// as they are statically verified by the borrow-checker to never dangle.
72+ pub DANGLING_POINTERS_FROM_LOCALS ,
73+ Warn ,
74+ "detects returning a pointer from a local variable"
75+ }
76+
4577/// FIXME: false negatives (i.e. the lint is not emitted when it should be)
4678/// 1. Ways to get a temporary that are not recognized:
4779/// - `owning_temporary.field`
@@ -53,20 +85,93 @@ declare_lint! {
5385#[ derive( Clone , Copy , Default ) ]
5486pub ( crate ) struct DanglingPointers ;
5587
56- impl_lint_pass ! ( DanglingPointers => [ DANGLING_POINTERS_FROM_TEMPORARIES ] ) ;
88+ impl_lint_pass ! ( DanglingPointers => [ DANGLING_POINTERS_FROM_TEMPORARIES , DANGLING_POINTERS_FROM_LOCALS ] ) ;
5789
5890// This skips over const blocks, but they cannot use or return a dangling pointer anyways.
5991impl < ' tcx > LateLintPass < ' tcx > for DanglingPointers {
6092 fn check_fn (
6193 & mut self ,
6294 cx : & LateContext < ' tcx > ,
6395 _: FnKind < ' tcx > ,
64- _ : & ' tcx FnDecl < ' tcx > ,
96+ fn_decl : & ' tcx FnDecl < ' tcx > ,
6597 body : & ' tcx Body < ' tcx > ,
6698 _: Span ,
67- _ : LocalDefId ,
99+ def_id : LocalDefId ,
68100 ) {
69- DanglingPointerSearcher { cx, inside_call_args : false } . visit_body ( body)
101+ DanglingPointerSearcher { cx, inside_call_args : false } . visit_body ( body) ;
102+
103+ if let FnRetTy :: Return ( ret_ty) = & fn_decl. output
104+ && let TyKind :: Ptr ( _) = ret_ty. kind
105+ {
106+ 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 ( ) . skip_binder ( ) ;
112+
113+ let inner_ty = match ty. kind ( ) {
114+ ty:: RawPtr ( inner_ty, _) => * inner_ty,
115+ _ => return ,
116+ } ;
117+
118+ if inner_ty. has_escaping_bound_vars ( ) {
119+ // avoid ICE if we have escaping bound vars
120+ return ;
121+ }
122+
123+ if cx
124+ . tcx
125+ . layout_of ( cx. typing_env ( ) . as_query_input ( inner_ty) )
126+ . is_ok_and ( |layout| !layout. is_1zst ( ) )
127+ {
128+ let fn_ret = ( ty, ret_ty. span ) ;
129+
130+ DanglingPointerReturnSearcher { cx, fn_ret } . visit_body ( body) ;
131+ if let ExprKind :: Block ( block, None ) = & body. value . kind
132+ && let innermost_block = block. innermost_block ( )
133+ && let Some ( expr) = innermost_block. expr
134+ {
135+ lint_addr_of_local ( cx, expr, fn_ret) ;
136+ }
137+ }
138+ }
139+ }
140+ }
141+
142+ struct DanglingPointerReturnSearcher < ' lcx , ' tcx > {
143+ cx : & ' lcx LateContext < ' tcx > ,
144+ fn_ret : ( Ty < ' tcx > , Span ) ,
145+ }
146+
147+ impl < ' tcx > Visitor < ' tcx > for DanglingPointerReturnSearcher < ' _ , ' tcx > {
148+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) -> Self :: Result {
149+ if let ExprKind :: Ret ( Some ( expr) ) = expr. kind {
150+ lint_addr_of_local ( self . cx , expr, self . fn_ret ) ;
151+ }
152+ walk_expr ( self , expr)
153+ }
154+ }
155+
156+ fn lint_addr_of_local < ' a > ( cx : & LateContext < ' a > , expr : & ' a Expr < ' a > , fn_ret : ( Ty < ' _ > , Span ) ) {
157+ let ( inner, _) = super :: utils:: peel_casts ( cx, FollowInit :: No , expr) ;
158+
159+ if let ExprKind :: AddrOf ( _, _, inner) = inner. kind
160+ && let ExprKind :: Path ( ref qpath) = inner. peel_blocks ( ) . kind
161+ && let Res :: Local ( from) = cx. qpath_res ( qpath, expr. hir_id )
162+ && !cx. typeck_results ( ) . expr_ty ( inner) . is_any_ptr ( )
163+ {
164+ cx. tcx . emit_node_span_lint (
165+ DANGLING_POINTERS_FROM_LOCALS ,
166+ expr. hir_id ,
167+ expr. span ,
168+ DanglingPointersFromLocals {
169+ ret_ty : fn_ret. 0 ,
170+ ret_ty_span : fn_ret. 1 ,
171+ local_var : cx. tcx . hir_span ( from) ,
172+ local_var_name : cx. tcx . hir_ident ( from) ,
173+ } ,
174+ ) ;
70175 }
71176}
72177
0 commit comments