diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 9409fca0a757..6e62e50d3af8 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -610,6 +610,10 @@ pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: LocalDefId) { let repr_type = def.repr().discr_type(); let initial = repr_type.initial_discriminant(tcx); let mut prev_discr = None::>; + // Some of the logic below relies on `i128` being able to hold all c_int and c_uint values. + assert!(tcx.sess.target.c_int_width < 128); + let mut min_discr = i128::MAX; + let mut max_discr = i128::MIN; // fill the discriminant values and field types for variant in def.variants() { @@ -631,19 +635,32 @@ pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: LocalDefId) { .unwrap_or(wrapped_discr); if def.repr().c() { + let c_int = Size::from_bits(tcx.sess.target.c_int_width); + let c_uint_max = i128::try_from(c_int.unsigned_int_max()).unwrap(); // c_int is a signed type, so get a proper signed version of the discriminant let discr_size = cur_discr.ty.int_size_and_signed(tcx).0; let discr_val = discr_size.sign_extend(cur_discr.val); + min_discr = min_discr.min(discr_val); + max_discr = max_discr.max(discr_val); - let c_int = Size::from_bits(tcx.sess.target.c_int_width); - if discr_val < c_int.signed_int_min() || discr_val > c_int.signed_int_max() { + // The discriminant range must either fit into c_int or c_uint. + if !(min_discr >= c_int.signed_int_min() && max_discr <= c_int.signed_int_max()) + && !(min_discr >= 0 && max_discr <= c_uint_max) + { let span = tcx.def_span(variant.def_id); + let msg = if discr_val < c_int.signed_int_min() || discr_val > c_uint_max { + "`repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`" + } else if discr_val < 0 { + "`repr(C)` enum discriminant does not fit into C `unsigned int`, and a previous discriminant does not fit into C `int`" + } else { + "`repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int`" + }; tcx.node_span_lint( rustc_session::lint::builtin::REPR_C_ENUMS_LARGER_THAN_INT, tcx.local_def_id_to_hir_id(def_id), span, |d| { - d.primary_message("`repr(C)` enum discriminant does not fit into C `int`") + d.primary_message(msg) .note("`repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C") .help("use `repr($int_ty)` instead to explicitly set the size of this enum"); } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 0cc4502bb73b..86aa6341aff8 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -5217,7 +5217,7 @@ declare_lint! { declare_lint! { /// The `repr_c_enums_larger_than_int` lint detects `repr(C)` enums with discriminant - /// values that do not fit into a C `int`. + /// values that do not fit into a C `int` or `unsigned int`. /// /// ### Example /// @@ -5231,7 +5231,7 @@ declare_lint! { /// This will produce: /// /// ```text - /// error: `repr(C)` enum discriminant does not fit into C `int` + /// error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int` /// --> $DIR/repr-c-big-discriminant1.rs:16:5 /// | /// LL | A = 9223372036854775807, // i64::MAX @@ -5243,15 +5243,17 @@ declare_lint! { /// /// ### Explanation /// - /// In C, enums with discriminants that do not fit into an `int` are a portability hazard: such - /// enums are only permitted since C23, and not supported e.g. by MSVC. Furthermore, Rust - /// interprets the discriminant values of `repr(C)` enums as expressions of type `isize`, which - /// cannot be changed in a backwards-compatible way. If the discriminant is given as a literal - /// that does not fit into `isize`, it is wrapped (with a warning). This makes it impossible to - /// implement the C23 behavior of enums where the enum discriminants have no predefined type and - /// instead the enum uses a type large enough to hold all discriminants. + /// In C, enums with discriminants that do not all fit into an `int` or all fit into an + /// `unsigned int` are a portability hazard: such enums are only permitted since C23, and not + /// supported e.g. by MSVC. /// - /// Therefore, `repr(C)` enums require all discriminants to fit into a C `int`. + /// Furthermore, Rust interprets the discriminant values of `repr(C)` enums as expressions of + /// type `isize`. This makes it impossible to implement the C23 behavior of enums where the enum + /// discriminants have no predefined type and instead the enum uses a type large enough to hold + /// all discriminants. + /// + /// Therefore, `repr(C)` enums in Rust require that either all discriminants to fit into a C + /// `int` or they all fit into an `unsigned int`. pub REPR_C_ENUMS_LARGER_THAN_INT, Warn, "repr(C) enums with discriminant values that do not fit into a C int", diff --git a/tests/ui/enum-discriminant/discriminant_size.rs b/tests/ui/enum-discriminant/discriminant_size.rs index b1feff3c59e1..537940cfb7ee 100644 --- a/tests/ui/enum-discriminant/discriminant_size.rs +++ b/tests/ui/enum-discriminant/discriminant_size.rs @@ -2,6 +2,7 @@ #![feature(core_intrinsics)] use std::intrinsics::discriminant_value; +use std::mem::size_of; enum E1 { A, @@ -20,6 +21,14 @@ enum E3 { B = 100, } +// Enums like this are found in the ecosystem, let's make sure they get the right size. +#[repr(C)] +#[allow(overflowing_literals)] +enum UnsignedIntEnum { + A = 0, + O = 0xffffffff, // doesn't fit into `int`, but fits into `unsigned int` +} + #[repr(i128)] enum E4 { A = 0x1223_3445_5667_7889, @@ -27,24 +36,38 @@ enum E4 { } fn main() { + assert_eq!(size_of::(), 1); let mut target: [isize; 3] = [0, 0, 0]; target[1] = discriminant_value(&E1::A); assert_eq!(target, [0, 0, 0]); target[1] = discriminant_value(&E1::B); assert_eq!(target, [0, 1, 0]); + assert_eq!(size_of::(), 1); let mut target: [i8; 3] = [0, 0, 0]; target[1] = discriminant_value(&E2::A); assert_eq!(target, [0, 7, 0]); target[1] = discriminant_value(&E2::B); assert_eq!(target, [0, -2, 0]); + // E3's size is target-dependent let mut target: [isize; 3] = [0, 0, 0]; target[1] = discriminant_value(&E3::A); assert_eq!(target, [0, 42, 0]); target[1] = discriminant_value(&E3::B); assert_eq!(target, [0, 100, 0]); + #[allow(overflowing_literals)] + { + assert_eq!(size_of::(), 4); + let mut target: [isize; 3] = [0, -1, 0]; + target[1] = discriminant_value(&UnsignedIntEnum::A); + assert_eq!(target, [0, 0, 0]); + target[1] = discriminant_value(&UnsignedIntEnum::O); + assert_eq!(target, [0, 0xffffffff as isize, 0]); + } + + assert_eq!(size_of::(), 16); let mut target: [i128; 3] = [0, 0, 0]; target[1] = discriminant_value(&E4::A); assert_eq!(target, [0, 0x1223_3445_5667_7889, 0]); diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr index 297380379385..db60fd1f7a3c 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr @@ -1,5 +1,5 @@ error: literal out of range for `isize` - --> $DIR/repr-c-big-discriminant1.rs:16:9 + --> $DIR/repr-c-big-discriminant1.rs:18:9 | LL | A = 9223372036854775807, // i64::MAX | ^^^^^^^^^^^^^^^^^^^ @@ -8,12 +8,28 @@ LL | A = 9223372036854775807, // i64::MAX = note: `#[deny(overflowing_literals)]` on by default error: literal out of range for `isize` - --> $DIR/repr-c-big-discriminant1.rs:24:9 + --> $DIR/repr-c-big-discriminant1.rs:26:9 | LL | A = -2147483649, // i32::MIN-1 | ^^^^^^^^^^^ | = note: the literal `-2147483649` does not fit into the type `isize` whose range is `-2147483648..=2147483647` -error: aborting due to 2 previous errors +error: literal out of range for `isize` + --> $DIR/repr-c-big-discriminant1.rs:34:9 + | +LL | A = 2147483648, // i32::MAX+1 + | ^^^^^^^^^^ + | + = note: the literal `2147483648` does not fit into the type `isize` whose range is `-2147483648..=2147483647` + +error: literal out of range for `isize` + --> $DIR/repr-c-big-discriminant1.rs:43:9 + | +LL | A = 2147483648, // i32::MAX+1 + | ^^^^^^^^^^ + | + = note: the literal `2147483648` does not fit into the type `isize` whose range is `-2147483648..=2147483647` + +error: aborting due to 4 previous errors diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr index 332f3023cfbe..e2517ab342f4 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr @@ -1,5 +1,5 @@ -error: `repr(C)` enum discriminant does not fit into C `int` - --> $DIR/repr-c-big-discriminant1.rs:16:5 +error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int` + --> $DIR/repr-c-big-discriminant1.rs:18:5 | LL | A = 9223372036854775807, // i64::MAX | ^ @@ -9,13 +9,13 @@ LL | A = 9223372036854775807, // i64::MAX = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C = help: use `repr($int_ty)` instead to explicitly set the size of this enum note: the lint level is defined here - --> $DIR/repr-c-big-discriminant1.rs:6:9 + --> $DIR/repr-c-big-discriminant1.rs:8:9 | LL | #![deny(repr_c_enums_larger_than_int)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `repr(C)` enum discriminant does not fit into C `int` - --> $DIR/repr-c-big-discriminant1.rs:24:5 +error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int` + --> $DIR/repr-c-big-discriminant1.rs:26:5 | LL | A = -2147483649, // i32::MIN-1 | ^ @@ -25,8 +25,30 @@ LL | A = -2147483649, // i32::MIN-1 = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C = help: use `repr($int_ty)` instead to explicitly set the size of this enum -error: `repr(C)` enum discriminant does not fit into C `int` - --> $DIR/repr-c-big-discriminant1.rs:34:5 +error: `repr(C)` enum discriminant does not fit into C `unsigned int`, and a previous discriminant does not fit into C `int` + --> $DIR/repr-c-big-discriminant1.rs:36:5 + | +LL | B = -1, + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #124403 + = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C + = help: use `repr($int_ty)` instead to explicitly set the size of this enum + +error: `repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int` + --> $DIR/repr-c-big-discriminant1.rs:43:5 + | +LL | A = 2147483648, // i32::MAX+1 + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #124403 + = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C + = help: use `repr($int_ty)` instead to explicitly set the size of this enum + +error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int` + --> $DIR/repr-c-big-discriminant1.rs:53:5 | LL | A = I64_MAX as isize, | ^ @@ -36,5 +58,5 @@ LL | A = I64_MAX as isize, = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C = help: use `repr($int_ty)` instead to explicitly set the size of this enum -error: aborting due to 3 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs b/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs index 16d007432a02..739f7cbed14b 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs @@ -3,6 +3,8 @@ //@[ptr32] needs-llvm-components: x86 //@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu //@[ptr64] needs-llvm-components: x86 +// GCC doesn't like cross-compilation +//@ ignore-backends: gcc #![deny(repr_c_enums_larger_than_int)] //@ add-minicore @@ -15,7 +17,7 @@ use minicore::*; enum OverflowingEnum1 { A = 9223372036854775807, // i64::MAX //[ptr32]~^ ERROR: literal out of range - //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int` //[ptr64]~^^^ WARN: previously accepted } @@ -23,18 +25,43 @@ enum OverflowingEnum1 { enum OverflowingEnum2 { A = -2147483649, // i32::MIN-1 //[ptr32]~^ ERROR: literal out of range - //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int` + //[ptr64]~^^^ WARN: previously accepted +} + +#[repr(C)] +enum OverflowingEnum3a { + A = 2147483648, // i32::MAX+1 + //[ptr32]~^ ERROR: literal out of range + B = -1, + //[ptr64]~^ ERROR: discriminant does not fit into C `unsigned int`, and a previous + //[ptr64]~^^ WARN: previously accepted +} +#[repr(C)] +enum OverflowingEnum3b { + B = -1, + A = 2147483648, // i32::MAX+1 + //[ptr32]~^ ERROR: literal out of range + //[ptr64]~^^ ERROR: discriminant does not fit into C `int`, and a previous //[ptr64]~^^^ WARN: previously accepted } const I64_MAX: i64 = 9223372036854775807; #[repr(C)] -enum OverflowingEnum3 { +enum OverflowingEnum4 { A = I64_MAX as isize, - //[ptr64]~^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int` //[ptr64]~^^ WARN: previously accepted // No warning/error on 32bit targets, but the `as isize` hints that wrapping is occurring. } +// Enums like this are found in the ecosystem, let's make sure they get accepted. +#[repr(C)] +#[allow(overflowing_literals)] +enum OkayEnum { + A = 0, + O = 0xffffffff, +} + fn main() {} diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr index 9fbadf3bc39e..85aaff46a689 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr @@ -1,5 +1,5 @@ error[E0370]: enum discriminant overflowed - --> $DIR/repr-c-big-discriminant2.rs:19:5 + --> $DIR/repr-c-big-discriminant2.rs:24:5 | LL | B, // +1 | ^ overflowed on value after 2147483647 diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr index 39c4f39b5707..8cd978ccb2fb 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr @@ -1,5 +1,5 @@ -error: `repr(C)` enum discriminant does not fit into C `int` - --> $DIR/repr-c-big-discriminant2.rs:19:5 +error: `repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int` + --> $DIR/repr-c-big-discriminant2.rs:24:5 | LL | B, // +1 | ^ @@ -9,7 +9,7 @@ LL | B, // +1 = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C = help: use `repr($int_ty)` instead to explicitly set the size of this enum note: the lint level is defined here - --> $DIR/repr-c-big-discriminant2.rs:6:9 + --> $DIR/repr-c-big-discriminant2.rs:8:9 | LL | #![deny(repr_c_enums_larger_than_int)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs b/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs index 87c9f0bea736..8e333b759892 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs @@ -3,6 +3,8 @@ //@[ptr32] needs-llvm-components: x86 //@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu //@[ptr64] needs-llvm-components: x86 +// GCC doesn't like cross-compilation +//@ ignore-backends: gcc #![deny(repr_c_enums_larger_than_int)] //@ add-minicore @@ -11,10 +13,13 @@ extern crate minicore; use minicore::*; -// Separate test since it suppresses other errors on ptr32 +// Separate test since it suppresses other errors on ptr32: +// ensure we find the bad discriminant when it is implicitly computed by incrementing +// the previous discriminant. #[repr(C)] enum OverflowingEnum { + NEG = -1, A = 2147483647, // i32::MAX B, // +1 //[ptr32]~^ ERROR: enum discriminant overflowed