Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compiler: be more clear about transparent layout violations #114015

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions compiler/rustc_error_codes/src/error_codes/E0690.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
A struct with the representation hint `repr(transparent)` had two or more fields
that were not guaranteed to be zero-sized.
that were not guaranteed to be zero-sized or have an alignment of 1.

Erroneous code example:

```compile_fail,E0690
#[repr(transparent)]
struct LengthWithUnit<U> { // error: transparent struct needs at most one
value: f32, // non-zero-sized field, but has 2
struct LengthWithUnit<U> { // error: transparent struct needs at most one field
value: f32, // with non-trivial layout, but has 2
unit: U,
}
```
Expand All @@ -17,7 +17,8 @@ it is not clear how the struct should be represented.
Note that fields of zero-sized types (e.g., `PhantomData`) can also exist
alongside the field that contains the actual data, they do not count for this
error. When generic types are involved (as in the above example), an error is
reported because the type parameter could be non-zero-sized.
reported because the type parameter could be non-zero-sized, or it could raise
the alignment requirements of the type.

To combine `repr(transparent)` with type parameters, `PhantomData` may be
useful:
Expand Down
18 changes: 11 additions & 7 deletions compiler/rustc_hir_analysis/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,17 @@ hir_analysis_transparent_enum_variant = transparent enum needs exactly one varia
.many_label = too many variants in `{$path}`
.multi_label = variant here

hir_analysis_transparent_non_zero_sized = transparent {$desc} needs at most one non-zero-sized field, but has {$field_count}
.label = needs at most one non-zero-sized field, but has {$field_count}
.labels = this field is non-zero-sized

hir_analysis_transparent_non_zero_sized_enum = the variant of a transparent {$desc} needs at most one non-zero-sized field, but has {$field_count}
.label = needs at most one non-zero-sized field, but has {$field_count}
.labels = this field is non-zero-sized
hir_analysis_transparent_layout = transparent {$desc} needs at most one field with non-trivial layout, but has {$field_count}
.label = needs at most one field with non-trivial layout, but has {$field_count}
.non_layout_labels = this field may have an alignment larger than 1
.non_zst_labels = this field is non-zero-sized
.note = a layout is trivial if, any only if, it is zero-sized with an alignment of 1

hir_analysis_transparent_layout_enum = the variant of a transparent {$desc} needs at most one field with non-trivial layout, but has {$field_count}
.label = needs at most one field with non-trivial layout, but has {$field_count}
.non_layout_labels = this field may have an alignment larger than 1
.non_zst_labels = this field is non-zero-sized
.note = a layout is trivial if, any only if, it is zero-sized with an alignment of 1

hir_analysis_typeof_reserved_keyword_used =
`typeof` is a reserved keyword but unimplemented
Expand Down
46 changes: 35 additions & 11 deletions compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,18 +1130,17 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
return;
}

// For each field, figure out if it's known to be a ZST and align(1), with "known"
// respecting #[non_exhaustive] attributes.
// For each field, collect information on its layout and whether it has a #[non_exhaustive]
// annotation.
let field_infos = adt.all_fields().map(|field| {
let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did));
let param_env = tcx.param_env(field.did);
let layout = tcx.layout_of(param_env.and(ty));
// We are currently checking the type this field came from, so it must be local
let span = tcx.hir().span_if_local(field.did).unwrap();
let zst = layout.is_ok_and(|layout| layout.is_zst());
let align = layout.ok().map(|layout| layout.align.abi.bytes());
if !zst {
return (span, zst, align, None);
return (span, zst, layout, None);
}

fn check_non_exhaustive<'tcx>(
Expand Down Expand Up @@ -1176,20 +1175,45 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
}
}

(span, zst, align, check_non_exhaustive(tcx, ty).break_value())
(span, zst, layout, check_non_exhaustive(tcx, ty).break_value())
});

let non_zst_fields = field_infos
// For a transparent ADT we must know which member field controls its layout. Hence, we only
// allow at most one field with non-trivial or unknown layout. A layout is trivial if, and only
// if, it is zero-sized with an alignment of 1. A layout is unknown if, for instance, it
// contains generic type parameters or has a #[non_exhaustive] annotation in a non-local type.
// We will reject any transparent ADT with more than one field with non-trivial or unknown
// layout, but we try our best to point out the violating properties.

let non_layout_fields = field_infos
.clone()
.filter_map(|(span, zst, _align, _non_exhaustive)| if !zst { Some(span) } else { None });
.filter_map(|(span, _, layout, _)| if layout.is_err() { Some(span) } else { None });
let non_layout_count = non_layout_fields.clone().count();
let non_zst_fields =
field_infos.clone().filter_map(
|(span, zst, layout, _)| if layout.is_ok() && !zst { Some(span) } else { None },
);
let non_zst_count = non_zst_fields.clone().count();
if non_zst_count >= 2 {
bad_non_zero_sized_fields(tcx, adt, non_zst_count, non_zst_fields, tcx.def_span(adt.did()));

let non_transparent_count = non_layout_count + non_zst_count;
if non_transparent_count >= 2 {
bad_transparent_layout(
tcx,
adt,
non_transparent_count,
non_layout_fields,
non_zst_fields,
tcx.def_span(adt.did()),
);
}

let incompatible_zst_fields =
field_infos.clone().filter(|(_, _, _, opt)| opt.is_some()).count();
let incompat = incompatible_zst_fields + non_zst_count >= 2 && non_zst_count < 2;
for (span, zst, align, non_exhaustive) in field_infos {
let incompat =
incompatible_zst_fields + non_transparent_count >= 2 && non_transparent_count < 2;

for (span, zst, layout, non_exhaustive) in field_infos {
let align = layout.ok().map(|layout| layout.align.abi.bytes());
if zst && align != Some(1) {
let mut err = struct_span_err!(
tcx.sess,
Expand Down
21 changes: 13 additions & 8 deletions compiler/rustc_hir_analysis/src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,28 +516,33 @@ fn bad_variant_count<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>, sp: Span, d
});
}

/// Emit an error when encountering two or more non-zero-sized fields in a transparent
/// enum.
fn bad_non_zero_sized_fields<'tcx>(
/// Emit an error when encountering two or more fields with non-trivial layout
/// in a transparent Adt.
fn bad_transparent_layout<'tcx>(
tcx: TyCtxt<'tcx>,
adt: ty::AdtDef<'tcx>,
field_count: usize,
field_spans: impl Iterator<Item = Span>,
non_layout_spans: impl Iterator<Item = Span>,
non_zst_spans: impl Iterator<Item = Span>,
sp: Span,
) {
if adt.is_enum() {
tcx.sess.emit_err(errors::TransparentNonZeroSizedEnum {
tcx.sess.emit_err(errors::TransparentLayoutEnum {
span: sp,
spans: field_spans.collect(),
non_layout_spans: non_layout_spans.collect(),
non_zst_spans: non_zst_spans.collect(),
field_count,
desc: adt.descr(),
note: (),
});
} else {
tcx.sess.emit_err(errors::TransparentNonZeroSized {
tcx.sess.emit_err(errors::TransparentLayout {
span: sp,
spans: field_spans.collect(),
non_layout_spans: non_layout_spans.collect(),
non_zst_spans: non_zst_spans.collect(),
field_count,
desc: adt.descr(),
note: (),
});
}
}
Expand Down
24 changes: 16 additions & 8 deletions compiler/rustc_hir_analysis/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,27 +777,35 @@ pub(crate) struct TransparentEnumVariant {
}

#[derive(Diagnostic)]
#[diag(hir_analysis_transparent_non_zero_sized_enum, code = "E0690")]
pub(crate) struct TransparentNonZeroSizedEnum<'a> {
#[diag(hir_analysis_transparent_layout_enum, code = "E0690")]
pub(crate) struct TransparentLayoutEnum<'a> {
#[primary_span]
#[label]
pub span: Span,
#[label(hir_analysis_labels)]
pub spans: Vec<Span>,
#[label(hir_analysis_non_layout_labels)]
pub non_layout_spans: Vec<Span>,
#[label(hir_analysis_non_zst_labels)]
pub non_zst_spans: Vec<Span>,
pub field_count: usize,
pub desc: &'a str,
#[note]
pub note: (),
}

#[derive(Diagnostic)]
#[diag(hir_analysis_transparent_non_zero_sized, code = "E0690")]
pub(crate) struct TransparentNonZeroSized<'a> {
#[diag(hir_analysis_transparent_layout, code = "E0690")]
pub(crate) struct TransparentLayout<'a> {
#[primary_span]
#[label]
pub span: Span,
#[label(hir_analysis_labels)]
pub spans: Vec<Span>,
#[label(hir_analysis_non_layout_labels)]
pub non_layout_spans: Vec<Span>,
#[label(hir_analysis_non_zst_labels)]
pub non_zst_spans: Vec<Span>,
pub field_count: usize,
pub desc: &'a str,
#[note]
pub note: (),
}

#[derive(Diagnostic)]
Expand Down
10 changes: 6 additions & 4 deletions tests/ui/repr/repr-transparent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ struct ContainsMultipleZst(PhantomData<*const i32>, NoFields);
struct ContainsZstAndNonZst((), [i32; 2]);

#[repr(transparent)]
struct MultipleNonZst(u8, u8); //~ ERROR needs at most one non-zero-sized field
struct MultipleNonZst(u8, u8);
//~^ ERROR needs at most one field with non-trivial layout

trait Mirror { type It: ?Sized; }
impl<T: ?Sized> Mirror for T { type It = Self; }

#[repr(transparent)]
pub struct StructWithProjection(f32, <f32 as Mirror>::It);
//~^ ERROR needs at most one non-zero-sized field
//~^ ERROR needs at most one field with non-trivial layout

#[repr(transparent)]
struct NontrivialAlignZst(u32, [u16; 0]); //~ ERROR alignment larger than 1
Expand Down Expand Up @@ -58,7 +59,7 @@ enum UnitFieldEnum {
enum TooManyFieldsEnum {
Foo(u32, String),
}
//~^^^ ERROR transparent enum needs at most one non-zero-sized field, but has 2
//~^^^ ERROR needs at most one field with non-trivial layout, but has 2

#[repr(transparent)]
enum MultipleVariants { //~ ERROR transparent enum needs exactly one variant, but has 2
Expand All @@ -82,9 +83,10 @@ union UnitUnion {
}

#[repr(transparent)]
union TooManyFields { //~ ERROR transparent union needs at most one non-zero-sized field, but has 2
union TooManyFields {
u: u32,
s: i32
}
//~^^^^ ERROR transparent union needs at most one field with non-trivial layout, but has 2

fn main() {}
44 changes: 26 additions & 18 deletions tests/ui/repr/repr-transparent.stderr
Original file line number Diff line number Diff line change
@@ -1,59 +1,65 @@
error[E0690]: transparent struct needs at most one non-zero-sized field, but has 2
error[E0690]: transparent struct needs at most one field with non-trivial layout, but has 2
--> $DIR/repr-transparent.rs:26:1
|
LL | struct MultipleNonZst(u8, u8);
| ^^^^^^^^^^^^^^^^^^^^^ -- -- this field is non-zero-sized
| | |
| | this field is non-zero-sized
| needs at most one non-zero-sized field, but has 2
| needs at most one field with non-trivial layout, but has 2
|
= note: a layout is trivial if, any only if, it is zero-sized with an alignment of 1

error[E0690]: transparent struct needs at most one non-zero-sized field, but has 2
--> $DIR/repr-transparent.rs:32:1
error[E0690]: transparent struct needs at most one field with non-trivial layout, but has 2
--> $DIR/repr-transparent.rs:33:1
|
LL | pub struct StructWithProjection(f32, <f32 as Mirror>::It);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ --- ------------------- this field is non-zero-sized
| | |
| | this field is non-zero-sized
| needs at most one non-zero-sized field, but has 2
| needs at most one field with non-trivial layout, but has 2
|
= note: a layout is trivial if, any only if, it is zero-sized with an alignment of 1

error[E0691]: zero-sized field in transparent struct has alignment larger than 1
--> $DIR/repr-transparent.rs:36:32
--> $DIR/repr-transparent.rs:37:32
|
LL | struct NontrivialAlignZst(u32, [u16; 0]);
| ^^^^^^^^ has alignment of 2, which is larger than 1

error[E0691]: zero-sized field in transparent struct has alignment larger than 1
--> $DIR/repr-transparent.rs:42:24
--> $DIR/repr-transparent.rs:43:24
|
LL | struct GenericAlign<T>(ZstAlign32<T>, u32);
| ^^^^^^^^^^^^^ has alignment of 32, which is larger than 1

error[E0084]: unsupported representation for zero-variant enum
--> $DIR/repr-transparent.rs:44:1
--> $DIR/repr-transparent.rs:45:1
|
LL | #[repr(transparent)]
| ^^^^^^^^^^^^^^^^^^^^
LL | enum Void {}
| --------- zero-variant enum

error[E0731]: transparent enum needs exactly one variant, but has 0
--> $DIR/repr-transparent.rs:45:1
--> $DIR/repr-transparent.rs:46:1
|
LL | enum Void {}
| ^^^^^^^^^ needs exactly one variant, but has 0

error[E0690]: the variant of a transparent enum needs at most one non-zero-sized field, but has 2
--> $DIR/repr-transparent.rs:58:1
error[E0690]: the variant of a transparent enum needs at most one field with non-trivial layout, but has 2
--> $DIR/repr-transparent.rs:59:1
|
LL | enum TooManyFieldsEnum {
| ^^^^^^^^^^^^^^^^^^^^^^ needs at most one non-zero-sized field, but has 2
| ^^^^^^^^^^^^^^^^^^^^^^ needs at most one field with non-trivial layout, but has 2
LL | Foo(u32, String),
| --- ------ this field is non-zero-sized
| |
| this field is non-zero-sized
|
= note: a layout is trivial if, any only if, it is zero-sized with an alignment of 1

error[E0731]: transparent enum needs exactly one variant, but has 2
--> $DIR/repr-transparent.rs:64:1
--> $DIR/repr-transparent.rs:65:1
|
LL | enum MultipleVariants {
| ^^^^^^^^^^^^^^^^^^^^^ needs exactly one variant, but has 2
Expand All @@ -63,26 +69,28 @@ LL | Bar,
| --- too many variants in `MultipleVariants`

error[E0691]: zero-sized field in transparent enum has alignment larger than 1
--> $DIR/repr-transparent.rs:71:14
--> $DIR/repr-transparent.rs:72:14
|
LL | Foo(u32, [u16; 0]),
| ^^^^^^^^ has alignment of 2, which is larger than 1

error[E0691]: zero-sized field in transparent enum has alignment larger than 1
--> $DIR/repr-transparent.rs:76:11
--> $DIR/repr-transparent.rs:77:11
|
LL | Foo { bar: ZstAlign32<T>, baz: u32 }
| ^^^^^^^^^^^^^^^^^^ has alignment of 32, which is larger than 1

error[E0690]: transparent union needs at most one non-zero-sized field, but has 2
--> $DIR/repr-transparent.rs:85:1
error[E0690]: transparent union needs at most one field with non-trivial layout, but has 2
--> $DIR/repr-transparent.rs:86:1
|
LL | union TooManyFields {
| ^^^^^^^^^^^^^^^^^^^ needs at most one non-zero-sized field, but has 2
| ^^^^^^^^^^^^^^^^^^^ needs at most one field with non-trivial layout, but has 2
LL | u: u32,
| ------ this field is non-zero-sized
LL | s: i32
| ------ this field is non-zero-sized
|
= note: a layout is trivial if, any only if, it is zero-sized with an alignment of 1

error: aborting due to 11 previous errors

Expand Down