Skip to content

Commit 40eb5d7

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 5a6036a commit 40eb5d7

File tree

6 files changed

+198
-6
lines changed

6 files changed

+198
-6
lines changed

compiler/rustc_lint/messages.ftl

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

compiler/rustc_lint/src/builtin.rs

+134-6
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ use crate::lints::{
5454
BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives,
5555
BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures,
5656
BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
57-
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes,
58-
BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed,
59-
BuiltinTrivialBounds, BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller,
60-
BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub,
61-
BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
62-
BuiltinWhileTrue, InvalidAsmLabel,
57+
BuiltinLocalVariablePointerImpl, BuiltinMissingCopyImpl, BuiltinMissingDebugImpl,
58+
BuiltinMissingDoc, BuiltinMutablesTransmutes, BuiltinNoMangleGeneric,
59+
BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds,
60+
BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
61+
BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures,
62+
BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
6363
};
6464
use crate::nonstandard_style::{MethodLateContext, method_context};
6565
use crate::{
@@ -2986,6 +2986,134 @@ impl<'tcx> LateLintPass<'tcx> for AsmLabels {
29862986
}
29872987
}
29882988

2989+
declare_lint! {
2990+
/// The `return_local_variable_ptr` lint detects when pointer to stack
2991+
/// memory associated with a local variable is returned. That pointer
2992+
/// is immediately dangling.
2993+
///
2994+
/// ### Example
2995+
///
2996+
/// ```rust,no_run
2997+
/// fn foo() -> *const i32 {
2998+
/// let x = 42;
2999+
/// &x
3000+
/// }
3001+
/// ```
3002+
///
3003+
/// This will produce:
3004+
///
3005+
/// ```text
3006+
/// error: returning a pointer to stack memory associated with a local variable
3007+
/// --> <source>:12:5
3008+
/// |
3009+
/// LL| &x
3010+
/// | ^^
3011+
/// ```
3012+
///
3013+
/// ### Explanation
3014+
///
3015+
/// Returning a pointer to memory refering to a local variable will always
3016+
/// end up in a dangling pointer after returning.
3017+
pub RETURN_LOCAL_VARIABLE_PTR,
3018+
Warn,
3019+
"returning a pointer to stack memory associated with a local variable",
3020+
}
3021+
3022+
declare_lint_pass!(ReturnLocalVariablePointer => [RETURN_LOCAL_VARIABLE_PTR]);
3023+
3024+
impl<'tcx> LateLintPass<'tcx> for ReturnLocalVariablePointer {
3025+
fn check_fn(
3026+
&mut self,
3027+
cx: &LateContext<'tcx>,
3028+
_: HirFnKind<'tcx>,
3029+
fn_decl: &'tcx FnDecl<'tcx>,
3030+
body: &'tcx Body<'tcx>,
3031+
_: Span,
3032+
_: LocalDefId,
3033+
) {
3034+
if !matches!(
3035+
fn_decl.output,
3036+
hir::FnRetTy::Return(&hir::Ty { kind: hir::TyKind::Ptr(_), .. }),
3037+
) {
3038+
return;
3039+
}
3040+
3041+
// Check the block of the function that we're looking at.
3042+
if let Some(block) = Self::get_enclosing_block(cx, body.value.hir_id) {
3043+
match block {
3044+
hir::Block {
3045+
stmts:
3046+
[
3047+
..,
3048+
hir::Stmt {
3049+
kind:
3050+
hir::StmtKind::Semi(&hir::Expr {
3051+
kind: hir::ExprKind::Ret(Some(return_expr)),
3052+
..
3053+
}),
3054+
..
3055+
},
3056+
],
3057+
..
3058+
} => {
3059+
Self::maybe_lint_return_expr(cx, return_expr);
3060+
}
3061+
hir::Block { expr: Some(return_expr), .. } => {
3062+
Self::maybe_lint_return_expr(cx, return_expr);
3063+
}
3064+
_ => return,
3065+
}
3066+
}
3067+
}
3068+
}
3069+
3070+
impl ReturnLocalVariablePointer {
3071+
/// Evaluates the return expression of a function and emits a lint if it
3072+
/// returns a pointer to a local variable.
3073+
fn maybe_lint_return_expr<'tcx>(cx: &LateContext<'tcx>, return_expr: &hir::Expr<'tcx>) {
3074+
if let hir::Expr { kind: hir::ExprKind::AddrOf(_, _, addr_expr), .. }
3075+
| hir::Expr {
3076+
kind:
3077+
hir::ExprKind::Cast(hir::Expr { kind: hir::ExprKind::AddrOf(_, _, addr_expr), .. }, _),
3078+
..
3079+
} = return_expr
3080+
{
3081+
if let hir::ExprKind::Path(
3082+
hir::QPath::Resolved(_, hir::Path { res: hir::def::Res::Local(_), .. }),
3083+
..,
3084+
) = addr_expr.kind
3085+
{
3086+
cx.emit_span_lint(
3087+
RETURN_LOCAL_VARIABLE_PTR,
3088+
return_expr.span,
3089+
BuiltinLocalVariablePointerImpl,
3090+
);
3091+
}
3092+
}
3093+
}
3094+
3095+
/// Returns the enclosing block for a [hir::HirId], if available.
3096+
fn get_enclosing_block<'tcx>(
3097+
cx: &LateContext<'tcx>,
3098+
hir_id: hir::HirId,
3099+
) -> Option<&'tcx hir::Block<'tcx>> {
3100+
let map = &cx.tcx.hir();
3101+
let enclosing_node =
3102+
map.get_enclosing_scope(hir_id).map(|enclosing_id| cx.tcx.hir_node(enclosing_id));
3103+
enclosing_node.and_then(|node| match node {
3104+
hir::Node::Block(block) => Some(block),
3105+
hir::Node::Item(&hir::Item { kind: hir::ItemKind::Fn(_, _, eid), .. })
3106+
| hir::Node::ImplItem(&hir::ImplItem { kind: hir::ImplItemKind::Fn(_, eid), .. }) => {
3107+
match cx.tcx.hir().body(eid).value.kind {
3108+
hir::ExprKind::Block(block, _) => Some(block),
3109+
_ => None,
3110+
}
3111+
}
3112+
_ => None,
3113+
})
3114+
}
3115+
}
3116+
29893117
declare_lint! {
29903118
/// The `special_module_name` lint detects module
29913119
/// declarations for files that have a special meaning.

compiler/rustc_lint/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ late_lint_methods!(
241241
IfLetRescope: IfLetRescope::default(),
242242
StaticMutRefs: StaticMutRefs,
243243
UnqualifiedLocalImports: UnqualifiedLocalImports,
244+
ReturnLocalVariablePointer : ReturnLocalVariablePointer,
244245
]
245246
]
246247
);

compiler/rustc_lint/src/lints.rs

+4
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,10 @@ pub(crate) enum BuiltinSpecialModuleNameUsed {
531531
Main,
532532
}
533533

534+
#[derive(LintDiagnostic)]
535+
#[diag(lint_builtin_return_local_variable_ptr)]
536+
pub(crate) struct BuiltinLocalVariablePointerImpl;
537+
534538
// deref_into_dyn_supertrait.rs
535539
#[derive(LintDiagnostic)]
536540
#[diag(lint_supertrait_as_deref_target)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// test the deref_nullptr lint
2+
3+
#![deny(return_local_variable_ptr)]
4+
5+
fn foo() -> *const u32 {
6+
let empty = 42u32;
7+
return &empty as *const _;
8+
}
9+
10+
fn bar() -> *const u32 {
11+
let empty = 42u32;
12+
&empty as *const _
13+
}
14+
15+
fn baz() -> *const u32 {
16+
let empty = 42u32;
17+
return &empty;
18+
}
19+
20+
fn faa() -> *const u32 {
21+
let empty = 42u32;
22+
&empty
23+
}
24+
25+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: returning a pointer to stack memory associated with a local variable
2+
--> $DIR/lint-return-local-variable-ptr.rs:7:12
3+
|
4+
LL | return &empty as *const _;
5+
| ^^^^^^^^^^^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/lint-return-local-variable-ptr.rs:3:9
9+
|
10+
LL | #![deny(return_local_variable_ptr)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: returning a pointer to stack memory associated with a local variable
14+
--> $DIR/lint-return-local-variable-ptr.rs:12:5
15+
|
16+
LL | &empty as *const _
17+
| ^^^^^^^^^^^^^^^^^^
18+
19+
error: returning a pointer to stack memory associated with a local variable
20+
--> $DIR/lint-return-local-variable-ptr.rs:17:12
21+
|
22+
LL | return &empty;
23+
| ^^^^^^
24+
25+
error: returning a pointer to stack memory associated with a local variable
26+
--> $DIR/lint-return-local-variable-ptr.rs:22:5
27+
|
28+
LL | &empty
29+
| ^^^^^^
30+
31+
error: aborting due to 4 previous errors
32+

0 commit comments

Comments
 (0)