Skip to content

Commit 4f6281b

Browse files
committed
Add lint against dangling pointers form local variables
1 parent 3f9f20f commit 4f6281b

File tree

5 files changed

+482
-7
lines changed

5 files changed

+482
-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: 138 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
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};
1112
use crate::{LateContext, LateLintPass};
1213

1314
declare_lint! {
@@ -42,6 +43,36 @@ declare_lint! {
4243
"detects getting a pointer from a temporary"
4344
}
4445

46+
declare_lint! {
47+
/// The `dangling_pointers_from_locals` lint detects getting a pointer to data
48+
/// of a local that will be dropped at the end of the function.
49+
///
50+
/// ### Example
51+
///
52+
/// ```rust
53+
/// fn f() -> *const u8 {
54+
/// let x = 0;
55+
/// &x // returns a dangling ptr to `x`
56+
/// }
57+
/// ```
58+
///
59+
/// {{produces}}
60+
///
61+
/// ### Explanation
62+
///
63+
/// Returning a pointer from a local value will not prolong its lifetime,
64+
/// which means that the value can be dropped and the allocation freed
65+
/// while the pointer still exists, making the pointer dangling.
66+
/// This is not an error (as far as the type system is concerned)
67+
/// but probably is not what the user intended either.
68+
///
69+
/// If you need stronger guarantees, consider using references instead,
70+
/// as they are statically verified by the borrow-checker to never dangle.
71+
pub DANGLING_POINTERS_FROM_LOCALS,
72+
Warn,
73+
"detects returning a pointer from a local variable"
74+
}
75+
4576
/// FIXME: false negatives (i.e. the lint is not emitted when it should be)
4677
/// 1. Ways to get a temporary that are not recognized:
4778
/// - `owning_temporary.field`
@@ -53,20 +84,120 @@ declare_lint! {
5384
#[derive(Clone, Copy, Default)]
5485
pub(crate) struct DanglingPointers;
5586

56-
impl_lint_pass!(DanglingPointers => [DANGLING_POINTERS_FROM_TEMPORARIES]);
87+
impl_lint_pass!(DanglingPointers => [DANGLING_POINTERS_FROM_TEMPORARIES, DANGLING_POINTERS_FROM_LOCALS]);
5788

5889
// This skips over const blocks, but they cannot use or return a dangling pointer anyways.
5990
impl<'tcx> LateLintPass<'tcx> for DanglingPointers {
6091
fn check_fn(
6192
&mut self,
6293
cx: &LateContext<'tcx>,
6394
_: FnKind<'tcx>,
64-
_: &'tcx FnDecl<'tcx>,
95+
fn_decl: &'tcx FnDecl<'tcx>,
6596
body: &'tcx Body<'tcx>,
6697
_: Span,
67-
_: LocalDefId,
98+
def_id: LocalDefId,
6899
) {
69-
DanglingPointerSearcher { cx, inside_call_args: false }.visit_body(body)
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+
// get the return type of the function or closure
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+
// verify that we have a pointer type
114+
let inner_ty = match ty.kind() {
115+
ty::RawPtr(inner_ty, _) => *inner_ty,
116+
_ => return,
117+
};
118+
119+
if inner_ty.has_escaping_bound_vars() {
120+
// avoid ICE if we have escaping bound vars
121+
return;
122+
}
123+
124+
if cx
125+
.tcx
126+
.layout_of(cx.typing_env().as_query_input(inner_ty))
127+
.is_ok_and(|layout| !layout.is_1zst())
128+
{
129+
let dcx = &DanglingPointerLocalContext {
130+
body: def_id,
131+
fn_ret: ty,
132+
fn_ret_span: ret_ty.span,
133+
fn_ret_inner: inner_ty,
134+
};
135+
136+
// look for `return`s
137+
DanglingPointerReturnSearcher { cx, dcx }.visit_body(body);
138+
139+
// analyze implicit return expression
140+
if let ExprKind::Block(block, None) = &body.value.kind
141+
&& let innermost_block = block.innermost_block()
142+
&& let Some(expr) = innermost_block.expr
143+
{
144+
lint_addr_of_local(cx, dcx, expr);
145+
}
146+
}
147+
}
148+
}
149+
}
150+
151+
struct DanglingPointerLocalContext<'tcx> {
152+
body: LocalDefId,
153+
fn_ret: Ty<'tcx>,
154+
fn_ret_span: Span,
155+
fn_ret_inner: Ty<'tcx>,
156+
}
157+
158+
struct DanglingPointerReturnSearcher<'lcx, 'tcx> {
159+
cx: &'lcx LateContext<'tcx>,
160+
dcx: &'lcx DanglingPointerLocalContext<'tcx>,
161+
}
162+
163+
impl<'tcx> Visitor<'tcx> for DanglingPointerReturnSearcher<'_, 'tcx> {
164+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result {
165+
if let ExprKind::Ret(Some(expr)) = expr.kind {
166+
lint_addr_of_local(self.cx, self.dcx, expr);
167+
}
168+
walk_expr(self, expr)
169+
}
170+
}
171+
172+
fn lint_addr_of_local<'a>(
173+
cx: &LateContext<'a>,
174+
dcx: &DanglingPointerLocalContext<'a>,
175+
expr: &'a Expr<'a>,
176+
) {
177+
// Peel casts as they do not interest us here, we want the inner expression.
178+
let (inner, _) = super::utils::peel_casts(cx, expr);
179+
180+
// Look for `&<path_to_local_in_same_body>` where the type of the local
181+
// is a value (ie not a ref or pointer).
182+
if let ExprKind::AddrOf(_, _, inner_of) = inner.kind
183+
&& let ExprKind::Path(ref qpath) = inner_of.peel_blocks().kind
184+
&& let Res::Local(from) = cx.qpath_res(qpath, inner_of.hir_id)
185+
&& !cx.typeck_results().expr_ty(inner_of).is_any_ptr()
186+
&& cx.tcx.hir_enclosing_body_owner(from) == dcx.body
187+
{
188+
cx.tcx.emit_node_span_lint(
189+
DANGLING_POINTERS_FROM_LOCALS,
190+
expr.hir_id,
191+
expr.span,
192+
DanglingPointersFromLocals {
193+
ret_ty: dcx.fn_ret,
194+
ret_ty_span: dcx.fn_ret_span,
195+
local_var: cx.tcx.hir_span(from),
196+
local_var_name: cx.tcx.hir_ident(from),
197+
local_var_ty: dcx.fn_ret_inner,
198+
created_at: (expr.hir_id != inner.hir_id).then_some(inner.span),
199+
},
200+
);
70201
}
71202
}
72203

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: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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 bindings_and_casts() -> *const u8 {
28+
let x = 0u8;
29+
let x = &x as *const u8;
30+
x as *const u8
31+
//~^ WARN a dangling pointer will be produced
32+
}
33+
34+
fn return_with_complex_cast() -> *mut u8 {
35+
let mut x = 0u8;
36+
return &mut x as *mut u8 as *const u8 as *mut u8;
37+
//~^ WARN a dangling pointer will be produced
38+
}
39+
40+
fn with_block() -> *const u8 {
41+
let x = 0;
42+
&{ x }
43+
//~^ WARN a dangling pointer will be produced
44+
}
45+
46+
fn with_many_blocks() -> *const u8 {
47+
let x = 0;
48+
{
49+
{
50+
&{
51+
//~^ WARN a dangling pointer will be produced
52+
{ x }
53+
}
54+
}
55+
}
56+
}
57+
58+
fn simple_return() -> *const u8 {
59+
let x = 0;
60+
return &x;
61+
//~^ WARN a dangling pointer will be produced
62+
}
63+
64+
fn return_mut() -> *mut u8 {
65+
let mut x = 0;
66+
return &mut x;
67+
//~^ WARN a dangling pointer will be produced
68+
}
69+
70+
fn const_and_flow() -> *const u8 {
71+
if false {
72+
let x = 8;
73+
return &x;
74+
//~^ WARN a dangling pointer will be produced
75+
}
76+
&X // not dangling
77+
}
78+
79+
fn vector<T: Default>() -> *const Vec<T> {
80+
let x = vec![T::default()];
81+
&x
82+
//~^ WARN a dangling pointer will be produced
83+
}
84+
85+
fn local_adt() -> *const Adt {
86+
let x = Adt(5);
87+
return &x;
88+
//~^ WARN a dangling pointer will be produced
89+
}
90+
91+
fn closure() -> *const u8 {
92+
let _x = || -> *const u8 {
93+
let x = 8;
94+
return &x;
95+
//~^ WARN a dangling pointer will be produced
96+
};
97+
&X // not dangling
98+
}
99+
100+
fn unit() -> *const () {
101+
let x = ();
102+
&x // not dangling
103+
}
104+
105+
fn zst() -> *const Zst {
106+
let x = Zst((), ());
107+
&x // not dangling
108+
}
109+
110+
fn ref_implicit(a: &Adt) -> *const Adt {
111+
a // not dangling
112+
}
113+
114+
fn ref_explicit(a: &Adt) -> *const Adt {
115+
&*a // not dangling
116+
}
117+
118+
fn identity(a: *const Adt) -> *const Adt {
119+
a // not dangling
120+
}
121+
122+
fn from_ref(a: &Adt) -> *const Adt {
123+
std::ptr::from_ref(a) // not dangling
124+
}
125+
126+
fn inner_static() -> *const u8 {
127+
static U: u8 = 5;
128+
if false {
129+
return &U as *const u8; // not dangling
130+
}
131+
&U // not dangling
132+
}
133+
134+
fn return_in_closure() {
135+
let x = 0;
136+
let c = || -> *const u8 {
137+
&x // not dangling by it-self
138+
};
139+
}
140+
141+
fn option<T: Default>() -> *const Option<T> {
142+
let x = Some(T::default());
143+
&x // can't compute layout of `Option<T>`, so cnat' be sure it won't be a ZST
144+
}
145+
146+
fn generic<T: Default>() -> *const T {
147+
let x = T::default();
148+
&x // can't compute layout of `T`, so can't be sure it won't be a ZST
149+
}
150+
151+
fn main() {}

0 commit comments

Comments
 (0)