Skip to content

Commit 9d1f790

Browse files
committed
Add warn-by-default lint against unpredictable fn pointer comparisons
1 parent 3bff51e commit 9d1f790

File tree

8 files changed

+521
-4
lines changed

8 files changed

+521
-4
lines changed

compiler/rustc_lint/messages.ftl

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

compiler/rustc_lint/src/lints.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,42 @@ pub(crate) enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> {
18101810
},
18111811
}
18121812

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

compiler/rustc_lint/src/types.rs

Lines changed: 134 additions & 4 deletions
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
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//@ check-pass
2+
3+
fn main() {
4+
let f: fn() = main;
5+
let g: fn() = main;
6+
7+
let _ = f > g;
8+
//~^ WARN function pointer comparisons
9+
let _ = f >= g;
10+
//~^ WARN function pointer comparisons
11+
let _ = f <= g;
12+
//~^ WARN function pointer comparisons
13+
let _ = f < g;
14+
//~^ WARN function pointer comparisons
15+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
warning: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
2+
--> $DIR/fn-ptr-comparisons-weird.rs:7:13
3+
|
4+
LL | let _ = f > g;
5+
| ^^^^^
6+
|
7+
= note: the address of the same function can vary between different codegen units
8+
= note: furthermore, different functions could have the same address after being merged together
9+
= note: for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>
10+
= note: `#[warn(unpredictable_function_pointer_comparisons)]` on by default
11+
12+
warning: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
13+
--> $DIR/fn-ptr-comparisons-weird.rs:9:13
14+
|
15+
LL | let _ = f >= g;
16+
| ^^^^^^
17+
|
18+
= note: the address of the same function can vary between different codegen units
19+
= note: furthermore, different functions could have the same address after being merged together
20+
= note: for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>
21+
22+
warning: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
23+
--> $DIR/fn-ptr-comparisons-weird.rs:11:13
24+
|
25+
LL | let _ = f <= g;
26+
| ^^^^^^
27+
|
28+
= note: the address of the same function can vary between different codegen units
29+
= note: furthermore, different functions could have the same address after being merged together
30+
= note: for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>
31+
32+
warning: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
33+
--> $DIR/fn-ptr-comparisons-weird.rs:13:13
34+
|
35+
LL | let _ = f < g;
36+
| ^^^^^
37+
|
38+
= note: the address of the same function can vary between different codegen units
39+
= note: furthermore, different functions could have the same address after being merged together
40+
= note: for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>
41+
42+
warning: 4 warnings emitted
43+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//@ check-pass
2+
//@ run-rustfix
3+
4+
extern "C" {
5+
fn test();
6+
}
7+
8+
fn a() {}
9+
10+
extern "C" fn c() {}
11+
12+
extern "C" fn args(_a: i32) -> i32 { 0 }
13+
14+
#[derive(PartialEq, Eq)]
15+
struct A {
16+
f: fn(),
17+
}
18+
19+
fn main() {
20+
let f: fn() = a;
21+
let g: fn() = f;
22+
23+
let a1 = A { f };
24+
let a2 = A { f };
25+
26+
let _ = std::ptr::fn_addr_eq(f, a as fn());
27+
//~^ WARN function pointer comparisons
28+
let _ = !std::ptr::fn_addr_eq(f, a as fn());
29+
//~^ WARN function pointer comparisons
30+
let _ = std::ptr::fn_addr_eq(f, g);
31+
//~^ WARN function pointer comparisons
32+
let _ = std::ptr::fn_addr_eq(f, f);
33+
//~^ WARN function pointer comparisons
34+
let _ = std::ptr::fn_addr_eq(g, g);
35+
//~^ WARN function pointer comparisons
36+
let _ = std::ptr::fn_addr_eq(g, g);
37+
//~^ WARN function pointer comparisons
38+
let _ = std::ptr::fn_addr_eq(g, g);
39+
//~^ WARN function pointer comparisons
40+
let _ = std::ptr::fn_addr_eq(a as fn(), g);
41+
//~^ WARN function pointer comparisons
42+
43+
let cfn: extern "C" fn() = c;
44+
let _ = std::ptr::fn_addr_eq(cfn, c as extern "C" fn());
45+
//~^ WARN function pointer comparisons
46+
47+
let argsfn: extern "C" fn(i32) -> i32 = args;
48+
let _ = std::ptr::fn_addr_eq(argsfn, args as extern "C" fn(i32) -> i32);
49+
//~^ WARN function pointer comparisons
50+
51+
let t: unsafe extern "C" fn() = test;
52+
let _ = std::ptr::fn_addr_eq(t, test as unsafe extern "C" fn());
53+
//~^ WARN function pointer comparisons
54+
55+
let _ = a1 == a2; // should not warn
56+
let _ = std::ptr::fn_addr_eq(a1.f, a2.f);
57+
//~^ WARN function pointer comparisons
58+
}

0 commit comments

Comments
 (0)