Skip to content

Commit 300b821

Browse files
committed
Auto merge of #7838 - nhamovitz:trailing_zs_arr_wo_repr, r=Manishearth
Warn on structs with a trailing zero-sized array but no `repr` attribute Closes #2868 changelog: Implement ``[`trailing_empty_array`]``, which warns if a struct is defined where the last field is a zero-sized array but there are no `repr` attributes. Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters. Either way, a `repr` attribute is needed.
2 parents f11905a + 0f9f591 commit 300b821

7 files changed

+389
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3022,6 +3022,7 @@ Released 2018-09-13
30223022
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
30233023
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
30243024
[`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg
3025+
[`trailing_empty_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#trailing_empty_array
30253026
[`trait_duplication_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#trait_duplication_in_bounds
30263027
[`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str
30273028
[`transmute_float_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_float_to_int

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ store.register_lints(&[
444444
temporary_assignment::TEMPORARY_ASSIGNMENT,
445445
to_digit_is_some::TO_DIGIT_IS_SOME,
446446
to_string_in_display::TO_STRING_IN_DISPLAY,
447+
trailing_empty_array::TRAILING_EMPTY_ARRAY,
447448
trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
448449
trait_bounds::TYPE_REPETITION_IN_BOUNDS,
449450
transmute::CROSSPOINTER_TRANSMUTE,

clippy_lints/src/lib.register_nursery.rs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
2525
LintId::of(regex::TRIVIAL_REGEX),
2626
LintId::of(strings::STRING_LIT_AS_BYTES),
2727
LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
28+
LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
2829
LintId::of(transmute::USELESS_TRANSMUTE),
2930
LintId::of(use_self::USE_SELF),
3031
])

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ mod tabs_in_doc_comments;
355355
mod temporary_assignment;
356356
mod to_digit_is_some;
357357
mod to_string_in_display;
358+
mod trailing_empty_array;
358359
mod trait_bounds;
359360
mod transmute;
360361
mod transmuting_null;
@@ -777,6 +778,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
777778
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default()));
778779
store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch));
779780
store.register_late_pass(move || Box::new(format_args::FormatArgs));
781+
store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray));
782+
780783
}
781784

782785
#[rustfmt::skip]
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use clippy_utils::diagnostics::span_lint_and_help;
2+
use rustc_hir::{HirId, Item, ItemKind};
3+
use rustc_lint::{LateContext, LateLintPass};
4+
use rustc_middle::ty::Const;
5+
use rustc_session::{declare_lint_pass, declare_tool_lint};
6+
use rustc_span::sym;
7+
8+
declare_clippy_lint! {
9+
/// ### What it does
10+
/// Displays a warning when a struct with a trailing zero-sized array is declared without a `repr` attribute.
11+
///
12+
/// ### Why is this bad?
13+
/// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjuction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed.
14+
///
15+
/// ### Example
16+
/// ```rust
17+
/// struct RarelyUseful {
18+
/// some_field: u32,
19+
/// last: [u32; 0],
20+
/// }
21+
/// ```
22+
///
23+
/// Use instead:
24+
/// ```rust
25+
/// #[repr(C)]
26+
/// struct MoreOftenUseful {
27+
/// some_field: usize,
28+
/// last: [u32; 0],
29+
/// }
30+
/// ```
31+
pub TRAILING_EMPTY_ARRAY,
32+
nursery,
33+
"struct with a trailing zero-sized array but without `#[repr(C)]` or another `repr` attribute"
34+
}
35+
declare_lint_pass!(TrailingEmptyArray => [TRAILING_EMPTY_ARRAY]);
36+
37+
impl<'tcx> LateLintPass<'tcx> for TrailingEmptyArray {
38+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
39+
if is_struct_with_trailing_zero_sized_array(cx, item) && !has_repr_attr(cx, item.hir_id()) {
40+
span_lint_and_help(
41+
cx,
42+
TRAILING_EMPTY_ARRAY,
43+
item.span,
44+
"trailing zero-sized array in a struct which is not marked with a `repr` attribute",
45+
None,
46+
&format!(
47+
"consider annotating `{}` with `#[repr(C)]` or another `repr` attribute",
48+
cx.tcx.def_path_str(item.def_id.to_def_id())
49+
),
50+
);
51+
}
52+
}
53+
}
54+
55+
fn is_struct_with_trailing_zero_sized_array(cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) -> bool {
56+
if_chain! {
57+
// First check if last field is an array
58+
if let ItemKind::Struct(data, _) = &item.kind;
59+
if let Some(last_field) = data.fields().last();
60+
if let rustc_hir::TyKind::Array(_, length) = last_field.ty.kind;
61+
62+
// Then check if that that array zero-sized
63+
let length_ldid = cx.tcx.hir().local_def_id(length.hir_id);
64+
let length = Const::from_anon_const(cx.tcx, length_ldid);
65+
let length = length.try_eval_usize(cx.tcx, cx.param_env);
66+
if let Some(length) = length;
67+
then {
68+
length == 0
69+
} else {
70+
false
71+
}
72+
}
73+
}
74+
75+
fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
76+
cx.tcx.hir().attrs(hir_id).iter().any(|attr| attr.has_name(sym::repr))
77+
}

tests/ui/trailing_empty_array.rs

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#![warn(clippy::trailing_empty_array)]
2+
#![feature(const_generics_defaults)]
3+
4+
// Do lint:
5+
6+
struct RarelyUseful {
7+
field: i32,
8+
last: [usize; 0],
9+
}
10+
11+
struct OnlyField {
12+
first_and_last: [usize; 0],
13+
}
14+
15+
struct GenericArrayType<T> {
16+
field: i32,
17+
last: [T; 0],
18+
}
19+
20+
#[must_use]
21+
struct OnlyAnotherAttribute {
22+
field: i32,
23+
last: [usize; 0],
24+
}
25+
26+
#[derive(Debug)]
27+
struct OnlyADeriveAttribute {
28+
field: i32,
29+
last: [usize; 0],
30+
}
31+
32+
const ZERO: usize = 0;
33+
struct ZeroSizedWithConst {
34+
field: i32,
35+
last: [usize; ZERO],
36+
}
37+
38+
#[allow(clippy::eq_op)]
39+
const fn compute_zero() -> usize {
40+
(4 + 6) - (2 * 5)
41+
}
42+
struct ZeroSizedWithConstFunction {
43+
field: i32,
44+
last: [usize; compute_zero()],
45+
}
46+
47+
const fn compute_zero_from_arg(x: usize) -> usize {
48+
x - 1
49+
}
50+
struct ZeroSizedWithConstFunction2 {
51+
field: i32,
52+
last: [usize; compute_zero_from_arg(1)],
53+
}
54+
55+
struct ZeroSizedArrayWrapper([usize; 0]);
56+
57+
struct TupleStruct(i32, [usize; 0]);
58+
59+
struct LotsOfFields {
60+
f1: u32,
61+
f2: u32,
62+
f3: u32,
63+
f4: u32,
64+
f5: u32,
65+
f6: u32,
66+
f7: u32,
67+
f8: u32,
68+
f9: u32,
69+
f10: u32,
70+
f11: u32,
71+
f12: u32,
72+
f13: u32,
73+
f14: u32,
74+
f15: u32,
75+
f16: u32,
76+
last: [usize; 0],
77+
}
78+
79+
// Don't lint
80+
81+
#[repr(C)]
82+
struct GoodReason {
83+
field: i32,
84+
last: [usize; 0],
85+
}
86+
87+
#[repr(C)]
88+
struct OnlyFieldWithReprC {
89+
first_and_last: [usize; 0],
90+
}
91+
92+
struct NonZeroSizedArray {
93+
field: i32,
94+
last: [usize; 1],
95+
}
96+
97+
struct NotLastField {
98+
f1: u32,
99+
zero_sized: [usize; 0],
100+
last: i32,
101+
}
102+
103+
const ONE: usize = 1;
104+
struct NonZeroSizedWithConst {
105+
field: i32,
106+
last: [usize; ONE],
107+
}
108+
109+
#[derive(Debug)]
110+
#[repr(C)]
111+
struct AlsoADeriveAttribute {
112+
field: i32,
113+
last: [usize; 0],
114+
}
115+
116+
#[must_use]
117+
#[repr(C)]
118+
struct AlsoAnotherAttribute {
119+
field: i32,
120+
last: [usize; 0],
121+
}
122+
123+
#[repr(packed)]
124+
struct ReprPacked {
125+
field: i32,
126+
last: [usize; 0],
127+
}
128+
129+
#[repr(C, packed)]
130+
struct ReprCPacked {
131+
field: i32,
132+
last: [usize; 0],
133+
}
134+
135+
#[repr(align(64))]
136+
struct ReprAlign {
137+
field: i32,
138+
last: [usize; 0],
139+
}
140+
#[repr(C, align(64))]
141+
struct ReprCAlign {
142+
field: i32,
143+
last: [usize; 0],
144+
}
145+
146+
// NOTE: because of https://doc.rust-lang.org/stable/reference/type-layout.html#primitive-representation-of-enums-with-fields and I'm not sure when in the compilation pipeline that would happen
147+
#[repr(C)]
148+
enum DontLintAnonymousStructsFromDesuraging {
149+
A(u32),
150+
B(f32, [u64; 0]),
151+
C { x: u32, y: [u64; 0] },
152+
}
153+
154+
#[repr(C)]
155+
struct TupleStructReprC(i32, [usize; 0]);
156+
157+
type NamedTuple = (i32, [usize; 0]);
158+
159+
#[rustfmt::skip] // [rustfmt#4995](https://github.com/rust-lang/rustfmt/issues/4995)
160+
struct ConstParamZeroDefault<const N: usize = 0> {
161+
field: i32,
162+
last: [usize; N],
163+
}
164+
165+
struct ConstParamNoDefault<const N: usize> {
166+
field: i32,
167+
last: [usize; N],
168+
}
169+
170+
#[rustfmt::skip]
171+
struct ConstParamNonZeroDefault<const N: usize = 1> {
172+
field: i32,
173+
last: [usize; N],
174+
}
175+
176+
struct TwoGenericParams<T, const N: usize> {
177+
field: i32,
178+
last: [T; N],
179+
}
180+
181+
type A = ConstParamZeroDefault;
182+
type B = ConstParamZeroDefault<0>;
183+
type C = ConstParamNoDefault<0>;
184+
type D = ConstParamNonZeroDefault<0>;
185+
186+
fn main() {}

0 commit comments

Comments
 (0)