Skip to content

Commit

Permalink
Rollup merge of rust-lang#118833 - Urgau:lint_function_pointer_compar…
Browse files Browse the repository at this point in the history
…isons, r=cjgillot

Add lint against function pointer comparisons

This is kind of a follow-up to rust-lang#117758 where we added a lint against wide pointer comparisons for being ambiguous and unreliable; well function pointer comparisons are also unreliable. We should IMO follow a similar logic and warn people about it.

-----

## `unpredictable_function_pointer_comparisons`

*warn-by-default*

The `unpredictable_function_pointer_comparisons` lint checks comparison of function pointer as the operands.

### Example

```rust
fn foo() {}
let a = foo as fn();

let _ = a == foo;
```

### 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 function could have the same address after being merged together.

----

This PR also uplift the very similar `clippy::fn_address_comparisons` lint, which only linted on if one of the operand was an `ty::FnDef` while this PR lints proposes to lint on all `ty::FnPtr` and `ty::FnDef`.

`@rustbot` labels +I-lang-nominated

~~Edit: Blocked on rust-lang/libs-team#323 being accepted and it's follow-up pr~~
  • Loading branch information
fmease authored Dec 4, 2024
2 parents acabb52 + 7b06fcf commit bfc6bb9
Show file tree
Hide file tree
Showing 29 changed files with 624 additions and 174 deletions.
6 changes: 6 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,12 @@ lint_unnameable_test_items = cannot test inner items
lint_unnecessary_qualification = unnecessary qualification
.suggestion = remove the unnecessary path segments
lint_unpredictable_fn_pointer_comparisons = function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
.note_duplicated_fn = the address of the same function can vary between different codegen units
.note_deduplicated_fn = furthermore, different functions could have the same address after being merged together
.note_visit_fn_addr_eq = for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>
.fn_addr_eq_suggestion = refactor your code, or use `std::ptr::fn_addr_eq` to suppress the lint
lint_unqualified_local_imports = `use` of a local item without leading `self::`, `super::`, or `crate::`
lint_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
Expand Down
36 changes: 36 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1815,6 +1815,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,
Expand Down
138 changes: 134 additions & 4 deletions compiler/rustc_lint/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}
}
}
Expand All @@ -411,13 +539,15 @@ 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)
&& let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
&& let Some(cmpop) = diag_item_cmpop(diag_item) =>
{
lint_wide_pointer(cx, e, cmpop, l, r);
lint_fn_pointer(cx, e, cmpop, l, r);
}
_ => {}
};
Expand Down
1 change: 1 addition & 0 deletions library/core/tests/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ fn test_const_nonnull_new() {
#[test]
#[cfg(unix)] // printf may not be available on other platforms
#[allow(deprecated)] // For SipHasher
#[cfg_attr(not(bootstrap), allow(unpredictable_function_pointer_comparisons))]
pub fn test_variadic_fnptr() {
use core::ffi;
use core::hash::{Hash, SipHasher};
Expand Down
1 change: 0 additions & 1 deletion src/tools/clippy/clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::unit_types::LET_UNIT_VALUE_INFO,
crate::unit_types::UNIT_ARG_INFO,
crate::unit_types::UNIT_CMP_INFO,
crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO,
crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
Expand Down
2 changes: 2 additions & 0 deletions src/tools/clippy/clippy_lints/src/deprecated_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
#[clippy::version = "1.53.0"]
("clippy::filter_map", "clippy::manual_filter_map"),
#[clippy::version = ""]
("clippy::fn_address_comparisons", "unpredictable_function_pointer_comparisons"),
#[clippy::version = ""]
("clippy::identity_conversion", "clippy::useless_conversion"),
#[clippy::version = "pre 1.29.0"]
("clippy::if_let_redundant_pattern_matching", "clippy::redundant_pattern_matching"),
Expand Down
2 changes: 0 additions & 2 deletions src/tools/clippy/clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,6 @@ mod uninhabited_references;
mod uninit_vec;
mod unit_return_expecting_ord;
mod unit_types;
mod unnamed_address;
mod unnecessary_box_returns;
mod unnecessary_literal_bound;
mod unnecessary_map_on_constructor;
Expand Down Expand Up @@ -786,7 +785,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(conf)));
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
Expand Down
60 changes: 0 additions & 60 deletions src/tools/clippy/clippy_lints/src/unnamed_address.rs

This file was deleted.

23 changes: 0 additions & 23 deletions src/tools/clippy/tests/ui/fn_address_comparisons.rs

This file was deleted.

17 changes: 0 additions & 17 deletions src/tools/clippy/tests/ui/fn_address_comparisons.stderr

This file was deleted.

Loading

0 comments on commit bfc6bb9

Please sign in to comment.