Skip to content

Commit 1594413

Browse files
Add Clang attribute to ensure that fields are initialized explicitly (#102040)
This is a new Clang-specific attribute to ensure that field initializations are performed explicitly. For example, if we have ``` struct B { [[clang::explicit]] int f1; }; ``` then the diagnostic would trigger if we do `B b{};`: ``` field 'f1' is left uninitialized, but was marked as requiring initialization ``` This prevents callers from accidentally forgetting to initialize fields, particularly when new fields are added to the class.
1 parent 576b538 commit 1594413

16 files changed

+404
-7
lines changed

clang/include/clang/AST/Decl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4285,6 +4285,14 @@ class RecordDecl : public TagDecl {
42854285
RecordDeclBits.HasNonTrivialToPrimitiveCopyCUnion = V;
42864286
}
42874287

4288+
bool hasUninitializedExplicitInitFields() const {
4289+
return RecordDeclBits.HasUninitializedExplicitInitFields;
4290+
}
4291+
4292+
void setHasUninitializedExplicitInitFields(bool V) {
4293+
RecordDeclBits.HasUninitializedExplicitInitFields = V;
4294+
}
4295+
42884296
/// Determine whether this class can be passed in registers. In C++ mode,
42894297
/// it must have at least one trivial, non-deleted copy or move constructor.
42904298
/// FIXME: This should be set as part of completeDefinition.

clang/include/clang/AST/DeclBase.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,9 @@ class DeclContext {
14461446
/// hasLazyLocalLexicalLookups, hasLazyExternalLexicalLookups
14471447
friend class ASTWriter;
14481448

1449+
protected:
1450+
enum { NumOdrHashBits = 25 };
1451+
14491452
// We use uint64_t in the bit-fields below since some bit-fields
14501453
// cross the unsigned boundary and this breaks the packing.
14511454

@@ -1667,6 +1670,14 @@ class DeclContext {
16671670
LLVM_PREFERRED_TYPE(bool)
16681671
uint64_t HasNonTrivialToPrimitiveCopyCUnion : 1;
16691672

1673+
/// True if any field is marked as requiring explicit initialization with
1674+
/// [[clang::requires_explicit_initialization]].
1675+
/// In C++, this is also set for types without a user-provided default
1676+
/// constructor, and is propagated from any base classes and/or member
1677+
/// variables whose types are aggregates.
1678+
LLVM_PREFERRED_TYPE(bool)
1679+
uint64_t HasUninitializedExplicitInitFields : 1;
1680+
16701681
/// Indicates whether this struct is destroyed in the callee.
16711682
LLVM_PREFERRED_TYPE(bool)
16721683
uint64_t ParamDestroyedInCallee : 1;
@@ -1681,7 +1692,7 @@ class DeclContext {
16811692

16821693
/// True if a valid hash is stored in ODRHash. This should shave off some
16831694
/// extra storage and prevent CXXRecordDecl to store unused bits.
1684-
uint64_t ODRHash : 26;
1695+
uint64_t ODRHash : NumOdrHashBits;
16851696
};
16861697

16871698
/// Number of inherited and non-inherited bits in RecordDeclBitfields.

clang/include/clang/Basic/Attr.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1902,6 +1902,13 @@ def Leaf : InheritableAttr {
19021902
let SimpleHandler = 1;
19031903
}
19041904

1905+
def ExplicitInit : InheritableAttr {
1906+
let Spellings = [Clang<"requires_explicit_initialization">];
1907+
let Subjects = SubjectList<[Field], ErrorDiag>;
1908+
let Documentation = [ExplicitInitDocs];
1909+
let SimpleHandler = 1;
1910+
}
1911+
19051912
def LifetimeBound : DeclOrTypeAttr {
19061913
let Spellings = [Clang<"lifetimebound", 0>];
19071914
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;

clang/include/clang/Basic/AttrDocs.td

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,55 @@ is not specified.
16841684
}];
16851685
}
16861686

1687+
def ExplicitInitDocs : Documentation {
1688+
let Category = DocCatField;
1689+
let Content = [{
1690+
The ``clang::requires_explicit_initialization`` attribute indicates that a
1691+
field of an aggregate must be initialized explicitly by the user when an object
1692+
of the aggregate type is constructed. The attribute supports both C and C++,
1693+
but its usage is invalid on non-aggregates.
1694+
1695+
Note that this attribute is *not* a memory safety feature, and is *not* intended
1696+
to guard against use of uninitialized memory.
1697+
1698+
Rather, it is intended for use in "parameter-objects", used to simulate,
1699+
for example, the passing of named parameters.
1700+
The attribute generates a warning when explicit initializers for such
1701+
variables are not provided (this occurs regardless of whether any in-class field
1702+
initializers exist):
1703+
1704+
.. code-block:: c++
1705+
1706+
struct Buffer {
1707+
void *address [[clang::requires_explicit_initialization]];
1708+
size_t length [[clang::requires_explicit_initialization]] = 0;
1709+
};
1710+
1711+
struct ArrayIOParams {
1712+
size_t count [[clang::requires_explicit_initialization]];
1713+
size_t element_size [[clang::requires_explicit_initialization]];
1714+
int flags = 0;
1715+
};
1716+
1717+
size_t ReadArray(FILE *file, struct Buffer buffer,
1718+
struct ArrayIOParams params);
1719+
1720+
int main() {
1721+
unsigned int buf[512];
1722+
ReadArray(stdin, {
1723+
buf
1724+
// warning: field 'length' is not explicitly initialized
1725+
}, {
1726+
.count = sizeof(buf) / sizeof(*buf),
1727+
// warning: field 'element_size' is not explicitly initialized
1728+
// (Note that a missing initializer for 'flags' is not diagnosed, because
1729+
// the field is not marked as requiring explicit initialization.)
1730+
});
1731+
}
1732+
1733+
}];
1734+
}
1735+
16871736
def NoUniqueAddressDocs : Documentation {
16881737
let Category = DocCatField;
16891738
let Content = [{

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,16 @@ def note_constexpr_assumption_failed : Note<
396396
def err_experimental_clang_interp_failed : Error<
397397
"the experimental clang interpreter failed to evaluate an expression">;
398398

399+
def warn_attribute_needs_aggregate : Warning<
400+
"%0 attribute is ignored in non-aggregate type %1">,
401+
InGroup<IgnoredAttributes>;
402+
403+
def warn_cxx20_compat_requires_explicit_init_non_aggregate : Warning<
404+
"explicit initialization of field %1 will not be enforced in C++20 and later "
405+
"because %2 has a user-declared constructor, making the type no longer an "
406+
"aggregate">,
407+
DefaultIgnore, InGroup<CXX20Compat>;
408+
399409
def warn_integer_constant_overflow : Warning<
400410
"overflow in expression; result is %0 with type %1">,
401411
InGroup<DiagGroup<"integer-overflow">>;

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,7 @@ def Trigraphs : DiagGroup<"trigraphs">;
796796
def UndefinedReinterpretCast : DiagGroup<"undefined-reinterpret-cast">;
797797
def ReinterpretBaseClass : DiagGroup<"reinterpret-base-class">;
798798
def Unicode : DiagGroup<"unicode">;
799+
def UninitializedExplicitInit : DiagGroup<"uninitialized-explicit-init">;
799800
def UninitializedMaybe : DiagGroup<"conditional-uninitialized">;
800801
def UninitializedSometimes : DiagGroup<"sometimes-uninitialized">;
801802
def UninitializedStaticSelfInit : DiagGroup<"static-self-init">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,6 +2347,9 @@ def err_init_reference_member_uninitialized : Error<
23472347
"reference member of type %0 uninitialized">;
23482348
def note_uninit_reference_member : Note<
23492349
"uninitialized reference member is here">;
2350+
def warn_field_requires_explicit_init : Warning<
2351+
"field %select{%1|in %1}0 requires explicit initialization but is not "
2352+
"explicitly initialized">, InGroup<UninitializedExplicitInit>;
23502353
def warn_field_is_uninit : Warning<"field %0 is uninitialized when used here">,
23512354
InGroup<Uninitialized>;
23522355
def warn_base_class_is_uninit : Warning<

clang/lib/AST/Decl.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5031,6 +5031,7 @@ RecordDecl::RecordDecl(Kind DK, TagKind TK, const ASTContext &C,
50315031
setHasNonTrivialToPrimitiveDefaultInitializeCUnion(false);
50325032
setHasNonTrivialToPrimitiveDestructCUnion(false);
50335033
setHasNonTrivialToPrimitiveCopyCUnion(false);
5034+
setHasUninitializedExplicitInitFields(false);
50345035
setParamDestroyedInCallee(false);
50355036
setArgPassingRestrictions(RecordArgPassingKind::CanPassInRegs);
50365037
setIsRandomized(false);
@@ -5231,9 +5232,10 @@ unsigned RecordDecl::getODRHash() {
52315232
// Only calculate hash on first call of getODRHash per record.
52325233
ODRHash Hash;
52335234
Hash.AddRecordDecl(this);
5234-
// For RecordDecl the ODRHash is stored in the remaining 26
5235-
// bit of RecordDeclBits, adjust the hash to accomodate.
5236-
setODRHash(Hash.CalculateHash() >> 6);
5235+
// For RecordDecl the ODRHash is stored in the remaining
5236+
// bits of RecordDeclBits, adjust the hash to accommodate.
5237+
static_assert(sizeof(Hash.CalculateHash()) * CHAR_BIT == 32);
5238+
setODRHash(Hash.CalculateHash() >> (32 - NumOdrHashBits));
52375239
return RecordDeclBits.ODRHash;
52385240
}
52395241

clang/lib/AST/DeclCXX.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "clang/AST/TypeLoc.h"
3030
#include "clang/AST/UnresolvedSet.h"
3131
#include "clang/Basic/Diagnostic.h"
32+
#include "clang/Basic/DiagnosticAST.h"
3233
#include "clang/Basic/IdentifierTable.h"
3334
#include "clang/Basic/LLVM.h"
3435
#include "clang/Basic/LangOptions.h"
@@ -457,6 +458,10 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
457458
if (BaseClassDecl->hasMutableFields())
458459
data().HasMutableFields = true;
459460

461+
if (BaseClassDecl->hasUninitializedExplicitInitFields() &&
462+
BaseClassDecl->isAggregate())
463+
setHasUninitializedExplicitInitFields(true);
464+
460465
if (BaseClassDecl->hasUninitializedReferenceMember())
461466
data().HasUninitializedReferenceMember = true;
462467

@@ -1113,6 +1118,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
11131118
} else if (!T.isCXX98PODType(Context))
11141119
data().PlainOldData = false;
11151120

1121+
if (Field->hasAttr<ExplicitInitAttr>())
1122+
setHasUninitializedExplicitInitFields(true);
1123+
11161124
if (T->isReferenceType()) {
11171125
if (!Field->hasInClassInitializer())
11181126
data().HasUninitializedReferenceMember = true;
@@ -1372,6 +1380,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
13721380
if (!FieldRec->hasCopyAssignmentWithConstParam())
13731381
data().ImplicitCopyAssignmentHasConstParam = false;
13741382

1383+
if (FieldRec->hasUninitializedExplicitInitFields() &&
1384+
FieldRec->isAggregate())
1385+
setHasUninitializedExplicitInitFields(true);
1386+
13751387
if (FieldRec->hasUninitializedReferenceMember() &&
13761388
!Field->hasInClassInitializer())
13771389
data().HasUninitializedReferenceMember = true;
@@ -2188,6 +2200,33 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
21882200
for (conversion_iterator I = conversion_begin(), E = conversion_end();
21892201
I != E; ++I)
21902202
I.setAccess((*I)->getAccess());
2203+
2204+
ASTContext &Context = getASTContext();
2205+
2206+
if (isAggregate() && hasUserDeclaredConstructor() &&
2207+
!Context.getLangOpts().CPlusPlus20) {
2208+
// Diagnose any aggregate behavior changes in C++20
2209+
for (const FieldDecl *FD : fields()) {
2210+
if (const auto *AT = FD->getAttr<ExplicitInitAttr>())
2211+
Context.getDiagnostics().Report(
2212+
AT->getLocation(),
2213+
diag::warn_cxx20_compat_requires_explicit_init_non_aggregate)
2214+
<< AT << FD << Context.getRecordType(this);
2215+
}
2216+
}
2217+
2218+
if (!isAggregate() && hasUninitializedExplicitInitFields()) {
2219+
// Diagnose any fields that required explicit initialization in a
2220+
// non-aggregate type. (Note that the fields may not be directly in this
2221+
// type, but in a subobject. In such cases we don't emit diagnoses here.)
2222+
for (const FieldDecl *FD : fields()) {
2223+
if (const auto *AT = FD->getAttr<ExplicitInitAttr>())
2224+
Context.getDiagnostics().Report(AT->getLocation(),
2225+
diag::warn_attribute_needs_aggregate)
2226+
<< AT << Context.getRecordType(this);
2227+
}
2228+
setHasUninitializedExplicitInitFields(false);
2229+
}
21912230
}
21922231

21932232
bool CXXRecordDecl::mayBeAbstract() const {

clang/lib/Sema/SemaDecl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19232,6 +19232,8 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
1923219232
if (FT.hasNonTrivialToPrimitiveCopyCUnion() || Record->isUnion())
1923319233
Record->setHasNonTrivialToPrimitiveCopyCUnion(true);
1923419234
}
19235+
if (FD->hasAttr<ExplicitInitAttr>())
19236+
Record->setHasUninitializedExplicitInitFields(true);
1923519237
if (FT.isDestructedType()) {
1923619238
Record->setNonTrivialToPrimitiveDestroy(true);
1923719239
Record->setParamDestroyedInCallee(true);

0 commit comments

Comments
 (0)