Skip to content

Commit 0f31ace

Browse files
Auto merge of #147017 - RalfJung:repr-c-big-discriminant, r=<try>
FCW for repr(C) enums whose discriminant values do not fit into a c_int
2 parents 6f34f4e + 491bfd6 commit 0f31ace

File tree

15 files changed

+251
-26
lines changed

15 files changed

+251
-26
lines changed

compiler/rustc_abi/src/layout.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
814814
let (max, min) = largest_niche
815815
// We might have no inhabited variants, so pretend there's at least one.
816816
.unwrap_or((0, 0));
817-
let (min_ity, signed) = discr_range_of_repr(min, max); //Integer::repr_discr(tcx, ty, &repr, min, max);
817+
let (min_ity, signed) = discr_range_of_repr(min, max); //Integer::discr_range_of_repr(tcx, ty, &repr, min, max);
818818

819819
let mut align = dl.aggregate_align;
820820
let mut max_repr_align = repr.align;

compiler/rustc_abi/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ impl ReprOptions {
183183

184184
/// Returns the discriminant type, given these `repr` options.
185185
/// This must only be called on enums!
186+
///
187+
/// This is the "typeck type" of the discriminant, which is effectively the maximum size:
188+
/// discriminant values will be wrapped to fit (with a lint). Layout can later decide to use a
189+
/// smaller type for the tag that stores the discriminant at runtime and that will work just
190+
/// fine, it just induces casts when getting/setting the discriminant.
186191
pub fn discr_type(&self) -> IntegerType {
187192
self.int.unwrap_or(IntegerType::Pointer(true))
188193
}

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: 34 additions & 17 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 {
625-
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
641+
let mut d = tcx
642+
.dcx()
643+
.struct_span_err(span, "`repr(C)` enum discriminant does not fit into C `int`");
644+
d.note("`repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C")
645+
.help("use `repr($int_ty)` instead to explicitly set the size of this enum");
646+
d.emit();
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_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+
/// ```text
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+
}

compiler/rustc_middle/src/ty/layout.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ impl abi::Integer {
7272
/// signed discriminant range and `#[repr]` attribute.
7373
/// N.B.: `u128` values above `i128::MAX` will be treated as signed, but
7474
/// that shouldn't affect anything, other than maybe debuginfo.
75-
fn repr_discr<'tcx>(
75+
///
76+
/// This is the basis for computing the type of the *tag* of an enum (which can be smaller than
77+
/// the type of the *discriminant*, which is determined by [`ReprOptions::discr_type`]).
78+
fn discr_range_of_repr<'tcx>(
7679
tcx: TyCtxt<'tcx>,
7780
ty: Ty<'tcx>,
7881
repr: &ReprOptions,

compiler/rustc_ty_utils/src/layout.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -613,8 +613,8 @@ fn layout_of_uncached<'tcx>(
613613
// UnsafeCell and UnsafePinned both disable niche optimizations
614614
let is_special_no_niche = def.is_unsafe_cell() || def.is_unsafe_pinned();
615615

616-
let get_discriminant_type =
617-
|min, max| abi::Integer::repr_discr(tcx, ty, &def.repr(), min, max);
616+
let discr_range_of_repr =
617+
|min, max| abi::Integer::discr_range_of_repr(tcx, ty, &def.repr(), min, max);
618618

619619
let discriminants_iter = || {
620620
def.is_enum()
@@ -637,7 +637,7 @@ fn layout_of_uncached<'tcx>(
637637
def.is_enum(),
638638
is_special_no_niche,
639639
tcx.layout_scalar_valid_range(def.did()),
640-
get_discriminant_type,
640+
discr_range_of_repr,
641641
discriminants_iter(),
642642
!maybe_unsized,
643643
)
@@ -662,7 +662,7 @@ fn layout_of_uncached<'tcx>(
662662
def.is_enum(),
663663
is_special_no_niche,
664664
tcx.layout_scalar_valid_range(def.did()),
665-
get_discriminant_type,
665+
discr_range_of_repr,
666666
discriminants_iter(),
667667
!maybe_unsized,
668668
) else {

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:23: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+

0 commit comments

Comments
 (0)