Rollup merge of #147017 - RalfJung:repr-c-big-discriminant, r=davidtwco

FCW for repr(C) enums whose discriminant values do not fit into a c_int or c_uint

Context: https://github.com/rust-lang/rust/issues/124403

The current behavior of repr(C) enums is as follows:
- The discriminant values are interpreted as const expressions of type `isize`
- We compute the smallest size that can hold all discriminant values
- The target spec contains the smallest size for repr(C) enums
- We take the larger of these two sizes

Unfortunately, this doesn't always match what C compilers do. In particular, MSVC seems to *always* give enums a size of 4 bytes, whereas the algorithm above will give enums a size of up to 8 bytes on 64bit targets. Here's an example enum affected by this:
```
// We give this size 4 on 32bit targets (with a warning since the discriminant is wrapped to fit an isize)
// and size 8 on 64bit targets.
#[repr(C)]
enum OverflowingEnum {
    A = 9223372036854775807, // i64::MAX
}

// MSVC always gives this size 4 (without any warning).
// GCC always gives it size 8 (without any warning).
// Godbolt: https://godbolt.org/z/P49MaYvMd
enum overflowing_enum {
    OVERFLOWING_ENUM_A = 9223372036854775807,
};
```

If we look at the C standard, then up until C20, there was no official support enums without an explicit underlying type and with discriminants that do not fit an `int`. With C23, this has changed: now enums have to grow automatically if there is an integer type that can hold all their discriminants. MSVC does not implement this part of C23.

Furthermore, Rust fundamentally cannot implement this (without major changes)! Enum discriminants work fundamentally different in Rust and C:
- In Rust, every enum has a discriminant type entirely determined by its repr flags, and then the discriminant values must be const expressions of that type. For repr(C), that type is `isize`. So from the outset we interpret 9223372036854775807 as an isize literal and never give it a chance to be stored in a bigger type. If the discriminant is given as a literal without type annotation, it gets wrapped implicitly with a warning; otherwise the user has to write `as isize` explicitly and thus trigger the wrapping. Later, we can then decide to make the *tag* that stores the discriminant smaller than the discriminant type if all discriminant values fit into a smaller type, but those values have allready all been made to fit an `isize` so nothing bigger than `isize` could ever come out of this. That makes the behavior of 32bit GCC impossible for us to match.
-  In C, things flow the other way around: every discriminant value has a type determined entirely by its constant expression, and then the type for the enum is determined based on that. IOW, the expression can have *any type* a priori, different variants can even use a different type, and then the compiler is supposed to look at the resulting *values* (presumably as mathematical integers) and find a type that can hold them all. For the example above, 9223372036854775807 is a signed integer, so the compiler looks for the smallest signed type that can hold it, which is `long long`, and then uses that to compute the size of the enum (at least that's what C23 says should happen and GCC does this correctly).

Realistically I think the best we can do is to not attempt to support C23 enums, and to require repr(C) enums to satisfy the C20 requirements: all discriminants must fit into a c_int. So that's what this PR implements, by adding a FCW for enums with discriminants that do not fit into `c_int`. As a slight extension, we do *not* lint enums where all discriminants fit into a `c_uint` (i.e. `unsigned int`): while C20 does (in my reading) not allow this, and C23 does not prescribe the size of such an enum, this seems to behave consistently across compilers (giving the enum the size of an `unsigned int`). IOW, the lint fires whenever our layout algorithm would make the enum larger than an `int`, irrespective of whether we pick a signed or unsigned discriminant. This extension was added because [crater found](https://github.com/rust-lang/rust/pull/147017#issuecomment-3357077199) multiple cases of such enums across the ecosystem.

Note that it is impossible to trigger this FCW on targets where isize and c_int are the same size (i.e., the typical 32bit target): since we interpret discriminant values as isize, by the time we look at them, they have already been wrapped. However, we have an existing lint (overflowing_literals) that should notify people when this kind of wrapping occurs implicitly. Also, 64bit targets are much more common. On the other hand, even on 64bit targets it is possible to fall into the same trap by writing a literal that is so big that it does not fit into isize, gets wrapped (triggering overflowing_literals), and the wrapped value fits into c_int. Furthermore, overflowing_literals is just a lint, so if it occurs in a dependency you won't notice. (Arguably there is also a more general problem here: for literals of type `usize`/`isize`, it is fairly easy to write code that only triggers `overflowing_literals` on 32bit targets, and to never see that lint if one develops on a 64bit target.)

Specifically, the above example triggers the FCW on 64bit targets, but on 32bit targets we get this err-by-default lint instead (which will be hidden if it occurs in a dependency):
```
error: literal out of range for `isize`
  --> $DIR/repr-c-big-discriminant1.rs:16:9
   |
LL |     A = 9223372036854775807,
   |         ^^^^^^^^^^^^^^^^^^^
   |
   = note: the literal `9223372036854775807` does not fit into the type `isize` whose range is `-2147483648..=2147483647`
   = note: `#[deny(overflowing_literals)]` on by default
```
Also see the tests added by this PR.

This isn't perfect, but so far I don't think I have seen a better option. In https://github.com/rust-lang/rust/pull/146504 I tried adjusting our enum logic to make the size of the example enum above actually match what C compilers do, but that's a massive breaking change since we have to change the expected type of the discriminant expression from `isize` to `i64` or even `i128` -- so that seems like a no-go. To improve the lint we could analyze things on the HIR level and specifically catch "repr(C) enums with discriminants defined as literals that are too big", but that would have to be on top of the lint in this PR I think since we'd still want to also always check the actually evaluated value (which we can't always determined on the HIR level).

Cc `@workingjubilee` `@CAD97`
This commit is contained in:
Stuart Cook 2025-11-04 23:01:10 +11:00 committed by GitHub
commit b40a20f16d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 392 additions and 26 deletions

View file

@ -812,7 +812,7 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
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;

View file

@ -186,6 +186,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))
}

View file

@ -782,7 +782,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);
}

View file

@ -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,32 +605,70 @@ 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);
let mut prev_discr = None::<Discr<'_>>;
// 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() {
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() {
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);
// 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);
tcx.dcx().emit_err(errors::EnumDiscriminantOverflowed {
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,
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(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");
}
);
}
.unwrap_or(wrapped_discr),
);
}
prev_discr = Some(cur_discr);
for f in &variant.fields {
tcx.ensure_ok().generics_of(f.did);

View file

@ -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! {

View file

@ -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_NON_ZST_FIELDS,
RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES,
RUST_2021_INCOMPATIBLE_OR_PATTERNS,
@ -5213,3 +5214,52 @@ 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` or `unsigned 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` nor into C `unsigned 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 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.
///
/// 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",
@future_incompatible = FutureIncompatibleInfo {
reason: FutureIncompatibilityReason::FutureReleaseError,
reference: "issue #124403 <https://github.com/rust-lang/rust/issues/124403>",
report_in_deps: false,
};
}

View file

@ -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,
@ -108,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 {

View file

@ -639,8 +639,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()
@ -663,7 +663,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,
)
@ -688,7 +688,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 {

View file

@ -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.

View file

@ -177,6 +177,21 @@ impl Add<isize> 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, Dst>(src: Src) -> Dst;
#[rustc_nounwind]
#[rustc_intrinsic]
pub const fn size_of<T>() -> usize;
#[rustc_nounwind]
#[rustc_intrinsic]
pub const fn align_of<T>() -> usize;
}
#[lang = "c_void"]

View file

@ -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::<E1>(), 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::<E2>(), 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::<UnsignedIntEnum>(), 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::<E4>(), 16);
let mut target: [i128; 3] = [0, 0, 0];
target[1] = discriminant_value(&E4::A);
assert_eq!(target, [0, 0x1223_3445_5667_7889, 0]);

View file

@ -0,0 +1,35 @@
error: literal out of range for `isize`
--> $DIR/repr-c-big-discriminant1.rs:18: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: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: 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

View file

@ -0,0 +1,62 @@
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
| ^
|
= 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 <https://github.com/rust-lang/rust/issues/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:8:9
|
LL | #![deny(repr_c_enums_larger_than_int)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
| ^
|
= 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 <https://github.com/rust-lang/rust/issues/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 `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 <https://github.com/rust-lang/rust/issues/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 <https://github.com/rust-lang/rust/issues/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,
| ^
|
= 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 <https://github.com/rust-lang/rust/issues/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 5 previous errors

View file

@ -0,0 +1,67 @@
//@ 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
// GCC doesn't like cross-compilation
//@ ignore-backends: gcc
#![deny(repr_c_enums_larger_than_int)]
//@ add-minicore
#![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` nor into C `unsigned 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` 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 OverflowingEnum4 {
A = I64_MAX as isize,
//[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() {}

View file

@ -0,0 +1,11 @@
error[E0370]: enum discriminant overflowed
--> $DIR/repr-c-big-discriminant2.rs:24: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`.

View file

@ -0,0 +1,18 @@
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
| ^
|
= 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 <https://github.com/rust-lang/rust/issues/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:8:9
|
LL | #![deny(repr_c_enums_larger_than_int)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 1 previous error

View file

@ -0,0 +1,30 @@
//@ 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
// GCC doesn't like cross-compilation
//@ ignore-backends: gcc
#![deny(repr_c_enums_larger_than_int)]
//@ add-minicore
#![feature(no_core)]
#![no_core]
extern crate minicore;
use minicore::*;
// 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
//[ptr64]~^^ ERROR: discriminant does not fit into C `int`
//[ptr64]~^^^ WARN: previously accepted
}
fn main() {}