[clang] Add Clang attribute to ensure that fields are initialized explicitly (PR #102040)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 30 11:23:51 PDT 2024
https://github.com/higher-performance updated https://github.com/llvm/llvm-project/pull/102040
>From 9833f57fe4b585fa4da55e71a84f8d206c3a9ef3 Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Mon, 5 Aug 2024 15:04:19 -0400
Subject: [PATCH] Add Clang attribute to ensure that fields are initialized
explicitly
---
.../clang/AST/CXXRecordDeclDefinitionBits.def | 8 +++++
clang/include/clang/AST/DeclCXX.h | 5 ++++
clang/include/clang/Basic/Attr.td | 8 +++++
clang/include/clang/Basic/AttrDocs.td | 29 +++++++++++++++++++
clang/include/clang/Basic/DiagnosticGroups.td | 1 +
.../clang/Basic/DiagnosticSemaKinds.td | 3 ++
clang/lib/AST/DeclCXX.cpp | 9 ++++++
clang/lib/Sema/SemaDeclAttr.cpp | 7 +++++
clang/lib/Sema/SemaInit.cpp | 13 +++++++++
...a-attribute-supported-attributes-list.test | 1 +
clang/test/SemaCXX/uninitialized.cpp | 22 ++++++++++++++
11 files changed, 106 insertions(+)
diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index cdf0804680ad0a..48790de67989d2 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -119,6 +119,14 @@ FIELD(HasInitMethod, 1, NO_MERGE)
/// within anonymous unions or structs.
FIELD(HasInClassInitializer, 1, NO_MERGE)
+/// Custom attribute that is True if any field is marked as explicit in a type
+/// without a user-provided default constructor, or if this is the case for any
+/// base classes and/or member variables whose types are aggregates.
+///
+/// In this case, default-construction is diagnosed, as it would not explicitly
+/// initialize the field.
+FIELD(HasUninitializedExplicitInitFields, 1, NO_MERGE)
+
/// True if any field is of reference type, and does not have an
/// in-class initializer.
///
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 0d72cc6a08dcb4..369655afa97351 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1152,6 +1152,11 @@ class CXXRecordDecl : public RecordDecl {
/// structs).
bool hasInClassInitializer() const { return data().HasInClassInitializer; }
+ bool hasUninitializedExplicitInitFields() const {
+ return !isUnion() && !hasUserProvidedDefaultConstructor() &&
+ data().HasUninitializedExplicitInitFields;
+ }
+
/// Whether this class or any of its subobjects has any members of
/// reference type which would make value-initialization ill-formed.
///
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index a83e908899c83b..7421b6e21d701b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1823,6 +1823,14 @@ def Leaf : InheritableAttr {
let SimpleHandler = 1;
}
+def ExplicitInit : InheritableAttr {
+ let Spellings = [Clang<"requires_explicit_initialization", 0>];
+ let Subjects = SubjectList<[Field], ErrorDiag>;
+ let Documentation = [ExplicitInitDocs];
+ let LangOpts = [CPlusPlus];
+ let SimpleHandler = 1;
+}
+
def LifetimeBound : DeclOrTypeAttr {
let Spellings = [Clang<"lifetimebound", 0>];
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index c2b9d7cb93c309..16c35bd0fe330d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1419,6 +1419,35 @@ is not specified.
}];
}
+def ExplicitInitDocs : Documentation {
+ let Category = DocCatField;
+ let Content = [{
+The ``clang::requires_explicit_initialization`` attribute indicates that the
+field of an aggregate must be initialized explicitly by users when the class
+is constructed. Its usage is invalid on non-aggregates.
+
+Example usage:
+
+.. code-block:: c++
+
+ struct some_aggregate {
+ int x;
+ int y [[clang::requires_explicit_initialization]];
+ };
+
+ some_aggregate create() {
+ return {.x = 1}; // error: y is not initialized explicitly
+ }
+
+This attribute is *not* a memory safety feature, and is *not* intended to guard
+against use of uninitialized memory.
+Rather, its intended use is in structs that represent "parameter objects", to
+allow extending them while ensuring that callers do not forget to specify
+values for newly added fields ("parameters").
+
+ }];
+}
+
def NoUniqueAddressDocs : Documentation {
let Category = DocCatField;
let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 28d315f63e5c47..68ee5c9aa8b1be 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -787,6 +787,7 @@ def Trigraphs : DiagGroup<"trigraphs">;
def UndefinedReinterpretCast : DiagGroup<"undefined-reinterpret-cast">;
def ReinterpretBaseClass : DiagGroup<"reinterpret-base-class">;
def Unicode : DiagGroup<"unicode">;
+def UninitializedExplicitInit : DiagGroup<"uninitialized-explicit-init">;
def UninitializedMaybe : DiagGroup<"conditional-uninitialized">;
def UninitializedSometimes : DiagGroup<"sometimes-uninitialized">;
def UninitializedStaticSelfInit : DiagGroup<"static-self-init">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index edf22b909c4d57..56e8dbe064cf71 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2332,6 +2332,9 @@ def err_init_reference_member_uninitialized : Error<
"reference member of type %0 uninitialized">;
def note_uninit_reference_member : Note<
"uninitialized reference member is here">;
+def warn_field_requires_explicit_init : Warning<
+ "field %0 is not explicitly initialized, but was marked as requiring "
+ "explicit initialization">, InGroup<UninitializedExplicitInit>;
def warn_field_is_uninit : Warning<"field %0 is uninitialized when used here">,
InGroup<Uninitialized>;
def warn_base_class_is_uninit : Warning<
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 9a3ede426e9143..9c3a5957fb8b95 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -81,6 +81,7 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
HasPrivateFields(false), HasProtectedFields(false),
HasPublicFields(false), HasMutableFields(false), HasVariantMembers(false),
HasOnlyCMembers(true), HasInitMethod(false), HasInClassInitializer(false),
+ HasUninitializedExplicitInitFields(false),
HasUninitializedReferenceMember(false), HasUninitializedFields(false),
HasInheritedConstructor(false), HasInheritedDefaultConstructor(false),
HasInheritedAssignment(false),
@@ -1108,6 +1109,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
} else if (!T.isCXX98PODType(Context))
data().PlainOldData = false;
+ if (Field->hasAttr<ExplicitInitAttr>() && !Field->hasInClassInitializer()) {
+ data().HasUninitializedExplicitInitFields = true;
+ }
+
if (T->isReferenceType()) {
if (!Field->hasInClassInitializer())
data().HasUninitializedReferenceMember = true;
@@ -1359,6 +1364,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (!FieldRec->hasCopyAssignmentWithConstParam())
data().ImplicitCopyAssignmentHasConstParam = false;
+ if (FieldRec->hasUninitializedExplicitInitFields() &&
+ FieldRec->isAggregate() && !Field->hasInClassInitializer())
+ data().HasUninitializedExplicitInitFields = true;
+
if (FieldRec->hasUninitializedReferenceMember() &&
!Field->hasInClassInitializer())
data().HasUninitializedReferenceMember = true;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 1e074298ac5289..1b1b7a6091a3d5 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5942,6 +5942,10 @@ static void handleNoMergeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(NoMergeAttr::Create(S.Context, AL));
}
+static void handleExplicitInitAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+ D->addAttr(ExplicitInitAttr::Create(S.Context, AL));
+}
+
static void handleNoUniqueAddressAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(NoUniqueAddressAttr::Create(S.Context, AL));
}
@@ -6847,6 +6851,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_NoMerge:
handleNoMergeAttr(S, D, AL);
break;
+ case ParsedAttr::AT_ExplicitInit:
+ handleExplicitInitAttr(S, D, AL);
+ break;
case ParsedAttr::AT_NoUniqueAddress:
handleNoUniqueAddressAttr(S, D, AL);
break;
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 5a19a3505454ca..6ec7159413ab8c 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -743,6 +743,12 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field,
ILE->updateInit(SemaRef.Context, Init, Filler);
return;
}
+
+ if (Field->hasAttr<ExplicitInitAttr>()) {
+ SemaRef.Diag(ILE->getExprLoc(), diag::warn_field_requires_explicit_init)
+ << Field;
+ }
+
// C++1y [dcl.init.aggr]p7:
// If there are fewer initializer-clauses in the list than there are
// members in the aggregate, then each member not explicitly initialized
@@ -4548,6 +4554,13 @@ static void TryConstructorInitialization(Sema &S,
CXXConstructorDecl *CtorDecl = cast<CXXConstructorDecl>(Best->Function);
if (Result != OR_Deleted) {
+ if (!IsListInit && Kind.getKind() == InitializationKind::IK_Default &&
+ DestRecordDecl != nullptr && DestRecordDecl->isAggregate() &&
+ DestRecordDecl->hasUninitializedExplicitInitFields()) {
+ S.Diag(Kind.getLocation(), diag::warn_field_requires_explicit_init)
+ << "in class";
+ }
+
// C++11 [dcl.init]p6:
// If a program calls for the default initialization of an object
// of a const-qualified type T, T shall be a class type with a
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 5ebbd29b316bfa..a403ebe5b88d44 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -76,6 +76,7 @@
// CHECK-NEXT: EnumExtensibility (SubjectMatchRule_enum)
// CHECK-NEXT: Error (SubjectMatchRule_function)
// CHECK-NEXT: ExcludeFromExplicitInstantiation (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record)
+// CHECK-NEXT: ExplicitInit (SubjectMatchRule_field)
// 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))
// CHECK-NEXT: FlagEnum (SubjectMatchRule_enum)
// CHECK-NEXT: Flatten (SubjectMatchRule_function)
diff --git a/clang/test/SemaCXX/uninitialized.cpp b/clang/test/SemaCXX/uninitialized.cpp
index 8a640c9691b321..17ebd68ae901be 100644
--- a/clang/test/SemaCXX/uninitialized.cpp
+++ b/clang/test/SemaCXX/uninitialized.cpp
@@ -1472,3 +1472,25 @@ template<typename T> struct Outer {
};
};
Outer<int>::Inner outerinner;
+
+void aggregate() {
+ struct B {
+ [[clang::requires_explicit_initialization]] int f1;
+ };
+
+ struct S : B { // expected-warning {{uninitialized}}
+ int f2;
+ int f3 [[clang::requires_explicit_initialization]];
+ };
+
+#if __cplusplus >= 202002L
+ S a({}, 0); // expected-warning {{'f1' is left uninitialized}} expected-warning {{'f3' is left uninitialized}}
+#endif
+ S b{.f3 = 1}; // expected-warning {{'f1' is left uninitialized}}
+ S c{.f2 = 5}; // expected-warning {{'f1' is left uninitialized}} expected-warning {{'f3' is left uninitialized}} expected-warning {{'f3' is left uninitialized}}
+ c = {{}, 0}; // expected-warning {{'f1' is left uninitialized}} expected-warning {{'f3' is left uninitialized}}
+ S d; // expected-warning {{uninitialized}} expected-note {{constructor}}
+ (void)b;
+ (void)c;
+ (void)d;
+}
More information about the cfe-commits
mailing list