Skip to content

Commit 59b186d

Browse files
Flying-Toastflip1995
authored andcommitted
Add enum_intrinsics_non_enums lint
1 parent 9a75781 commit 59b186d

File tree

8 files changed

+277
-1
lines changed

8 files changed

+277
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use crate::{context::LintContext, LateContext, LateLintPass};
2+
use rustc_hir as hir;
3+
use rustc_middle::ty::{fold::TypeFoldable, Ty};
4+
use rustc_span::{symbol::sym, Span};
5+
6+
declare_lint! {
7+
/// The `enum_intrinsics_non_enums` lint detects calls to
8+
/// intrinsic functions that require an enum ([`core::mem::discriminant`],
9+
/// [`core::mem::variant_count`]), but are called with a non-enum type.
10+
///
11+
/// [`core::mem::discriminant`]: https://doc.rust-lang.org/core/mem/fn.discriminant.html
12+
/// [`core::mem::variant_count`]: https://doc.rust-lang.org/core/mem/fn.variant_count.html
13+
///
14+
/// ### Example
15+
///
16+
/// ```rust,compile_fail
17+
/// #![deny(enum_intrinsics_non_enums)]
18+
/// core::mem::discriminant::<i32>(&123);
19+
/// ```
20+
///
21+
/// {{produces}}
22+
///
23+
/// ### Explanation
24+
///
25+
/// In order to accept any enum, the `mem::discriminant` and
26+
/// `mem::variant_count` functions are generic over a type `T`.
27+
/// This makes it technically possible for `T` to be a non-enum,
28+
/// in which case the return value is unspecified.
29+
///
30+
/// This lint prevents such incorrect usage of these functions.
31+
ENUM_INTRINSICS_NON_ENUMS,
32+
Deny,
33+
"detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types"
34+
}
35+
36+
declare_lint_pass!(EnumIntrinsicsNonEnums => [ENUM_INTRINSICS_NON_ENUMS]);
37+
38+
/// Returns `true` if we know for sure that the given type is not an enum. Note that for cases where
39+
/// the type is generic, we can't be certain if it will be an enum so we have to assume that it is.
40+
fn is_non_enum(t: Ty<'_>) -> bool {
41+
!t.is_enum() && !t.potentially_needs_subst()
42+
}
43+
44+
fn enforce_mem_discriminant(
45+
cx: &LateContext<'_>,
46+
func_expr: &hir::Expr<'_>,
47+
expr_span: Span,
48+
args_span: Span,
49+
) {
50+
let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0);
51+
if is_non_enum(ty_param) {
52+
cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, expr_span, |builder| {
53+
builder
54+
.build(
55+
"the return value of `mem::discriminant` is \
56+
unspecified when called with a non-enum type",
57+
)
58+
.span_note(
59+
args_span,
60+
&format!(
61+
"the argument to `discriminant` should be a \
62+
reference to an enum, but it was passed \
63+
a reference to a `{}`, which is not an enum.",
64+
ty_param,
65+
),
66+
)
67+
.emit();
68+
});
69+
}
70+
}
71+
72+
fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) {
73+
let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0);
74+
if is_non_enum(ty_param) {
75+
cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, |builder| {
76+
builder
77+
.build(
78+
"the return value of `mem::variant_count` is \
79+
unspecified when called with a non-enum type",
80+
)
81+
.note(&format!(
82+
"the type parameter of `variant_count` should \
83+
be an enum, but it was instantiated with \
84+
the type `{}`, which is not an enum.",
85+
ty_param,
86+
))
87+
.emit();
88+
});
89+
}
90+
}
91+
92+
impl<'tcx> LateLintPass<'tcx> for EnumIntrinsicsNonEnums {
93+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
94+
if let hir::ExprKind::Call(ref func, ref args) = expr.kind {
95+
if let hir::ExprKind::Path(ref qpath) = func.kind {
96+
if let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() {
97+
if cx.tcx.is_diagnostic_item(sym::mem_discriminant, def_id) {
98+
enforce_mem_discriminant(cx, func, expr.span, args[0].span);
99+
} else if cx.tcx.is_diagnostic_item(sym::mem_variant_count, def_id) {
100+
enforce_mem_variant_count(cx, func, expr.span);
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ mod array_into_iter;
4747
pub mod builtin;
4848
mod context;
4949
mod early;
50+
mod enum_intrinsics_non_enums;
5051
mod internal;
5152
mod late;
5253
mod levels;
@@ -76,6 +77,7 @@ use rustc_span::Span;
7677

7778
use array_into_iter::ArrayIntoIter;
7879
use builtin::*;
80+
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
7981
use internal::*;
8082
use methods::*;
8183
use non_ascii_idents::*;
@@ -168,6 +170,7 @@ macro_rules! late_lint_passes {
168170
TemporaryCStringAsPtr: TemporaryCStringAsPtr,
169171
NonPanicFmt: NonPanicFmt,
170172
NoopMethodCall: NoopMethodCall,
173+
EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums,
171174
InvalidAtomicOrdering: InvalidAtomicOrdering,
172175
NamedAsmLabels: NamedAsmLabels,
173176
]

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,7 @@ symbols! {
816816
mem_size_of,
817817
mem_size_of_val,
818818
mem_uninitialized,
819+
mem_variant_count,
819820
mem_zeroed,
820821
member_constraints,
821822
memory,

library/core/src/mem/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,7 @@ pub const fn discriminant<T>(v: &T) -> Discriminant<T> {
10531053
#[inline(always)]
10541054
#[unstable(feature = "variant_count", issue = "73662")]
10551055
#[rustc_const_unstable(feature = "variant_count", issue = "73662")]
1056+
#[rustc_diagnostic_item = "mem_variant_count"]
10561057
pub const fn variant_count<T>() -> usize {
10571058
intrinsics::variant_count::<T>()
10581059
}

src/test/ui/consts/const-variant-count.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// run-pass
2-
#![allow(dead_code)]
2+
#![allow(dead_code, enum_intrinsics_non_enums)]
33
#![feature(variant_count)]
44
#![feature(never_type)]
55

src/test/ui/enum-discriminant/discriminant_value-wrapper.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
// run-pass
2+
3+
#![allow(enum_intrinsics_non_enums)]
4+
25
use std::mem;
36

47
enum ADT {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Test the enum_intrinsics_non_enums lint.
2+
3+
#![feature(variant_count)]
4+
5+
use std::mem::{discriminant, variant_count};
6+
7+
enum SomeEnum {
8+
A,
9+
B,
10+
}
11+
12+
struct SomeStruct;
13+
14+
fn generic_discriminant<T>(v: &T) {
15+
discriminant::<T>(v);
16+
}
17+
18+
fn generic_variant_count<T>() -> usize {
19+
variant_count::<T>()
20+
}
21+
22+
fn test_discriminant() {
23+
discriminant(&SomeEnum::A);
24+
generic_discriminant(&SomeEnum::B);
25+
26+
discriminant(&());
27+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
28+
29+
discriminant(&&SomeEnum::B);
30+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
31+
32+
discriminant(&SomeStruct);
33+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
34+
35+
discriminant(&123u32);
36+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
37+
38+
discriminant(&&123i8);
39+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
40+
}
41+
42+
fn test_variant_count() {
43+
variant_count::<SomeEnum>();
44+
generic_variant_count::<SomeEnum>();
45+
46+
variant_count::<&str>();
47+
//~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
48+
49+
variant_count::<*const u8>();
50+
//~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
51+
52+
variant_count::<()>();
53+
//~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
54+
55+
variant_count::<&SomeEnum>();
56+
//~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
57+
}
58+
59+
fn main() {
60+
test_discriminant();
61+
test_variant_count();
62+
63+
// The lint ignores cases where the type is generic, so these should be
64+
// allowed even though their return values are unspecified
65+
generic_variant_count::<SomeStruct>();
66+
generic_discriminant::<SomeStruct>(&SomeStruct);
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
2+
--> $DIR/lint-enum-intrinsics-non-enums.rs:26:5
3+
|
4+
LL | discriminant(&());
5+
| ^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[deny(enum_intrinsics_non_enums)]` on by default
8+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `()`, which is not an enum.
9+
--> $DIR/lint-enum-intrinsics-non-enums.rs:26:18
10+
|
11+
LL | discriminant(&());
12+
| ^^^
13+
14+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
15+
--> $DIR/lint-enum-intrinsics-non-enums.rs:29:5
16+
|
17+
LL | discriminant(&&SomeEnum::B);
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
19+
|
20+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `&SomeEnum`, which is not an enum.
21+
--> $DIR/lint-enum-intrinsics-non-enums.rs:29:18
22+
|
23+
LL | discriminant(&&SomeEnum::B);
24+
| ^^^^^^^^^^^^^
25+
26+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
27+
--> $DIR/lint-enum-intrinsics-non-enums.rs:32:5
28+
|
29+
LL | discriminant(&SomeStruct);
30+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
31+
|
32+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `SomeStruct`, which is not an enum.
33+
--> $DIR/lint-enum-intrinsics-non-enums.rs:32:18
34+
|
35+
LL | discriminant(&SomeStruct);
36+
| ^^^^^^^^^^^
37+
38+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
39+
--> $DIR/lint-enum-intrinsics-non-enums.rs:35:5
40+
|
41+
LL | discriminant(&123u32);
42+
| ^^^^^^^^^^^^^^^^^^^^^
43+
|
44+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `u32`, which is not an enum.
45+
--> $DIR/lint-enum-intrinsics-non-enums.rs:35:18
46+
|
47+
LL | discriminant(&123u32);
48+
| ^^^^^^^
49+
50+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
51+
--> $DIR/lint-enum-intrinsics-non-enums.rs:38:5
52+
|
53+
LL | discriminant(&&123i8);
54+
| ^^^^^^^^^^^^^^^^^^^^^
55+
|
56+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `&i8`, which is not an enum.
57+
--> $DIR/lint-enum-intrinsics-non-enums.rs:38:18
58+
|
59+
LL | discriminant(&&123i8);
60+
| ^^^^^^^
61+
62+
error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
63+
--> $DIR/lint-enum-intrinsics-non-enums.rs:46:5
64+
|
65+
LL | variant_count::<&str>();
66+
| ^^^^^^^^^^^^^^^^^^^^^^^
67+
|
68+
= note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `&str`, which is not an enum.
69+
70+
error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
71+
--> $DIR/lint-enum-intrinsics-non-enums.rs:49:5
72+
|
73+
LL | variant_count::<*const u8>();
74+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
75+
|
76+
= note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `*const u8`, which is not an enum.
77+
78+
error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
79+
--> $DIR/lint-enum-intrinsics-non-enums.rs:52:5
80+
|
81+
LL | variant_count::<()>();
82+
| ^^^^^^^^^^^^^^^^^^^^^
83+
|
84+
= note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `()`, which is not an enum.
85+
86+
error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
87+
--> $DIR/lint-enum-intrinsics-non-enums.rs:55:5
88+
|
89+
LL | variant_count::<&SomeEnum>();
90+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91+
|
92+
= note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `&SomeEnum`, which is not an enum.
93+
94+
error: aborting due to 9 previous errors
95+

0 commit comments

Comments
 (0)