From 59b186d99aec738eafb39f171f963138be4c1a86 Mon Sep 17 00:00:00 2001 From: Flying-Toast <38232168+Flying-Toast@users.noreply.github.com> Date: Sat, 31 Jul 2021 12:14:30 -0400 Subject: [PATCH] Add enum_intrinsics_non_enums lint --- .../src/enum_intrinsics_non_enums.rs | 106 ++++++++++++++++++ compiler/rustc_lint/src/lib.rs | 3 + compiler/rustc_span/src/symbol.rs | 1 + library/core/src/mem/mod.rs | 1 + src/test/ui/consts/const-variant-count.rs | 2 +- .../discriminant_value-wrapper.rs | 3 + .../ui/lint/lint-enum-intrinsics-non-enums.rs | 67 +++++++++++ .../lint-enum-intrinsics-non-enums.stderr | 95 ++++++++++++++++ 8 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 compiler/rustc_lint/src/enum_intrinsics_non_enums.rs create mode 100644 src/test/ui/lint/lint-enum-intrinsics-non-enums.rs create mode 100644 src/test/ui/lint/lint-enum-intrinsics-non-enums.stderr diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs new file mode 100644 index 00000000000..876245747f6 --- /dev/null +++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs @@ -0,0 +1,106 @@ +use crate::{context::LintContext, LateContext, LateLintPass}; +use rustc_hir as hir; +use rustc_middle::ty::{fold::TypeFoldable, Ty}; +use rustc_span::{symbol::sym, Span}; + +declare_lint! { + /// The `enum_intrinsics_non_enums` lint detects calls to + /// intrinsic functions that require an enum ([`core::mem::discriminant`], + /// [`core::mem::variant_count`]), but are called with a non-enum type. + /// + /// [`core::mem::discriminant`]: https://doc.rust-lang.org/core/mem/fn.discriminant.html + /// [`core::mem::variant_count`]: https://doc.rust-lang.org/core/mem/fn.variant_count.html + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(enum_intrinsics_non_enums)] + /// core::mem::discriminant::(&123); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In order to accept any enum, the `mem::discriminant` and + /// `mem::variant_count` functions are generic over a type `T`. + /// This makes it technically possible for `T` to be a non-enum, + /// in which case the return value is unspecified. + /// + /// This lint prevents such incorrect usage of these functions. + ENUM_INTRINSICS_NON_ENUMS, + Deny, + "detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types" +} + +declare_lint_pass!(EnumIntrinsicsNonEnums => [ENUM_INTRINSICS_NON_ENUMS]); + +/// Returns `true` if we know for sure that the given type is not an enum. Note that for cases where +/// the type is generic, we can't be certain if it will be an enum so we have to assume that it is. +fn is_non_enum(t: Ty<'_>) -> bool { + !t.is_enum() && !t.potentially_needs_subst() +} + +fn enforce_mem_discriminant( + cx: &LateContext<'_>, + func_expr: &hir::Expr<'_>, + expr_span: Span, + args_span: Span, +) { + let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); + if is_non_enum(ty_param) { + cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, expr_span, |builder| { + builder + .build( + "the return value of `mem::discriminant` is \ + unspecified when called with a non-enum type", + ) + .span_note( + args_span, + &format!( + "the argument to `discriminant` should be a \ + reference to an enum, but it was passed \ + a reference to a `{}`, which is not an enum.", + ty_param, + ), + ) + .emit(); + }); + } +} + +fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) { + let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); + if is_non_enum(ty_param) { + cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, |builder| { + builder + .build( + "the return value of `mem::variant_count` is \ + unspecified when called with a non-enum type", + ) + .note(&format!( + "the type parameter of `variant_count` should \ + be an enum, but it was instantiated with \ + the type `{}`, which is not an enum.", + ty_param, + )) + .emit(); + }); + } +} + +impl<'tcx> LateLintPass<'tcx> for EnumIntrinsicsNonEnums { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + if let hir::ExprKind::Call(ref func, ref args) = expr.kind { + if let hir::ExprKind::Path(ref qpath) = func.kind { + if let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() { + if cx.tcx.is_diagnostic_item(sym::mem_discriminant, def_id) { + enforce_mem_discriminant(cx, func, expr.span, args[0].span); + } else if cx.tcx.is_diagnostic_item(sym::mem_variant_count, def_id) { + enforce_mem_variant_count(cx, func, expr.span); + } + } + } + } + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 17eb0054584..6f684a0fe51 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -47,6 +47,7 @@ mod array_into_iter; pub mod builtin; mod context; mod early; +mod enum_intrinsics_non_enums; mod internal; mod late; mod levels; @@ -76,6 +77,7 @@ use rustc_span::Span; use array_into_iter::ArrayIntoIter; use builtin::*; +use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums; use internal::*; use methods::*; use non_ascii_idents::*; @@ -168,6 +170,7 @@ macro_rules! late_lint_passes { TemporaryCStringAsPtr: TemporaryCStringAsPtr, NonPanicFmt: NonPanicFmt, NoopMethodCall: NoopMethodCall, + EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums, InvalidAtomicOrdering: InvalidAtomicOrdering, NamedAsmLabels: NamedAsmLabels, ] diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 9551120ca55..6c889e88a59 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -816,6 +816,7 @@ symbols! { mem_size_of, mem_size_of_val, mem_uninitialized, + mem_variant_count, mem_zeroed, member_constraints, memory, diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 84fd1a532c1..894ae10e1b4 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1053,6 +1053,7 @@ pub const fn discriminant(v: &T) -> Discriminant { #[inline(always)] #[unstable(feature = "variant_count", issue = "73662")] #[rustc_const_unstable(feature = "variant_count", issue = "73662")] +#[rustc_diagnostic_item = "mem_variant_count"] pub const fn variant_count() -> usize { intrinsics::variant_count::() } diff --git a/src/test/ui/consts/const-variant-count.rs b/src/test/ui/consts/const-variant-count.rs index 455419d2c7f..50eaeeb4685 100644 --- a/src/test/ui/consts/const-variant-count.rs +++ b/src/test/ui/consts/const-variant-count.rs @@ -1,5 +1,5 @@ // run-pass -#![allow(dead_code)] +#![allow(dead_code, enum_intrinsics_non_enums)] #![feature(variant_count)] #![feature(never_type)] diff --git a/src/test/ui/enum-discriminant/discriminant_value-wrapper.rs b/src/test/ui/enum-discriminant/discriminant_value-wrapper.rs index daef2de87a9..65dc9166330 100644 --- a/src/test/ui/enum-discriminant/discriminant_value-wrapper.rs +++ b/src/test/ui/enum-discriminant/discriminant_value-wrapper.rs @@ -1,4 +1,7 @@ // run-pass + +#![allow(enum_intrinsics_non_enums)] + use std::mem; enum ADT { diff --git a/src/test/ui/lint/lint-enum-intrinsics-non-enums.rs b/src/test/ui/lint/lint-enum-intrinsics-non-enums.rs new file mode 100644 index 00000000000..8ad337064e5 --- /dev/null +++ b/src/test/ui/lint/lint-enum-intrinsics-non-enums.rs @@ -0,0 +1,67 @@ +// Test the enum_intrinsics_non_enums lint. + +#![feature(variant_count)] + +use std::mem::{discriminant, variant_count}; + +enum SomeEnum { + A, + B, +} + +struct SomeStruct; + +fn generic_discriminant(v: &T) { + discriminant::(v); +} + +fn generic_variant_count() -> usize { + variant_count::() +} + +fn test_discriminant() { + discriminant(&SomeEnum::A); + generic_discriminant(&SomeEnum::B); + + discriminant(&()); + //~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type + + discriminant(&&SomeEnum::B); + //~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type + + discriminant(&SomeStruct); + //~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type + + discriminant(&123u32); + //~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type + + discriminant(&&123i8); + //~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type +} + +fn test_variant_count() { + variant_count::(); + generic_variant_count::(); + + variant_count::<&str>(); + //~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type + + variant_count::<*const u8>(); + //~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type + + variant_count::<()>(); + //~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type + + variant_count::<&SomeEnum>(); + //~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type +} + +fn main() { + test_discriminant(); + test_variant_count(); + + // The lint ignores cases where the type is generic, so these should be + // allowed even though their return values are unspecified + generic_variant_count::(); + generic_discriminant::(&SomeStruct); +} diff --git a/src/test/ui/lint/lint-enum-intrinsics-non-enums.stderr b/src/test/ui/lint/lint-enum-intrinsics-non-enums.stderr new file mode 100644 index 00000000000..bec9fb62efa --- /dev/null +++ b/src/test/ui/lint/lint-enum-intrinsics-non-enums.stderr @@ -0,0 +1,95 @@ +error: the return value of `mem::discriminant` is unspecified when called with a non-enum type + --> $DIR/lint-enum-intrinsics-non-enums.rs:26:5 + | +LL | discriminant(&()); + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[deny(enum_intrinsics_non_enums)]` on by default +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. + --> $DIR/lint-enum-intrinsics-non-enums.rs:26:18 + | +LL | discriminant(&()); + | ^^^ + +error: the return value of `mem::discriminant` is unspecified when called with a non-enum type + --> $DIR/lint-enum-intrinsics-non-enums.rs:29:5 + | +LL | discriminant(&&SomeEnum::B); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +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. + --> $DIR/lint-enum-intrinsics-non-enums.rs:29:18 + | +LL | discriminant(&&SomeEnum::B); + | ^^^^^^^^^^^^^ + +error: the return value of `mem::discriminant` is unspecified when called with a non-enum type + --> $DIR/lint-enum-intrinsics-non-enums.rs:32:5 + | +LL | discriminant(&SomeStruct); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +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. + --> $DIR/lint-enum-intrinsics-non-enums.rs:32:18 + | +LL | discriminant(&SomeStruct); + | ^^^^^^^^^^^ + +error: the return value of `mem::discriminant` is unspecified when called with a non-enum type + --> $DIR/lint-enum-intrinsics-non-enums.rs:35:5 + | +LL | discriminant(&123u32); + | ^^^^^^^^^^^^^^^^^^^^^ + | +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. + --> $DIR/lint-enum-intrinsics-non-enums.rs:35:18 + | +LL | discriminant(&123u32); + | ^^^^^^^ + +error: the return value of `mem::discriminant` is unspecified when called with a non-enum type + --> $DIR/lint-enum-intrinsics-non-enums.rs:38:5 + | +LL | discriminant(&&123i8); + | ^^^^^^^^^^^^^^^^^^^^^ + | +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. + --> $DIR/lint-enum-intrinsics-non-enums.rs:38:18 + | +LL | discriminant(&&123i8); + | ^^^^^^^ + +error: the return value of `mem::variant_count` is unspecified when called with a non-enum type + --> $DIR/lint-enum-intrinsics-non-enums.rs:46:5 + | +LL | variant_count::<&str>(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `&str`, which is not an enum. + +error: the return value of `mem::variant_count` is unspecified when called with a non-enum type + --> $DIR/lint-enum-intrinsics-non-enums.rs:49:5 + | +LL | variant_count::<*const u8>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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. + +error: the return value of `mem::variant_count` is unspecified when called with a non-enum type + --> $DIR/lint-enum-intrinsics-non-enums.rs:52:5 + | +LL | variant_count::<()>(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `()`, which is not an enum. + +error: the return value of `mem::variant_count` is unspecified when called with a non-enum type + --> $DIR/lint-enum-intrinsics-non-enums.rs:55:5 + | +LL | variant_count::<&SomeEnum>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `&SomeEnum`, which is not an enum. + +error: aborting due to 9 previous errors +