Add warn-by-default lint against unpredictable fn pointer comparisons
This commit is contained in:
parent
3bff51ea91
commit
9d1f790594
8 changed files with 521 additions and 4 deletions
|
@ -1810,6 +1810,42 @@ pub(crate) enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
pub(crate) enum UnpredictableFunctionPointerComparisons<'a> {
|
||||
#[diag(lint_unpredictable_fn_pointer_comparisons)]
|
||||
#[note(lint_note_duplicated_fn)]
|
||||
#[note(lint_note_deduplicated_fn)]
|
||||
#[note(lint_note_visit_fn_addr_eq)]
|
||||
Suggestion {
|
||||
#[subdiagnostic]
|
||||
sugg: UnpredictableFunctionPointerComparisonsSuggestion<'a>,
|
||||
},
|
||||
#[diag(lint_unpredictable_fn_pointer_comparisons)]
|
||||
#[note(lint_note_duplicated_fn)]
|
||||
#[note(lint_note_deduplicated_fn)]
|
||||
#[note(lint_note_visit_fn_addr_eq)]
|
||||
Warn,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
#[multipart_suggestion(
|
||||
lint_fn_addr_eq_suggestion,
|
||||
style = "verbose",
|
||||
applicability = "maybe-incorrect"
|
||||
)]
|
||||
pub(crate) struct UnpredictableFunctionPointerComparisonsSuggestion<'a> {
|
||||
pub ne: &'a str,
|
||||
pub cast_right: String,
|
||||
pub deref_left: &'a str,
|
||||
pub deref_right: &'a str,
|
||||
#[suggestion_part(code = "{ne}std::ptr::fn_addr_eq({deref_left}")]
|
||||
pub left: Span,
|
||||
#[suggestion_part(code = ", {deref_right}")]
|
||||
pub middle: Span,
|
||||
#[suggestion_part(code = "{cast_right})")]
|
||||
pub right: Span,
|
||||
}
|
||||
|
||||
pub(crate) struct ImproperCTypes<'a> {
|
||||
pub ty: Ty<'a>,
|
||||
pub desc: &'a str,
|
||||
|
|
|
@ -23,7 +23,9 @@ use crate::lints::{
|
|||
AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion,
|
||||
AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad,
|
||||
AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons,
|
||||
InvalidNanComparisonsSuggestion, UnusedComparisons, VariantSizeDifferencesDiag,
|
||||
InvalidNanComparisonsSuggestion, UnpredictableFunctionPointerComparisons,
|
||||
UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons,
|
||||
VariantSizeDifferencesDiag,
|
||||
};
|
||||
use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent};
|
||||
|
||||
|
@ -166,6 +168,35 @@ declare_lint! {
|
|||
"detects ambiguous wide pointer comparisons"
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// The `unpredictable_function_pointer_comparisons` lint checks comparison
|
||||
/// of function pointer as the operands.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust
|
||||
/// fn a() {}
|
||||
/// fn b() {}
|
||||
///
|
||||
/// let f: fn() = a;
|
||||
/// let g: fn() = b;
|
||||
///
|
||||
/// let _ = f == g;
|
||||
/// ```
|
||||
///
|
||||
/// {{produces}}
|
||||
///
|
||||
/// ### Explanation
|
||||
///
|
||||
/// Function pointers comparisons do not produce meaningful result since
|
||||
/// they are never guaranteed to be unique and could vary between different
|
||||
/// code generation units. Furthermore, different functions could have the
|
||||
/// same address after being merged together.
|
||||
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
|
||||
Warn,
|
||||
"detects unpredictable function pointer comparisons"
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub(crate) struct TypeLimits {
|
||||
/// Id of the last visited negated expression
|
||||
|
@ -178,7 +209,8 @@ impl_lint_pass!(TypeLimits => [
|
|||
UNUSED_COMPARISONS,
|
||||
OVERFLOWING_LITERALS,
|
||||
INVALID_NAN_COMPARISONS,
|
||||
AMBIGUOUS_WIDE_POINTER_COMPARISONS
|
||||
AMBIGUOUS_WIDE_POINTER_COMPARISONS,
|
||||
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS
|
||||
]);
|
||||
|
||||
impl TypeLimits {
|
||||
|
@ -255,7 +287,7 @@ fn lint_nan<'tcx>(
|
|||
cx.emit_span_lint(INVALID_NAN_COMPARISONS, e.span, lint);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
enum ComparisonOp {
|
||||
BinOp(hir::BinOpKind),
|
||||
Other,
|
||||
|
@ -383,6 +415,100 @@ fn lint_wide_pointer<'tcx>(
|
|||
);
|
||||
}
|
||||
|
||||
fn lint_fn_pointer<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx hir::Expr<'tcx>,
|
||||
cmpop: ComparisonOp,
|
||||
l: &'tcx hir::Expr<'tcx>,
|
||||
r: &'tcx hir::Expr<'tcx>,
|
||||
) {
|
||||
let peel_refs = |mut ty: Ty<'tcx>| -> (Ty<'tcx>, usize) {
|
||||
let mut refs = 0;
|
||||
|
||||
while let ty::Ref(_, inner_ty, _) = ty.kind() {
|
||||
ty = *inner_ty;
|
||||
refs += 1;
|
||||
}
|
||||
|
||||
(ty, refs)
|
||||
};
|
||||
|
||||
// Left and right operands can have borrows, remove them
|
||||
let l = l.peel_borrows();
|
||||
let r = r.peel_borrows();
|
||||
|
||||
let Some(l_ty) = cx.typeck_results().expr_ty_opt(l) else { return };
|
||||
let Some(r_ty) = cx.typeck_results().expr_ty_opt(r) else { return };
|
||||
|
||||
// Remove any references as `==` will deref through them (and count the
|
||||
// number of references removed, for latter).
|
||||
let (l_ty, l_ty_refs) = peel_refs(l_ty);
|
||||
let (r_ty, r_ty_refs) = peel_refs(r_ty);
|
||||
|
||||
if !l_ty.is_fn() || !r_ty.is_fn() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's try to suggest `ptr::fn_addr_eq` if/when possible.
|
||||
|
||||
let is_eq_ne = matches!(cmpop, ComparisonOp::BinOp(hir::BinOpKind::Eq | hir::BinOpKind::Ne));
|
||||
|
||||
if !is_eq_ne {
|
||||
// Neither `==` nor `!=`, we can't suggest `ptr::fn_addr_eq`, just show the warning.
|
||||
return cx.emit_span_lint(
|
||||
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
|
||||
e.span,
|
||||
UnpredictableFunctionPointerComparisons::Warn,
|
||||
);
|
||||
}
|
||||
|
||||
let (Some(l_span), Some(r_span)) =
|
||||
(l.span.find_ancestor_inside(e.span), r.span.find_ancestor_inside(e.span))
|
||||
else {
|
||||
// No appropriate spans for the left and right operands, just show the warning.
|
||||
return cx.emit_span_lint(
|
||||
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
|
||||
e.span,
|
||||
UnpredictableFunctionPointerComparisons::Warn,
|
||||
);
|
||||
};
|
||||
|
||||
let ne = if cmpop == ComparisonOp::BinOp(hir::BinOpKind::Ne) { "!" } else { "" };
|
||||
|
||||
// `ptr::fn_addr_eq` only works with raw pointer, deref any references.
|
||||
let deref_left = &*"*".repeat(l_ty_refs);
|
||||
let deref_right = &*"*".repeat(r_ty_refs);
|
||||
|
||||
let left = e.span.shrink_to_lo().until(l_span.shrink_to_lo());
|
||||
let middle = l_span.shrink_to_hi().until(r_span.shrink_to_lo());
|
||||
let right = r_span.shrink_to_hi().until(e.span.shrink_to_hi());
|
||||
|
||||
// We only check for a right cast as `FnDef` == `FnPtr` is not possible,
|
||||
// only `FnPtr == FnDef` is possible.
|
||||
let cast_right = if !r_ty.is_fn_ptr() {
|
||||
let fn_sig = r_ty.fn_sig(cx.tcx);
|
||||
format!(" as {fn_sig}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
cx.emit_span_lint(
|
||||
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
|
||||
e.span,
|
||||
UnpredictableFunctionPointerComparisons::Suggestion {
|
||||
sugg: UnpredictableFunctionPointerComparisonsSuggestion {
|
||||
ne,
|
||||
deref_left,
|
||||
deref_right,
|
||||
left,
|
||||
middle,
|
||||
right,
|
||||
cast_right,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for TypeLimits {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
|
||||
match e.kind {
|
||||
|
@ -399,7 +525,9 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
|
|||
cx.emit_span_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons);
|
||||
} else {
|
||||
lint_nan(cx, e, binop, l, r);
|
||||
lint_wide_pointer(cx, e, ComparisonOp::BinOp(binop.node), l, r);
|
||||
let cmpop = ComparisonOp::BinOp(binop.node);
|
||||
lint_wide_pointer(cx, e, cmpop, l, r);
|
||||
lint_fn_pointer(cx, e, cmpop, l, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -411,6 +539,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
|
|||
&& let Some(cmpop) = diag_item_cmpop(diag_item) =>
|
||||
{
|
||||
lint_wide_pointer(cx, e, cmpop, l, r);
|
||||
lint_fn_pointer(cx, e, cmpop, l, r);
|
||||
}
|
||||
hir::ExprKind::MethodCall(_, l, [r], _)
|
||||
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
|
||||
|
@ -418,6 +547,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
|
|||
&& let Some(cmpop) = diag_item_cmpop(diag_item) =>
|
||||
{
|
||||
lint_wide_pointer(cx, e, cmpop, l, r);
|
||||
lint_fn_pointer(cx, e, cmpop, l, r);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue