diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 60c183bd56b1f..f695c16ce9c8a 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -138,6 +138,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_returning_pointers_to_local_variables = 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 e65f4beab2411..033defc2ef35b 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -55,10 +55,10 @@ use crate::lints::{ BuiltinIncompleteFeatures, BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents, BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, - BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasBounds, - BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, - BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, - BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel, + BuiltinReturningPointersToLocalVariables, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, + BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, + BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, + BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel, }; use crate::nonstandard_style::{MethodLateContext, method_context}; use crate::{ @@ -3063,6 +3063,159 @@ impl<'tcx> LateLintPass<'tcx> for AsmLabels { } } +declare_lint! { + /// The `returning_pointers_to_local_variables` 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 + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Returning a pointer to memory refering to a local variable will always + /// end up in a dangling pointer after returning. + pub RETURNING_POINTERS_TO_LOCAL_VARIABLES, + Warn, + "returning a pointer to stack memory associated with a local variable", +} + +declare_lint_pass!(ReturningPointersToLocalVariables => [RETURNING_POINTERS_TO_LOCAL_VARIABLES]); + +impl<'tcx> LateLintPass<'tcx> for ReturningPointersToLocalVariables { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: HirFnKind<'tcx>, + fn_decl: &'tcx FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + _: Span, + _: LocalDefId, + ) { + let hir::FnRetTy::Return(&hir::Ty { kind: hir::TyKind::Ptr(ptr_ty), .. }) = fn_decl.output + else { + return; + }; + if matches!(ptr_ty.ty.kind, hir::TyKind::Tup([])) { + 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, fn_decl.inputs); + } + hir::Block { expr: Some(return_expr), .. } => { + Self::maybe_lint_return_expr(cx, return_expr, fn_decl.inputs); + } + _ => return, + } + } + } +} + +impl ReturningPointersToLocalVariables { + /// 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>, + params: &'tcx [hir::Ty<'tcx>], + ) { + // Early exit if we see that this is a pointer to an input parameter. + if Self::expr_is_param(cx.typeck_results(), return_expr, params) { + return; + } + + match return_expr { + hir::Expr { kind: hir::ExprKind::AddrOf(_, _, addr_expr), .. } => { + Self::maybe_lint_return_expr(cx, addr_expr, params) + } + hir::Expr { + kind: + hir::ExprKind::Cast( + hir::Expr { kind: hir::ExprKind::AddrOf(_, _, addr_expr), .. }, + _, + ), + .. + } => Self::maybe_lint_return_expr(cx, addr_expr, params), + hir::Expr { kind: hir::ExprKind::Cast(expr, _), .. } => { + Self::maybe_lint_return_expr(cx, expr, params) + } + hir::Expr { + kind: + hir::ExprKind::Path( + hir::QPath::Resolved(_, hir::Path { res: hir::def::Res::Local(_), .. }), + .., + ), + .. + } => cx.emit_span_lint( + RETURNING_POINTERS_TO_LOCAL_VARIABLES, + return_expr.span, + BuiltinReturningPointersToLocalVariables, + ), + _ => (), + } + } + + fn expr_is_param<'tcx>( + typeck_results: &ty::TypeckResults<'tcx>, + expr: &hir::Expr<'tcx>, + params: &'tcx [hir::Ty<'tcx>], + ) -> bool { + params + .iter() + .map(|param| typeck_results.type_dependent_def_id(param.hir_id)) + .collect::>() + .contains(&typeck_results.type_dependent_def_id(expr.hir_id)) + } + + /// 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 enclosing_node = cx + .tcx + .hir_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 { body: 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 daddd45d59790..79d48d24c807c 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -243,6 +243,7 @@ late_lint_methods!( IfLetRescope: IfLetRescope::default(), StaticMutRefs: StaticMutRefs, UnqualifiedLocalImports: UnqualifiedLocalImports, + ReturningPointersToLocalVariables : ReturningPointersToLocalVariables, ] ] ); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 8ab64fbd127af..34d4240bab86c 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -529,6 +529,10 @@ pub(crate) enum BuiltinSpecialModuleNameUsed { Main, } +#[derive(LintDiagnostic)] +#[diag(lint_builtin_returning_pointers_to_local_variables)] +pub(crate) struct BuiltinReturningPointersToLocalVariables; + // deref_into_dyn_supertrait.rs #[derive(LintDiagnostic)] #[diag(lint_supertrait_as_deref_target)] diff --git a/src/tools/miri/tests/fail/validity/dangling_ref3.rs b/src/tools/miri/tests/fail/validity/dangling_ref3.rs index 8e8a75bd7ec04..47ab747c23327 100644 --- a/src/tools/miri/tests/fail/validity/dangling_ref3.rs +++ b/src/tools/miri/tests/fail/validity/dangling_ref3.rs @@ -1,5 +1,6 @@ // Make sure we catch this even without Stacked Borrows //@compile-flags: -Zmiri-disable-stacked-borrows +#![allow(returning_pointers_to_local_variables)] use std::mem; fn dangling() -> *const u8 { diff --git a/tests/ui/lint/lint-returning-pointers-to-local-variables.rs b/tests/ui/lint/lint-returning-pointers-to-local-variables.rs new file mode 100644 index 0000000000000..5957b26c53415 --- /dev/null +++ b/tests/ui/lint/lint-returning-pointers-to-local-variables.rs @@ -0,0 +1,52 @@ +//@ check-pass + +#![warn(returning_pointers_to_local_variables)] + +fn foo() -> *const u32 { + let empty = 42u32; + return &empty as *const _; + //~^ WARN returning a pointer to stack memory associated with a local variable +} + +fn bar() -> *const u32 { + let empty = 42u32; + &empty as *const _ + //~^ WARN returning a pointer to stack memory associated with a local variable +} + +fn baz() -> *const u32 { + let empty = 42u32; + return ∅ + //~^ WARN returning a pointer to stack memory associated with a local variable +} + +fn faa() -> *const u32 { + let empty = 42u32; + &empty + //~^ WARN returning a pointer to stack memory associated with a local variable +} + +fn pointer_to_pointer() -> *const *mut u32 { + let mut empty = 42u32; + &(&mut empty as *mut u32) as *const _ + //~^ WARN returning a pointer to stack memory associated with a local variable +} + +fn dont_lint_unit() -> *const () { + let foo = (); + &foo as *const _ +} + +fn dont_lint_param(val: u32) -> *const u32 { + &val +} + +struct Foo {} + +impl Foo { + fn dont_lint_self_param(&self) -> *const Foo { + self + } +} + +fn main() {} diff --git a/tests/ui/lint/lint-returning-pointers-to-local-variables.stderr b/tests/ui/lint/lint-returning-pointers-to-local-variables.stderr new file mode 100644 index 0000000000000..3394ae94d8a23 --- /dev/null +++ b/tests/ui/lint/lint-returning-pointers-to-local-variables.stderr @@ -0,0 +1,38 @@ +warning: returning a pointer to stack memory associated with a local variable + --> $DIR/lint-returning-pointers-to-local-variables.rs:7:13 + | +LL | return &empty as *const _; + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/lint-returning-pointers-to-local-variables.rs:3:9 + | +LL | #![warn(returning_pointers_to_local_variables)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: returning a pointer to stack memory associated with a local variable + --> $DIR/lint-returning-pointers-to-local-variables.rs:13:6 + | +LL | &empty as *const _ + | ^^^^^ + +warning: returning a pointer to stack memory associated with a local variable + --> $DIR/lint-returning-pointers-to-local-variables.rs:19:13 + | +LL | return ∅ + | ^^^^^ + +warning: returning a pointer to stack memory associated with a local variable + --> $DIR/lint-returning-pointers-to-local-variables.rs:25:6 + | +LL | &empty + | ^^^^^ + +warning: returning a pointer to stack memory associated with a local variable + --> $DIR/lint-returning-pointers-to-local-variables.rs:31:12 + | +LL | &(&mut empty as *mut u32) as *const _ + | ^^^^^ + +warning: 5 warnings emitted +