Skip to content

Commit 8a05c20

Browse files
authored
Add an off-by-default warning to complain about MSVC bitfield padding (#117428)
This just adds a warning for bitfields placed next to other bitfields where the underlying type has different storage. Under the MS struct bitfield packing ABI such bitfields are not packed.
1 parent 22fac63 commit 8a05c20

File tree

10 files changed

+323
-10
lines changed

10 files changed

+323
-10
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,10 @@ Improvements to Clang's diagnostics
524524
- An error is now emitted when OpenMP ``collapse`` and ``ordered`` clauses have an
525525
argument larger than what can fit within a 64-bit integer.
526526

527+
- A new off-by-default warning ``-Wms-bitfield-padding`` has been added to alert to cases where bit-field
528+
packing may differ under the MS struct ABI (#GH117428).
529+
530+
527531
Improvements to Clang's time-trace
528532
----------------------------------
529533

clang/include/clang/Basic/AttrDocs.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8680,6 +8680,7 @@ its underlying representation to be a WebAssembly ``funcref``.
86808680

86818681
def PreferredTypeDocumentation : Documentation {
86828682
let Category = DocCatField;
8683+
let Label = "langext-preferred_type_documentation";
86838684
let Content = [{
86848685
This attribute allows adjusting the type of a bit-field in debug information.
86858686
This can be helpful when a bit-field is intended to store an enumeration value,

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,52 @@ def Packed : DiagGroup<"packed", [PackedNonPod]>;
690690
def PaddedBitField : DiagGroup<"padded-bitfield">;
691691
def Padded : DiagGroup<"padded", [PaddedBitField]>;
692692
def UnalignedAccess : DiagGroup<"unaligned-access">;
693+
def MSBitfieldCompatibility : DiagGroup<"ms-bitfield-padding"> {
694+
code Documentation = [{
695+
Under the Microsoft ABI, adjacent bit-fields are not packed if the
696+
underlying type has a different storage size. This warning indicates that a
697+
pair of adjacent bit-fields may not pack in the same way due to this behavioural
698+
difference.
699+
700+
This can occur when mixing different types explicitly:
701+
702+
.. code-block:: c++
703+
704+
struct S {
705+
uint16_t field1 : 1;
706+
uint32_t field2 : 1;
707+
};
708+
709+
or more subtly through enums
710+
711+
.. code-block:: c++
712+
713+
enum Enum1 { /* ... */ };
714+
enum class Enum2 : unsigned char { /* ... */ };
715+
struct S {
716+
Enum1 field1 : 1;
717+
Enum2 field2 : 1;
718+
};
719+
720+
In each of these cases under the Microsoft ABI the second bit-field
721+
will not be packed with the preceding bit-field, and instead will be aligned
722+
as if the fields were each separately defined integer fields of their respective
723+
storage size. For binary compatibility this is obviously and observably
724+
incompatible, however where bit-fields are being used solely for memory use
725+
reduction this incomplete packing may silently increase the size of objects vs
726+
what is expected.
727+
728+
This issue can be addressed by ensuring the storage type of each bit-field is
729+
the same, either by explicitly using the same integer type, or in the case of
730+
enum types declaring the enum types with the same storage size. For enum types
731+
where you cannot specify the underlying type, the options are to either switch
732+
to int sized storage for all specifiers or to resort to declaring the
733+
bit-fields with explicit integer storage types and cast in and out of the field.
734+
If such a solution is required the
735+
:ref:`preferred_type <langext-preferred_type_documentation>` attribute can be
736+
used to convey the actual field type to debuggers and other tooling.
737+
}];
738+
}
693739

694740
def PessimizingMove : DiagGroup<"pessimizing-move">;
695741
def ReturnStdMove : DiagGroup<"return-std-move">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6541,6 +6541,13 @@ def note_change_bitfield_sign : Note<
65416541
"consider making the bit-field type %select{unsigned|signed}0">;
65426542
def note_bitfield_preferred_type
65436543
: Note<"preferred type for bit-field %0 specified here">;
6544+
def warn_ms_bitfield_mismatched_storage_packing : Warning<
6545+
"bit-field %0 of type %1 has a different storage size than the "
6546+
"preceding bit-field (%2 vs %3 bytes) and will not be packed under "
6547+
"the Microsoft ABI">,
6548+
InGroup<MSBitfieldCompatibility>, DefaultIgnore;
6549+
def note_ms_bitfield_mismatched_storage_size_previous : Note<
6550+
"preceding bit-field %0 declared here with type %1">;
65446551

65456552
def warn_missing_braces : Warning<
65466553
"suggest braces around initialization of subobject">,

clang/lib/Sema/SemaDecl.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19399,9 +19399,9 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
1939919399

1940019400
// Verify that all the fields are okay.
1940119401
SmallVector<FieldDecl*, 32> RecFields;
19402-
19402+
const FieldDecl *PreviousField = nullptr;
1940319403
for (ArrayRef<Decl *>::iterator i = Fields.begin(), end = Fields.end();
19404-
i != end; ++i) {
19404+
i != end; PreviousField = cast<FieldDecl>(*i), ++i) {
1940519405
FieldDecl *FD = cast<FieldDecl>(*i);
1940619406

1940719407
// Get the type for the field.
@@ -19617,6 +19617,29 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
1961719617

1961819618
if (Record && FD->getType().isVolatileQualified())
1961919619
Record->setHasVolatileMember(true);
19620+
bool ReportMSBitfieldStoragePacking =
19621+
Record && PreviousField &&
19622+
!Diags.isIgnored(diag::warn_ms_bitfield_mismatched_storage_packing,
19623+
Record->getLocation());
19624+
auto IsNonDependentBitField = [](const FieldDecl *FD) {
19625+
return FD->isBitField() && !FD->getType()->isDependentType();
19626+
};
19627+
19628+
if (ReportMSBitfieldStoragePacking && IsNonDependentBitField(FD) &&
19629+
IsNonDependentBitField(PreviousField)) {
19630+
CharUnits FDStorageSize = Context.getTypeSizeInChars(FD->getType());
19631+
CharUnits PreviousFieldStorageSize =
19632+
Context.getTypeSizeInChars(PreviousField->getType());
19633+
if (FDStorageSize != PreviousFieldStorageSize) {
19634+
Diag(FD->getLocation(),
19635+
diag::warn_ms_bitfield_mismatched_storage_packing)
19636+
<< FD << FD->getType() << FDStorageSize.getQuantity()
19637+
<< PreviousFieldStorageSize.getQuantity();
19638+
Diag(PreviousField->getLocation(),
19639+
diag::note_ms_bitfield_mismatched_storage_size_previous)
19640+
<< PreviousField << PreviousField->getType();
19641+
}
19642+
}
1962019643
// Keep track of the number of named members.
1962119644
if (FD->getIdentifier())
1962219645
++NumNamedMembers;

clang/test/Sema/bitfield-layout.c

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=aarch64-linux-gnu
44
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=x86_64-pc-linux-gnu
55
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=x86_64-scei-ps4
6+
// RUN: %clang_cc1 %s -fsyntax-only -verify=checkms -triple=i686-apple-darwin9 -Wms-bitfield-padding
7+
68
// expected-no-diagnostics
79
#include <stddef.h>
810

@@ -24,12 +26,27 @@ CHECK_ALIGN(struct, a, 1)
2426
#endif
2527

2628
// Zero-width bit-fields with packed
27-
struct __attribute__((packed)) a2 { short x : 9; char : 0; int y : 17; };
29+
struct __attribute__((packed)) a2 {
30+
short x : 9; // #a2x
31+
char : 0; // #a2anon
32+
// checkms-warning@-1 {{bit-field '' of type 'char' has a different storage size than the preceding bit-field (1 vs 2 bytes) and will not be packed under the Microsoft ABI}}
33+
// checkms-note@#a2x {{preceding bit-field 'x' declared here with type 'short'}}
34+
int y : 17;
35+
// checkms-warning@-1 {{bit-field 'y' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the Microsoft ABI}}
36+
// checkms-note@#a2anon {{preceding bit-field '' declared here with type 'char'}}
37+
};
38+
2839
CHECK_SIZE(struct, a2, 5)
2940
CHECK_ALIGN(struct, a2, 1)
3041

3142
// Zero-width bit-fields at the end of packed struct
32-
struct __attribute__((packed)) a3 { short x : 9; int : 0; };
43+
struct __attribute__((packed)) a3 {
44+
short x : 9; // #a3x
45+
int : 0;
46+
// checkms-warning@-1 {{bit-field '' of type 'int' has a different storage size than the preceding bit-field (4 vs 2 bytes) and will not be packed under the Microsoft ABI}}
47+
// checkms-note@#a3x {{preceding bit-field 'x' declared here with type 'short'}}
48+
};
49+
3350
#if defined(__arm__) || defined(__aarch64__)
3451
CHECK_SIZE(struct, a3, 4)
3552
CHECK_ALIGN(struct, a3, 4)
@@ -39,7 +56,12 @@ CHECK_ALIGN(struct, a3, 1)
3956
#endif
4057

4158
// For comparison, non-zero-width bit-fields at the end of packed struct
42-
struct __attribute__((packed)) a4 { short x : 9; int : 1; };
59+
struct __attribute__((packed)) a4 {
60+
short x : 9; // #a4x
61+
int : 1;
62+
// checkms-warning@-1 {{bit-field '' of type 'int' has a different storage size than the preceding bit-field (4 vs 2 bytes) and will not be packed under the Microsoft ABI}}
63+
// checkms-note@#a4x {{preceding bit-field 'x' declared here with type 'short'}}
64+
};
4365
CHECK_SIZE(struct, a4, 2)
4466
CHECK_ALIGN(struct, a4, 1)
4567

@@ -165,22 +187,28 @@ CHECK_OFFSET(struct, g4, c, 3);
165187
#endif
166188

167189
struct g5 {
168-
char : 1;
190+
char : 1; // #g5
169191
__attribute__((aligned(1))) int n : 24;
192+
// checkms-warning@-1 {{bit-field 'n' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the Microsoft ABI}}
193+
// checkms-note@#g5 {{preceding bit-field '' declared here with type 'char'}}
170194
};
171195
CHECK_SIZE(struct, g5, 4);
172196
CHECK_ALIGN(struct, g5, 4);
173197

174198
struct __attribute__((packed)) g6 {
175-
char : 1;
199+
char : 1; // #g6
176200
__attribute__((aligned(1))) int n : 24;
201+
// checkms-warning@-1 {{bit-field 'n' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the Microsoft ABI}}
202+
// checkms-note@#g6 {{preceding bit-field '' declared here with type 'char'}}
177203
};
178204
CHECK_SIZE(struct, g6, 4);
179205
CHECK_ALIGN(struct, g6, 1);
180206

181207
struct g7 {
182-
char : 1;
208+
char : 1; // #g7
183209
__attribute__((aligned(1))) int n : 25;
210+
// checkms-warning@-1 {{bit-field 'n' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the Microsoft ABI}}
211+
// checkms-note@#g7 {{preceding bit-field '' declared here with type 'char'}}
184212
};
185213
#if defined(__ORBIS__)
186214
CHECK_SIZE(struct, g7, 4);
@@ -190,8 +218,10 @@ CHECK_SIZE(struct, g7, 8);
190218
CHECK_ALIGN(struct, g7, 4);
191219

192220
struct __attribute__((packed)) g8 {
193-
char : 1;
221+
char : 1; // #g8
194222
__attribute__((aligned(1))) int n : 25;
223+
// checkms-warning@-1 {{bit-field 'n' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the Microsoft ABI}}
224+
// checkms-note@#g8 {{preceding bit-field '' declared here with type 'char'}}
195225
};
196226
#if defined(__ORBIS__)
197227
CHECK_SIZE(struct, g8, 4);

clang/test/Sema/bitfield-layout_1.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=arm-linux-gnueabihf
33
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=aarch64-linux-gnu
44
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=x86_64-pc-linux-gnu
5+
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=i686-apple-darwin9 -Wms-bitfield-padding
56
// expected-no-diagnostics
67

78
#define CHECK_SIZE(name, size) \

clang/test/Sema/mms-bitfields.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
// RUN: %clang_cc1 -mms-bitfields -fsyntax-only -verify -triple x86_64-apple-darwin9 %s
2+
// RUN: %clang_cc1 -mms-bitfields -fsyntax-only -Wms-bitfield-padding -verify=checkms -triple x86_64-apple-darwin9 %s
3+
24
// expected-no-diagnostics
35

46
// The -mms-bitfields commandline parameter should behave the same
57
// as the ms_struct attribute.
68
struct
79
{
8-
int a : 1;
10+
int a : 1; // #a
911
short b : 1;
12+
// checkms-warning@-1 {{bit-field 'b' of type 'short' has a different storage size than the preceding bit-field (2 vs 4 bytes) and will not be packed under the Microsoft ABI}}
13+
// checkms-note@#a {{preceding bit-field 'a' declared here with type 'int'}}
1014
} t;
1115

1216
// MS pads out bitfields between different types.

clang/test/SemaCXX/bitfield.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// RUN: %clang_cc1 %s -verify
2+
// RUN: %clang_cc1 %s -verify -Wms-bitfield-padding
23

34
// expected-no-diagnostics
45

0 commit comments

Comments
 (0)