Add new function_casts_as_integer lint

This commit is contained in:
Guillaume Gomez 2025-05-23 22:01:37 +02:00
parent a7b3715826
commit a23b8c48a5
5 changed files with 90 additions and 1 deletions

View file

@ -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

View file

@ -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,
},
},
);
}
}
}

View file

@ -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,
]

View file

@ -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<Vec<Span>>,

View file

@ -72,7 +72,7 @@ fn current_dll_path() -> Result<PathBuf, String> {
#[cfg(not(target_os = "aix"))]
unsafe {
let addr = current_dll_path as usize as *mut _;
let addr = current_dll_path as fn() -> Result<PathBuf, String> as *mut _;
let mut info = std::mem::zeroed();
if libc::dladdr(addr, &mut info) == 0 {
return Err("dladdr failed".into());