Skip to content

Commit

Permalink
Add a new lint that warns for pointers to stack memory
Browse files Browse the repository at this point in the history
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
   |  ^^
```
  • Loading branch information
1c3t3a committed Dec 12, 2024
1 parent 5a6036a commit 40eb5d7
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 6 deletions.
2 changes: 2 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ lint_builtin_overridden_symbol_name =
lint_builtin_overridden_symbol_section =
the program's behavior with overridden link sections on items is unpredictable and Rust cannot provide guarantees when you manually override them
lint_builtin_return_local_variable_ptr = returning a pointer to stack memory associated with a local variable
lint_builtin_special_module_name_used_lib = found module declaration for lib.rs
.note = lib.rs is the root of this crate's library target
.help = to refer to it from other targets, use the library's name as the path
Expand Down
140 changes: 134 additions & 6 deletions compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ use crate::lints::{
BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives,
BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures,
BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes,
BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed,
BuiltinTrivialBounds, BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller,
BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub,
BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
BuiltinWhileTrue, InvalidAsmLabel,
BuiltinLocalVariablePointerImpl, BuiltinMissingCopyImpl, BuiltinMissingDebugImpl,
BuiltinMissingDoc, BuiltinMutablesTransmutes, BuiltinNoMangleGeneric,
BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds,
BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures,
BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
};
use crate::nonstandard_style::{MethodLateContext, method_context};
use crate::{
Expand Down Expand Up @@ -2986,6 +2986,134 @@ impl<'tcx> LateLintPass<'tcx> for AsmLabels {
}
}

declare_lint! {
/// The `return_local_variable_ptr` lint detects when pointer to stack
/// memory associated with a local variable is returned. That pointer
/// is immediately dangling.
///
/// ### Example
///
/// ```rust,no_run
/// fn foo() -> *const i32 {
/// let x = 42;
/// &x
/// }
/// ```
///
/// This will produce:
///
/// ```text
/// error: returning a pointer to stack memory associated with a local variable
/// --> <source>:12:5
/// |
/// LL| &x
/// | ^^
/// ```
///
/// ### Explanation
///
/// Returning a pointer to memory refering to a local variable will always
/// end up in a dangling pointer after returning.
pub RETURN_LOCAL_VARIABLE_PTR,
Warn,
"returning a pointer to stack memory associated with a local variable",
}

declare_lint_pass!(ReturnLocalVariablePointer => [RETURN_LOCAL_VARIABLE_PTR]);

impl<'tcx> LateLintPass<'tcx> for ReturnLocalVariablePointer {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
_: HirFnKind<'tcx>,
fn_decl: &'tcx FnDecl<'tcx>,
body: &'tcx Body<'tcx>,
_: Span,
_: LocalDefId,
) {
if !matches!(
fn_decl.output,
hir::FnRetTy::Return(&hir::Ty { kind: hir::TyKind::Ptr(_), .. }),
) {
return;
}

// Check the block of the function that we're looking at.
if let Some(block) = Self::get_enclosing_block(cx, body.value.hir_id) {
match block {
hir::Block {
stmts:
[
..,
hir::Stmt {
kind:
hir::StmtKind::Semi(&hir::Expr {
kind: hir::ExprKind::Ret(Some(return_expr)),
..
}),
..
},
],
..
} => {
Self::maybe_lint_return_expr(cx, return_expr);
}
hir::Block { expr: Some(return_expr), .. } => {
Self::maybe_lint_return_expr(cx, return_expr);
}
_ => return,
}
}
}
}

impl ReturnLocalVariablePointer {
/// Evaluates the return expression of a function and emits a lint if it
/// returns a pointer to a local variable.
fn maybe_lint_return_expr<'tcx>(cx: &LateContext<'tcx>, return_expr: &hir::Expr<'tcx>) {
if let hir::Expr { kind: hir::ExprKind::AddrOf(_, _, addr_expr), .. }
| hir::Expr {
kind:
hir::ExprKind::Cast(hir::Expr { kind: hir::ExprKind::AddrOf(_, _, addr_expr), .. }, _),
..
} = return_expr
{
if let hir::ExprKind::Path(
hir::QPath::Resolved(_, hir::Path { res: hir::def::Res::Local(_), .. }),
..,
) = addr_expr.kind
{
cx.emit_span_lint(
RETURN_LOCAL_VARIABLE_PTR,
return_expr.span,
BuiltinLocalVariablePointerImpl,
);
}
}
}

/// Returns the enclosing block for a [hir::HirId], if available.
fn get_enclosing_block<'tcx>(
cx: &LateContext<'tcx>,
hir_id: hir::HirId,
) -> Option<&'tcx hir::Block<'tcx>> {
let map = &cx.tcx.hir();
let enclosing_node =
map.get_enclosing_scope(hir_id).map(|enclosing_id| cx.tcx.hir_node(enclosing_id));
enclosing_node.and_then(|node| match node {
hir::Node::Block(block) => Some(block),
hir::Node::Item(&hir::Item { kind: hir::ItemKind::Fn(_, _, eid), .. })
| hir::Node::ImplItem(&hir::ImplItem { kind: hir::ImplItemKind::Fn(_, eid), .. }) => {
match cx.tcx.hir().body(eid).value.kind {
hir::ExprKind::Block(block, _) => Some(block),
_ => None,
}
}
_ => None,
})
}
}

declare_lint! {
/// The `special_module_name` lint detects module
/// declarations for files that have a special meaning.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ late_lint_methods!(
IfLetRescope: IfLetRescope::default(),
StaticMutRefs: StaticMutRefs,
UnqualifiedLocalImports: UnqualifiedLocalImports,
ReturnLocalVariablePointer : ReturnLocalVariablePointer,
]
]
);
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,10 @@ pub(crate) enum BuiltinSpecialModuleNameUsed {
Main,
}

#[derive(LintDiagnostic)]
#[diag(lint_builtin_return_local_variable_ptr)]
pub(crate) struct BuiltinLocalVariablePointerImpl;

// deref_into_dyn_supertrait.rs
#[derive(LintDiagnostic)]
#[diag(lint_supertrait_as_deref_target)]
Expand Down
25 changes: 25 additions & 0 deletions tests/ui/lint/lint-return-local-variable-ptr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// test the deref_nullptr lint

#![deny(return_local_variable_ptr)]

fn foo() -> *const u32 {
let empty = 42u32;
return &empty as *const _;
}

fn bar() -> *const u32 {
let empty = 42u32;
&empty as *const _
}

fn baz() -> *const u32 {
let empty = 42u32;
return &empty;
}

fn faa() -> *const u32 {
let empty = 42u32;
&empty
}

fn main() {}
32 changes: 32 additions & 0 deletions tests/ui/lint/lint-return-local-variable-ptr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
error: returning a pointer to stack memory associated with a local variable
--> $DIR/lint-return-local-variable-ptr.rs:7:12
|
LL | return &empty as *const _;
| ^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> $DIR/lint-return-local-variable-ptr.rs:3:9
|
LL | #![deny(return_local_variable_ptr)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^

error: returning a pointer to stack memory associated with a local variable
--> $DIR/lint-return-local-variable-ptr.rs:12:5
|
LL | &empty as *const _
| ^^^^^^^^^^^^^^^^^^

error: returning a pointer to stack memory associated with a local variable
--> $DIR/lint-return-local-variable-ptr.rs:17:12
|
LL | return &empty;
| ^^^^^^

error: returning a pointer to stack memory associated with a local variable
--> $DIR/lint-return-local-variable-ptr.rs:22:5
|
LL | &empty
| ^^^^^^

error: aborting due to 4 previous errors

0 comments on commit 40eb5d7

Please sign in to comment.