Skip to content

Commit 35ea48d

Browse files
authored
Rollup merge of rust-lang#118833 - Urgau:lint_function_pointer_comparisons, 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~~
2 parents acabb52 + 7b06fcf commit 35ea48d

29 files changed

+624
-174
lines changed

compiler/rustc_lint/messages.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,12 @@ lint_unnameable_test_items = cannot test inner items
885885
lint_unnecessary_qualification = unnecessary qualification
886886
.suggestion = remove the unnecessary path segments
887887
888+
lint_unpredictable_fn_pointer_comparisons = function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
889+
.note_duplicated_fn = the address of the same function can vary between different codegen units
890+
.note_deduplicated_fn = furthermore, different functions could have the same address after being merged together
891+
.note_visit_fn_addr_eq = for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>
892+
.fn_addr_eq_suggestion = refactor your code, or use `std::ptr::fn_addr_eq` to suppress the lint
893+
888894
lint_unqualified_local_imports = `use` of a local item without leading `self::`, `super::`, or `crate::`
889895
890896
lint_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe

compiler/rustc_lint/src/lints.rs

+36
Original file line numberDiff line numberDiff line change
@@ -1815,6 +1815,42 @@ pub(crate) enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> {
18151815
},
18161816
}
18171817

1818+
#[derive(LintDiagnostic)]
1819+
pub(crate) enum UnpredictableFunctionPointerComparisons<'a> {
1820+
#[diag(lint_unpredictable_fn_pointer_comparisons)]
1821+
#[note(lint_note_duplicated_fn)]
1822+
#[note(lint_note_deduplicated_fn)]
1823+
#[note(lint_note_visit_fn_addr_eq)]
1824+
Suggestion {
1825+
#[subdiagnostic]
1826+
sugg: UnpredictableFunctionPointerComparisonsSuggestion<'a>,
1827+
},
1828+
#[diag(lint_unpredictable_fn_pointer_comparisons)]
1829+
#[note(lint_note_duplicated_fn)]
1830+
#[note(lint_note_deduplicated_fn)]
1831+
#[note(lint_note_visit_fn_addr_eq)]
1832+
Warn,
1833+
}
1834+
1835+
#[derive(Subdiagnostic)]
1836+
#[multipart_suggestion(
1837+
lint_fn_addr_eq_suggestion,
1838+
style = "verbose",
1839+
applicability = "maybe-incorrect"
1840+
)]
1841+
pub(crate) struct UnpredictableFunctionPointerComparisonsSuggestion<'a> {
1842+
pub ne: &'a str,
1843+
pub cast_right: String,
1844+
pub deref_left: &'a str,
1845+
pub deref_right: &'a str,
1846+
#[suggestion_part(code = "{ne}std::ptr::fn_addr_eq({deref_left}")]
1847+
pub left: Span,
1848+
#[suggestion_part(code = ", {deref_right}")]
1849+
pub middle: Span,
1850+
#[suggestion_part(code = "{cast_right})")]
1851+
pub right: Span,
1852+
}
1853+
18181854
pub(crate) struct ImproperCTypes<'a> {
18191855
pub ty: Ty<'a>,
18201856
pub desc: &'a str,

compiler/rustc_lint/src/types.rs

+134-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ use crate::lints::{
2323
AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion,
2424
AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad,
2525
AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons,
26-
InvalidNanComparisonsSuggestion, UnusedComparisons, VariantSizeDifferencesDiag,
26+
InvalidNanComparisonsSuggestion, UnpredictableFunctionPointerComparisons,
27+
UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons,
28+
VariantSizeDifferencesDiag,
2729
};
2830
use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent};
2931

@@ -166,6 +168,35 @@ declare_lint! {
166168
"detects ambiguous wide pointer comparisons"
167169
}
168170

171+
declare_lint! {
172+
/// The `unpredictable_function_pointer_comparisons` lint checks comparison
173+
/// of function pointer as the operands.
174+
///
175+
/// ### Example
176+
///
177+
/// ```rust
178+
/// fn a() {}
179+
/// fn b() {}
180+
///
181+
/// let f: fn() = a;
182+
/// let g: fn() = b;
183+
///
184+
/// let _ = f == g;
185+
/// ```
186+
///
187+
/// {{produces}}
188+
///
189+
/// ### Explanation
190+
///
191+
/// Function pointers comparisons do not produce meaningful result since
192+
/// they are never guaranteed to be unique and could vary between different
193+
/// code generation units. Furthermore, different functions could have the
194+
/// same address after being merged together.
195+
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
196+
Warn,
197+
"detects unpredictable function pointer comparisons"
198+
}
199+
169200
#[derive(Copy, Clone, Default)]
170201
pub(crate) struct TypeLimits {
171202
/// Id of the last visited negated expression
@@ -178,7 +209,8 @@ impl_lint_pass!(TypeLimits => [
178209
UNUSED_COMPARISONS,
179210
OVERFLOWING_LITERALS,
180211
INVALID_NAN_COMPARISONS,
181-
AMBIGUOUS_WIDE_POINTER_COMPARISONS
212+
AMBIGUOUS_WIDE_POINTER_COMPARISONS,
213+
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS
182214
]);
183215

184216
impl TypeLimits {
@@ -255,7 +287,7 @@ fn lint_nan<'tcx>(
255287
cx.emit_span_lint(INVALID_NAN_COMPARISONS, e.span, lint);
256288
}
257289

258-
#[derive(Debug, PartialEq)]
290+
#[derive(Debug, PartialEq, Copy, Clone)]
259291
enum ComparisonOp {
260292
BinOp(hir::BinOpKind),
261293
Other,
@@ -383,6 +415,100 @@ fn lint_wide_pointer<'tcx>(
383415
);
384416
}
385417

418+
fn lint_fn_pointer<'tcx>(
419+
cx: &LateContext<'tcx>,
420+
e: &'tcx hir::Expr<'tcx>,
421+
cmpop: ComparisonOp,
422+
l: &'tcx hir::Expr<'tcx>,
423+
r: &'tcx hir::Expr<'tcx>,
424+
) {
425+
let peel_refs = |mut ty: Ty<'tcx>| -> (Ty<'tcx>, usize) {
426+
let mut refs = 0;
427+
428+
while let ty::Ref(_, inner_ty, _) = ty.kind() {
429+
ty = *inner_ty;
430+
refs += 1;
431+
}
432+
433+
(ty, refs)
434+
};
435+
436+
// Left and right operands can have borrows, remove them
437+
let l = l.peel_borrows();
438+
let r = r.peel_borrows();
439+
440+
let Some(l_ty) = cx.typeck_results().expr_ty_opt(l) else { return };
441+
let Some(r_ty) = cx.typeck_results().expr_ty_opt(r) else { return };
442+
443+
// Remove any references as `==` will deref through them (and count the
444+
// number of references removed, for latter).
445+
let (l_ty, l_ty_refs) = peel_refs(l_ty);
446+
let (r_ty, r_ty_refs) = peel_refs(r_ty);
447+
448+
if !l_ty.is_fn() || !r_ty.is_fn() {
449+
return;
450+
}
451+
452+
// Let's try to suggest `ptr::fn_addr_eq` if/when possible.
453+
454+
let is_eq_ne = matches!(cmpop, ComparisonOp::BinOp(hir::BinOpKind::Eq | hir::BinOpKind::Ne));
455+
456+
if !is_eq_ne {
457+
// Neither `==` nor `!=`, we can't suggest `ptr::fn_addr_eq`, just show the warning.
458+
return cx.emit_span_lint(
459+
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
460+
e.span,
461+
UnpredictableFunctionPointerComparisons::Warn,
462+
);
463+
}
464+
465+
let (Some(l_span), Some(r_span)) =
466+
(l.span.find_ancestor_inside(e.span), r.span.find_ancestor_inside(e.span))
467+
else {
468+
// No appropriate spans for the left and right operands, just show the warning.
469+
return cx.emit_span_lint(
470+
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
471+
e.span,
472+
UnpredictableFunctionPointerComparisons::Warn,
473+
);
474+
};
475+
476+
let ne = if cmpop == ComparisonOp::BinOp(hir::BinOpKind::Ne) { "!" } else { "" };
477+
478+
// `ptr::fn_addr_eq` only works with raw pointer, deref any references.
479+
let deref_left = &*"*".repeat(l_ty_refs);
480+
let deref_right = &*"*".repeat(r_ty_refs);
481+
482+
let left = e.span.shrink_to_lo().until(l_span.shrink_to_lo());
483+
let middle = l_span.shrink_to_hi().until(r_span.shrink_to_lo());
484+
let right = r_span.shrink_to_hi().until(e.span.shrink_to_hi());
485+
486+
// We only check for a right cast as `FnDef` == `FnPtr` is not possible,
487+
// only `FnPtr == FnDef` is possible.
488+
let cast_right = if !r_ty.is_fn_ptr() {
489+
let fn_sig = r_ty.fn_sig(cx.tcx);
490+
format!(" as {fn_sig}")
491+
} else {
492+
String::new()
493+
};
494+
495+
cx.emit_span_lint(
496+
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
497+
e.span,
498+
UnpredictableFunctionPointerComparisons::Suggestion {
499+
sugg: UnpredictableFunctionPointerComparisonsSuggestion {
500+
ne,
501+
deref_left,
502+
deref_right,
503+
left,
504+
middle,
505+
right,
506+
cast_right,
507+
},
508+
},
509+
);
510+
}
511+
386512
impl<'tcx> LateLintPass<'tcx> for TypeLimits {
387513
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
388514
match e.kind {
@@ -399,7 +525,9 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
399525
cx.emit_span_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons);
400526
} else {
401527
lint_nan(cx, e, binop, l, r);
402-
lint_wide_pointer(cx, e, ComparisonOp::BinOp(binop.node), l, r);
528+
let cmpop = ComparisonOp::BinOp(binop.node);
529+
lint_wide_pointer(cx, e, cmpop, l, r);
530+
lint_fn_pointer(cx, e, cmpop, l, r);
403531
}
404532
}
405533
}
@@ -411,13 +539,15 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
411539
&& let Some(cmpop) = diag_item_cmpop(diag_item) =>
412540
{
413541
lint_wide_pointer(cx, e, cmpop, l, r);
542+
lint_fn_pointer(cx, e, cmpop, l, r);
414543
}
415544
hir::ExprKind::MethodCall(_, l, [r], _)
416545
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
417546
&& let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
418547
&& let Some(cmpop) = diag_item_cmpop(diag_item) =>
419548
{
420549
lint_wide_pointer(cx, e, cmpop, l, r);
550+
lint_fn_pointer(cx, e, cmpop, l, r);
421551
}
422552
_ => {}
423553
};

library/core/tests/ptr.rs

+1
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ fn test_const_nonnull_new() {
304304
#[test]
305305
#[cfg(unix)] // printf may not be available on other platforms
306306
#[allow(deprecated)] // For SipHasher
307+
#[cfg_attr(not(bootstrap), allow(unpredictable_function_pointer_comparisons))]
307308
pub fn test_variadic_fnptr() {
308309
use core::ffi;
309310
use core::hash::{Hash, SipHasher};

src/tools/clippy/clippy_lints/src/declared_lints.rs

-1
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
744744
crate::unit_types::LET_UNIT_VALUE_INFO,
745745
crate::unit_types::UNIT_ARG_INFO,
746746
crate::unit_types::UNIT_CMP_INFO,
747-
crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
748747
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
749748
crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO,
750749
crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,

src/tools/clippy/clippy_lints/src/deprecated_lints.rs

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
7474
#[clippy::version = "1.53.0"]
7575
("clippy::filter_map", "clippy::manual_filter_map"),
7676
#[clippy::version = ""]
77+
("clippy::fn_address_comparisons", "unpredictable_function_pointer_comparisons"),
78+
#[clippy::version = ""]
7779
("clippy::identity_conversion", "clippy::useless_conversion"),
7880
#[clippy::version = "pre 1.29.0"]
7981
("clippy::if_let_redundant_pattern_matching", "clippy::redundant_pattern_matching"),

src/tools/clippy/clippy_lints/src/lib.rs

-2
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,6 @@ mod uninhabited_references;
363363
mod uninit_vec;
364364
mod unit_return_expecting_ord;
365365
mod unit_types;
366-
mod unnamed_address;
367366
mod unnecessary_box_returns;
368367
mod unnecessary_literal_bound;
369368
mod unnecessary_map_on_constructor;
@@ -786,7 +785,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
786785
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
787786
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(conf)));
788787
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
789-
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
790788
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
791789
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
792790
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));

src/tools/clippy/clippy_lints/src/unnamed_address.rs

-60
This file was deleted.

src/tools/clippy/tests/ui/fn_address_comparisons.rs

-23
This file was deleted.

src/tools/clippy/tests/ui/fn_address_comparisons.stderr

-17
This file was deleted.

0 commit comments

Comments
 (0)