Skip to content

Commit 3ad4f3d

Browse files
committed
Add a new lint that warns for pointers to stack memory
This adds a new lint with level `Warn` to check for code like: ```rust fn foo() -> *const i32 { let x = 42; &x } ``` and produce a warning like: ```text error: returning a pointer to stack memory associated with a local variable --> <source>:12:5 | LL| &x | ^^ ```
1 parent 7f36543 commit 3ad4f3d

File tree

7 files changed

+248
-4
lines changed

7 files changed

+248
-4
lines changed

compiler/rustc_lint/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ lint_builtin_overridden_symbol_name =
138138
lint_builtin_overridden_symbol_section =
139139
the program's behavior with overridden link sections on items is unpredictable and Rust cannot provide guarantees when you manually override them
140140
141+
lint_builtin_returning_pointers_to_local_variables = returning a pointer to stack memory associated with a local variable
142+
141143
lint_builtin_special_module_name_used_lib = found module declaration for lib.rs
142144
.note = lib.rs is the root of this crate's library target
143145
.help = to refer to it from other targets, use the library's name as the path

compiler/rustc_lint/src/builtin.rs

+155-4
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ use crate::lints::{
5555
BuiltinIncompleteFeatures, BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures,
5656
BuiltinKeywordIdents, BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
5757
BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns,
58-
BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasBounds,
59-
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub,
60-
BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment,
61-
BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
58+
BuiltinReturningPointersToLocalVariables, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds,
59+
BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
60+
BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures,
61+
BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
6262
};
6363
use crate::nonstandard_style::{MethodLateContext, method_context};
6464
use crate::{
@@ -3027,6 +3027,157 @@ impl<'tcx> LateLintPass<'tcx> for AsmLabels {
30273027
}
30283028
}
30293029

3030+
declare_lint! {
3031+
/// The `returning_pointers_to_local_variables` lint detects when pointer
3032+
/// to stack memory associated with a local variable is returned. That
3033+
/// pointer is immediately dangling.
3034+
///
3035+
/// ### Example
3036+
///
3037+
/// ```rust,no_run
3038+
/// fn foo() -> *const i32 {
3039+
/// let x = 42;
3040+
/// &x
3041+
/// }
3042+
/// ```
3043+
///
3044+
/// {{produces}}
3045+
///
3046+
/// ### Explanation
3047+
///
3048+
/// Returning a pointer to memory refering to a local variable will always
3049+
/// end up in a dangling pointer after returning.
3050+
pub RETURNING_POINTERS_TO_LOCAL_VARIABLES,
3051+
Warn,
3052+
"returning a pointer to stack memory associated with a local variable",
3053+
}
3054+
3055+
declare_lint_pass!(ReturningPointersToLocalVariables => [RETURNING_POINTERS_TO_LOCAL_VARIABLES]);
3056+
3057+
impl<'tcx> LateLintPass<'tcx> for ReturningPointersToLocalVariables {
3058+
fn check_fn(
3059+
&mut self,
3060+
cx: &LateContext<'tcx>,
3061+
_: HirFnKind<'tcx>,
3062+
fn_decl: &'tcx FnDecl<'tcx>,
3063+
body: &'tcx Body<'tcx>,
3064+
_: Span,
3065+
_: LocalDefId,
3066+
) {
3067+
if !matches!(
3068+
fn_decl.output,
3069+
hir::FnRetTy::Return(&hir::Ty { kind: hir::TyKind::Ptr(_), .. }),
3070+
) {
3071+
return;
3072+
}
3073+
3074+
// Check the block of the function that we're looking at.
3075+
if let Some(block) = Self::get_enclosing_block(cx, body.value.hir_id) {
3076+
match block {
3077+
hir::Block {
3078+
stmts:
3079+
[
3080+
..,
3081+
hir::Stmt {
3082+
kind:
3083+
hir::StmtKind::Semi(&hir::Expr {
3084+
kind: hir::ExprKind::Ret(Some(return_expr)),
3085+
..
3086+
}),
3087+
..
3088+
},
3089+
],
3090+
..
3091+
} => {
3092+
Self::maybe_lint_return_expr(cx, return_expr, fn_decl.inputs);
3093+
}
3094+
hir::Block { expr: Some(return_expr), .. } => {
3095+
Self::maybe_lint_return_expr(cx, return_expr, fn_decl.inputs);
3096+
}
3097+
_ => return,
3098+
}
3099+
}
3100+
}
3101+
}
3102+
3103+
impl ReturningPointersToLocalVariables {
3104+
/// Evaluates the return expression of a function and emits a lint if it
3105+
/// returns a pointer to a local variable.
3106+
fn maybe_lint_return_expr<'tcx>(
3107+
cx: &LateContext<'tcx>,
3108+
return_expr: &hir::Expr<'tcx>,
3109+
params: &'tcx [hir::Ty<'tcx>],
3110+
) {
3111+
// Early exit if we see that this is a pointer to an input parameter.
3112+
if Self::expr_is_param(cx.typeck_results(), return_expr, params) {
3113+
return;
3114+
}
3115+
3116+
match return_expr {
3117+
hir::Expr { kind: hir::ExprKind::AddrOf(_, _, addr_expr), .. } => {
3118+
Self::maybe_lint_return_expr(cx, addr_expr, params)
3119+
}
3120+
hir::Expr {
3121+
kind:
3122+
hir::ExprKind::Cast(
3123+
hir::Expr { kind: hir::ExprKind::AddrOf(_, _, addr_expr), .. },
3124+
_,
3125+
),
3126+
..
3127+
} => Self::maybe_lint_return_expr(cx, addr_expr, params),
3128+
hir::Expr { kind: hir::ExprKind::Cast(expr, _), .. } => {
3129+
Self::maybe_lint_return_expr(cx, expr, params)
3130+
}
3131+
hir::Expr {
3132+
kind:
3133+
hir::ExprKind::Path(
3134+
hir::QPath::Resolved(_, hir::Path { res: hir::def::Res::Local(_), .. }),
3135+
..,
3136+
),
3137+
..
3138+
} => cx.emit_span_lint(
3139+
RETURNING_POINTERS_TO_LOCAL_VARIABLES,
3140+
return_expr.span,
3141+
BuiltinReturningPointersToLocalVariables,
3142+
),
3143+
_ => (),
3144+
}
3145+
}
3146+
3147+
fn expr_is_param<'tcx>(
3148+
typeck_results: &ty::TypeckResults<'tcx>,
3149+
expr: &hir::Expr<'tcx>,
3150+
params: &'tcx [hir::Ty<'tcx>],
3151+
) -> bool {
3152+
params
3153+
.iter()
3154+
.map(|param| typeck_results.type_dependent_def_id(param.hir_id))
3155+
.collect::<Vec<_>>()
3156+
.contains(&typeck_results.type_dependent_def_id(expr.hir_id))
3157+
}
3158+
3159+
/// Returns the enclosing block for a [hir::HirId], if available.
3160+
fn get_enclosing_block<'tcx>(
3161+
cx: &LateContext<'tcx>,
3162+
hir_id: hir::HirId,
3163+
) -> Option<&'tcx hir::Block<'tcx>> {
3164+
let map = &cx.tcx.hir();
3165+
let enclosing_node =
3166+
map.get_enclosing_scope(hir_id).map(|enclosing_id| cx.tcx.hir_node(enclosing_id));
3167+
enclosing_node.and_then(|node| match node {
3168+
hir::Node::Block(block) => Some(block),
3169+
hir::Node::Item(&hir::Item { kind: hir::ItemKind::Fn { body: eid, .. }, .. })
3170+
| hir::Node::ImplItem(&hir::ImplItem { kind: hir::ImplItemKind::Fn(_, eid), .. }) => {
3171+
match cx.tcx.hir().body(eid).value.kind {
3172+
hir::ExprKind::Block(block, _) => Some(block),
3173+
_ => None,
3174+
}
3175+
}
3176+
_ => None,
3177+
})
3178+
}
3179+
}
3180+
30303181
declare_lint! {
30313182
/// The `special_module_name` lint detects module
30323183
/// declarations for files that have a special meaning.

compiler/rustc_lint/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ late_lint_methods!(
245245
IfLetRescope: IfLetRescope::default(),
246246
StaticMutRefs: StaticMutRefs,
247247
UnqualifiedLocalImports: UnqualifiedLocalImports,
248+
ReturningPointersToLocalVariables : ReturningPointersToLocalVariables,
248249
]
249250
]
250251
);

compiler/rustc_lint/src/lints.rs

+4
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,10 @@ pub(crate) enum BuiltinSpecialModuleNameUsed {
536536
Main,
537537
}
538538

539+
#[derive(LintDiagnostic)]
540+
#[diag(lint_builtin_returning_pointers_to_local_variables)]
541+
pub(crate) struct BuiltinReturningPointersToLocalVariables;
542+
539543
// deref_into_dyn_supertrait.rs
540544
#[derive(LintDiagnostic)]
541545
#[diag(lint_supertrait_as_deref_target)]

src/tools/miri/tests/fail/validity/dangling_ref3.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Make sure we catch this even without Stacked Borrows
22
//@compile-flags: -Zmiri-disable-stacked-borrows
3+
#![allow(returning_pointers_to_local_variables)]
34
use std::mem;
45

56
fn dangling() -> *const u8 {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//@ check-pass
2+
3+
#![warn(returning_pointers_to_local_variables)]
4+
5+
fn foo() -> *const u32 {
6+
let empty = 42u32;
7+
return &empty as *const _;
8+
//~^ WARN returning a pointer to stack memory associated with a local variable
9+
}
10+
11+
fn bar() -> *const u32 {
12+
let empty = 42u32;
13+
&empty as *const _
14+
//~^ WARN returning a pointer to stack memory associated with a local variable
15+
}
16+
17+
fn baz() -> *const u32 {
18+
let empty = 42u32;
19+
return &empty;
20+
//~^ WARN returning a pointer to stack memory associated with a local variable
21+
}
22+
23+
fn faa() -> *const u32 {
24+
let empty = 42u32;
25+
&empty
26+
//~^ WARN returning a pointer to stack memory associated with a local variable
27+
}
28+
29+
fn pointer_to_pointer() -> *const *mut u32 {
30+
let mut empty = 42u32;
31+
&(&mut empty as *mut u32) as *const _
32+
//~^ WARN returning a pointer to stack memory associated with a local variable
33+
}
34+
35+
fn dont_lint_param(val: u32) -> *const u32 {
36+
&val
37+
}
38+
39+
struct Foo {}
40+
41+
impl Foo {
42+
fn dont_lint_self_param(&self) -> *const Foo {
43+
self
44+
}
45+
}
46+
47+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
warning: returning a pointer to stack memory associated with a local variable
2+
--> $DIR/lint-returning-pointers-to-local-variables.rs:7:13
3+
|
4+
LL | return &empty as *const _;
5+
| ^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/lint-returning-pointers-to-local-variables.rs:3:9
9+
|
10+
LL | #![warn(returning_pointers_to_local_variables)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
warning: returning a pointer to stack memory associated with a local variable
14+
--> $DIR/lint-returning-pointers-to-local-variables.rs:13:6
15+
|
16+
LL | &empty as *const _
17+
| ^^^^^
18+
19+
warning: returning a pointer to stack memory associated with a local variable
20+
--> $DIR/lint-returning-pointers-to-local-variables.rs:19:13
21+
|
22+
LL | return &empty;
23+
| ^^^^^
24+
25+
warning: returning a pointer to stack memory associated with a local variable
26+
--> $DIR/lint-returning-pointers-to-local-variables.rs:25:6
27+
|
28+
LL | &empty
29+
| ^^^^^
30+
31+
warning: returning a pointer to stack memory associated with a local variable
32+
--> $DIR/lint-returning-pointers-to-local-variables.rs:31:12
33+
|
34+
LL | &(&mut empty as *mut u32) as *const _
35+
| ^^^^^
36+
37+
warning: 5 warnings emitted
38+

0 commit comments

Comments
 (0)