From 40eb5d77123cd7158aaf56db22f141644c35ea07 Mon Sep 17 00:00:00 2001 From: Bastian Kersting Date: Thu, 12 Dec 2024 16:28:27 +0000 Subject: [PATCH] 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 --> :12:5 | LL| &x | ^^ ``` --- compiler/rustc_lint/messages.ftl | 2 + compiler/rustc_lint/src/builtin.rs | 140 +++++++++++++++++- compiler/rustc_lint/src/lib.rs | 1 + compiler/rustc_lint/src/lints.rs | 4 + .../ui/lint/lint-return-local-variable-ptr.rs | 25 ++++ .../lint-return-local-variable-ptr.stderr | 32 ++++ 6 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 tests/ui/lint/lint-return-local-variable-ptr.rs create mode 100644 tests/ui/lint/lint-return-local-variable-ptr.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 49e6b763590b0..b907c4763e815 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -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 diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 093cc16fb4c18..0310cefee882f 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -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::{ @@ -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 + /// --> :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. diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index a99c94592b302..9819d66d1240e 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -241,6 +241,7 @@ late_lint_methods!( IfLetRescope: IfLetRescope::default(), StaticMutRefs: StaticMutRefs, UnqualifiedLocalImports: UnqualifiedLocalImports, + ReturnLocalVariablePointer : ReturnLocalVariablePointer, ] ] ); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 20822f23bf15d..1cf948d75dc44 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -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)] diff --git a/tests/ui/lint/lint-return-local-variable-ptr.rs b/tests/ui/lint/lint-return-local-variable-ptr.rs new file mode 100644 index 0000000000000..6caf699724636 --- /dev/null +++ b/tests/ui/lint/lint-return-local-variable-ptr.rs @@ -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 ∅ +} + +fn faa() -> *const u32 { + let empty = 42u32; + &empty +} + +fn main() {} diff --git a/tests/ui/lint/lint-return-local-variable-ptr.stderr b/tests/ui/lint/lint-return-local-variable-ptr.stderr new file mode 100644 index 0000000000000..dbd66895d1be3 --- /dev/null +++ b/tests/ui/lint/lint-return-local-variable-ptr.stderr @@ -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 ∅ + | ^^^^^^ + +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 +