rust/compiler
Stuart Cook b40a20f16d
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`
2025-11-04 23:01:10 +11:00
..
rustc Make llvm_enzyme a regular cargo feature 2025-09-15 15:31:56 +00:00
rustc_abi Rollup merge of #147017 - RalfJung:repr-c-big-discriminant, r=davidtwco 2025-11-04 23:01:10 +11:00
rustc_arena some cleanups in compiler 2025-10-12 08:08:30 +00:00
rustc_ast Rollup merge of #139751 - frank-king:feature/pin-project, r=Nadrieril,traviscross 2025-11-01 08:25:44 +01:00
rustc_ast_ir Implement pattern matching for &pin mut|const T 2025-10-30 07:56:16 +08:00
rustc_ast_lowering Rollup merge of #144291 - oli-obk:const_trait_alias, r=fee1-dead 2025-10-31 02:39:14 +01:00
rustc_ast_passes Generate const predicates for const trait aliases 2025-10-30 08:05:37 +00:00
rustc_ast_pretty Rollup merge of #139751 - frank-king:feature/pin-project, r=Nadrieril,traviscross 2025-11-01 08:25:44 +01:00
rustc_attr_parsing Add #[rustc_pass_indirectly_in_non_rustic_abis] 2025-11-04 09:56:17 +01:00
rustc_baked_icu_data Use default locale fallback data 2025-08-28 09:48:54 +00:00
rustc_borrowck Rollup merge of #133149 - estebank:niko-rustnation, r=wesleywiser 2025-11-04 13:44:47 +11:00
rustc_builtin_macros Add ParsedDescription to the attribute parsers 2025-11-03 18:27:06 +01:00
rustc_codegen_cranelift Skip codegen_crate call in check mode 2025-10-24 10:25:13 +00:00
rustc_codegen_gcc remove broken link 2025-10-20 20:50:44 +00:00
rustc_codegen_llvm Rollup merge of #145974 - pmur:murp/stabilize-zno-jump-tables, r=wesleywiser 2025-11-04 13:44:48 +11:00
rustc_codegen_ssa Rollup merge of #148247 - bjorn3:minor_symbol_export_cleanup, r=WaffleLapkin 2025-11-03 11:52:40 +11:00
rustc_const_eval Rollup merge of #147520 - sayantn:simd-const-eval, r=RalfJung 2025-11-03 17:20:32 +01:00
rustc_data_structures Rollup merge of #147716 - zhetaicheleba:master, r=jdonszelmann 2025-10-15 23:41:05 +02:00
rustc_driver compiler: Add Windows resources to rustc-main and rustc_driver 2025-09-05 14:06:31 -04:00
rustc_driver_impl Better warning message for crate type unsupported by codegen backend 2025-11-02 12:33:38 +00:00
rustc_error_codes Port cfg!() macro to the new attribute parsing system 2025-11-03 18:23:41 +01:00
rustc_error_messages Revert introduction of [workspace.dependencies]. 2025-09-02 19:12:54 +10:00
rustc_errors Rollup merge of #148004 - Muscraft:only-single-line-item-attributes, r=estebank 2025-11-03 21:20:22 +01:00
rustc_expand Rollup merge of #147438 - reddevilmidzy:rename-non-inline-module-in-msg, r=fee1-dead 2025-10-18 23:54:44 +02:00
rustc_feature Add #[rustc_pass_indirectly_in_non_rustic_abis] 2025-11-04 09:56:17 +01:00
rustc_fluent_macro some cleanups in compiler 2025-10-12 08:08:30 +00:00
rustc_fs_util Revert introduction of [workspace.dependencies]. 2025-09-02 19:12:54 +10:00
rustc_graphviz some cleanups in compiler 2025-10-12 08:08:30 +00:00
rustc_hashes some cleanups in compiler 2025-10-12 08:08:30 +00:00
rustc_hir Add #[rustc_pass_indirectly_in_non_rustic_abis] 2025-11-04 09:56:17 +01:00
rustc_hir_analysis do not complain about enums where all discriminants fit into a c_uint 2025-11-04 11:44:55 +01:00
rustc_hir_id rustc_hir_id: Add a comment explaining why the crate exists 2025-08-20 15:04:00 -07:00
rustc_hir_pretty Rollup merge of #139751 - frank-king:feature/pin-project, r=Nadrieril,traviscross 2025-11-01 08:25:44 +01:00
rustc_hir_typeck Rollup merge of #147951 - Kivooeo:plus-equal-let-chains, r=davidtwco 2025-11-03 21:20:21 +01:00
rustc_incremental Use the actual StableCrateId for the incr comp session dir 2025-10-27 15:56:09 +00:00
rustc_index clippy fixes and code simplification 2025-11-02 08:16:38 +00:00
rustc_index_macros Revert introduction of [workspace.dependencies]. 2025-09-02 19:12:54 +10:00
rustc_infer Rollup merge of #148290 - oli-obk:push-qwxvxyopypry, r=nnethercote 2025-11-02 09:10:38 +01:00
rustc_interface Stabilize -Zjump-tables=<bool> into -Cjump-table=<bool> 2025-11-03 08:12:16 -06:00
rustc_lexer Fix a crash/mislex when more than one frontmatter closing possibility is considered 2025-09-22 15:10:41 -04:00
rustc_lint FCW for repr(C) enums whose discriminant values do not fit into a c_int 2025-11-04 11:44:55 +01:00
rustc_lint_defs do not complain about enums where all discriminants fit into a c_uint 2025-11-04 11:44:55 +01:00
rustc_llvm Rollup merge of #148103 - Zalathar:compression, r=wesleywiser 2025-10-31 18:41:51 +01:00
rustc_log Restrict sysroot crate imports to those defined in this repo. 2025-10-15 13:17:25 +01:00
rustc_macros use declarative macro for #[derive(TryFromU32)] 2025-10-06 14:54:38 +00:00
rustc_metadata Rollup merge of #148306 - zetanumbers:expn_id_decode, r=nnethercote 2025-11-03 17:20:38 +01:00
rustc_middle Rollup merge of #147017 - RalfJung:repr-c-big-discriminant, r=davidtwco 2025-11-04 23:01:10 +11:00
rustc_mir_build Rollup merge of #147642 - camelid:misc-const-fixes, r=BoxyUwU 2025-11-02 15:56:48 +01:00
rustc_mir_dataflow Put Analysis back into Results. 2025-10-28 10:26:50 +11:00
rustc_mir_transform clippy fixes and code simplification 2025-11-02 08:16:38 +00:00
rustc_monomorphize Allow unsizing pattern types with pointer base 2025-10-21 11:22:51 +00:00
rustc_next_trait_solver Rollup merge of #144291 - oli-obk:const_trait_alias, r=fee1-dead 2025-10-31 02:39:14 +01:00
rustc_parse Rollup merge of #145903 - Kivooeo:c-style-pointer, r=davidtwco 2025-11-03 17:20:31 +01:00
rustc_parse_format Revert introduction of [workspace.dependencies]. 2025-09-02 19:12:54 +10:00
rustc_passes additional tests for pass_indirectly_in_non_rustic_abis 2025-11-04 10:05:02 +01:00
rustc_pattern_analysis split definition and use site hidden tys 2025-10-31 14:48:43 +01:00
rustc_privacy Remove QPath::LangItem 2025-10-27 21:19:38 -05:00
rustc_proc_macro Revert introduction of [workspace.dependencies]. 2025-09-02 19:12:54 +10:00
rustc_public Simplify rustc_public context handling 2025-10-29 23:31:50 +00:00
rustc_public_bridge Simplify rustc_public context handling 2025-10-29 23:31:50 +00:00
rustc_query_impl fixup limit handling code 2025-09-08 15:07:12 -07:00
rustc_query_system Rollup merge of #147830 - Noratrieb:reword-unstable-fingerprint-ice, r=jackh726 2025-10-22 13:20:23 -04:00
rustc_resolve Add note to E0401 2025-11-03 16:24:36 +00:00
rustc_sanitizers Rollup merge of #144936 - rcvalle:rust-cfi-fix-144641, r=lcnr 2025-10-28 20:39:32 +11:00
rustc_serialize Revert introduction of [workspace.dependencies]. 2025-09-02 19:12:54 +10:00
rustc_session Stabilize -Zjump-tables=<bool> into -Cjump-table=<bool> 2025-11-03 08:12:16 -06:00
rustc_span Add #[rustc_pass_indirectly_in_non_rustic_abis] 2025-11-04 09:56:17 +01:00
rustc_symbol_mangling Fix wasm_import_module attribute cross-crate 2025-11-01 05:04:30 -07:00
rustc_target assert that #[rustc_pass_indirectly_in_non_rustic_abis] is respected 2025-11-04 09:56:21 +01:00
rustc_thread_pool some cleanups in compiler 2025-10-12 08:08:30 +00:00
rustc_trait_selection Auto merge of #148420 - Zalathar:rollup-1rrbzk7, r=Zalathar 2025-11-03 02:52:00 +00:00
rustc_traits Auto merge of #146096 - adwinwhite:handle_normalization_overflow_in_mono1, r=saethlin 2025-10-13 00:20:10 +00:00
rustc_transmute prefer to use repeat_n over repeat and take 2025-10-09 01:24:55 +08:00
rustc_ty_utils Rollup merge of #147017 - RalfJung:repr-c-big-discriminant, r=davidtwco 2025-11-04 23:01:10 +11:00
rustc_type_ir Rollup merge of #148153 - osamakader:fix-duplicate-the, r=JonathanBrouwer 2025-10-27 22:13:23 +11:00
rustc_type_ir_macros some cleanups in compiler 2025-10-12 08:08:30 +00:00
rustc_windows_rc [win] Use find-msvc-tools instead of cc to find the linker and rc on Windows 2025-09-19 12:00:30 -07:00