Skip to content

Commit 8037014

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 42b384e + 470702b commit 8037014

File tree

16 files changed

+349
-26
lines changed

16 files changed

+349
-26
lines changed

compiler/rustc_abi/src/layout.rs

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

817817
let mut align = dl.aggregate_align;
818818
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: 49 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,32 +605,64 @@ 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);
612612
let mut prev_discr = None::<Discr<'_>>;
613+
// Some of the logic below relies on `i128` being able to hold all c_int and c_uint values.
614+
assert!(tcx.sess.target.c_int_width < 128);
615+
let mut min_discr = i128::MAX;
616+
let mut max_discr = i128::MIN;
613617

614618
// fill the discriminant values and field types
615619
for variant in def.variants() {
616620
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 {
621+
let cur_discr = if let ty::VariantDiscr::Explicit(const_def_id) = variant.discr {
622+
def.eval_explicit_discr(tcx, const_def_id).ok()
623+
} else if let Some(discr) = repr_type.disr_incr(tcx, prev_discr) {
624+
Some(discr)
625+
} else {
626+
let span = tcx.def_span(variant.def_id);
627+
tcx.dcx().emit_err(errors::EnumDiscriminantOverflowed {
628+
span,
629+
discr: prev_discr.unwrap().to_string(),
630+
item_name: tcx.item_ident(variant.def_id),
631+
wrapped_discr: wrapped_discr.to_string(),
632+
});
633+
None
634+
}
635+
.unwrap_or(wrapped_discr);
636+
637+
if def.repr().c() {
638+
let c_int = Size::from_bits(tcx.sess.target.c_int_width);
639+
let c_uint_max = i128::try_from(c_int.unsigned_int_max()).unwrap();
640+
// c_int is a signed type, so get a proper signed version of the discriminant
641+
let discr_size = cur_discr.ty.int_size_and_signed(tcx).0;
642+
let discr_val = discr_size.sign_extend(cur_discr.val);
643+
min_discr = min_discr.min(discr_val);
644+
max_discr = max_discr.max(discr_val);
645+
646+
// The discriminant range must either fit into c_int or c_uint.
647+
if !(min_discr >= c_int.signed_int_min() && max_discr <= c_int.signed_int_max())
648+
&& !(min_discr >= 0 && max_discr <= c_uint_max)
649+
{
623650
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
651+
let msg = if discr_val < c_int.signed_int_min() || discr_val > c_uint_max {
652+
"`repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`"
653+
} else if discr_val < 0 {
654+
"`repr(C)` enum discriminant does not fit into C `unsigned int`, and a previous discriminant does not fit into C `int`"
655+
} else {
656+
"`repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int`"
657+
};
658+
let mut d = tcx.dcx().struct_span_err(span, msg);
659+
d.note("`repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C")
660+
.help("use `repr($int_ty)` instead to explicitly set the size of this enum");
661+
d.emit();
631662
}
632-
.unwrap_or(wrapped_discr),
633-
);
663+
}
664+
665+
prev_discr = Some(cur_discr);
634666

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

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"]

tests/ui/enum-discriminant/discriminant_size.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![feature(core_intrinsics)]
33

44
use std::intrinsics::discriminant_value;
5+
use std::mem::size_of;
56

67
enum E1 {
78
A,
@@ -20,31 +21,53 @@ enum E3 {
2021
B = 100,
2122
}
2223

24+
// Enums like this are found in the ecosystem, let's make sure they get the right size.
25+
#[repr(C)]
26+
#[allow(overflowing_literals)]
27+
enum UnsignedIntEnum {
28+
A = 0,
29+
O = 0xffffffff, // doesn't fit into `int`, but fits into `unsigned int`
30+
}
31+
2332
#[repr(i128)]
2433
enum E4 {
2534
A = 0x1223_3445_5667_7889,
2635
B = -0x1223_3445_5667_7889,
2736
}
2837

2938
fn main() {
39+
assert_eq!(size_of::<E1>(), 1);
3040
let mut target: [isize; 3] = [0, 0, 0];
3141
target[1] = discriminant_value(&E1::A);
3242
assert_eq!(target, [0, 0, 0]);
3343
target[1] = discriminant_value(&E1::B);
3444
assert_eq!(target, [0, 1, 0]);
3545

46+
assert_eq!(size_of::<E2>(), 1);
3647
let mut target: [i8; 3] = [0, 0, 0];
3748
target[1] = discriminant_value(&E2::A);
3849
assert_eq!(target, [0, 7, 0]);
3950
target[1] = discriminant_value(&E2::B);
4051
assert_eq!(target, [0, -2, 0]);
4152

53+
// E3's size is target-dependent
4254
let mut target: [isize; 3] = [0, 0, 0];
4355
target[1] = discriminant_value(&E3::A);
4456
assert_eq!(target, [0, 42, 0]);
4557
target[1] = discriminant_value(&E3::B);
4658
assert_eq!(target, [0, 100, 0]);
4759

60+
#[allow(overflowing_literals)]
61+
{
62+
assert_eq!(size_of::<UnsignedIntEnum>(), 4);
63+
let mut target: [isize; 3] = [0, -1, 0];
64+
target[1] = discriminant_value(&UnsignedIntEnum::A);
65+
assert_eq!(target, [0, 0, 0]);
66+
target[1] = discriminant_value(&UnsignedIntEnum::O);
67+
assert_eq!(target, [0, 0xffffffff as isize, 0]);
68+
}
69+
70+
assert_eq!(size_of::<E4>(), 16);
4871
let mut target: [i128; 3] = [0, 0, 0];
4972
target[1] = discriminant_value(&E4::A);
5073
assert_eq!(target, [0, 0x1223_3445_5667_7889, 0]);

0 commit comments

Comments
 (0)