diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1944,6 +1944,18 @@ let LangOpts = [CPlusPlus]; } +def RequiresDesignator : InheritableAttr { + let Spellings = [Clang<"requires_designator">, GCC<"designated_init">]; + let Subjects = SubjectList<[Record]>; + let Documentation = [RequiresDesignatorDocs]; +} + +def RequiresInit : InheritableAttr { + let Spellings = [Clang<"requires_init">]; + let Subjects = SubjectList<[Field]>; + let Documentation = [RequiresInitDocs]; +} + def WorkGroupSizeHint : InheritableAttr { // Does not have a [[]] spelling because it is an OpenCL-related attribute. let Spellings = [GNU<"work_group_size_hint">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1445,6 +1445,77 @@ }]; } +def RequiresDesignatorDocs : Documentation { + let Category = DocCatType; + let Content = [{ +This attribute can be applied to a struct definition to require that any time a +variable of that structure's type is declared, the declaration uses designated +initializer syntax ([dcl.init.aggr]p3.1). +For a struct ``Foo`` with one integer field ``x``, the following declarations +are valid and invalid when this attribute is applied to the struct's definition. + +.. code-block:: c++ + + Foo foo {.x = 1}; // valid + Foo foo {}; // valid + Foo foo {1}; // invalid + Foo foo; // invalid + +For a struct ``Foo`` with three integer fields ``x``, ``y``, and ``z``, the +following declarations are valid and invalid when this attribute is applied to +the struct's definition. + +.. code-block:: c++ + + Foo foo {.x = 1, .y = 2, .z = 3}; // valid + Foo foo {.z = 3, .x = 1, .y = 2}; // valid + Foo foo {.x = 1, .z = 3}; // valid + Foo foo {}; // valid + Foo foo {1, 2, 3}; // invalid + Foo foo; // invalid + +This attribute can similarly be applied to union types, requiring that all +declarations of that union type use designated initializer syntax to initialize +the field. If the struct or class to which this attribute is applied has any +non-public fields, this attribute will be ignored. + +This attribute is similar to GCC's ``designated_init`` attribute in that it +requires designated initializers be used rather than positional initializers. +This attribute additionally has a stronger restriction that declarations must be +brace-initialized (which is why ``Foo foo;`` is considered invalid above. The +final way this attribute differs from GCC's ``designated_init`` attribute is +that it can be applied to union and class types, as well as struct types. + }]; +} + +def RequiresInitDocs : Documentation { + let Category = DocCatType; + let Content = [{ +This attribute can be applied to a field definition within a struct or a class +to require that any time a variable of the struct's type is declared, that field +must be initialized using designated initializer syntax ([dcl.init.aggr]p3.1). +A field marked with this attribute may not be omitted or default-constructed. +For a struct ``Foo`` with a ``designated_init_required`` integer field ``x``, +the following declarations are valid and invalid. + +.. code-block:: c++ + + struct Foo { + [[clang::requires_init]] int x; + }; + + Foo foo; // invalid + Foo foo {}; // invalid + Foo foo {1}; // invalid + Foo foo {.x = 1}; // valid + +This attribute cannot be applied to fields in a union. If it is, a warning will +be emitted and the attribute will be ignored. This attribute is also ignored +if any of the fields in the struct/class are non-public, regardless of whether +the non-public field is marked as ``requires_init``. + }]; +} + def WarnMaybeUnusedDocs : Documentation { let Category = DocCatVariable; let Heading = "maybe_unused, unused"; @@ -4194,4 +4265,4 @@ not initialized on device side. It has internal linkage and is initialized by the initializer on host side. }]; -} \ No newline at end of file +} diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1065,3 +1065,6 @@ def CTADMaybeUnsupported : DiagGroup<"ctad-maybe-unsupported">; def FortifySource : DiagGroup<"fortify-source">; + +// Warnings for requires_designator and requires_init attributes +def DesignatedInit : DiagGroup<"designated-init">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3530,6 +3530,17 @@ "'objc_designated_initializer' attribute only applies to init methods " "of interface or class extension declarations">; +def warn_requires_designator_failed : Warning< + "variable declaration does not use designated initializer syntax">, + InGroup; +def note_declared_requires_designator_here : Note< + "required by 'requires_designator' attribute here">; +def warn_requires_init_failed : Warning< + "initializer for variable %0 must explicitly initialize field %1">, + InGroup; +def note_declared_requires_init_here : Note< + "enforced by 'requires_init' attribute here">; + // objc_bridge attribute diagnostics. def err_objc_attr_not_id : Error< "parameter of %0 attribute must be a single name of an Objective-C %select{class|protocol}1">; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -11211,6 +11211,79 @@ Init = Result.get(); } + if (const auto *TD = VDecl->getType().getTypePtr()->getAsTagDecl()) { + // If the type of the declaration is a struct/class and that type has the + // require_designated_init attribute, check that the initializer has + // the proper designated initializer syntax. + if (const auto *RAttr = TD->getAttr()) { + if (const auto *ILE = dyn_cast(Init)) { + for (const auto *I : ILE->inits()) { + if (isa(I)) + continue; + SourceRange SR(VDecl->getSourceRange().getBegin(), + Init->getSourceRange().getEnd()); + Diag(I->getExprLoc(), diag::warn_requires_designator_failed) + << SR; + Diag(RAttr->getLocation(), + diag::note_declared_requires_designator_here) + << RAttr->getRange(); + VDecl->setInvalidDecl(); + return; + } + } + } + + // If the type of the declaration is a struct/class, we must check whether + // any of the fields have the required attribute. If any of them do, we must + // confirm that each of those fields are initialized with designated + // initializer syntax. + if (const auto *RD = dyn_cast(TD)) { + // Iterate through all the fields of the record and add all of the + // required fields to a set. The field will be removed later if it is + // properly initialized. + std::set RequiredFields; + for (const auto *FD : RD->fields()) { + if (FD->hasAttr()) { + RequiredFields.insert(FD->getIdentifier()); + } + } + // Iterate through all the initializers and remove a field from the set if + // it is initialized correctly using designated initializer syntax. + if (RequiredFields.size() > 0) { + if (const auto *ILE = dyn_cast(Init)) { + for (const auto *I : ILE->inits()) { + if (const auto *DIE = dyn_cast(I)) { + const auto *D = DIE->getDesignator(0); + if (D->isFieldDesignator()) { + IdentifierInfo *Name = D->getFieldName(); + if (RequiredFields.count(Name) != 0) + RequiredFields.erase(Name); + } + } + } + } + // Iterate through all the remaining fields and emit a diagnostic for + // each field. + for (const auto FD : RD->fields()) { + if (RequiredFields.count(FD->getIdentifier()) != 0) { + SourceRange SR(VDecl->getSourceRange().getBegin(), + Init->getSourceRange().getEnd()); + Diag(Init->getExprLoc(), diag::warn_requires_init_failed) + << SR << VDecl->getName() << FD->getName(); + const auto *A = FD->getAttr(); + Diag(A->getLocation(), diag::note_declared_requires_init_here) + << A->getRange(); + VDecl->setInvalidDecl(); + RequiredFields.erase(FD->getIdentifier()); + } + } + // Return here so all attribute violation errors get emitted. + if (VDecl->isInvalidDecl()) + return; + } + } + } + // Perform the initialization. ParenListExpr *CXXDirectInit = dyn_cast(Init); if (!VDecl->isInvalidDecl()) { @@ -11552,6 +11625,37 @@ if (VarDecl *Var = dyn_cast(RealDecl)) { QualType Type = Var->getType(); + if (const auto *TD = Type.getTypePtr()->getAsTagDecl()) { + // If the type of the declaration is a struct/class and that type has the + // require_designated_init attribute, an initializer is mandatory. + if (const auto *A = TD->getAttr()) { + Diag(Var->getLocation(), diag::warn_requires_designator_failed) + << Var->getSourceRange(); + Diag(A->getLocation(), + diag::note_declared_requires_designator_here) + << A->getRange(); + Var->setInvalidDecl(); + return; + } + // If the type of the declaration is a struct/class, we must check whether + // any of the fields have the required attribute. For each that does, emit + // an error since it is not initialized with designated initializer + // syntax. + if (const auto *RD = dyn_cast(TD)) { + for (const auto *FD : RD->fields()) { + if (const auto *A = FD->getAttr()) { + Diag(Var->getLocation(), diag::warn_requires_init_failed) + << Var->getSourceRange() << Var->getName() << FD->getName(); + Diag(A->getLocation(), diag::note_declared_requires_init_here) + << A->getRange(); + Var->setInvalidDecl(); + } + } + if (Var->isInvalidDecl()) + return; + } + } + // C++1z [dcl.dcl]p1 grammar implies that an initializer is mandatory. if (isa(RealDecl)) { Diag(Var->getLocation(), diag::err_decomp_decl_requires_init) << Var; @@ -15207,6 +15311,36 @@ TagDecl *Tag = cast(TagD); Tag->setBraceRange(BraceRange); + // If this TagDecl has any non-public fields, all requires_designator and + // requires_init attributes should be ignored. + bool AllMembersPublic = true; + if (const auto *RD = dyn_cast(Tag)) { + for (auto const *FD : RD->fields()) { + if (FD->getAccess() != AS_public) + AllMembersPublic = false; + } + if (!AllMembersPublic) { + if (const auto *RDAttr = Tag->getAttr()) { + auto Attrs = Tag->getAttrs(); + Tag->dropAttrs(); + Attrs.erase(std::remove(Attrs.begin(), Attrs.end(), RDAttr), + Attrs.end()); + Tag->setAttrs(Attrs); + Diag(RDAttr->getLocation(), diag::warn_attribute_ignored) << RDAttr; + } + for (auto *FD : RD->fields()) { + if (const auto *RIAttr = FD->getAttr()) { + auto Attrs = FD->getAttrs(); + FD->dropAttrs(); + Attrs.erase(std::remove(Attrs.begin(), Attrs.end(), RIAttr), + Attrs.end()); + FD->setAttrs(Attrs); + Diag(RIAttr->getLocation(), diag::warn_attribute_ignored) << RIAttr; + } + } + } + } + // Make sure we "complete" the definition even it is invalid. if (Tag->isBeingDefined()) { assert(Tag->isInvalidDecl() && "We should already have completed it"); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5309,6 +5309,19 @@ AL.getAttributeSpellingListIndex())); } +static void handleRequiresInitAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (auto *FD = dyn_cast(D)) { + auto const *RD = FD->getParent(); + if (RD->isUnion()) + S.Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL.getName(); + else { + FD->addAttr(::new (S.Context) RequiresInitAttr( + AL.getRange(), S.Context, AL.getAttributeSpellingListIndex())); + } + } else + S.Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL.getName(); +} + static void handleObjCRuntimeName(Sema &S, Decl *D, const ParsedAttr &AL) { StringRef MetaDataName; if (!S.checkStringLiteralArgumentAttr(AL, 0, MetaDataName)) @@ -6959,6 +6972,12 @@ case ParsedAttr::AT_RequireConstantInit: handleSimpleAttribute(S, D, AL); break; + case ParsedAttr::AT_RequiresDesignator: + handleSimpleAttribute(S, D, AL); + break; + case ParsedAttr::AT_RequiresInit: + handleRequiresInitAttr(S, D, AL); + break; case ParsedAttr::AT_InitPriority: handleInitPriorityAttr(S, D, AL); break; diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -122,6 +122,8 @@ // CHECK-NEXT: RenderScriptKernel (SubjectMatchRule_function) // CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function) // CHECK-NEXT: RequireConstantInit (SubjectMatchRule_variable_is_global) +// CHECK-NEXT: RequiresDesignator (SubjectMatchRule_record) +// CHECK-NEXT: RequiresInit (SubjectMatchRule_field) // CHECK-NEXT: Restrict (SubjectMatchRule_function) // CHECK-NEXT: ReturnTypestate (SubjectMatchRule_function, SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: ReturnsNonNull (SubjectMatchRule_objc_method, SubjectMatchRule_function) diff --git a/clang/test/SemaCXX/attr-requires-designator.cpp b/clang/test/SemaCXX/attr-requires-designator.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-requires-designator.cpp @@ -0,0 +1,129 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// The requires_designator attribute only applies to types. It will +// generate a warning when attached to variables, functions, arrays, etc. +[[clang::requires_designator]] int x; // expected-warning{{'requires_designator' attribute only applies to structs, unions, and classes}} +[[clang::requires_designator]] void fun(int x) { // expected-warning{{'requires_designator' attribute only applies to structs, unions, and classes}} + return; +} +[[clang::requires_designator]] int arr[10]; // expected-warning{{'requires_designator' attribute only applies to structs, unions, and classes}} + +// Struct with one field with requires_designator attribute +struct [[clang::requires_designator]] Foo { // expected-note 0+ {{required by 'requires_designator' attribute here}} + int a; +}; + +// The following are invalid ways of initializing instances of this struct. +Foo f1; // expected-warning{{variable declaration does not use designated initializer syntax}} +Foo f2{1}; // expected-warning{{variable declaration does not use designated initializer syntax}} +Foo f3 = {1}; // expected-warning{{variable declaration does not use designated initializer syntax}} +// The following are valid ways of initializing instances of this struct. +Foo f4{}; +Foo f5 = {}; +Foo f6{.a = 1}; +Foo f7 = {.a = 1}; + +// Struct with multiple fields wth requires_designator attribute +struct [[clang::requires_designator]] Bar { // expected-note 0+ {{required by 'requires_designator' attribute here}} + int b; + int c; +}; + +// The following are invalid ways of initializing instances of this struct. +Bar b1; // expected-warning{{variable declaration does not use designated initializer syntax}} +Bar b2{1, 2}; // expected-warning{{variable declaration does not use designated initializer syntax}} +Bar b3 = {1, 2}; // expected-warning{{variable declaration does not use designated initializer syntax}} +Bar b4{.b = 1, 2}; // expected-warning{{variable declaration does not use designated initializer syntax}} +Bar b5 = {.b = 1, 2}; // expected-warning{{variable declaration does not use designated initializer syntax}} +// The following are valid ways of initializing instances of this struct. +Bar b6{}; +Bar b7 = {}; +Bar b8{.b = 1}; +Bar b9 = {.b = 1}; +Bar b10{.b = 1, .c = 2}; +Bar b11 = {.b = 1, .c = 2}; +Bar b12 = {.c = 2, .b = 1}; + +// Struct without requires_designator attribute +struct Baz { + int d; + int e; +}; + +// The following are all valid ways of initializing instances of this struct. +Baz z1; +Baz z2{}; +Baz z3 = {}; +Baz z4{1, 2}; +Baz z5 = {1, 2}; +Baz z6{.d = 1, .e = 2}; +Baz z7 = {.d = 1, .e = 2}; +Baz z8{1}; +Baz z9 = {1}; +Baz z10{.d = 1, 2}; +Baz z11 = {.d = 1, 2}; + +// The requires_designator attribute can also be attached to unions. +union [[clang::requires_designator]] Uni { // expected-note 0+ {{required by 'requires_designator' attribute here}} + int x; + int y; +}; + +// The following are invalid ways of initializing instances of this union. +Uni u1; // expected-warning{{variable declaration does not use designated initializer syntax}} +Uni u2{1}; // expected-warning{{variable declaration does not use designated initializer syntax}} +Uni u3 = {1}; // expected-warning{{variable declaration does not use designated initializer syntax}} +// The following are valid ways of initializing instances of this union. +Uni u4{}; +Uni u5 = {}; +Uni u6{.x = 1}; +Uni u7 = {.x = 1}; + +// The requires_designator attribute can also be attached to classes. +class [[clang::requires_designator]] Cla { // expected-note 0+ {{required by 'requires_designator' attribute here}} +public: + int x; + int y; +}; + +// The following are invalid ways of initializing instances of this class. +Cla c1; // expected-warning{{variable declaration does not use designated initializer syntax}} +Cla c2{1, 2}; // expected-warning{{variable declaration does not use designated initializer syntax}} +Cla c3 = {1, 2}; // expected-warning{{variable declaration does not use designated initializer syntax}} +Cla c4{.x = 1, 2}; // expected-warning{{variable declaration does not use designated initializer syntax}} +Cla c5 = {.x = 1, 2}; // expected-warning{{variable declaration does not use designated initializer syntax}} +// The following are valid ways of initializing instances of this class. +Cla c6{}; +Cla c7 = {}; +Cla c8{.x = 1}; +Cla c9 = {.x = 1}; +Cla c10{.x = 1, .y = 2}; +Cla c11 = {.x = 1, .y = 2}; +Cla c12 = {.y = 2, .x = 1}; + +// The requires_designator attribute is ignored when attached to a record with +// non-public members. +struct [[clang::requires_designator]] Pri { // expected-warning{{'requires_designator' attribute ignored}} + int x; +private: + int y; +}; +Pri pri1; +Pri pri2(); + +struct [[clang::requires_designator]] Pro { // expected-warning{{'requires_designator' attribute ignored}} + int x; +protected: + int y; +}; +Pro pro1; +Pro pro2(); + +class [[clang::requires_designator]] PriClass { // expected-warning{{'requires_designator' attribute ignored}} + int x; +public: + int y; +}; +Pro pc1; +Pro pc2(); + diff --git a/clang/test/SemaCXX/attr-requires-init.cpp b/clang/test/SemaCXX/attr-requires-init.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-requires-init.cpp @@ -0,0 +1,86 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +[[clang::requires_init]] int x; // expected-warning{{'requires_init' attribute only applies to non-static data members}} +[[clang::requires_init]] void fun(int x) { // expected-warning{{'requires_init' attribute only applies to non-static data members}} + return; +} +struct [[clang::requires_init]] Foo { // expected-warning{{'requires_init' attribute only applies to non-static data members}} + int x; +}; + +// Struct with one required field +struct Bar { + [[clang::requires_init]] int y; // expected-note 0+ {{enforced by 'requires_init' attribute here}} +}; + +// The following are invalid ways of initializing instances of this struct. +Bar b1; // expected-warning{{initializer for variable b1 must explicitly initialize field y}} +Bar b2{}; // expected-warning{{initializer for variable b2 must explicitly initialize field y}} +Bar b3{1}; // expected-warning{{initializer for variable b3 must explicitly initialize field y}} + +// The following are valid ways of initializing instances of this struct. +Bar b6{.y = 1}; + +// Struct with multiple required fields +struct Baz { + [[clang::requires_init]] int x; // expected-note 0+ {{enforced by 'requires_init' attribute here}} + int y; + [[clang::requires_init]] int z; // expected-note 0+ {{enforced by 'requires_init' attribute here}} +}; + +// The following are invalid ways of initializing instances of this struct. +Baz z1; // expected-warning{{initializer for variable z1 must explicitly initialize field x}} expected-warning{{initializer for variable z1 must explicitly initialize field z}} +Baz z2{}; // expected-warning{{initializer for variable z2 must explicitly initialize field x}} expected-warning{{initializer for variable z2 must explicitly initialize field z}} +Baz z3{1, 2}; // expected-warning{{initializer for variable z3 must explicitly initialize field x}} expected-warning{{initializer for variable z3 must explicitly initialize field z}} +Baz z4{1, 2, 3}; // expected-warning{{initializer for variable z4 must explicitly initialize field x}} expected-warning{{initializer for variable z4 must explicitly initialize field z}} +Baz z5{.x = 1, 2}; // expected-warning{{initializer for variable z5 must explicitly initialize field z}} +Baz z6{.x = 1, .y = 2}; // expected-warning{{initializer for variable z6 must explicitly initialize field z}} + +// The following are valid ways of initializing instances of this struct. +Baz z7{.x = 1, .y = 2, .z = 3}; +Baz z8{.x = 1, .z = 3}; +Baz z9{.x = 1, 2, .z = 3}; + +// The requires_init attribute can also be applied to public fields of classes. +class Cla { +public: + [[clang::requires_init]] int x; // expected-note 0+ {{enforced by 'requires_init' attribute here}} + int y; +}; + +// The following are invalid ways of initializing instances of this class. +Cla c1; // expected-warning{{initializer for variable c1 must explicitly initialize field x}} +Cla c2{}; // expected-warning{{initializer for variable c2 must explicitly initialize field x}} +Cla c3{1}; // expected-warning{{initializer for variable c3 must explicitly initialize field x}} +Cla c4{1, 2}; // expected-warning{{initializer for variable c4 must explicitly initialize field x}} +Cla c5{1, .y = 2}; // expected-warning{{initializer for variable c5 must explicitly initialize field x}} + +// The following are valid ways of initializing instances of this class. +Cla c6{.x = 1}; +Cla c7{.x = 1, .y = 2}; +Cla c8{.x = 1, 2}; + +// This attribute cannot be applied to fields of a union. +union Uni { + [[clang::requires_init]] int x; // expected-warning{{'requires_init' attribute ignored}} + int y; +}; + +// If any of the fields in the record are non-public all requires_init +// attributes in the record are ignored. +struct PriMems { + [[clang::requires_init]] int x; // expected-warning{{'requires_init' attribute ignored}} +private: + int y; +}; +PriMems pm1; +PriMems pm2(); + +class PriClass { + int x; +public: + [[clang::requires_init]] int y; // expected-warning{{'requires_init' attribute ignored}} + int z; +}; +PriClass pc1; +PriClass pc2();