Skip to content

Commit 7734af3

Browse files
committed
Add lint against dangling pointers form local variables
1 parent 4f475d8 commit 7734af3

File tree

5 files changed

+437
-7
lines changed

5 files changed

+437
-7
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ lint_confusable_identifier_pair = found both `{$existing_sym}` and `{$sym}` as i
207207
208208
lint_custom_inner_attribute_unstable = custom inner attributes are unstable
209209
210+
lint_dangling_pointers_from_locals = a dangling pointer will be produced because the local variable `{$local_var_name}` will be dropped
211+
.ret_ty = return type of the function is `{$ret_ty}`
212+
.local_var = `{$local_var_name}` is defined inside the function and will be drop at the end of the function
213+
.created_at = dangling pointer created here
214+
.note = pointers do not have a lifetime; after returning, the `{$local_var_ty}` will be deallocated at the end of the function because nothing is referencing it as far as the type system is concerned
215+
210216
lint_dangling_pointers_from_temporaries = a dangling pointer will be produced because the temporary `{$ty}` will be dropped
211217
.label_ptr = this pointer will immediately be invalid
212218
.label_temporary = this `{$ty}` is deallocated at the end of the statement, bind it to a variable to extend its lifetime

compiler/rustc_lint/src/dangling.rs

Lines changed: 142 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use rustc_ast::visit::{visit_opt, walk_list};
22
use rustc_attr_data_structures::{AttributeKind, find_attr};
3+
use rustc_hir::def::Res;
34
use rustc_hir::def_id::LocalDefId;
45
use 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};
78
use rustc_session::{declare_lint, impl_lint_pass};
89
use rustc_span::{Span, sym};
910

10-
use crate::lints::DanglingPointersFromTemporaries;
11+
use crate::lints::{DanglingPointersFromLocals, DanglingPointersFromTemporaries};
12+
use crate::utils::FollowInit;
1113
use crate::{LateContext, LateLintPass};
1214

1315
declare_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,123 @@ declare_lint! {
5385
#[derive(Clone, Copy, Default)]
5486
pub(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.
5991
impl<'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+
// get the return type of the function or closure
107+
let ty = match cx.tcx.type_of(def_id).instantiate_identity().kind() {
108+
ty::FnDef(..) => cx.tcx.fn_sig(def_id).instantiate_identity(),
109+
ty::Closure(_, args) => args.as_closure().sig(),
110+
_ => return,
111+
};
112+
let ty = ty.output().skip_binder();
113+
114+
// verify that we have a pointer type
115+
let inner_ty = match ty.kind() {
116+
ty::RawPtr(inner_ty, _) => *inner_ty,
117+
_ => return,
118+
};
119+
120+
if inner_ty.has_escaping_bound_vars() {
121+
// avoid ICE if we have escaping bound vars
122+
return;
123+
}
124+
125+
if cx
126+
.tcx
127+
.layout_of(cx.typing_env().as_query_input(inner_ty))
128+
.is_ok_and(|layout| !layout.is_1zst())
129+
{
130+
let dcx = &DanglingPointerLocalContext {
131+
body: def_id,
132+
fn_ret: ty,
133+
fn_ret_span: ret_ty.span,
134+
fn_ret_inner: inner_ty,
135+
};
136+
137+
// look for `return`s
138+
DanglingPointerReturnSearcher { cx, dcx }.visit_body(body);
139+
140+
// analyze implicit return expression
141+
if let ExprKind::Block(block, None) = &body.value.kind
142+
&& let innermost_block = block.innermost_block()
143+
&& let Some(expr) = innermost_block.expr
144+
{
145+
lint_addr_of_local(cx, dcx, expr);
146+
}
147+
}
148+
}
149+
}
150+
}
151+
152+
struct DanglingPointerLocalContext<'tcx> {
153+
body: LocalDefId,
154+
fn_ret: Ty<'tcx>,
155+
fn_ret_span: Span,
156+
fn_ret_inner: Ty<'tcx>,
157+
}
158+
159+
struct DanglingPointerReturnSearcher<'lcx, 'tcx> {
160+
cx: &'lcx LateContext<'tcx>,
161+
dcx: &'lcx DanglingPointerLocalContext<'tcx>,
162+
}
163+
164+
impl<'tcx> Visitor<'tcx> for DanglingPointerReturnSearcher<'_, 'tcx> {
165+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result {
166+
if let ExprKind::Ret(Some(expr)) = expr.kind {
167+
lint_addr_of_local(self.cx, self.dcx, expr);
168+
}
169+
walk_expr(self, expr)
170+
}
171+
}
172+
173+
fn lint_addr_of_local<'a>(
174+
cx: &LateContext<'a>,
175+
dcx: &DanglingPointerLocalContext<'a>,
176+
expr: &'a Expr<'a>,
177+
) {
178+
// Find the innermost init expression going through transparent bindings
179+
let inner = cx.expr_or_init(expr);
180+
181+
// Peel casts as they do not interest us here, we want the inner expression.
182+
let (inner, _) = super::utils::peel_casts(cx, FollowInit::No, inner);
183+
184+
// Look for `&<path_to_local_in_same_body>` where the type of the local
185+
// is a value (ie not a ref or pointer).
186+
if let ExprKind::AddrOf(_, _, inner_of) = inner.kind
187+
&& let ExprKind::Path(ref qpath) = inner_of.peel_blocks().kind
188+
&& let Res::Local(from) = cx.qpath_res(qpath, inner_of.hir_id)
189+
&& !cx.typeck_results().expr_ty(inner_of).is_any_ptr()
190+
&& cx.tcx.hir_enclosing_body_owner(from) == dcx.body
191+
{
192+
cx.tcx.emit_node_span_lint(
193+
DANGLING_POINTERS_FROM_LOCALS,
194+
expr.hir_id,
195+
expr.span,
196+
DanglingPointersFromLocals {
197+
ret_ty: dcx.fn_ret,
198+
ret_ty_span: dcx.fn_ret_span,
199+
local_var: cx.tcx.hir_span(from),
200+
local_var_name: cx.tcx.hir_ident(from),
201+
local_var_ty: dcx.fn_ret_inner,
202+
created_at: (expr.hir_id != inner.hir_id).then_some(inner.span),
203+
},
204+
);
70205
}
71206
}
72207

compiler/rustc_lint/src/lints.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,21 @@ pub(crate) struct DanglingPointersFromTemporaries<'tcx> {
11881188
pub temporary_span: Span,
11891189
}
11901190

1191+
#[derive(LintDiagnostic)]
1192+
#[diag(lint_dangling_pointers_from_locals)]
1193+
#[note]
1194+
pub(crate) struct DanglingPointersFromLocals<'tcx> {
1195+
pub ret_ty: Ty<'tcx>,
1196+
#[label(lint_ret_ty)]
1197+
pub ret_ty_span: Span,
1198+
#[label(lint_local_var)]
1199+
pub local_var: Span,
1200+
pub local_var_name: Ident,
1201+
pub local_var_ty: Ty<'tcx>,
1202+
#[label(lint_created_at)]
1203+
pub created_at: Option<Span>,
1204+
}
1205+
11911206
// multiple_supertrait_upcastable.rs
11921207
#[derive(LintDiagnostic)]
11931208
#[diag(lint_multiple_supertrait_upcastable)]
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//@ check-pass
2+
3+
struct Zst((), ());
4+
struct Adt(u8);
5+
6+
const X: u8 = 5;
7+
8+
fn simple() -> *const u8 {
9+
let x = 0;
10+
&x
11+
//~^ WARN a dangling pointer will be produced
12+
}
13+
14+
fn bindings() -> *const u8 {
15+
let x = 0;
16+
let x = &x;
17+
x
18+
//~^ WARN a dangling pointer will be produced
19+
}
20+
21+
fn with_simple_cast() -> *const u8 {
22+
let x = 0u8;
23+
&x as *const u8
24+
//~^ WARN a dangling pointer will be produced
25+
}
26+
27+
fn return_with_complex_cast() -> *mut u8 {
28+
let mut x = 0u8;
29+
return &mut x as *mut u8 as *const u8 as *mut u8;
30+
//~^ WARN a dangling pointer will be produced
31+
}
32+
33+
fn with_block() -> *const u8 {
34+
let x = 0;
35+
&{ x }
36+
//~^ WARN a dangling pointer will be produced
37+
}
38+
39+
fn with_many_blocks() -> *const u8 {
40+
let x = 0;
41+
{
42+
{
43+
&{
44+
//~^ WARN a dangling pointer will be produced
45+
{ x }
46+
}
47+
}
48+
}
49+
}
50+
51+
fn simple_return() -> *const u8 {
52+
let x = 0;
53+
return &x;
54+
//~^ WARN a dangling pointer will be produced
55+
}
56+
57+
fn return_mut() -> *mut u8 {
58+
let mut x = 0;
59+
return &mut x;
60+
//~^ WARN a dangling pointer will be produced
61+
}
62+
63+
fn const_and_flow() -> *const u8 {
64+
if false {
65+
let x = 8;
66+
return &x;
67+
//~^ WARN a dangling pointer will be produced
68+
}
69+
&X // not dangling
70+
}
71+
72+
fn local_adt() -> *const Adt {
73+
let x = Adt(5);
74+
return &x;
75+
//~^ WARN a dangling pointer will be produced
76+
}
77+
78+
fn closure() -> *const u8 {
79+
let _x = || -> *const u8 {
80+
let x = 8;
81+
return &x;
82+
//~^ WARN a dangling pointer will be produced
83+
};
84+
&X // not dangling
85+
}
86+
87+
fn unit() -> *const () {
88+
let x = ();
89+
&x // not dangling
90+
}
91+
92+
fn zst() -> *const Zst {
93+
let x = Zst((), ());
94+
&x // not dangling
95+
}
96+
97+
fn ref_implicit(a: &Adt) -> *const Adt {
98+
a // not dangling
99+
}
100+
101+
fn ref_explicit(a: &Adt) -> *const Adt {
102+
&*a // not dangling
103+
}
104+
105+
fn identity(a: *const Adt) -> *const Adt {
106+
a // not dangling
107+
}
108+
109+
fn from_ref(a: &Adt) -> *const Adt {
110+
std::ptr::from_ref(a) // not dangling
111+
}
112+
113+
fn inner_static() -> *const u8 {
114+
static U: u8 = 5;
115+
if false {
116+
return &U as *const u8; // not dangling
117+
}
118+
&U // not dangling
119+
}
120+
121+
fn return_in_closure() {
122+
let x = 0;
123+
let c = || -> *const u8 {
124+
&x // not dangling by it-self
125+
};
126+
}
127+
128+
fn main() {}

0 commit comments

Comments
 (0)