Skip to content

Commit 8e0126b

Browse files
committed
Add warn-by-default lint against pointer trait comparisons
1 parent 287ae4d commit 8e0126b

File tree

5 files changed

+186
-6
lines changed

5 files changed

+186
-6
lines changed

compiler/rustc_lint/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,10 @@ lint_invalid_nan_comparisons_eq_ne = incorrect NaN comparison, NaN cannot be dir
315315
316316
lint_invalid_nan_comparisons_lt_le_gt_ge = incorrect NaN comparison, NaN is not orderable
317317
318+
lint_invalid_pointer_trait_comparisons = incorrect pointer trait comparison, the comparison include metadata which is not guaranteed to be equivalent
319+
.addr_metadata_suggestion = use explicit `std::ptr::eq` method to compare metadata and addresses
320+
.addr_suggestion = convert to un-typed pointers to only compare their addresses
321+
318322
lint_invalid_reference_casting_assign_to_ref = assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
319323
.label = casting happend here
320324

compiler/rustc_lint/src/lints.rs

+39
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,45 @@ pub enum InvalidNanComparisonsSuggestion {
15851585
Spanless,
15861586
}
15871587

1588+
#[derive(LintDiagnostic)]
1589+
#[diag(lint_invalid_pointer_trait_comparisons)]
1590+
pub struct InvalidPointerTraitComparisons {
1591+
#[subdiagnostic]
1592+
pub addr_metadata_suggestion: InvalidPointerTraitComparisonsAddrMetadataSuggestion,
1593+
#[subdiagnostic]
1594+
pub addr_suggestion: InvalidPointerTraitComparisonsAddrSuggestion,
1595+
}
1596+
1597+
#[derive(Subdiagnostic)]
1598+
#[multipart_suggestion(
1599+
lint_addr_metadata_suggestion,
1600+
style = "verbose",
1601+
applicability = "machine-applicable"
1602+
)]
1603+
pub struct InvalidPointerTraitComparisonsAddrMetadataSuggestion {
1604+
#[suggestion_part(code = "!std::ptr::eq(")]
1605+
pub left_ne: Option<Span>,
1606+
#[suggestion_part(code = "std::ptr::eq(")]
1607+
pub left_eq: Option<Span>,
1608+
#[suggestion_part(code = ", ")]
1609+
pub middle: Span,
1610+
#[suggestion_part(code = ")")]
1611+
pub right: Span,
1612+
}
1613+
1614+
#[derive(Subdiagnostic)]
1615+
#[multipart_suggestion(
1616+
lint_addr_suggestion,
1617+
style = "verbose",
1618+
applicability = "machine-applicable"
1619+
)]
1620+
pub struct InvalidPointerTraitComparisonsAddrSuggestion {
1621+
#[suggestion_part(code = " as *const ()")]
1622+
pub left: Span,
1623+
#[suggestion_part(code = " as *const ()")]
1624+
pub right: Span,
1625+
}
1626+
15881627
pub struct ImproperCTypes<'a> {
15891628
pub ty: Ty<'a>,
15901629
pub desc: &'a str,

compiler/rustc_lint/src/types.rs

+89-6
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use crate::{
33
lints::{
44
AtomicOrderingFence, AtomicOrderingLoad, AtomicOrderingStore, ImproperCTypes,
55
InvalidAtomicOrderingDiag, InvalidNanComparisons, InvalidNanComparisonsSuggestion,
6-
OnlyCastu8ToChar, OverflowingBinHex, OverflowingBinHexSign, OverflowingBinHexSignBitSub,
7-
OverflowingBinHexSub, OverflowingInt, OverflowingIntHelp, OverflowingLiteral,
8-
OverflowingUInt, RangeEndpointOutOfRange, UnusedComparisons, UseInclusiveRange,
9-
VariantSizeDifferencesDiag,
6+
InvalidPointerTraitComparisons, InvalidPointerTraitComparisonsAddrMetadataSuggestion,
7+
InvalidPointerTraitComparisonsAddrSuggestion, OnlyCastu8ToChar, OverflowingBinHex,
8+
OverflowingBinHexSign, OverflowingBinHexSignBitSub, OverflowingBinHexSub, OverflowingInt,
9+
OverflowingIntHelp, OverflowingLiteral, OverflowingUInt, RangeEndpointOutOfRange,
10+
UnusedComparisons, UseInclusiveRange, VariantSizeDifferencesDiag,
1011
},
1112
};
1213
use crate::{LateContext, LateLintPass, LintContext};
@@ -17,10 +18,10 @@ use rustc_errors::DiagnosticMessage;
1718
use rustc_hir as hir;
1819
use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
1920
use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton};
20-
use rustc_middle::ty::GenericArgsRef;
2121
use rustc_middle::ty::{
2222
self, AdtKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
2323
};
24+
use rustc_middle::ty::{GenericArgsRef, TypeAndMut};
2425
use rustc_span::def_id::LocalDefId;
2526
use rustc_span::source_map;
2627
use rustc_span::symbol::sym;
@@ -136,6 +137,37 @@ declare_lint! {
136137
"detects invalid floating point NaN comparisons"
137138
}
138139

140+
declare_lint! {
141+
/// The `invalid_pointer_trait_comparisons` lint checks comparison
142+
/// of `*const dyn Trait` as the operands.
143+
///
144+
/// ### Example
145+
///
146+
/// ```rust
147+
/// # struct A;
148+
/// # struct B;
149+
/// #
150+
/// # trait T {}
151+
/// # impl T for A {}
152+
/// # impl T for B {}
153+
///
154+
/// let ab = (A, B);
155+
/// let a = &ab.0 as *const dyn T;
156+
/// let b = &ab.1 as *const dyn T;
157+
///
158+
/// let _ = a == b;
159+
/// ```
160+
///
161+
/// {{produces}}
162+
///
163+
/// ### Explanation
164+
///
165+
/// The comparison include metadata which is not guaranteed to be equivalent.
166+
INVALID_POINTER_TRAIT_COMPARISONS,
167+
Warn,
168+
"detects invalid pointer trait comparisons"
169+
}
170+
139171
#[derive(Copy, Clone)]
140172
pub struct TypeLimits {
141173
/// Id of the last visited negated expression
@@ -144,7 +176,12 @@ pub struct TypeLimits {
144176
negated_expr_span: Option<Span>,
145177
}
146178

147-
impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS, INVALID_NAN_COMPARISONS]);
179+
impl_lint_pass!(TypeLimits => [
180+
UNUSED_COMPARISONS,
181+
OVERFLOWING_LITERALS,
182+
INVALID_NAN_COMPARISONS,
183+
INVALID_POINTER_TRAIT_COMPARISONS
184+
]);
148185

149186
impl TypeLimits {
150187
pub fn new() -> TypeLimits {
@@ -620,6 +657,51 @@ fn lint_nan<'tcx>(
620657
cx.emit_spanned_lint(INVALID_NAN_COMPARISONS, e.span, lint);
621658
}
622659

660+
fn lint_pointer_trait<'tcx>(
661+
cx: &LateContext<'tcx>,
662+
e: &'tcx hir::Expr<'tcx>,
663+
binop: hir::BinOp,
664+
l: &'tcx hir::Expr<'tcx>,
665+
r: &'tcx hir::Expr<'tcx>,
666+
) {
667+
let Some(l_ty) = cx.typeck_results().expr_ty_adjusted_opt(l) else {
668+
return;
669+
};
670+
let Some(r_ty) = cx.typeck_results().expr_ty_adjusted_opt(r) else {
671+
return;
672+
};
673+
674+
fn is_ptr_trait(ty: Ty<'_>) -> bool {
675+
match ty.kind() {
676+
ty::RawPtr(TypeAndMut { mutbl: _, ty }) => {
677+
matches!(ty.kind(), ty::Dynamic(_, _, ty::Dyn))
678+
}
679+
_ => false,
680+
}
681+
}
682+
683+
if !is_ptr_trait(l_ty) || !is_ptr_trait(r_ty) {
684+
return;
685+
}
686+
687+
cx.emit_spanned_lint(
688+
INVALID_POINTER_TRAIT_COMPARISONS,
689+
e.span,
690+
InvalidPointerTraitComparisons {
691+
addr_metadata_suggestion: InvalidPointerTraitComparisonsAddrMetadataSuggestion {
692+
left_ne: (binop.node == hir::BinOpKind::Ne).then(|| l.span.shrink_to_lo()),
693+
left_eq: (binop.node != hir::BinOpKind::Ne).then(|| l.span.shrink_to_lo()),
694+
middle: l.span.shrink_to_hi().until(r.span.shrink_to_lo()),
695+
right: r.span.shrink_to_hi(),
696+
},
697+
addr_suggestion: InvalidPointerTraitComparisonsAddrSuggestion {
698+
left: l.span.shrink_to_hi(),
699+
right: r.span.shrink_to_hi(),
700+
},
701+
},
702+
);
703+
}
704+
623705
impl<'tcx> LateLintPass<'tcx> for TypeLimits {
624706
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
625707
match e.kind {
@@ -636,6 +718,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
636718
cx.emit_spanned_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons);
637719
} else {
638720
lint_nan(cx, e, binop, l, r);
721+
lint_pointer_trait(cx, e, binop, l, r);
639722
}
640723
}
641724
}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// check-pass
2+
3+
struct A;
4+
struct B;
5+
6+
trait T {}
7+
impl T for A {}
8+
impl T for B {}
9+
10+
fn main() {
11+
let ab = (A, B);
12+
let a = &ab.0 as *const dyn T;
13+
let b = &ab.1 as *const dyn T;
14+
15+
let _ = a == b;
16+
//~^ WARN incorrect pointer trait comparison
17+
let _ = a != b;
18+
//~^ WARN incorrect pointer trait comparison
19+
20+
let _ = a as *const () == b as *const ();
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
warning: incorrect pointer trait comparison, the comparison include metadata which is not guaranteed to be equivalent
2+
--> $DIR/pointer_trait_comparisons.rs:15:13
3+
|
4+
LL | let _ = a == b;
5+
| ^^^^^^
6+
|
7+
= note: `#[warn(invalid_pointer_trait_comparisons)]` on by default
8+
help: use explicit `std::ptr::eq` method to compare metadata and addresses
9+
|
10+
LL | let _ = std::ptr::eq(a, b);
11+
| +++++++++++++ ~ +
12+
help: convert to un-typed pointers to only compare their addresses
13+
|
14+
LL | let _ = a as *const () == b as *const ();
15+
| ++++++++++++ ++++++++++++
16+
17+
warning: incorrect pointer trait comparison, the comparison include metadata which is not guaranteed to be equivalent
18+
--> $DIR/pointer_trait_comparisons.rs:17:13
19+
|
20+
LL | let _ = a != b;
21+
| ^^^^^^
22+
|
23+
help: use explicit `std::ptr::eq` method to compare metadata and addresses
24+
|
25+
LL | let _ = !std::ptr::eq(a, b);
26+
| ++++++++++++++ ~ +
27+
help: convert to un-typed pointers to only compare their addresses
28+
|
29+
LL | let _ = a as *const () != b as *const ();
30+
| ++++++++++++ ++++++++++++
31+
32+
warning: 2 warnings emitted
33+

0 commit comments

Comments
 (0)