From a23b8c48a516befaa9b2ebed736ff85c983ce434 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 23 May 2025 22:01:37 +0200 Subject: [PATCH] Add new `function_casts_as_integer` lint --- compiler/rustc_lint/messages.ftl | 3 + .../src/function_cast_as_integer.rs | 62 +++++++++++++++++++ compiler/rustc_lint/src/lib.rs | 3 + compiler/rustc_lint/src/lints.rs | 21 +++++++ compiler/rustc_session/src/filesearch.rs | 2 +- 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 compiler/rustc_lint/src/function_cast_as_integer.rs diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 8aa90c070acd..3f83a7922e36 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -265,6 +265,9 @@ lint_forgetting_copy_types = calls to `std::mem::forget` with a value that imple lint_forgetting_references = calls to `std::mem::forget` with a reference instead of an owned value does nothing .label = argument has type `{$arg_ty}` +lint_function_casts_as_integer = direct cast of function item into an integer + .cast_as_fn = first cast to a function pointer `{$cast_from_ty}` + lint_hidden_glob_reexport = private item shadows public glob re-export .note_glob_reexport = the name `{$name}` in the {$namespace} namespace is supposed to be publicly re-exported here .note_private_item = but the private item here shadows it diff --git a/compiler/rustc_lint/src/function_cast_as_integer.rs b/compiler/rustc_lint/src/function_cast_as_integer.rs new file mode 100644 index 000000000000..2d608766844c --- /dev/null +++ b/compiler/rustc_lint/src/function_cast_as_integer.rs @@ -0,0 +1,62 @@ +use rustc_hir as hir; +use rustc_middle::ty; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::BytePos; + +use crate::lints::{FunctionCastsAsIntegerDiag, FunctionCastsAsIntegerSugg}; +use crate::{LateContext, LateLintPass}; + +declare_lint! { + /// The `function_casts_as_integer` lint detects cases where a function item is casted + /// into an integer. + /// + /// ### Example + /// + /// ```rust + /// fn foo() {} + /// let x = foo as usize; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// When casting a function item into an integer, it's implicitly creating a + /// function pointer that will in turn be casted into an integer. By making + /// it explicit, it improves readability of the code and prevents bugs. + pub FUNCTION_CASTS_AS_INTEGER, + Warn, + "casting a function into an integer", +} + +declare_lint_pass!( + /// Lint for casts of functions into integers. + FunctionCastsAsInteger => [FUNCTION_CASTS_AS_INTEGER] +); + +impl<'tcx> LateLintPass<'tcx> for FunctionCastsAsInteger { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + let hir::ExprKind::Cast(cast_from_expr, cast_to_expr) = expr.kind else { return }; + let cast_to_ty = cx.typeck_results().expr_ty(expr); + // Casting to a function (pointer?), so all good. + if matches!(cast_to_ty.kind(), ty::FnDef(..) | ty::FnPtr(..)) { + return; + } + let cast_from_ty = cx.typeck_results().expr_ty(cast_from_expr); + if matches!(cast_from_ty.kind(), ty::FnDef(..)) { + cx.tcx.emit_node_span_lint( + FUNCTION_CASTS_AS_INTEGER, + expr.hir_id, + cast_to_expr.span.with_lo(cast_from_expr.span.hi() + BytePos(1)), + FunctionCastsAsIntegerDiag { + sugg: FunctionCastsAsIntegerSugg { + suggestion: cast_from_expr.span.shrink_to_hi(), + // We get the function pointer to have a nice display. + cast_from_ty: cx.typeck_results().expr_ty_adjusted(cast_from_expr), + cast_to_ty, + }, + }, + ); + } + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 8a83434e10c1..7c3a81e89130 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -45,6 +45,7 @@ mod errors; mod expect; mod for_loops_over_fallibles; mod foreign_modules; +mod function_cast_as_integer; mod if_let_rescope; mod impl_trait_overcaptures; mod internal; @@ -89,6 +90,7 @@ use deref_into_dyn_supertrait::*; use drop_forget_useless::*; use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums; use for_loops_over_fallibles::*; +use function_cast_as_integer::*; use if_let_rescope::IfLetRescope; use impl_trait_overcaptures::ImplTraitOvercaptures; use internal::*; @@ -241,6 +243,7 @@ late_lint_methods!( IfLetRescope: IfLetRescope::default(), StaticMutRefs: StaticMutRefs, UnqualifiedLocalImports: UnqualifiedLocalImports, + FunctionCastsAsInteger: FunctionCastsAsInteger, CheckTransmutes: CheckTransmutes, LifetimeSyntax: LifetimeSyntax, ] diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index c55f2b9dd6f2..93c405a94086 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3019,6 +3019,27 @@ pub(crate) struct ReservedMultihash { pub suggestion: Span, } +#[derive(LintDiagnostic)] +#[diag(lint_function_casts_as_integer)] +pub(crate) struct FunctionCastsAsIntegerDiag<'tcx> { + #[subdiagnostic] + pub(crate) sugg: FunctionCastsAsIntegerSugg<'tcx>, +} + +#[derive(Subdiagnostic)] +#[suggestion( + lint_cast_as_fn, + code = " as {cast_from_ty}", + applicability = "machine-applicable", + style = "verbose" +)] +pub(crate) struct FunctionCastsAsIntegerSugg<'tcx> { + #[primary_span] + pub suggestion: Span, + pub cast_from_ty: Ty<'tcx>, + pub cast_to_ty: Ty<'tcx>, +} + #[derive(Debug)] pub(crate) struct MismatchedLifetimeSyntaxes { pub inputs: LifetimeSyntaxCategories>, diff --git a/compiler/rustc_session/src/filesearch.rs b/compiler/rustc_session/src/filesearch.rs index f64fa86948c8..f83685b2ad85 100644 --- a/compiler/rustc_session/src/filesearch.rs +++ b/compiler/rustc_session/src/filesearch.rs @@ -72,7 +72,7 @@ fn current_dll_path() -> Result { #[cfg(not(target_os = "aix"))] unsafe { - let addr = current_dll_path as usize as *mut _; + let addr = current_dll_path as fn() -> Result as *mut _; let mut info = std::mem::zeroed(); if libc::dladdr(addr, &mut info) == 0 { return Err("dladdr failed".into());