Skip to content

Commit

Permalink
Add warn-by-default lint against unpredictable fn pointer comparisons
Browse files Browse the repository at this point in the history
  • Loading branch information
Urgau committed Oct 20, 2024
1 parent bfab34a commit 4548ce1
Show file tree
Hide file tree
Showing 8 changed files with 513 additions and 4 deletions.
6 changes: 6 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,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 @@ -1806,6 +1806,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)]
Spanful {
#[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)]
Spanless,
}

#[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: &'a str,
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})")]
pub right: Span,
}

pub(crate) struct ImproperCTypes<'a> {
pub ty: Ty<'a>,
pub desc: &'a str,
Expand Down
126 changes: 122 additions & 4 deletions compiler/rustc_lint/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use std::ops::ControlFlow;

use rustc_data_structures::fx::FxHashSet;
use rustc_errors::DiagMessage;
use rustc_hir::def::Namespace;
use rustc_hir::{Expr, ExprKind};
use rustc_middle::bug;
use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton};
use rustc_middle::ty::print::{FmtPrinter, Print};
use rustc_middle::ty::{
self, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
};
Expand All @@ -24,7 +26,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 @@ -167,6 +171,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)]
pub(crate) struct TypeLimits {
/// Id of the last visited negated expression
Expand All @@ -179,7 +212,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 @@ -253,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 @@ -381,6 +415,86 @@ 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)
};

// the left and right operands can have borrows, remove any explicit borrow
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 };

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 is_eq_ne = matches!(cmpop, ComparisonOp::BinOp(hir::BinOpKind::Eq | hir::BinOpKind::Ne));

let (Some(l_span), Some(r_span), true) =
(l.span.find_ancestor_inside(e.span), r.span.find_ancestor_inside(e.span), is_eq_ne)
else {
return cx.emit_span_lint(
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
e.span,
UnpredictableFunctionPointerComparisons::Spanless,
);
};

let ne = if cmpop == ComparisonOp::BinOp(hir::BinOpKind::Ne) { "!" } else { "" };

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());

let cast = if !r_ty.is_fn_ptr() {
let fn_sig = r_ty.fn_sig(cx.tcx);
let mut fn_sig = FmtPrinter::print_string(cx.tcx, Namespace::TypeNS, |cx| fn_sig.print(cx))
.unwrap_or_else(|_| String::from("<error>"));
fn_sig.insert_str(0, " as ");
fn_sig
} else {
String::new()
};

cx.emit_span_lint(
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
e.span,
UnpredictableFunctionPointerComparisons::Spanful {
sugg: UnpredictableFunctionPointerComparisonsSuggestion {
ne,
deref_left,
deref_right,
left,
middle,
right,
cast: &*cast,
},
},
);
}

impl<'tcx> LateLintPass<'tcx> for TypeLimits {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
match e.kind {
Expand All @@ -397,7 +511,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 @@ -409,13 +525,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
15 changes: 15 additions & 0 deletions tests/ui/lint/fn-ptr-comparisons-weird.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//@ check-pass

fn main() {
let f: fn() = main;
let g: fn() = main;

let _ = f > g;
//~^ WARN function pointer comparisons
let _ = f >= g;
//~^ WARN function pointer comparisons
let _ = f <= g;
//~^ WARN function pointer comparisons
let _ = f < g;
//~^ WARN function pointer comparisons
}
43 changes: 43 additions & 0 deletions tests/ui/lint/fn-ptr-comparisons-weird.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
warning: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
--> $DIR/fn-ptr-comparisons-weird.rs:7:13
|
LL | let _ = f > g;
| ^^^^^
|
= note: the address of the same function can vary between different codegen units
= note: furthermore, different functions could have the same address after being merged together
= note: for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>
= note: `#[warn(unpredictable_function_pointer_comparisons)]` on by default

warning: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
--> $DIR/fn-ptr-comparisons-weird.rs:9:13
|
LL | let _ = f >= g;
| ^^^^^^
|
= note: the address of the same function can vary between different codegen units
= note: furthermore, different functions could have the same address after being merged together
= note: for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>

warning: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
--> $DIR/fn-ptr-comparisons-weird.rs:11:13
|
LL | let _ = f <= g;
| ^^^^^^
|
= note: the address of the same function can vary between different codegen units
= note: furthermore, different functions could have the same address after being merged together
= note: for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>

warning: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
--> $DIR/fn-ptr-comparisons-weird.rs:13:13
|
LL | let _ = f < g;
| ^^^^^
|
= note: the address of the same function can vary between different codegen units
= note: furthermore, different functions could have the same address after being merged together
= note: for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>

warning: 4 warnings emitted

60 changes: 60 additions & 0 deletions tests/ui/lint/fn-ptr-comparisons.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//@ check-pass
//@ run-rustfix

#![feature(ptr_fn_addr_eq)]

extern "C" {
fn test();
}

fn a() {}

extern "C" fn c() {}

extern "C" fn args(_a: i32) -> i32 { 0 }

#[derive(PartialEq, Eq)]
struct A {
f: fn(),
}

fn main() {
let f: fn() = a;
let g: fn() = f;

let a1 = A { f };
let a2 = A { f };

let _ = std::ptr::fn_addr_eq(f, a as fn());
//~^ WARN function pointer comparisons
let _ = !std::ptr::fn_addr_eq(f, a as fn());
//~^ WARN function pointer comparisons
let _ = std::ptr::fn_addr_eq(f, g);
//~^ WARN function pointer comparisons
let _ = std::ptr::fn_addr_eq(f, f);
//~^ WARN function pointer comparisons
let _ = std::ptr::fn_addr_eq(g, g);
//~^ WARN function pointer comparisons
let _ = std::ptr::fn_addr_eq(g, g);
//~^ WARN function pointer comparisons
let _ = std::ptr::fn_addr_eq(g, g);
//~^ WARN function pointer comparisons
let _ = std::ptr::fn_addr_eq(a as fn(), g);
//~^ WARN function pointer comparisons

let cfn: extern "C" fn() = c;
let _ = std::ptr::fn_addr_eq(cfn, c as extern "C" fn());
//~^ WARN function pointer comparisons

let argsfn: extern "C" fn(i32) -> i32 = args;
let _ = std::ptr::fn_addr_eq(argsfn, args as extern "C" fn(i32) -> i32);
//~^ WARN function pointer comparisons

let t: unsafe extern "C" fn() = test;
let _ = std::ptr::fn_addr_eq(t, test as unsafe extern "C" fn());
//~^ WARN function pointer comparisons

let _ = a1 == a2; // should not warn
let _ = std::ptr::fn_addr_eq(a1.f, a2.f);
//~^ WARN function pointer comparisons
}
Loading

0 comments on commit 4548ce1

Please sign in to comment.