From c626431e213b289e67d63303c19fac3e1bb8072d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 24 Sep 2025 23:08:00 +0200 Subject: [PATCH 1/3] extend some comments and clarify some names around enum tag type computation --- compiler/rustc_abi/src/layout.rs | 2 +- compiler/rustc_abi/src/lib.rs | 5 +++++ compiler/rustc_middle/src/ty/layout.rs | 5 ++++- compiler/rustc_ty_utils/src/layout.rs | 8 ++++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_abi/src/layout.rs b/compiler/rustc_abi/src/layout.rs index 14356813b7bb0..b078fc92482fb 100644 --- a/compiler/rustc_abi/src/layout.rs +++ b/compiler/rustc_abi/src/layout.rs @@ -812,7 +812,7 @@ impl LayoutCalculator { let (max, min) = largest_niche // We might have no inhabited variants, so pretend there's at least one. .unwrap_or((0, 0)); - let (min_ity, signed) = discr_range_of_repr(min, max); //Integer::repr_discr(tcx, ty, &repr, min, max); + let (min_ity, signed) = discr_range_of_repr(min, max); //Integer::discr_range_of_repr(tcx, ty, &repr, min, max); let mut align = dl.aggregate_align; let mut max_repr_align = repr.align; diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index de44c8755a078..45c114acf25da 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -183,6 +183,11 @@ impl ReprOptions { /// Returns the discriminant type, given these `repr` options. /// This must only be called on enums! + /// + /// This is the "typeck type" of the discriminant, which is effectively the maximum size: + /// discriminant values will be wrapped to fit (with a lint). Layout can later decide to use a + /// smaller type for the tag that stores the discriminant at runtime and that will work just + /// fine, it just induces casts when getting/setting the discriminant. pub fn discr_type(&self) -> IntegerType { self.int.unwrap_or(IntegerType::Pointer(true)) } diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index 507a25a432594..d975b8d920710 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -72,7 +72,10 @@ impl abi::Integer { /// signed discriminant range and `#[repr]` attribute. /// N.B.: `u128` values above `i128::MAX` will be treated as signed, but /// that shouldn't affect anything, other than maybe debuginfo. - fn repr_discr<'tcx>( + /// + /// This is the basis for computing the type of the *tag* of an enum (which can be smaller than + /// the type of the *discriminant*, which is determined by [`ReprOptions::discr_type`]). + fn discr_range_of_repr<'tcx>( tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, repr: &ReprOptions, diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs index 317d101dafe05..763ed18bec9c3 100644 --- a/compiler/rustc_ty_utils/src/layout.rs +++ b/compiler/rustc_ty_utils/src/layout.rs @@ -613,8 +613,8 @@ fn layout_of_uncached<'tcx>( // UnsafeCell and UnsafePinned both disable niche optimizations let is_special_no_niche = def.is_unsafe_cell() || def.is_unsafe_pinned(); - let get_discriminant_type = - |min, max| abi::Integer::repr_discr(tcx, ty, &def.repr(), min, max); + let discr_range_of_repr = + |min, max| abi::Integer::discr_range_of_repr(tcx, ty, &def.repr(), min, max); let discriminants_iter = || { def.is_enum() @@ -637,7 +637,7 @@ fn layout_of_uncached<'tcx>( def.is_enum(), is_special_no_niche, tcx.layout_scalar_valid_range(def.did()), - get_discriminant_type, + discr_range_of_repr, discriminants_iter(), !maybe_unsized, ) @@ -662,7 +662,7 @@ fn layout_of_uncached<'tcx>( def.is_enum(), is_special_no_niche, tcx.layout_scalar_valid_range(def.did()), - get_discriminant_type, + discr_range_of_repr, discriminants_iter(), !maybe_unsized, ) else { From 597724030ff4351054b6b6b4f5e70d8047d06197 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 25 Sep 2025 11:08:08 +0200 Subject: [PATCH 2/3] FCW for repr(C) enums whose discriminant values do not fit into a c_int --- .../rustc_hir_analysis/src/check/check.rs | 2 +- compiler/rustc_hir_analysis/src/collect.rs | 53 +++++++++++++------ compiler/rustc_lint/src/types.rs | 3 +- compiler/rustc_lint_defs/src/builtin.rs | 48 +++++++++++++++++ compiler/rustc_middle/src/ty/layout.rs | 3 +- .../rustc_ty_utils/src/layout/invariant.rs | 2 + tests/auxiliary/minicore.rs | 22 ++++++++ .../repr-c-big-discriminant1.ptr32.stderr | 19 +++++++ .../repr-c-big-discriminant1.ptr64.stderr | 40 ++++++++++++++ .../repr-c-big-discriminant1.rs | 40 ++++++++++++++ .../repr-c-big-discriminant2.ptr32.stderr | 11 ++++ .../repr-c-big-discriminant2.ptr64.stderr | 18 +++++++ .../repr-c-big-discriminant2.rs | 25 +++++++++ 13 files changed, 266 insertions(+), 20 deletions(-) create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant1.rs create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant2.rs diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 4c910d25c30a8..8acfac6e46d14 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -784,7 +784,7 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), tcx.ensure_ok().generics_of(def_id); tcx.ensure_ok().type_of(def_id); tcx.ensure_ok().predicates_of(def_id); - crate::collect::lower_enum_variant_types(tcx, def_id.to_def_id()); + crate::collect::lower_enum_variant_types(tcx, def_id); check_enum(tcx, def_id); check_variances_for_type_defn(tcx, def_id); } diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index b6a662f425602..85839f7fc5808 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -19,7 +19,7 @@ use std::cell::Cell; use std::iter; use std::ops::Bound; -use rustc_abi::ExternAbi; +use rustc_abi::{ExternAbi, Size}; use rustc_ast::Recovered; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_data_structures::unord::UnordMap; @@ -605,7 +605,7 @@ pub(super) fn lower_variant_ctor(tcx: TyCtxt<'_>, def_id: LocalDefId) { tcx.ensure_ok().predicates_of(def_id); } -pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: DefId) { +pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: LocalDefId) { let def = tcx.adt_def(def_id); let repr_type = def.repr().discr_type(); let initial = repr_type.initial_discriminant(tcx); @@ -614,23 +614,44 @@ pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: DefId) { // fill the discriminant values and field types for variant in def.variants() { let wrapped_discr = prev_discr.map_or(initial, |d| d.wrap_incr(tcx)); - prev_discr = Some( - if let ty::VariantDiscr::Explicit(const_def_id) = variant.discr { - def.eval_explicit_discr(tcx, const_def_id).ok() - } else if let Some(discr) = repr_type.disr_incr(tcx, prev_discr) { - Some(discr) - } else { + let cur_discr = if let ty::VariantDiscr::Explicit(const_def_id) = variant.discr { + def.eval_explicit_discr(tcx, const_def_id).ok() + } else if let Some(discr) = repr_type.disr_incr(tcx, prev_discr) { + Some(discr) + } else { + let span = tcx.def_span(variant.def_id); + tcx.dcx().emit_err(errors::EnumDiscriminantOverflowed { + span, + discr: prev_discr.unwrap().to_string(), + item_name: tcx.item_ident(variant.def_id), + wrapped_discr: wrapped_discr.to_string(), + }); + None + } + .unwrap_or(wrapped_discr); + + if def.repr().c() { + // 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); + + 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() { let span = tcx.def_span(variant.def_id); - tcx.dcx().emit_err(errors::EnumDiscriminantOverflowed { + tcx.node_span_lint( + rustc_session::lint::builtin::REPR_C_ENUMS_LARGER_THAN_INT, + tcx.local_def_id_to_hir_id(def_id), span, - discr: prev_discr.unwrap().to_string(), - item_name: tcx.item_ident(variant.def_id), - wrapped_discr: wrapped_discr.to_string(), - }); - None + |d| { + d.primary_message("`repr(C)` enum discriminant does not fit into C `int`") + .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"); + } + ); } - .unwrap_or(wrapped_discr), - ); + } + + prev_discr = Some(cur_discr); for f in &variant.fields { tcx.ensure_ok().generics_of(f.did); diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index eaec0c9857d28..c29892adb1631 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -10,7 +10,7 @@ use rustc_span::{Span, Symbol, sym}; use tracing::debug; use {rustc_ast as ast, rustc_hir as hir}; -mod improper_ctypes; // these filed do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations +mod improper_ctypes; // these files do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations pub(crate) use improper_ctypes::ImproperCTypesLint; use crate::lints::{ @@ -25,7 +25,6 @@ use crate::lints::{ use crate::{LateContext, LateLintPass, LintContext}; mod literal; - use literal::{int_ty_range, lint_literal, uint_ty_range}; declare_lint! { diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 8b7b13a4c3622..0c36137b2125a 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -86,6 +86,7 @@ declare_lint_pass! { REFINING_IMPL_TRAIT_INTERNAL, REFINING_IMPL_TRAIT_REACHABLE, RENAMED_AND_REMOVED_LINTS, + REPR_C_ENUMS_LARGER_THAN_INT, REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES, RUST_2021_INCOMPATIBLE_OR_PATTERNS, @@ -5197,3 +5198,50 @@ declare_lint! { Warn, r#"detects when a function annotated with `#[inline(always)]` and `#[target_feature(enable = "..")]` is inlined into a caller without the required target feature"#, } + +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`. + /// + /// ### Example + /// + /// ```rust,ignore (only errors on 64bit) + /// #[repr(C)] + /// enum E { + /// V = 9223372036854775807, // i64::MAX + /// } + /// ``` + /// + /// This will produce: + /// + /// ```text + /// error: `repr(C)` enum discriminant does not fit into C `int` + /// --> $DIR/repr-c-big-discriminant1.rs:16:5 + /// | + /// 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 + /// ``` + /// + /// ### 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. + /// + /// Therefore, `repr(C)` enums require all discriminants to fit into a C `int`. + pub REPR_C_ENUMS_LARGER_THAN_INT, + Warn, + "repr(C) enums with discriminant values that do not fit into a C int", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseError, + reference: "issue #124403 ", + report_in_deps: false, + }; +} diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index d975b8d920710..91d14f404a107 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -111,7 +111,8 @@ impl abi::Integer { abi::Integer::I8 }; - // Pick the smallest fit. + // Pick the smallest fit. Prefer unsigned; that matches clang in cases where this makes a + // difference (https://godbolt.org/z/h4xEasW1d) so it is crucial for repr(C). if unsigned_fit <= signed_fit { (cmp::max(unsigned_fit, at_least), false) } else { diff --git a/compiler/rustc_ty_utils/src/layout/invariant.rs b/compiler/rustc_ty_utils/src/layout/invariant.rs index b768269215fa2..d1484aed16718 100644 --- a/compiler/rustc_ty_utils/src/layout/invariant.rs +++ b/compiler/rustc_ty_utils/src/layout/invariant.rs @@ -14,6 +14,8 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou if layout.size.bytes() >= tcx.data_layout.obj_size_bound() { bug!("size is too large, in the following layout:\n{layout:#?}"); } + // FIXME(#124403): Once `repr_c_enums_larger_than_int` is a hard error, we could assert + // here that a repr(c) enum discriminant is never larger than a c_int. if !cfg!(debug_assertions) { // Stop here, the rest is kind of expensive. diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 4f4c653cb46e7..076474caf5580 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -177,6 +177,21 @@ impl Add for isize { } } +#[lang = "neg"] +pub trait Neg { + type Output; + + fn neg(self) -> Self::Output; +} + +impl Neg for isize { + type Output = isize; + + fn neg(self) -> isize { + loop {} // Dummy impl, not actually used + } +} + #[lang = "sync"] trait Sync {} impl_marker_trait!( @@ -231,6 +246,13 @@ pub mod mem { #[rustc_nounwind] #[rustc_intrinsic] pub unsafe fn transmute(src: Src) -> Dst; + + #[rustc_nounwind] + #[rustc_intrinsic] + pub const fn size_of() -> usize; + #[rustc_nounwind] + #[rustc_intrinsic] + pub const fn align_of() -> usize; } #[lang = "c_void"] diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr new file mode 100644 index 0000000000000..2973803793854 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr @@ -0,0 +1,19 @@ +error: literal out of range for `isize` + --> $DIR/repr-c-big-discriminant1.rs:16:9 + | +LL | A = 9223372036854775807, // i64::MAX + | ^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `9223372036854775807` does not fit into the type `isize` whose range is `-2147483648..=2147483647` + = note: `#[deny(overflowing_literals)]` on by default + +error: literal out of range for `isize` + --> $DIR/repr-c-big-discriminant1.rs:24: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 + diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr new file mode 100644 index 0000000000000..332f3023cfbe8 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr @@ -0,0 +1,40 @@ +error: `repr(C)` enum discriminant does not fit into C `int` + --> $DIR/repr-c-big-discriminant1.rs:16:5 + | +LL | A = 9223372036854775807, // i64::MAX + | ^ + | + = 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 +note: the lint level is defined here + --> $DIR/repr-c-big-discriminant1.rs:6: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 + | +LL | A = -2147483649, // i32::MIN-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` + --> $DIR/repr-c-big-discriminant1.rs:34:5 + | +LL | A = I64_MAX as isize, + | ^ + | + = 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: aborting due to 3 previous errors + diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs b/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs new file mode 100644 index 0000000000000..508ef080e3633 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs @@ -0,0 +1,40 @@ +//@ revisions: ptr32 ptr64 +//@[ptr32] compile-flags: --target i686-unknown-linux-gnu +//@[ptr32] needs-llvm-components: x86 +//@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu +//@[ptr64] needs-llvm-components: x86 +#![deny(repr_c_enums_larger_than_int)] + +//@ add-core-stubs +#![feature(no_core)] +#![no_core] +extern crate minicore; +use minicore::*; + +#[repr(C)] +enum OverflowingEnum1 { + A = 9223372036854775807, // i64::MAX + //[ptr32]~^ ERROR: literal out of range + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^^ WARN: previously accepted +} + +#[repr(C)] +enum OverflowingEnum2 { + A = -2147483649, // i32::MIN-1 + //[ptr32]~^ ERROR: literal out of range + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^^ WARN: previously accepted +} + +const I64_MAX: i64 = 9223372036854775807; + +#[repr(C)] +enum OverflowingEnum3 { + A = I64_MAX as isize, + //[ptr64]~^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^ WARN: previously accepted + // No warning/error on 32bit targets, but the `as isize` hints that wrapping is occurring. +} + +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 new file mode 100644 index 0000000000000..9fbadf3bc39e5 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr @@ -0,0 +1,11 @@ +error[E0370]: enum discriminant overflowed + --> $DIR/repr-c-big-discriminant2.rs:19:5 + | +LL | B, // +1 + | ^ overflowed on value after 2147483647 + | + = note: explicitly set `B = -2147483648` if that is desired outcome + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0370`. diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr new file mode 100644 index 0000000000000..39c4f39b5707d --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr @@ -0,0 +1,18 @@ +error: `repr(C)` enum discriminant does not fit into C `int` + --> $DIR/repr-c-big-discriminant2.rs:19: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 +note: the lint level is defined here + --> $DIR/repr-c-big-discriminant2.rs:6:9 + | +LL | #![deny(repr_c_enums_larger_than_int)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs b/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs new file mode 100644 index 0000000000000..d00e516b80c33 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs @@ -0,0 +1,25 @@ +//@ revisions: ptr32 ptr64 +//@[ptr32] compile-flags: --target i686-unknown-linux-gnu +//@[ptr32] needs-llvm-components: x86 +//@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu +//@[ptr64] needs-llvm-components: x86 +#![deny(repr_c_enums_larger_than_int)] + +//@ add-core-stubs +#![feature(no_core)] +#![no_core] +extern crate minicore; +use minicore::*; + +// Separate test since it suppresses other errors on ptr32 + +#[repr(C)] +enum OverflowingEnum { + A = 2147483647, // i32::MAX + B, // +1 + //[ptr32]~^ ERROR: enum discriminant overflowed + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^^ WARN: previously accepted +} + +fn main() {} From da16dbacd016abf4a48356d92df1ecda17eb259f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 2 Oct 2025 09:58:34 +0200 Subject: [PATCH 3/3] do not complain about enums where all discriminants fit into a c_uint --- compiler/rustc_hir_analysis/src/collect.rs | 23 +++++++++-- compiler/rustc_lint_defs/src/builtin.rs | 22 ++++++----- .../ui/enum-discriminant/discriminant_size.rs | 23 +++++++++++ .../repr-c-big-discriminant1.ptr32.stderr | 22 +++++++++-- .../repr-c-big-discriminant1.ptr64.stderr | 38 +++++++++++++++---- .../repr-c-big-discriminant1.rs | 35 +++++++++++++++-- .../repr-c-big-discriminant2.ptr32.stderr | 2 +- .../repr-c-big-discriminant2.ptr64.stderr | 6 +-- .../repr-c-big-discriminant2.rs | 7 +++- 9 files changed, 145 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 85839f7fc5808..4aabc4fd4aeef 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 0c36137b2125a..460f2d2a179a8 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -5201,7 +5201,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 /// @@ -5215,7 +5215,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 @@ -5227,15 +5227,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 b1feff3c59e1d..537940cfb7ee3 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 2973803793854..db60fd1f7a3cb 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 332f3023cfbe8..e2517ab342f45 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 508ef080e3633..a2245841d461d 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-core-stubs @@ -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 9fbadf3bc39e5..85aaff46a6892 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 39c4f39b5707d..8cd978ccb2fb1 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 d00e516b80c33..b5bcf5bc30d29 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-core-stubs @@ -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