Skip to content

Commit b8688d6

Browse files
committed
FCW for repr(C) enums whose discriminant values do not fit into a c_int
1 parent 50c483b commit b8688d6

File tree

13 files changed

+268
-19
lines changed

13 files changed

+268
-19
lines changed

compiler/rustc_hir_analysis/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ hir_analysis_assoc_item_is_private = {$kind} `{$name}` is private
1818
1919
hir_analysis_assoc_item_not_found = associated {$assoc_kind} `{$assoc_ident}` not found for `{$qself}`
2020
21+
hir_analysis_repr_c_enum_larger_than_int = `repr(C)` enum discriminant does not fit into C `int`
22+
.note = `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
23+
.help = use `repr($int_ty)` instead to explicitly set the size of this enum
24+
2125
hir_analysis_assoc_item_not_found_found_in_other_trait_label = there is {$identically_named ->
2226
[true] an
2327
*[false] a similarly named

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(),
784784
tcx.ensure_ok().generics_of(def_id);
785785
tcx.ensure_ok().type_of(def_id);
786786
tcx.ensure_ok().predicates_of(def_id);
787-
crate::collect::lower_enum_variant_types(tcx, def_id.to_def_id());
787+
crate::collect::lower_enum_variant_types(tcx, def_id);
788788
check_enum(tcx, def_id);
789789
check_variances_for_type_defn(tcx, def_id);
790790
}

compiler/rustc_hir_analysis/src/collect.rs

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use std::cell::Cell;
1919
use std::iter;
2020
use std::ops::Bound;
2121

22-
use rustc_abi::ExternAbi;
22+
use rustc_abi::{ExternAbi, Size};
2323
use rustc_ast::Recovered;
2424
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
2525
use rustc_data_structures::unord::UnordMap;
@@ -605,7 +605,7 @@ pub(super) fn lower_variant_ctor(tcx: TyCtxt<'_>, def_id: LocalDefId) {
605605
tcx.ensure_ok().predicates_of(def_id);
606606
}
607607

608-
pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: DefId) {
608+
pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: LocalDefId) {
609609
let def = tcx.adt_def(def_id);
610610
let repr_type = def.repr().discr_type();
611611
let initial = repr_type.initial_discriminant(tcx);
@@ -614,23 +614,40 @@ pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: DefId) {
614614
// fill the discriminant values and field types
615615
for variant in def.variants() {
616616
let wrapped_discr = prev_discr.map_or(initial, |d| d.wrap_incr(tcx));
617-
prev_discr = Some(
618-
if let ty::VariantDiscr::Explicit(const_def_id) = variant.discr {
619-
def.eval_explicit_discr(tcx, const_def_id).ok()
620-
} else if let Some(discr) = repr_type.disr_incr(tcx, prev_discr) {
621-
Some(discr)
622-
} else {
617+
let cur_discr = if let ty::VariantDiscr::Explicit(const_def_id) = variant.discr {
618+
def.eval_explicit_discr(tcx, const_def_id).ok()
619+
} else if let Some(discr) = repr_type.disr_incr(tcx, prev_discr) {
620+
Some(discr)
621+
} else {
622+
let span = tcx.def_span(variant.def_id);
623+
tcx.dcx().emit_err(errors::EnumDiscriminantOverflowed {
624+
span,
625+
discr: prev_discr.unwrap().to_string(),
626+
item_name: tcx.item_ident(variant.def_id),
627+
wrapped_discr: wrapped_discr.to_string(),
628+
});
629+
None
630+
}
631+
.unwrap_or(wrapped_discr);
632+
633+
if def.repr().c() {
634+
// c_int is a signed type, so get a proper signed version of the discriminant
635+
let discr_size = cur_discr.ty.int_size_and_signed(tcx).0;
636+
let discr_val = discr_size.sign_extend(cur_discr.val);
637+
638+
let c_int = Size::from_bits(tcx.sess.target.c_int_width);
639+
if discr_val < c_int.signed_int_min() || discr_val > c_int.signed_int_max() {
623640
let span = tcx.def_span(variant.def_id);
624-
tcx.dcx().emit_err(errors::EnumDiscriminantOverflowed {
641+
tcx.emit_node_span_lint(
642+
rustc_session::lint::builtin::REPR_C_ENUMS_LARGER_THAN_INT,
643+
tcx.local_def_id_to_hir_id(def_id),
625644
span,
626-
discr: prev_discr.unwrap().to_string(),
627-
item_name: tcx.item_ident(variant.def_id),
628-
wrapped_discr: wrapped_discr.to_string(),
629-
});
630-
None
645+
errors::ReprCEnumLargerThanInt,
646+
);
631647
}
632-
.unwrap_or(wrapped_discr),
633-
);
648+
}
649+
650+
prev_discr = Some(cur_discr);
634651

635652
for f in &variant.fields {
636653
tcx.ensure_ok().generics_of(f.did);

compiler/rustc_hir_analysis/src/errors.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,3 +1707,9 @@ pub(crate) struct AsyncDropWithoutSyncDrop {
17071707
#[primary_span]
17081708
pub span: Span,
17091709
}
1710+
1711+
#[derive(LintDiagnostic)]
1712+
#[diag(hir_analysis_repr_c_enum_larger_than_int)]
1713+
#[note]
1714+
#[help]
1715+
pub(crate) struct ReprCEnumLargerThanInt;

compiler/rustc_lint/src/types.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rustc_span::{Span, Symbol, sym};
1010
use tracing::debug;
1111
use {rustc_ast as ast, rustc_hir as hir};
1212

13-
mod improper_ctypes; // these filed do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations
13+
mod improper_ctypes; // these files do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations
1414
pub(crate) use improper_ctypes::ImproperCTypesLint;
1515

1616
use crate::lints::{
@@ -25,7 +25,6 @@ use crate::lints::{
2525
use crate::{LateContext, LateLintPass, LintContext};
2626

2727
mod literal;
28-
2928
use literal::{int_ty_range, lint_literal, uint_ty_range};
3029

3130
declare_lint! {

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ declare_lint_pass! {
8686
REFINING_IMPL_TRAIT_INTERNAL,
8787
REFINING_IMPL_TRAIT_REACHABLE,
8888
RENAMED_AND_REMOVED_LINTS,
89+
REPR_C_ENUMS_LARGER_THAN_INT,
8990
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
9091
RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES,
9192
RUST_2021_INCOMPATIBLE_OR_PATTERNS,
@@ -5200,3 +5201,50 @@ declare_lint! {
52005201
Warn,
52015202
r#"detects when a function annotated with `#[inline(always)]` and `#[target_feature(enable = "..")]` is inlined into a caller without the required target feature"#,
52025203
}
5204+
5205+
declare_lint! {
5206+
/// The `repr_c_enums_larger_than_int` lint detects `repr(C)`` enums with discriminant
5207+
/// values that do not fit into a C `int`.
5208+
///
5209+
/// ### Example
5210+
///
5211+
/// ```rust,ignore (only errors on 64bit)
5212+
/// #[repr(C)]
5213+
/// enum E {
5214+
/// V = 9223372036854775807, // i64::MAX
5215+
/// }
5216+
/// ```
5217+
///
5218+
/// This will produce:
5219+
///
5220+
/// ```
5221+
/// error: `repr(C)` enum discriminant does not fit into C `int`
5222+
/// --> $DIR/repr-c-big-discriminant1.rs:16:5
5223+
/// |
5224+
///LL | A = 9223372036854775807, // i64::MAX
5225+
/// | ^
5226+
/// |
5227+
/// = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
5228+
/// = help: use `repr($int_ty)` instead to explicitly set the size of this enum
5229+
/// ```
5230+
///
5231+
/// ### Explanation
5232+
///
5233+
/// In C, enums with discriminants that do not fit into an `int` are a portability hazard: such
5234+
/// enums are only permitted since C23, and not supported e.g. by MSVC. Furthermore, Rust
5235+
/// interprets the discriminant values of `repr(C)` enums as expressions of type `isize`, which
5236+
/// cannot be changed in a backwards-compatible way. If the discriminant is given as a literal
5237+
/// that does not fit into `isize`, it is wrapped (with a warning). This makes it impossible to
5238+
/// implement the C23 behavior of enums where the enum discriminants have no predefined type and
5239+
/// instead the enum uses a type large enough to hold all discriminants.
5240+
///
5241+
/// Therefore, `repr(C)` enums require all discriminants to fit into a C `int`.
5242+
pub REPR_C_ENUMS_LARGER_THAN_INT,
5243+
Warn,
5244+
"repr(C) enums with discriminant values that do not fit into a C int",
5245+
@future_incompatible = FutureIncompatibleInfo {
5246+
reason: FutureIncompatibilityReason::FutureReleaseError,
5247+
reference: "issue #124403 <https://github.com/rust-lang/rust/issues/124403>",
5248+
report_in_deps: false,
5249+
};
5250+
}

tests/auxiliary/minicore.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,21 @@ impl Add<isize> for isize {
177177
}
178178
}
179179

180+
#[lang = "neg"]
181+
pub trait Neg {
182+
type Output;
183+
184+
fn neg(self) -> Self::Output;
185+
}
186+
187+
impl Neg for isize {
188+
type Output = isize;
189+
190+
fn neg(self) -> isize {
191+
loop {} // Dummy impl, not actually used
192+
}
193+
}
194+
180195
#[lang = "sync"]
181196
trait Sync {}
182197
impl_marker_trait!(
@@ -231,6 +246,13 @@ pub mod mem {
231246
#[rustc_nounwind]
232247
#[rustc_intrinsic]
233248
pub unsafe fn transmute<Src, Dst>(src: Src) -> Dst;
249+
250+
#[rustc_nounwind]
251+
#[rustc_intrinsic]
252+
pub const fn size_of<T>() -> usize;
253+
#[rustc_nounwind]
254+
#[rustc_intrinsic]
255+
pub const fn align_of<T>() -> usize;
234256
}
235257

236258
#[lang = "c_void"]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: literal out of range for `isize`
2+
--> $DIR/repr-c-big-discriminant1.rs:16:9
3+
|
4+
LL | A = 9223372036854775807, // i64::MAX
5+
| ^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: the literal `9223372036854775807` does not fit into the type `isize` whose range is `-2147483648..=2147483647`
8+
= note: `#[deny(overflowing_literals)]` on by default
9+
10+
error: literal out of range for `isize`
11+
--> $DIR/repr-c-big-discriminant1.rs:24:9
12+
|
13+
LL | A = -2147483649, // i32::MIN-1
14+
| ^^^^^^^^^^^
15+
|
16+
= note: the literal `-2147483649` does not fit into the type `isize` whose range is `-2147483648..=2147483647`
17+
18+
error: aborting due to 2 previous errors
19+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: `repr(C)` enum discriminant does not fit into C `int`
2+
--> $DIR/repr-c-big-discriminant1.rs:16:5
3+
|
4+
LL | A = 9223372036854775807, // i64::MAX
5+
| ^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #124403 <https://github.com/rust-lang/rust/issues/124403>
9+
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
10+
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
11+
note: the lint level is defined here
12+
--> $DIR/repr-c-big-discriminant1.rs:6:9
13+
|
14+
LL | #![deny(repr_c_enums_larger_than_int)]
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
17+
error: `repr(C)` enum discriminant does not fit into C `int`
18+
--> $DIR/repr-c-big-discriminant1.rs:24:5
19+
|
20+
LL | A = -2147483649, // i32::MIN-1
21+
| ^
22+
|
23+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
24+
= note: for more information, see issue #124403 <https://github.com/rust-lang/rust/issues/124403>
25+
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
26+
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
27+
28+
error: `repr(C)` enum discriminant does not fit into C `int`
29+
--> $DIR/repr-c-big-discriminant1.rs:34:5
30+
|
31+
LL | A = I64_MAX as isize,
32+
| ^
33+
|
34+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
35+
= note: for more information, see issue #124403 <https://github.com/rust-lang/rust/issues/124403>
36+
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
37+
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
38+
39+
error: aborting due to 3 previous errors
40+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@ revisions: ptr32 ptr64
2+
//@[ptr32] compile-flags: --target i686-unknown-linux-gnu
3+
//@[ptr32] needs-llvm-components: x86
4+
//@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu
5+
//@[ptr64] needs-llvm-components: x86
6+
#![deny(repr_c_enums_larger_than_int)]
7+
8+
//@ add-core-stubs
9+
#![feature(no_core)]
10+
#![no_core]
11+
extern crate minicore;
12+
use minicore::*;
13+
14+
#[repr(C)]
15+
enum OverflowingEnum1 {
16+
A = 9223372036854775807, // i64::MAX
17+
//[ptr32]~^ ERROR: literal out of range
18+
//[ptr64]~^^ ERROR: discriminant does not fit into C `int`
19+
//[ptr64]~^^^ WARN: previously accepted
20+
}
21+
22+
#[repr(C)]
23+
enum OverflowingEnum2 {
24+
A = -2147483649, // i32::MIN-1
25+
//[ptr32]~^ ERROR: literal out of range
26+
//[ptr64]~^^ ERROR: discriminant does not fit into C `int`
27+
//[ptr64]~^^^ WARN: previously accepted
28+
}
29+
30+
const I64_MAX: i64 = 9223372036854775807;
31+
32+
#[repr(C)]
33+
enum OverflowingEnum3 {
34+
A = I64_MAX as isize,
35+
//[ptr64]~^ ERROR: discriminant does not fit into C `int`
36+
//[ptr64]~^^ WARN: previously accepted
37+
// No warning/error on 32bit targets, but the `as isize` hints that wrapping is occurring.
38+
}
39+
40+
fn main() {}

0 commit comments

Comments
 (0)