Skip to content

Commit 7ea9d3d

Browse files
Add Clang attribute to ensure that fields are initialized explicitly
1 parent a643836 commit 7ea9d3d

File tree

11 files changed

+152
-0
lines changed

11 files changed

+152
-0
lines changed

clang/include/clang/AST/CXXRecordDeclDefinitionBits.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ FIELD(HasInitMethod, 1, NO_MERGE)
119119
/// within anonymous unions or structs.
120120
FIELD(HasInClassInitializer, 1, NO_MERGE)
121121

122+
/// Custom attribute that is True if any field is marked as requiring explicit
123+
/// initialization with [[clang::requires_explicit_initialization]] in a type
124+
/// without a user-provided default constructor, or if this is the case for any
125+
/// base classes and/or member variables whose types are aggregates.
126+
///
127+
/// In this case, default-construction is diagnosed, as it would not explicitly
128+
/// initialize the field.
129+
FIELD(HasUninitializedExplicitInitFields, 1, NO_MERGE)
130+
122131
/// True if any field is of reference type, and does not have an
123132
/// in-class initializer.
124133
///

clang/include/clang/AST/DeclCXX.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,11 @@ class CXXRecordDecl : public RecordDecl {
11521152
/// structs).
11531153
bool hasInClassInitializer() const { return data().HasInClassInitializer; }
11541154

1155+
bool hasUninitializedExplicitInitFields() const {
1156+
return !isUnion() && !hasUserProvidedDefaultConstructor() &&
1157+
data().HasUninitializedExplicitInitFields;
1158+
}
1159+
11551160
/// Whether this class or any of its subobjects has any members of
11561161
/// reference type which would make value-initialization ill-formed.
11571162
///

clang/include/clang/Basic/Attr.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,14 @@ def Leaf : InheritableAttr {
18611861
let SimpleHandler = 1;
18621862
}
18631863

1864+
def ExplicitInit : InheritableAttr {
1865+
let Spellings = [Clang<"requires_explicit_initialization", 0>];
1866+
let Subjects = SubjectList<[Field], ErrorDiag>;
1867+
let Documentation = [ExplicitInitDocs];
1868+
let LangOpts = [CPlusPlus];
1869+
let SimpleHandler = 1;
1870+
}
1871+
18641872
def LifetimeBound : DeclOrTypeAttr {
18651873
let Spellings = [Clang<"lifetimebound", 0>];
18661874
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;

clang/include/clang/Basic/AttrDocs.td

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,35 @@ is not specified.
14191419
}];
14201420
}
14211421

1422+
def ExplicitInitDocs : Documentation {
1423+
let Category = DocCatField;
1424+
let Content = [{
1425+
The ``clang::requires_explicit_initialization`` attribute indicates that the
1426+
field of an aggregate must be initialized explicitly by users when the class
1427+
is constructed. Its usage is invalid on non-aggregates.
1428+
1429+
Example usage:
1430+
1431+
.. code-block:: c++
1432+
1433+
struct some_aggregate {
1434+
int x;
1435+
int y [[clang::requires_explicit_initialization]];
1436+
};
1437+
1438+
some_aggregate create() {
1439+
return {.x = 1}; // error: y is not initialized explicitly
1440+
}
1441+
1442+
This attribute is *not* a memory safety feature, and is *not* intended to guard
1443+
against use of uninitialized memory.
1444+
Rather, its intended use is in structs that represent "parameter objects", to
1445+
allow extending them while ensuring that callers do not forget to specify
1446+
values for newly added fields ("parameters").
1447+
1448+
}];
1449+
}
1450+
14221451
def NoUniqueAddressDocs : Documentation {
14231452
let Category = DocCatField;
14241453
let Content = [{

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ def Trigraphs : DiagGroup<"trigraphs">;
787787
def UndefinedReinterpretCast : DiagGroup<"undefined-reinterpret-cast">;
788788
def ReinterpretBaseClass : DiagGroup<"reinterpret-base-class">;
789789
def Unicode : DiagGroup<"unicode">;
790+
def UninitializedExplicitInit : DiagGroup<"uninitialized-explicit-init">;
790791
def UninitializedMaybe : DiagGroup<"conditional-uninitialized">;
791792
def UninitializedSometimes : DiagGroup<"sometimes-uninitialized">;
792793
def UninitializedStaticSelfInit : DiagGroup<"static-self-init">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,6 +2302,10 @@ def err_init_list_bad_dest_type : Error<
23022302
def warn_cxx20_compat_aggregate_init_with_ctors : Warning<
23032303
"aggregate initialization of type %0 with user-declared constructors "
23042304
"is incompatible with C++20">, DefaultIgnore, InGroup<CXX20Compat>;
2305+
def warn_cxx20_compat_requires_explicit_init_non_aggregate : Warning<
2306+
"explicit initialization of field %0 may not be enforced in C++20 as type %1 "
2307+
"will become a non-aggregate due to the presence of user-declared "
2308+
"constructors">, DefaultIgnore, InGroup<CXX20Compat>;
23052309
def warn_cxx17_compat_aggregate_init_paren_list : Warning<
23062310
"aggregate initialization of type %0 from a parenthesized list of values "
23072311
"is a C++20 extension">, DefaultIgnore, InGroup<CXX20>;
@@ -2331,6 +2335,9 @@ def err_init_reference_member_uninitialized : Error<
23312335
"reference member of type %0 uninitialized">;
23322336
def note_uninit_reference_member : Note<
23332337
"uninitialized reference member is here">;
2338+
def warn_field_requires_explicit_init : Warning<
2339+
"field %0 is not explicitly initialized, but was marked as requiring "
2340+
"explicit initialization">, InGroup<UninitializedExplicitInit>;
23342341
def warn_field_is_uninit : Warning<"field %0 is uninitialized when used here">,
23352342
InGroup<Uninitialized>;
23362343
def warn_base_class_is_uninit : Warning<

clang/lib/AST/DeclCXX.cpp

Lines changed: 24 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/DiagnosticSema.h"
3233
#include "clang/Basic/IdentifierTable.h"
3334
#include "clang/Basic/LLVM.h"
3435
#include "clang/Basic/LangOptions.h"
@@ -81,6 +82,7 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
8182
HasPrivateFields(false), HasProtectedFields(false),
8283
HasPublicFields(false), HasMutableFields(false), HasVariantMembers(false),
8384
HasOnlyCMembers(true), HasInitMethod(false), HasInClassInitializer(false),
85+
HasUninitializedExplicitInitFields(false),
8486
HasUninitializedReferenceMember(false), HasUninitializedFields(false),
8587
HasInheritedConstructor(false), HasInheritedDefaultConstructor(false),
8688
HasInheritedAssignment(false),
@@ -1111,6 +1113,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
11111113
} else if (!T.isCXX98PODType(Context))
11121114
data().PlainOldData = false;
11131115

1116+
if (Field->hasAttr<ExplicitInitAttr>() && !Field->hasInClassInitializer()) {
1117+
data().HasUninitializedExplicitInitFields = true;
1118+
}
1119+
11141120
if (T->isReferenceType()) {
11151121
if (!Field->hasInClassInitializer())
11161122
data().HasUninitializedReferenceMember = true;
@@ -1362,6 +1368,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
13621368
if (!FieldRec->hasCopyAssignmentWithConstParam())
13631369
data().ImplicitCopyAssignmentHasConstParam = false;
13641370

1371+
if (FieldRec->hasUninitializedExplicitInitFields() &&
1372+
FieldRec->isAggregate() && !Field->hasInClassInitializer())
1373+
data().HasUninitializedExplicitInitFields = true;
1374+
13651375
if (FieldRec->hasUninitializedReferenceMember() &&
13661376
!Field->hasInClassInitializer())
13671377
data().HasUninitializedReferenceMember = true;
@@ -2148,6 +2158,20 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
21482158
for (conversion_iterator I = conversion_begin(), E = conversion_end();
21492159
I != E; ++I)
21502160
I.setAccess((*I)->getAccess());
2161+
2162+
ASTContext &Context = getASTContext();
2163+
if (!Context.getLangOpts().CPlusPlus20 && isAggregate() &&
2164+
hasUserDeclaredConstructor()) {
2165+
// Diagnose any aggregate behavior changes in C++20
2166+
for (field_iterator I = field_begin(), E = field_end(); I != E; ++I) {
2167+
if (const auto *attr = I->getAttr<ExplicitInitAttr>()) {
2168+
Context.getDiagnostics().Report(
2169+
getLocation(),
2170+
diag::warn_cxx20_compat_requires_explicit_init_non_aggregate)
2171+
<< attr->getRange() << Context.getRecordType(this);
2172+
}
2173+
}
2174+
}
21512175
}
21522176

21532177
bool CXXRecordDecl::mayBeAbstract() const {

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6014,6 +6014,10 @@ static void handleNoMergeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
60146014
D->addAttr(NoMergeAttr::Create(S.Context, AL));
60156015
}
60166016

6017+
static void handleExplicitInitAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
6018+
D->addAttr(ExplicitInitAttr::Create(S.Context, AL));
6019+
}
6020+
60176021
static void handleNoUniqueAddressAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
60186022
D->addAttr(NoUniqueAddressAttr::Create(S.Context, AL));
60196023
}
@@ -6919,6 +6923,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
69196923
case ParsedAttr::AT_NoMerge:
69206924
handleNoMergeAttr(S, D, AL);
69216925
break;
6926+
case ParsedAttr::AT_ExplicitInit:
6927+
handleExplicitInitAttr(S, D, AL);
6928+
break;
69226929
case ParsedAttr::AT_NoUniqueAddress:
69236930
handleNoUniqueAddressAttr(S, D, AL);
69246931
break;

clang/lib/Sema/SemaInit.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,14 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field,
743743
ILE->updateInit(SemaRef.Context, Init, Filler);
744744
return;
745745
}
746+
747+
if (!VerifyOnly && Field->hasAttr<ExplicitInitAttr>()) {
748+
SemaRef.Diag(ILE->getExprLoc(), diag::warn_field_requires_explicit_init)
749+
<< Field;
750+
SemaRef.Diag(Field->getLocation(), diag::note_entity_declared_at)
751+
<< Field;
752+
}
753+
746754
// C++1y [dcl.init.aggr]p7:
747755
// If there are fewer initializer-clauses in the list than there are
748756
// members in the aggregate, then each member not explicitly initialized
@@ -4561,6 +4569,13 @@ static void TryConstructorInitialization(Sema &S,
45614569

45624570
CXXConstructorDecl *CtorDecl = cast<CXXConstructorDecl>(Best->Function);
45634571
if (Result != OR_Deleted) {
4572+
if (!IsListInit && Kind.getKind() == InitializationKind::IK_Default &&
4573+
DestRecordDecl != nullptr && DestRecordDecl->isAggregate() &&
4574+
DestRecordDecl->hasUninitializedExplicitInitFields()) {
4575+
S.Diag(Kind.getLocation(), diag::warn_field_requires_explicit_init)
4576+
<< "in class";
4577+
}
4578+
45644579
// C++11 [dcl.init]p6:
45654580
// If a program calls for the default initialization of an object
45664581
// of a const-qualified type T, T shall be a class type with a

clang/test/Misc/pragma-attribute-supported-attributes-list.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
// CHECK-NEXT: EnumExtensibility (SubjectMatchRule_enum)
7878
// CHECK-NEXT: Error (SubjectMatchRule_function)
7979
// CHECK-NEXT: ExcludeFromExplicitInstantiation (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record)
80+
// CHECK-NEXT: ExplicitInit (SubjectMatchRule_field)
8081
// CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable))
8182
// CHECK-NEXT: FlagEnum (SubjectMatchRule_enum)
8283
// CHECK-NEXT: Flatten (SubjectMatchRule_function)

0 commit comments

Comments
 (0)