Skip to content

Commit c6dddf6

Browse files
authored
Unrolled build for #147185
Rollup merge of #147185 - RalfJung:repr-c-not-zst, r=petrochenkov repr(transparent): do not consider repr(C) types to be 1-ZST Context: rust-lang/unsafe-code-guidelines#552 This experiments with a [suggestion](rust-lang/rfcs#3845 (comment)) by ```@RustyYato``` to stop considering repr(C) types as 1-ZST for the purpose of repr(transparent). If we go with rust-lang/rfcs#3845 (or another approach for fixing repr(C)), they will anyway not be ZST on all targets any more, so this removes a portability hazard. Furthermore, zero-sized repr(C) structs [may have to be treated](rust-lang/unsafe-code-guidelines#552 (comment)) as non-ZST for the win64 ABI (at least that's what gcc/clang do), so allowing them to be ignored in repr(transparent) types is not entirely coherent. Turns out we already have an FCW for repr(transparent), namely #78586. This extends that lint to also check for repr(C).
2 parents df984ed + b9b29c4 commit c6dddf6

File tree

13 files changed

+582
-123
lines changed

13 files changed

+582
-123
lines changed

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 68 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ use rustc_hir::def::{CtorKind, DefKind};
1212
use rustc_hir::{LangItem, Node, attrs, find_attr, intravisit};
1313
use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt};
1414
use rustc_infer::traits::{Obligation, ObligationCauseCode, WellFormedLoc};
15-
use rustc_lint_defs::builtin::{
16-
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, UNSUPPORTED_CALLING_CONVENTIONS,
17-
};
15+
use rustc_lint_defs::builtin::{REPR_TRANSPARENT_NON_ZST_FIELDS, UNSUPPORTED_CALLING_CONVENTIONS};
1816
use rustc_middle::hir::nested_filter;
1917
use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
2018
use rustc_middle::middle::stability::EvalResult;
@@ -1509,31 +1507,46 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15091507
}
15101508

15111509
let typing_env = ty::TypingEnv::non_body_analysis(tcx, adt.did());
1512-
// For each field, figure out if it's known to have "trivial" layout (i.e., is a 1-ZST), with
1513-
// "known" respecting #[non_exhaustive] attributes.
1510+
// For each field, figure out if it has "trivial" layout (i.e., is a 1-ZST).
1511+
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive` or private
1512+
// fields or `repr(C)`. We call those fields "unsuited".
1513+
struct FieldInfo<'tcx> {
1514+
span: Span,
1515+
trivial: bool,
1516+
unsuited: Option<UnsuitedInfo<'tcx>>,
1517+
}
1518+
struct UnsuitedInfo<'tcx> {
1519+
/// The source of the problem, a type that is found somewhere within the field type.
1520+
ty: Ty<'tcx>,
1521+
reason: UnsuitedReason,
1522+
}
1523+
enum UnsuitedReason {
1524+
NonExhaustive,
1525+
PrivateField,
1526+
ReprC,
1527+
}
1528+
15141529
let field_infos = adt.all_fields().map(|field| {
15151530
let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did));
15161531
let layout = tcx.layout_of(typing_env.as_query_input(ty));
15171532
// We are currently checking the type this field came from, so it must be local
15181533
let span = tcx.hir_span_if_local(field.did).unwrap();
15191534
let trivial = layout.is_ok_and(|layout| layout.is_1zst());
15201535
if !trivial {
1521-
return (span, trivial, None);
1536+
// No need to even compute `unsuited`.
1537+
return FieldInfo { span, trivial, unsuited: None };
15221538
}
1523-
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive`.
15241539

1525-
fn check_non_exhaustive<'tcx>(
1540+
fn check_unsuited<'tcx>(
15261541
tcx: TyCtxt<'tcx>,
15271542
typing_env: ty::TypingEnv<'tcx>,
1528-
t: Ty<'tcx>,
1529-
) -> ControlFlow<(&'static str, DefId, GenericArgsRef<'tcx>, bool)> {
1543+
ty: Ty<'tcx>,
1544+
) -> ControlFlow<UnsuitedInfo<'tcx>> {
15301545
// We can encounter projections during traversal, so ensure the type is normalized.
1531-
let t = tcx.try_normalize_erasing_regions(typing_env, t).unwrap_or(t);
1532-
match t.kind() {
1533-
ty::Tuple(list) => {
1534-
list.iter().try_for_each(|t| check_non_exhaustive(tcx, typing_env, t))
1535-
}
1536-
ty::Array(ty, _) => check_non_exhaustive(tcx, typing_env, *ty),
1546+
let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
1547+
match ty.kind() {
1548+
ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, typing_env, t)),
1549+
ty::Array(ty, _) => check_unsuited(tcx, typing_env, *ty),
15371550
ty::Adt(def, args) => {
15381551
if !def.did().is_local()
15391552
&& !find_attr!(
@@ -1548,28 +1561,36 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15481561
.any(ty::VariantDef::is_field_list_non_exhaustive);
15491562
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
15501563
if non_exhaustive || has_priv {
1551-
return ControlFlow::Break((
1552-
def.descr(),
1553-
def.did(),
1554-
args,
1555-
non_exhaustive,
1556-
));
1564+
return ControlFlow::Break(UnsuitedInfo {
1565+
ty,
1566+
reason: if non_exhaustive {
1567+
UnsuitedReason::NonExhaustive
1568+
} else {
1569+
UnsuitedReason::PrivateField
1570+
},
1571+
});
15571572
}
15581573
}
1574+
if def.repr().c() {
1575+
return ControlFlow::Break(UnsuitedInfo {
1576+
ty,
1577+
reason: UnsuitedReason::ReprC,
1578+
});
1579+
}
15591580
def.all_fields()
15601581
.map(|field| field.ty(tcx, args))
1561-
.try_for_each(|t| check_non_exhaustive(tcx, typing_env, t))
1582+
.try_for_each(|t| check_unsuited(tcx, typing_env, t))
15621583
}
15631584
_ => ControlFlow::Continue(()),
15641585
}
15651586
}
15661587

1567-
(span, trivial, check_non_exhaustive(tcx, typing_env, ty).break_value())
1588+
FieldInfo { span, trivial, unsuited: check_unsuited(tcx, typing_env, ty).break_value() }
15681589
});
15691590

15701591
let non_trivial_fields = field_infos
15711592
.clone()
1572-
.filter_map(|(span, trivial, _non_exhaustive)| if !trivial { Some(span) } else { None });
1593+
.filter_map(|field| if !field.trivial { Some(field.span) } else { None });
15731594
let non_trivial_count = non_trivial_fields.clone().count();
15741595
if non_trivial_count >= 2 {
15751596
bad_non_zero_sized_fields(
@@ -1581,36 +1602,40 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15811602
);
15821603
return;
15831604
}
1584-
let mut prev_non_exhaustive_1zst = false;
1585-
for (span, _trivial, non_exhaustive_1zst) in field_infos {
1586-
if let Some((descr, def_id, args, non_exhaustive)) = non_exhaustive_1zst {
1605+
1606+
let mut prev_unsuited_1zst = false;
1607+
for field in field_infos {
1608+
if let Some(unsuited) = field.unsuited {
1609+
assert!(field.trivial);
15871610
// If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts.
15881611
// Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst.
1589-
if non_trivial_count > 0 || prev_non_exhaustive_1zst {
1612+
if non_trivial_count > 0 || prev_unsuited_1zst {
15901613
tcx.node_span_lint(
1591-
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
1614+
REPR_TRANSPARENT_NON_ZST_FIELDS,
15921615
tcx.local_def_id_to_hir_id(adt.did().expect_local()),
1593-
span,
1616+
field.span,
15941617
|lint| {
1618+
let title = match unsuited.reason {
1619+
UnsuitedReason::NonExhaustive => "external non-exhaustive types",
1620+
UnsuitedReason::PrivateField => "external types with private fields",
1621+
UnsuitedReason::ReprC => "`repr(C)` types",
1622+
};
15951623
lint.primary_message(
1596-
"zero-sized fields in `repr(transparent)` cannot \
1597-
contain external non-exhaustive types",
1624+
format!("zero-sized fields in `repr(transparent)` cannot contain {title}"),
15981625
);
1599-
let note = if non_exhaustive {
1600-
"is marked with `#[non_exhaustive]`"
1601-
} else {
1602-
"contains private fields"
1626+
let note = match unsuited.reason {
1627+
UnsuitedReason::NonExhaustive => "is marked with `#[non_exhaustive]`, so it could become non-zero-sized in the future.",
1628+
UnsuitedReason::PrivateField => "contains private fields, so it could become non-zero-sized in the future.",
1629+
UnsuitedReason::ReprC => "is a `#[repr(C)]` type, so it is not guaranteed to be zero-sized on all targets.",
16031630
};
1604-
let field_ty = tcx.def_path_str_with_args(def_id, args);
16051631
lint.note(format!(
1606-
"this {descr} contains `{field_ty}`, which {note}, \
1607-
and makes it not a breaking change to become \
1608-
non-zero-sized in the future."
1632+
"this field contains `{field_ty}`, which {note}",
1633+
field_ty = unsuited.ty,
16091634
));
16101635
},
1611-
)
1636+
);
16121637
} else {
1613-
prev_non_exhaustive_1zst = true;
1638+
prev_unsuited_1zst = true;
16141639
}
16151640
}
16161641
}

compiler/rustc_lint/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,10 @@ fn register_builtins(store: &mut LintStore) {
362362
store.register_renamed("static_mut_ref", "static_mut_refs");
363363
store.register_renamed("temporary_cstring_as_ptr", "dangling_pointers_from_temporaries");
364364
store.register_renamed("elided_named_lifetimes", "mismatched_lifetime_syntaxes");
365+
store.register_renamed(
366+
"repr_transparent_external_private_fields",
367+
"repr_transparent_non_zst_fields",
368+
);
365369

366370
// These were moved to tool lints, but rustc still sees them when compiling normally, before
367371
// tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ declare_lint_pass! {
8686
REFINING_IMPL_TRAIT_INTERNAL,
8787
REFINING_IMPL_TRAIT_REACHABLE,
8888
RENAMED_AND_REMOVED_LINTS,
89-
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
89+
REPR_TRANSPARENT_NON_ZST_FIELDS,
9090
RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES,
9191
RUST_2021_INCOMPATIBLE_OR_PATTERNS,
9292
RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
@@ -3011,19 +3011,23 @@ declare_lint! {
30113011
}
30123012

30133013
declare_lint! {
3014-
/// The `repr_transparent_external_private_fields` lint
3014+
/// The `repr_transparent_non_zst_fields` lint
30153015
/// detects types marked `#[repr(transparent)]` that (transitively)
3016-
/// contain an external ZST type marked `#[non_exhaustive]` or containing
3017-
/// private fields
3016+
/// contain a type that is not guaranteed to remain a ZST type under all configurations.
30183017
///
30193018
/// ### Example
30203019
///
30213020
/// ```rust,ignore (needs external crate)
30223021
/// #![deny(repr_transparent_external_private_fields)]
30233022
/// use foo::NonExhaustiveZst;
30243023
///
3024+
/// #[repr(C)]
3025+
/// struct CZst([u8; 0]);
3026+
///
30253027
/// #[repr(transparent)]
30263028
/// struct Bar(u32, ([u32; 0], NonExhaustiveZst));
3029+
/// #[repr(transparent)]
3030+
/// struct Baz(u32, CZst);
30273031
/// ```
30283032
///
30293033
/// This will produce:
@@ -3042,26 +3046,39 @@ declare_lint! {
30423046
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30433047
/// = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
30443048
/// = note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
3045-
/// = note: this struct contains `NonExhaustiveZst`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future.
3049+
/// = note: this field contains `NonExhaustiveZst`, which is marked with `#[non_exhaustive]`, so it could become non-zero-sized in the future.
3050+
///
3051+
/// error: zero-sized fields in repr(transparent) cannot contain `#[repr(C)]` types
3052+
/// --> src/main.rs:5:28
3053+
/// |
3054+
/// 5 | struct Baz(u32, CZst);
3055+
/// | ^^^^
3056+
/// = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
3057+
/// = note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
3058+
/// = note: this field contains `CZst`, which is a `#[repr(C)]` type, so it is not guaranteed to be zero-sized on all targets.
30463059
/// ```
30473060
///
30483061
/// ### Explanation
30493062
///
3050-
/// Previous, Rust accepted fields that contain external private zero-sized types,
3051-
/// even though it should not be a breaking change to add a non-zero-sized field to
3052-
/// that private type.
3063+
/// Previous, Rust accepted fields that contain external private zero-sized types, even though
3064+
/// those types could gain a non-zero-sized field in a future, semver-compatible update.
3065+
///
3066+
/// Rust also accepted fields that contain `repr(C)` zero-sized types, even though those types
3067+
/// are not guaranteed to be zero-sized on all targets, and even though those types can
3068+
/// make a difference for the ABI (and therefore cannot be ignored by `repr(transparent)`).
30533069
///
30543070
/// This is a [future-incompatible] lint to transition this
30553071
/// to a hard error in the future. See [issue #78586] for more details.
30563072
///
30573073
/// [issue #78586]: https://github.com/rust-lang/rust/issues/78586
30583074
/// [future-incompatible]: ../index.md#future-incompatible-lints
3059-
pub REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
3060-
Warn,
3075+
pub REPR_TRANSPARENT_NON_ZST_FIELDS,
3076+
Deny,
30613077
"transparent type contains an external ZST that is marked #[non_exhaustive] or contains private fields",
30623078
@future_incompatible = FutureIncompatibleInfo {
30633079
reason: FutureIncompatibilityReason::FutureReleaseError,
30643080
reference: "issue #78586 <https://github.com/rust-lang/rust/issues/78586>",
3081+
report_in_deps: true,
30653082
};
30663083
}
30673084

compiler/rustc_lint_defs/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ pub enum FutureIncompatibilityReason {
403403
///
404404
/// After a lint has been in this state for a while and you feel like it is ready to graduate
405405
/// to warning everyone, consider setting [`FutureIncompatibleInfo::report_in_deps`] to true.
406-
/// (see it's documentation for more guidance)
406+
/// (see its documentation for more guidance)
407407
///
408408
/// After some period of time, lints with this variant can be turned into
409409
/// hard errors (and the lint removed). Preferably when there is some

tests/ui/lint/improper-ctypes/lint-ctypes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub struct TransparentLifetime<'a>(*const u8, PhantomData<&'a ()>);
3838
#[repr(transparent)]
3939
pub struct TransparentUnit<U>(f32, PhantomData<U>);
4040
#[repr(transparent)]
41+
#[allow(repr_transparent_non_zst_fields)]
4142
pub struct TransparentCustomZst(i32, ZeroSize);
4243

4344
#[repr(C)]

0 commit comments

Comments
 (0)