Skip to content

Commit 87653b9

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

File tree

5 files changed

+350
-7
lines changed

5 files changed

+350
-7
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ 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+
210214
lint_dangling_pointers_from_temporaries = a dangling pointer will be produced because the temporary `{$ty}` will be dropped
211215
.label_ptr = this pointer will immediately be invalid
212216
.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: 112 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,93 @@ 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+
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

compiler/rustc_lint/src/lints.rs

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

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

0 commit comments

Comments
 (0)