Skip to content

Commit

Permalink
Add Clang attribute to ensure that fields are initialized explicitly
Browse files Browse the repository at this point in the history
  • Loading branch information
higher-performance committed Aug 30, 2024
1 parent a9ffb71 commit 002027b
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 0 deletions.
8 changes: 8 additions & 0 deletions clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/AST/DeclCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -1823,6 +1823,14 @@ def Leaf : InheritableAttr {
let SimpleHandler = 1;
}

def ExplicitInit : InheritableAttr {
let Spellings = [Clang<"explicit_init", 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>;
Expand Down
22 changes: 22 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,28 @@ is not specified.
}];
}

def ExplicitInitDocs : Documentation {
let Category = DocCatField;
let Content = [{
The ``clang::explicit_init`` attribute indicates that the field must be
initialized explicitly by the caller when the class is constructed.

Example usage:

.. code-block:: c++

struct some_aggregate {
int x;
int y [[clang::explicit_init]];
};

some_aggregate create() {
return {.x = 1}; // error: y is not initialized explicitly
}

}];
}

def NoUniqueAddressDocs : Documentation {
let Category = DocCatField;
let Content = [{
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">;
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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_init : Warning<
"field %0 is left uninitialized, 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<
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -4548,6 +4554,12 @@ 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_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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 22 additions & 0 deletions clang/test/SemaCXX/uninitialized.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1472,3 +1472,25 @@ template<typename T> struct Outer {
};
};
Outer<int>::Inner outerinner;

void aggregate() {
struct B {
[[clang::explicit_init]] int f1;
};

struct S : B { // expected-warning {{uninitialized}}
int f2;
int f3 [[clang::explicit_init]];
};

#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;
}

0 comments on commit 002027b

Please sign in to comment.