[clang] Add Clang attribute to ensure that fields are initialized explicitly (PR #102040)

via cfe-commits cfe-commits at lists.llvm.org
Mon Nov 25 03:02:21 PST 2024


https://github.com/higher-performance updated https://github.com/llvm/llvm-project/pull/102040

>From e98e6f210f02af0813393d88e1bc4f02c0682e5f 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 01/12] Add Clang attribute to ensure that fields are
 initialized explicitly

---
 .../clang/AST/CXXRecordDeclDefinitionBits.def |  9 ++++
 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        |  7 +++
 clang/lib/AST/DeclCXX.cpp                     | 24 ++++++++++
 clang/lib/Sema/SemaDeclAttr.cpp               |  7 +++
 clang/lib/Sema/SemaInit.cpp                   | 15 ++++++
 ...a-attribute-supported-attributes-list.test |  1 +
 clang/test/SemaCXX/uninitialized.cpp          | 46 +++++++++++++++++++
 11 files changed, 152 insertions(+)

diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 6620840df0ced2..54f28046c63ebb 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -119,6 +119,15 @@ 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 requiring explicit
+/// initialization with [[clang::requires_explicit_initialization]] 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 2693cc0e95b4b2..7a9c28b6af9d9e 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 6035a563d5fce7..2878ed0a9fec1b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1885,6 +1885,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 94d6d15365cef6..fb5def41deadc8 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1599,6 +1599,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 df9bf94b5d0398..a18072e7dd5cc8 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -790,6 +790,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 157d77b38b354e..b52cc3c870295e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2318,6 +2318,10 @@ def err_init_list_bad_dest_type : Error<
 def warn_cxx20_compat_aggregate_init_with_ctors : Warning<
   "aggregate initialization of type %0 with user-declared constructors "
   "is incompatible with C++20">, DefaultIgnore, InGroup<CXX20Compat>;
+def warn_cxx20_compat_requires_explicit_init_non_aggregate : Warning<
+  "explicit initialization of field %0 may not be enforced in C++20 as type %1 "
+  "will become a non-aggregate due to the presence of user-declared "
+  "constructors">, DefaultIgnore, InGroup<CXX20Compat>;
 def warn_cxx17_compat_aggregate_init_paren_list : Warning<
   "aggregate initialization of type %0 from a parenthesized list of values "
   "is a C++20 extension">, DefaultIgnore, InGroup<CXX20>;
@@ -2347,6 +2351,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 39c548e9c22539..e1a59620158d9a 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -29,6 +29,7 @@
 #include "clang/AST/TypeLoc.h"
 #include "clang/AST/UnresolvedSet.h"
 #include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticSema.h"
 #include "clang/Basic/IdentifierTable.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/LangOptions.h"
@@ -80,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),
@@ -1113,6 +1115,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;
@@ -1367,6 +1373,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;
@@ -2183,6 +2193,20 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
   for (conversion_iterator I = conversion_begin(), E = conversion_end();
        I != E; ++I)
     I.setAccess((*I)->getAccess());
+
+  ASTContext &Context = getASTContext();
+  if (!Context.getLangOpts().CPlusPlus20 && isAggregate() &&
+      hasUserDeclaredConstructor()) {
+    // Diagnose any aggregate behavior changes in C++20
+    for (field_iterator I = field_begin(), E = field_end(); I != E; ++I) {
+      if (const auto *attr = I->getAttr<ExplicitInitAttr>()) {
+        Context.getDiagnostics().Report(
+            getLocation(),
+            diag::warn_cxx20_compat_requires_explicit_init_non_aggregate)
+            << attr->getRange() << Context.getRecordType(this);
+      }
+    }
+  }
 }
 
 bool CXXRecordDecl::mayBeAbstract() const {
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 146d9c86e0715a..045bc23af52b00 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6147,6 +6147,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));
 }
@@ -7061,6 +7065,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 7c03a12e812809..3c78555c4fadab 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -738,6 +738,14 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field,
         ILE->updateInit(SemaRef.Context, Init, Filler);
       return;
     }
+
+    if (!VerifyOnly && Field->hasAttr<ExplicitInitAttr>()) {
+      SemaRef.Diag(ILE->getExprLoc(), diag::warn_field_requires_explicit_init)
+          << Field;
+      SemaRef.Diag(Field->getLocation(), diag::note_entity_declared_at)
+          << 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
@@ -4562,6 +4570,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 4a6ac39da18ad2..55f196625770ab 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -79,6 +79,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..37cf6d1dd60c0a 100644
--- a/clang/test/SemaCXX/uninitialized.cpp
+++ b/clang/test/SemaCXX/uninitialized.cpp
@@ -1472,3 +1472,49 @@ template<typename T> struct Outer {
   };
 };
 Outer<int>::Inner outerinner;
+
+void aggregate() {
+  struct S {
+    [[clang::requires_explicit_initialization]] int x; // expected-note {{declared}} // expected-note {{declared}} // expected-note {{declared}} // expected-note {{declared}}
+    int y;
+    int z = 12;
+    [[clang::requires_explicit_initialization]] int q = 100; // expected-note {{declared}} // expected-note {{declared}} // expected-note {{declared}} // expected-note {{declared}}
+    static void foo(S) { }
+  };
+
+  struct D : S { // expected-warning {{not explicitly initialized}}
+    int f1;
+    int f2 [[clang::requires_explicit_initialization]]; // expected-note {{declared}} // expected-note {{declared}}
+  };
+
+  struct C {
+    [[clang::requires_explicit_initialization]] int w;
+    C() = default;  // Test pre-C++20 aggregates
+  };
+
+  S::foo(S{1, 2, 3, 4});
+  S::foo(S{.x = 100, .q = 100});
+  S::foo(S{.x = 100}); // expected-warning {{'q' is not explicitly initialized}}
+  S s{.x = 100, .q = 100};
+  (void)s;
+  S t{.q = 100}; // expected-warning {{'x' is not explicitly initialized}}
+  (void)t;
+  S *ptr1 = new S; // expected-warning {{not explicitly initialized}}
+  delete ptr1;
+  S *ptr2 = new S{.x = 100, .q = 100};
+  delete ptr2;
+#if __cplusplus >= 202002L
+  D a({}, 0); // expected-warning {{'x' is not explicitly initialized}} expected-warning {{'f2' is not explicitly initialized}}
+  (void)a;
+#else
+  C a; // expected-warning {{not explicitly initialized}}
+  (void)a;
+#endif
+  D b{.f2 = 1}; // expected-warning {{'x' is not explicitly initialized}} expected-warning {{'q' is not explicitly initialized}}
+  (void)b;
+  D c{.f1 = 5}; // expected-warning {{'x' is not explicitly initialized}} expected-warning {{'q' is not explicitly initialized}} expected-warning {{'f2' is not explicitly initialized}}
+  c = {{}, 0}; // expected-warning {{'x' is not explicitly initialized}} expected-warning {{'q' is not explicitly initialized}} expected-warning {{'f2' is not explicitly initialized}}
+  (void)c;
+  D d; // expected-warning {{not explicitly initialized}}  // expected-note {{constructor}}
+  (void)d;
+}

>From 539c0cdaaa1cfb28d24b83eadfba85248a42ebe8 Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Wed, 25 Sep 2024 13:28:46 -0400
Subject: [PATCH 02/12] Implement explicit field initialization checks in
 RecordDecl and make it work in both C and C++

---
 .../clang/AST/CXXRecordDeclDefinitionBits.def |   9 --
 clang/include/clang/AST/Decl.h                |   8 ++
 clang/include/clang/AST/DeclBase.h            |  13 ++-
 clang/include/clang/AST/DeclCXX.h             |   5 -
 clang/include/clang/Basic/Attr.td             |   3 +-
 clang/include/clang/Basic/AttrDocs.td         |  31 +++---
 .../clang/Basic/DiagnosticSemaKinds.td        |   8 +-
 clang/lib/AST/Decl.cpp                        |   8 +-
 clang/lib/AST/DeclCXX.cpp                     |  32 ++++--
 clang/lib/Sema/SemaDecl.cpp                   |   3 +
 clang/lib/Sema/SemaInit.cpp                   |  26 ++++-
 clang/lib/Serialization/ASTReaderDecl.cpp     |   1 +
 clang/lib/Serialization/ASTWriterDecl.cpp     |   4 +-
 clang/test/Sema/uninit-variables.c            |  11 ++
 clang/test/SemaCXX/uninitialized.cpp          | 100 ++++++++++++++----
 15 files changed, 196 insertions(+), 66 deletions(-)

diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 54f28046c63ebb..6620840df0ced2 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -119,15 +119,6 @@ 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 requiring explicit
-/// initialization with [[clang::requires_explicit_initialization]] 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/Decl.h b/clang/include/clang/AST/Decl.h
index 8c39ef3d5a9fa6..5e8939384fc39d 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -4273,6 +4273,14 @@ class RecordDecl : public TagDecl {
     RecordDeclBits.HasNonTrivialToPrimitiveCopyCUnion = V;
   }
 
+  bool hasUninitializedExplicitInitFields() const {
+    return RecordDeclBits.HasUninitializedExplicitInitFields;
+  }
+
+  void setHasUninitializedExplicitInitFields(bool V) {
+    RecordDeclBits.HasUninitializedExplicitInitFields = V;
+  }
+
   /// Determine whether this class can be passed in registers. In C++ mode,
   /// it must have at least one trivial, non-deleted copy or move constructor.
   /// FIXME: This should be set as part of completeDefinition.
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index a3447d19909752..885c4c7a0bd27c 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -1450,6 +1450,9 @@ class DeclContext {
   /// hasLazyLocalLexicalLookups, hasLazyExternalLexicalLookups
   friend class ASTWriter;
 
+protected:
+  enum { NumOdrHashBits = 25 };
+
   // We use uint64_t in the bit-fields below since some bit-fields
   // cross the unsigned boundary and this breaks the packing.
 
@@ -1671,6 +1674,14 @@ class DeclContext {
     LLVM_PREFERRED_TYPE(bool)
     uint64_t HasNonTrivialToPrimitiveCopyCUnion : 1;
 
+    /// True if any field is marked as requiring explicit initialization with
+    /// [[clang::requires_explicit_initialization]].
+    /// In C++, this is also set for types without a user-provided default
+    /// constructor, and is propagated from any base classes and/or member
+    /// variables whose types are aggregates.
+    LLVM_PREFERRED_TYPE(bool)
+    uint64_t HasUninitializedExplicitInitFields : 1;
+
     /// Indicates whether this struct is destroyed in the callee.
     LLVM_PREFERRED_TYPE(bool)
     uint64_t ParamDestroyedInCallee : 1;
@@ -1685,7 +1696,7 @@ class DeclContext {
 
     /// True if a valid hash is stored in ODRHash. This should shave off some
     /// extra storage and prevent CXXRecordDecl to store unused bits.
-    uint64_t ODRHash : 26;
+    uint64_t ODRHash : NumOdrHashBits;
   };
 
   /// Number of inherited and non-inherited bits in RecordDeclBitfields.
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 7a9c28b6af9d9e..2693cc0e95b4b2 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1152,11 +1152,6 @@ 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 2878ed0a9fec1b..80fb55a403ac9b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1886,10 +1886,9 @@ def Leaf : InheritableAttr {
 }
 
 def ExplicitInit : InheritableAttr {
-  let Spellings = [Clang<"requires_explicit_initialization", 0>];
+  let Spellings = [Clang<"requires_explicit_initialization", 1>];
   let Subjects = SubjectList<[Field], ErrorDiag>;
   let Documentation = [ExplicitInitDocs];
-  let LangOpts = [CPlusPlus];
   let SimpleHandler = 1;
 }
 
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index fb5def41deadc8..34fef2caf841cf 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1606,24 +1606,31 @@ 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:
+Note that this attribute is *not* a memory safety feature, and is *not* intended
+to guard against use of uninitialized memory.
+
+Rather, it is intended for use in "parameter-objects", used to simulate the
+passing of named parameters.
+The attribute generates a warning when explicit initializers for such
+"named parameters" are not provided:
 
 .. code-block:: c++
 
-  struct some_aggregate {
-    int x;
-    int y [[clang::requires_explicit_initialization]];
+  struct ArrayIOParams {
+    size_t count [[clang::requires_explicit_initialization]];
+    size_t element_size [[clang::requires_explicit_initialization]];
+    int flags = 0;
   };
 
-  some_aggregate create() {
-    return {.x = 1};  // error: y is not initialized explicitly
-  }
+  size_t ReadArray(FILE *file, void *buffer, ArrayIOParams params);
 
-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").
+  int main() {
+    unsigned int buf[512];
+    ReadArray(stdin, buf, {
+      .count = sizeof(buf) / sizeof(*buf),
+      // warning: field 'element_size' is not explicitly initialized
+    });
+  }
 
   }];
 }
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b52cc3c870295e..31f846c1b0825c 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2352,8 +2352,8 @@ def err_init_reference_member_uninitialized : Error<
 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>;
+  "field %select{%1|in %1}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<
@@ -3156,6 +3156,10 @@ def warn_attribute_ignored_no_calls_in_stmt: Warning<
   "statement">,
   InGroup<IgnoredAttributes>;
 
+def warn_attribute_needs_aggregate : Warning<
+  "%0 attribute is ignored in non-aggregate type %1">,
+  InGroup<IgnoredAttributes>;
+
 def warn_attribute_ignored_non_function_pointer: Warning<
   "%0 attribute is ignored because %1 is not a function pointer">,
   InGroup<IgnoredAttributes>;
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index bfeb4827f79587..d5e8f75b877d89 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -5029,6 +5029,7 @@ RecordDecl::RecordDecl(Kind DK, TagKind TK, const ASTContext &C,
   setHasNonTrivialToPrimitiveDefaultInitializeCUnion(false);
   setHasNonTrivialToPrimitiveDestructCUnion(false);
   setHasNonTrivialToPrimitiveCopyCUnion(false);
+  setHasUninitializedExplicitInitFields(false);
   setParamDestroyedInCallee(false);
   setArgPassingRestrictions(RecordArgPassingKind::CanPassInRegs);
   setIsRandomized(false);
@@ -5229,9 +5230,10 @@ unsigned RecordDecl::getODRHash() {
   // Only calculate hash on first call of getODRHash per record.
   ODRHash Hash;
   Hash.AddRecordDecl(this);
-  // For RecordDecl the ODRHash is stored in the remaining 26
-  // bit of RecordDeclBits, adjust the hash to accomodate.
-  setODRHash(Hash.CalculateHash() >> 6);
+  // For RecordDecl the ODRHash is stored in the remaining
+  // bits of RecordDeclBits, adjust the hash to accommodate.
+  static_assert(sizeof(Hash.CalculateHash()) * CHAR_BIT == 32);
+  setODRHash(Hash.CalculateHash() >> (32 - NumOdrHashBits));
   return RecordDeclBits.ODRHash;
 }
 
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index e1a59620158d9a..11eaff4284d5fd 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -81,7 +81,6 @@ 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),
@@ -459,6 +458,10 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
     if (BaseClassDecl->hasMutableFields())
       data().HasMutableFields = true;
 
+    if (BaseClassDecl->hasUninitializedExplicitInitFields() &&
+        BaseClassDecl->isAggregate())
+      setHasUninitializedExplicitInitFields(true);
+
     if (BaseClassDecl->hasUninitializedReferenceMember())
       data().HasUninitializedReferenceMember = true;
 
@@ -1116,7 +1119,7 @@ void CXXRecordDecl::addedMember(Decl *D) {
       data().PlainOldData = false;
 
     if (Field->hasAttr<ExplicitInitAttr>() && !Field->hasInClassInitializer()) {
-      data().HasUninitializedExplicitInitFields = true;
+      setHasUninitializedExplicitInitFields(true);
     }
 
     if (T->isReferenceType()) {
@@ -1375,7 +1378,7 @@ void CXXRecordDecl::addedMember(Decl *D) {
 
         if (FieldRec->hasUninitializedExplicitInitFields() &&
             FieldRec->isAggregate() && !Field->hasInClassInitializer())
-          data().HasUninitializedExplicitInitFields = true;
+          setHasUninitializedExplicitInitFields(true);
 
         if (FieldRec->hasUninitializedReferenceMember() &&
             !Field->hasInClassInitializer())
@@ -2195,17 +2198,32 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
     I.setAccess((*I)->getAccess());
 
   ASTContext &Context = getASTContext();
-  if (!Context.getLangOpts().CPlusPlus20 && isAggregate() &&
-      hasUserDeclaredConstructor()) {
+
+  if (isAggregate() && hasUserDeclaredConstructor() &&
+      !Context.getLangOpts().CPlusPlus20) {
     // Diagnose any aggregate behavior changes in C++20
     for (field_iterator I = field_begin(), E = field_end(); I != E; ++I) {
       if (const auto *attr = I->getAttr<ExplicitInitAttr>()) {
         Context.getDiagnostics().Report(
-            getLocation(),
+            attr->getLocation(),
             diag::warn_cxx20_compat_requires_explicit_init_non_aggregate)
-            << attr->getRange() << Context.getRecordType(this);
+            << attr << Context.getRecordType(this);
+      }
+    }
+  }
+
+  if (!isAggregate() && hasUninitializedExplicitInitFields()) {
+    // Diagnose any fields that required explicit initialization in a
+    // non-aggregate type. (Note that the fields may not be directly in this
+    // type, but in a subobject. In such cases we don't emit diagnoses here.)
+    for (field_iterator I = field_begin(), E = field_end(); I != E; ++I) {
+      if (const auto *attr = I->getAttr<ExplicitInitAttr>()) {
+        Context.getDiagnostics().Report(attr->getLocation(),
+                                        diag::warn_attribute_needs_aggregate)
+            << attr << Context.getRecordType(this);
       }
     }
+    setHasUninitializedExplicitInitFields(false);
   }
 }
 
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index be570f3a1829d0..6885403169ee76 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -19189,6 +19189,9 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
         if (FT.hasNonTrivialToPrimitiveCopyCUnion() || Record->isUnion())
           Record->setHasNonTrivialToPrimitiveCopyCUnion(true);
       }
+      if (FD->hasAttr<ExplicitInitAttr>()) {
+        Record->setHasUninitializedExplicitInitFields(true);
+      }
       if (FT.isDestructedType()) {
         Record->setNonTrivialToPrimitiveDestroy(true);
         Record->setParamDestroyedInCallee(true);
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 3c78555c4fadab..a553f76e0caad6 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -264,6 +264,14 @@ static void CheckStringInit(Expr *Str, QualType &DeclT, const ArrayType *AT,
   updateStringLiteralType(Str, DeclT);
 }
 
+void emitUninitializedExplicitInitFields(Sema &S, const RecordDecl *R) {
+  for (const FieldDecl *Field : R->fields()) {
+    if (Field->hasAttr<ExplicitInitAttr>()) {
+      S.Diag(Field->getLocation(), diag::note_entity_declared_at) << Field;
+    }
+  }
+}
+
 //===----------------------------------------------------------------------===//
 // Semantic checking for initializer lists.
 //===----------------------------------------------------------------------===//
@@ -741,7 +749,7 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field,
 
     if (!VerifyOnly && Field->hasAttr<ExplicitInitAttr>()) {
       SemaRef.Diag(ILE->getExprLoc(), diag::warn_field_requires_explicit_init)
-          << Field;
+          << /* Var-in-Record */ 0 << Field;
       SemaRef.Diag(Field->getLocation(), diag::note_entity_declared_at)
           << Field;
     }
@@ -4574,7 +4582,8 @@ static void TryConstructorInitialization(Sema &S,
         DestRecordDecl != nullptr && DestRecordDecl->isAggregate() &&
         DestRecordDecl->hasUninitializedExplicitInitFields()) {
       S.Diag(Kind.getLocation(), diag::warn_field_requires_explicit_init)
-          << "in class";
+          << /* Var-in-Record */ 1 << DestRecordDecl;
+      emitUninitializedExplicitInitFields(S, DestRecordDecl);
     }
 
     // C++11 [dcl.init]p6:
@@ -6476,6 +6485,19 @@ void InitializationSequence::InitializeFrom(Sema &S,
     }
   }
 
+  if (!S.getLangOpts().CPlusPlus &&
+      Kind.getKind() == InitializationKind::IK_Default) {
+    RecordDecl *Rec = DestType->getAsRecordDecl();
+    if (Rec && Rec->hasUninitializedExplicitInitFields()) {
+      VarDecl *Var = dyn_cast_or_null<VarDecl>(Entity.getDecl());
+      if (Var && !Initializer) {
+        S.Diag(Var->getLocation(), diag::warn_field_requires_explicit_init)
+            << /* Var-in-Record */ 1 << Rec;
+        emitUninitializedExplicitInitFields(S, Rec);
+      }
+    }
+  }
+
   //     - If the destination type is a reference type, see 8.5.3.
   if (DestType->isReferenceType()) {
     // C++0x [dcl.init.ref]p1:
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 6ece3ba7af9f4b..6f78daad97fd71 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -859,6 +859,7 @@ RedeclarableResult ASTDeclReader::VisitRecordDeclImpl(RecordDecl *RD) {
       RecordDeclBits.getNextBit());
   RD->setHasNonTrivialToPrimitiveDestructCUnion(RecordDeclBits.getNextBit());
   RD->setHasNonTrivialToPrimitiveCopyCUnion(RecordDeclBits.getNextBit());
+  RD->setHasUninitializedExplicitInitFields(RecordDeclBits.getNextBit());
   RD->setParamDestroyedInCallee(RecordDeclBits.getNextBit());
   RD->setArgPassingRestrictions(
       (RecordArgPassingKind)RecordDeclBits.getNextBits(/*Width=*/2));
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index ad357e30d57529..f9b35a65bf95eb 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -573,6 +573,7 @@ void ASTDeclWriter::VisitRecordDecl(RecordDecl *D) {
   RecordDeclBits.addBit(D->hasNonTrivialToPrimitiveDefaultInitializeCUnion());
   RecordDeclBits.addBit(D->hasNonTrivialToPrimitiveDestructCUnion());
   RecordDeclBits.addBit(D->hasNonTrivialToPrimitiveCopyCUnion());
+  RecordDeclBits.addBit(D->hasUninitializedExplicitInitFields());
   RecordDeclBits.addBit(D->isParamDestroyedInCallee());
   RecordDeclBits.addBits(llvm::to_underlying(D->getArgPassingRestrictions()), 2);
   Record.push_back(RecordDeclBits);
@@ -2430,7 +2431,8 @@ void ASTWriter::WriteDeclAbbrevs() {
             // isNonTrivialToPrimitiveCopy, isNonTrivialToPrimitiveDestroy,
             // hasNonTrivialToPrimitiveDefaultInitializeCUnion,
             // hasNonTrivialToPrimitiveDestructCUnion,
-            // hasNonTrivialToPrimitiveCopyCUnion, isParamDestroyedInCallee,
+            // hasNonTrivialToPrimitiveCopyCUnion,
+            // hasUninitializedExplicitInitFields, isParamDestroyedInCallee,
             // getArgPassingRestrictions
   // ODRHash
   Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 26));
diff --git a/clang/test/Sema/uninit-variables.c b/clang/test/Sema/uninit-variables.c
index 70a00793fd29ed..d4f4686b600c40 100644
--- a/clang/test/Sema/uninit-variables.c
+++ b/clang/test/Sema/uninit-variables.c
@@ -551,3 +551,14 @@ struct full_of_empty empty_test_2(void) {
   struct full_of_empty e;
   return e; // no-warning
 }
+
+struct with_explicit_field {
+  int x;
+  int y [[clang::requires_explicit_initialization]]; // #FIELD_Y
+};
+
+void aggregate() {
+  struct with_explicit_field a; // expected-warning {{field in 'with_explicit_field' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_Y {{'y' declared here}}
+  struct with_explicit_field b = {1}; // expected-warning {{field 'y' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_Y {{'y' declared here}}
+  (void)(&a != &b);
+}
diff --git a/clang/test/SemaCXX/uninitialized.cpp b/clang/test/SemaCXX/uninitialized.cpp
index 37cf6d1dd60c0a..95e04cdfe5909a 100644
--- a/clang/test/SemaCXX/uninitialized.cpp
+++ b/clang/test/SemaCXX/uninitialized.cpp
@@ -1474,47 +1474,103 @@ template<typename T> struct Outer {
 Outer<int>::Inner outerinner;
 
 void aggregate() {
-  struct S {
-    [[clang::requires_explicit_initialization]] int x; // expected-note {{declared}} // expected-note {{declared}} // expected-note {{declared}} // expected-note {{declared}}
-    int y;
-    int z = 12;
-    [[clang::requires_explicit_initialization]] int q = 100; // expected-note {{declared}} // expected-note {{declared}} // expected-note {{declared}} // expected-note {{declared}}
-    static void foo(S) { }
+  struct NonAgg {
+    NonAgg() { }
+    [[clang::requires_explicit_initialization]] int na;  // expected-warning {{'requires_explicit_initialization' attribute is ignored in non-aggregate type 'NonAgg'}}
   };
+  NonAgg nonagg;  // no-warning
+  (void)nonagg;
 
-  struct D : S { // expected-warning {{not explicitly initialized}}
-    int f1;
-    int f2 [[clang::requires_explicit_initialization]]; // expected-note {{declared}} // expected-note {{declared}}
+  struct S {
+    [[clang::requires_explicit_initialization]] int s1; // #FIELD_S1
+    int s2;
+    int s3 = 12;
+    [[clang::requires_explicit_initialization]] int s4 = 100; // #FIELD_S4
+    static void foo(S) { }
   };
 
   struct C {
-    [[clang::requires_explicit_initialization]] int w;
+    [[clang::requires_explicit_initialization]] int c1; // #FIELD_C1
     C() = default;  // Test pre-C++20 aggregates
   };
 
+  struct D : S { // #TYPE_D
+    int d1;
+    int d2 [[clang::requires_explicit_initialization]]; // #FIELD_D2
+  };
+
+  struct D2 : D {  // #TYPE_D2
+  };
+
+  struct E {  // #TYPE_E
+    int e1;
+    D e2 [[clang::requires_explicit_initialization]]; // #FIELD_E2
+    struct {
+      [[clang::requires_explicit_initialization]] D e3;
+      D2 e4 [[clang::requires_explicit_initialization]];
+    };
+  };
+
   S::foo(S{1, 2, 3, 4});
-  S::foo(S{.x = 100, .q = 100});
-  S::foo(S{.x = 100}); // expected-warning {{'q' is not explicitly initialized}}
-  S s{.x = 100, .q = 100};
+  S::foo(S{.s1 = 100, .s4 = 100});
+  S::foo(S{.s1 = 100}); // expected-warning {{field 's4' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}}
+
+  S s{.s1 = 100, .s4 = 100};
   (void)s;
-  S t{.q = 100}; // expected-warning {{'x' is not explicitly initialized}}
+
+  S t{.s4 = 100}; // expected-warning {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
   (void)t;
-  S *ptr1 = new S; // expected-warning {{not explicitly initialized}}
+
+  S *ptr1 = new S; // expected-warning {{field in 'S' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}} expected-note@#FIELD_S1 {{'s1' declared here}}
   delete ptr1;
-  S *ptr2 = new S{.x = 100, .q = 100};
+
+  S *ptr2 = new S{.s1 = 100, .s4 = 100};
   delete ptr2;
+
 #if __cplusplus >= 202002L
-  D a({}, 0); // expected-warning {{'x' is not explicitly initialized}} expected-warning {{'f2' is not explicitly initialized}}
+  // expected-warning at +2 {{field 'd2' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  // expected-warning {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  D a({}, 0);
   (void)a;
 #else
-  C a; // expected-warning {{not explicitly initialized}}
+  C a; // expected-warning {{field in 'C' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_C1 {{'c1' declared here}}
   (void)a;
 #endif
-  D b{.f2 = 1}; // expected-warning {{'x' is not explicitly initialized}} expected-warning {{'q' is not explicitly initialized}}
+
+  // expected-warning at +2 {{field 's4' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  D b{.d2 = 1};
   (void)b;
-  D c{.f1 = 5}; // expected-warning {{'x' is not explicitly initialized}} expected-warning {{'q' is not explicitly initialized}} expected-warning {{'f2' is not explicitly initialized}}
-  c = {{}, 0}; // expected-warning {{'x' is not explicitly initialized}} expected-warning {{'q' is not explicitly initialized}} expected-warning {{'f2' is not explicitly initialized}}
+
+  // expected-warning at +3 {{field 's4' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +2 {{field 'd2' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  // expected-warning at +1 {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  D c{.d1 = 5};
+
+  // expected-warning at +3 {{field 's4' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +2 {{field 'd2' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  // expected-warning at +1 {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  c = {{}, 0};
   (void)c;
-  D d; // expected-warning {{not explicitly initialized}}  // expected-note {{constructor}}
+
+  // expected-note at +3 {{in implicit default constructor for 'D' first required here}}
+  // expected-warning@#TYPE_D {{field in 'S' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field in 'D' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  D d;
   (void)d;
+
+  // expected-warning at +12 {{field in 'E' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-note@#FIELD_E2 {{'e2' declared here}}
+  // expected-warning@#TYPE_E {{field in 'D' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-note at +9 {{in implicit default constructor for 'E' first required here}}
+  // expected-note@#FIELD_D2 {{'d2' declared here}}
+  // expected-warning@#TYPE_E {{field in 'D' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-note@#FIELD_D2 {{'d2' declared here}}
+  // expected-warning@#TYPE_E {{field in 'D2' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-note@#TYPE_E {{in implicit default constructor for 'D2' first required here}}
+  // expected-warning@#TYPE_D2 {{field in 'D' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-note at +2 {{in implicit default constructor for 'E' first required here}}
+  // expected-note@#FIELD_D2 {{'d2' declared here}}
+  E e;
+  (void)e;
 }

>From 47c244a88a4a65c4d26566a7e3a660598c32c40c Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Wed, 20 Nov 2024 15:05:55 -0500
Subject: [PATCH 03/12] Remove handleExplicitInitAttr() as suggested

---
 clang/lib/Sema/SemaDeclAttr.cpp | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 045bc23af52b00..146d9c86e0715a 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6147,10 +6147,6 @@ 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));
 }
@@ -7065,9 +7061,6 @@ 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;

>From 44961d247feae0b2581e70cb50d33a54c4e35068 Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Wed, 20 Nov 2024 15:07:04 -0500
Subject: [PATCH 04/12] Linting

---
 clang/lib/AST/DeclCXX.cpp   | 21 +++++++++------------
 clang/lib/Sema/SemaDecl.cpp |  3 +--
 clang/lib/Sema/SemaInit.cpp |  3 +--
 3 files changed, 11 insertions(+), 16 deletions(-)

diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 11eaff4284d5fd..21e1cdc2ac4541 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -1118,9 +1118,8 @@ void CXXRecordDecl::addedMember(Decl *D) {
     } else if (!T.isCXX98PODType(Context))
       data().PlainOldData = false;
 
-    if (Field->hasAttr<ExplicitInitAttr>() && !Field->hasInClassInitializer()) {
+    if (Field->hasAttr<ExplicitInitAttr>() && !Field->hasInClassInitializer())
       setHasUninitializedExplicitInitFields(true);
-    }
 
     if (T->isReferenceType()) {
       if (!Field->hasInClassInitializer())
@@ -2202,13 +2201,12 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
   if (isAggregate() && hasUserDeclaredConstructor() &&
       !Context.getLangOpts().CPlusPlus20) {
     // Diagnose any aggregate behavior changes in C++20
-    for (field_iterator I = field_begin(), E = field_end(); I != E; ++I) {
-      if (const auto *attr = I->getAttr<ExplicitInitAttr>()) {
+    for (const FieldDecl *FD : fields()) {
+      if (const auto *AT = FD->getAttr<ExplicitInitAttr>())
         Context.getDiagnostics().Report(
-            attr->getLocation(),
+            AT->getLocation(),
             diag::warn_cxx20_compat_requires_explicit_init_non_aggregate)
-            << attr << Context.getRecordType(this);
-      }
+            << AT << Context.getRecordType(this);
     }
   }
 
@@ -2216,12 +2214,11 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
     // Diagnose any fields that required explicit initialization in a
     // non-aggregate type. (Note that the fields may not be directly in this
     // type, but in a subobject. In such cases we don't emit diagnoses here.)
-    for (field_iterator I = field_begin(), E = field_end(); I != E; ++I) {
-      if (const auto *attr = I->getAttr<ExplicitInitAttr>()) {
-        Context.getDiagnostics().Report(attr->getLocation(),
+    for (const FieldDecl *FD : fields()) {
+      if (const auto *AT = FD->getAttr<ExplicitInitAttr>())
+        Context.getDiagnostics().Report(AT->getLocation(),
                                         diag::warn_attribute_needs_aggregate)
-            << attr << Context.getRecordType(this);
-      }
+            << AT << Context.getRecordType(this);
     }
     setHasUninitializedExplicitInitFields(false);
   }
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 6885403169ee76..f2847555f803e6 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -19189,9 +19189,8 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
         if (FT.hasNonTrivialToPrimitiveCopyCUnion() || Record->isUnion())
           Record->setHasNonTrivialToPrimitiveCopyCUnion(true);
       }
-      if (FD->hasAttr<ExplicitInitAttr>()) {
+      if (FD->hasAttr<ExplicitInitAttr>())
         Record->setHasUninitializedExplicitInitFields(true);
-      }
       if (FT.isDestructedType()) {
         Record->setNonTrivialToPrimitiveDestroy(true);
         Record->setParamDestroyedInCallee(true);
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index a553f76e0caad6..d293e2d9944675 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -266,9 +266,8 @@ static void CheckStringInit(Expr *Str, QualType &DeclT, const ArrayType *AT,
 
 void emitUninitializedExplicitInitFields(Sema &S, const RecordDecl *R) {
   for (const FieldDecl *Field : R->fields()) {
-    if (Field->hasAttr<ExplicitInitAttr>()) {
+    if (Field->hasAttr<ExplicitInitAttr>())
       S.Diag(Field->getLocation(), diag::note_entity_declared_at) << Field;
-    }
   }
 }
 

>From eb073dac177db2c66ab58f1831c7e9c1eb12256a Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Wed, 20 Nov 2024 15:15:29 -0500
Subject: [PATCH 05/12] Error message improvements

---
 .../clang/Basic/DiagnosticSemaKinds.td        | 11 ++---
 clang/test/Sema/uninit-variables.c            |  4 +-
 clang/test/SemaCXX/uninitialized.cpp          | 42 +++++++++----------
 3 files changed, 29 insertions(+), 28 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 31f846c1b0825c..47bd44e1ff0e3a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2319,9 +2319,10 @@ def warn_cxx20_compat_aggregate_init_with_ctors : Warning<
   "aggregate initialization of type %0 with user-declared constructors "
   "is incompatible with C++20">, DefaultIgnore, InGroup<CXX20Compat>;
 def warn_cxx20_compat_requires_explicit_init_non_aggregate : Warning<
-  "explicit initialization of field %0 may not be enforced in C++20 as type %1 "
-  "will become a non-aggregate due to the presence of user-declared "
-  "constructors">, DefaultIgnore, InGroup<CXX20Compat>;
+  "explicit initialization of field %0 will not be enforced in C++20 and later "
+  "because %1 has a user-declared constructor, making the type no longer an "
+  "aggregate">,
+  DefaultIgnore, InGroup<CXX20Compat>;
 def warn_cxx17_compat_aggregate_init_paren_list : Warning<
   "aggregate initialization of type %0 from a parenthesized list of values "
   "is a C++20 extension">, DefaultIgnore, InGroup<CXX20>;
@@ -2352,8 +2353,8 @@ def err_init_reference_member_uninitialized : Error<
 def note_uninit_reference_member : Note<
   "uninitialized reference member is here">;
 def warn_field_requires_explicit_init : Warning<
-  "field %select{%1|in %1}0 is not explicitly initialized, but was marked as "
-  "requiring explicit initialization">, InGroup<UninitializedExplicitInit>;
+  "field %select{%1|in %1}0 requires explicit initialization but is not "
+   "explicitly initialized">, 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/test/Sema/uninit-variables.c b/clang/test/Sema/uninit-variables.c
index d4f4686b600c40..decb4700d07362 100644
--- a/clang/test/Sema/uninit-variables.c
+++ b/clang/test/Sema/uninit-variables.c
@@ -558,7 +558,7 @@ struct with_explicit_field {
 };
 
 void aggregate() {
-  struct with_explicit_field a; // expected-warning {{field in 'with_explicit_field' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_Y {{'y' declared here}}
-  struct with_explicit_field b = {1}; // expected-warning {{field 'y' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_Y {{'y' declared here}}
+  struct with_explicit_field a; // expected-warning {{field in 'with_explicit_field' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_Y {{'y' declared here}}
+  struct with_explicit_field b = {1}; // expected-warning {{field 'y' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_Y {{'y' declared here}}
   (void)(&a != &b);
 }
diff --git a/clang/test/SemaCXX/uninitialized.cpp b/clang/test/SemaCXX/uninitialized.cpp
index 95e04cdfe5909a..48426b444f5b27 100644
--- a/clang/test/SemaCXX/uninitialized.cpp
+++ b/clang/test/SemaCXX/uninitialized.cpp
@@ -1513,62 +1513,62 @@ void aggregate() {
 
   S::foo(S{1, 2, 3, 4});
   S::foo(S{.s1 = 100, .s4 = 100});
-  S::foo(S{.s1 = 100}); // expected-warning {{field 's4' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  S::foo(S{.s1 = 100}); // expected-warning {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
 
   S s{.s1 = 100, .s4 = 100};
   (void)s;
 
-  S t{.s4 = 100}; // expected-warning {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  S t{.s4 = 100}; // expected-warning {{field 's1' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}}
   (void)t;
 
-  S *ptr1 = new S; // expected-warning {{field in 'S' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  S *ptr1 = new S; // expected-warning {{field in 'S' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}} expected-note@#FIELD_S1 {{'s1' declared here}}
   delete ptr1;
 
   S *ptr2 = new S{.s1 = 100, .s4 = 100};
   delete ptr2;
 
 #if __cplusplus >= 202002L
-  // expected-warning at +2 {{field 'd2' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_D2 {{'d2' declared here}}
-  // expected-warning {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  // expected-warning at +2 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  // expected-warning {{field 's1' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}}
   D a({}, 0);
   (void)a;
 #else
-  C a; // expected-warning {{field in 'C' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_C1 {{'c1' declared here}}
+  C a; // expected-warning {{field in 'C' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_C1 {{'c1' declared here}}
   (void)a;
 #endif
 
-  // expected-warning at +2 {{field 's4' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}}
-  // expected-warning at +1 {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  // expected-warning at +2 {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field 's1' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}}
   D b{.d2 = 1};
   (void)b;
 
-  // expected-warning at +3 {{field 's4' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}}
-  // expected-warning at +2 {{field 'd2' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_D2 {{'d2' declared here}}
-  // expected-warning at +1 {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  // expected-warning at +3 {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +2 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  // expected-warning at +1 {{field 's1' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}}
   D c{.d1 = 5};
 
-  // expected-warning at +3 {{field 's4' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S4 {{'s4' declared here}}
-  // expected-warning at +2 {{field 'd2' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_D2 {{'d2' declared here}}
-  // expected-warning at +1 {{field 's1' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  // expected-warning at +3 {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +2 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  // expected-warning at +1 {{field 's1' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}}
   c = {{}, 0};
   (void)c;
 
   // expected-note at +3 {{in implicit default constructor for 'D' first required here}}
-  // expected-warning@#TYPE_D {{field in 'S' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_S1 {{'s1' declared here}} expected-note@#FIELD_S4 {{'s4' declared here}}
-  // expected-warning at +1 {{field in 'D' is not explicitly initialized, but was marked as requiring explicit initialization}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  // expected-warning@#TYPE_D {{field in 'S' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field in 'D' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
   D d;
   (void)d;
 
-  // expected-warning at +12 {{field in 'E' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-warning at +12 {{field in 'E' requires explicit initialization but is not explicitly initialized}}
   // expected-note@#FIELD_E2 {{'e2' declared here}}
-  // expected-warning@#TYPE_E {{field in 'D' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-warning@#TYPE_E {{field in 'D' requires explicit initialization but is not explicitly initialized}}
   // expected-note at +9 {{in implicit default constructor for 'E' first required here}}
   // expected-note@#FIELD_D2 {{'d2' declared here}}
-  // expected-warning@#TYPE_E {{field in 'D' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-warning@#TYPE_E {{field in 'D' requires explicit initialization but is not explicitly initialized}}
   // expected-note@#FIELD_D2 {{'d2' declared here}}
-  // expected-warning@#TYPE_E {{field in 'D2' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-warning@#TYPE_E {{field in 'D2' requires explicit initialization but is not explicitly initialized}}
   // expected-note@#TYPE_E {{in implicit default constructor for 'D2' first required here}}
-  // expected-warning@#TYPE_D2 {{field in 'D' is not explicitly initialized, but was marked as requiring explicit initialization}}
+  // expected-warning@#TYPE_D2 {{field in 'D' requires explicit initialization but is not explicitly initialized}}
   // expected-note at +2 {{in implicit default constructor for 'E' first required here}}
   // expected-note@#FIELD_D2 {{'d2' declared here}}
   E e;

>From 84a820f3d3fdc659f6641acfa0c75116f4da66f0 Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Wed, 20 Nov 2024 15:26:37 -0500
Subject: [PATCH 06/12] Update documentation

---
 clang/include/clang/Basic/AttrDocs.td | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 34fef2caf841cf..e5ed575db3971b 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1602,31 +1602,42 @@ 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.
+The ``clang::requires_explicit_initialization`` attribute indicates that a
+field of an aggregate must be initialized explicitly by the user when an object
+of the aggregate type is constructed. The attribute supports both C and C++,
+but its usage is invalid on non-aggregates.
 
 Note that this attribute is *not* a memory safety feature, and is *not* intended
 to guard against use of uninitialized memory.
 
-Rather, it is intended for use in "parameter-objects", used to simulate the
-passing of named parameters.
+Rather, it is intended for use in "parameter-objects", used to simulate,
+for example, the passing of named parameters.
 The attribute generates a warning when explicit initializers for such
-"named parameters" are not provided:
+variables are not provided (this occurs regardless of whether any in-class field
+initializers exist):
 
 .. code-block:: c++
 
+  struct Buffer {
+    void *address [[clang::requires_explicit_initialization]];
+    size_t length [[clang::requires_explicit_initialization]] = 0;
+  };
+
   struct ArrayIOParams {
     size_t count [[clang::requires_explicit_initialization]];
     size_t element_size [[clang::requires_explicit_initialization]];
     int flags = 0;
   };
 
-  size_t ReadArray(FILE *file, void *buffer, ArrayIOParams params);
+  size_t ReadArray(FILE *file, struct Buffer buffer,
+                   struct ArrayIOParams params);
 
   int main() {
     unsigned int buf[512];
-    ReadArray(stdin, buf, {
+    ReadArray(stdin, {
+      buf
+      // warning: field 'length' is not explicitly initialized
+    }, {
       .count = sizeof(buf) / sizeof(*buf),
       // warning: field 'element_size' is not explicitly initialized
     });

>From eac04e10556189f1d11de45c888d39a05705770a Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Wed, 20 Nov 2024 15:22:33 -0500
Subject: [PATCH 07/12] Fix bug in detecting in-class member initializers

---
 clang/lib/AST/DeclCXX.cpp            | 4 ++--
 clang/test/SemaCXX/uninitialized.cpp | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 21e1cdc2ac4541..99fc884c2ca0dc 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -1118,7 +1118,7 @@ void CXXRecordDecl::addedMember(Decl *D) {
     } else if (!T.isCXX98PODType(Context))
       data().PlainOldData = false;
 
-    if (Field->hasAttr<ExplicitInitAttr>() && !Field->hasInClassInitializer())
+    if (Field->hasAttr<ExplicitInitAttr>())
       setHasUninitializedExplicitInitFields(true);
 
     if (T->isReferenceType()) {
@@ -1376,7 +1376,7 @@ void CXXRecordDecl::addedMember(Decl *D) {
           data().ImplicitCopyAssignmentHasConstParam = false;
 
         if (FieldRec->hasUninitializedExplicitInitFields() &&
-            FieldRec->isAggregate() && !Field->hasInClassInitializer())
+            FieldRec->isAggregate())
           setHasUninitializedExplicitInitFields(true);
 
         if (FieldRec->hasUninitializedReferenceMember() &&
diff --git a/clang/test/SemaCXX/uninitialized.cpp b/clang/test/SemaCXX/uninitialized.cpp
index 48426b444f5b27..7c9783c3a86e8e 100644
--- a/clang/test/SemaCXX/uninitialized.cpp
+++ b/clang/test/SemaCXX/uninitialized.cpp
@@ -1490,7 +1490,7 @@ void aggregate() {
   };
 
   struct C {
-    [[clang::requires_explicit_initialization]] int c1; // #FIELD_C1
+    [[clang::requires_explicit_initialization]] int c1 = 2; // #FIELD_C1
     C() = default;  // Test pre-C++20 aggregates
   };
 

>From 7ddeb705678fb9d618aa16a05ea0f1f28ea3a2b6 Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Wed, 20 Nov 2024 15:42:31 -0500
Subject: [PATCH 08/12] Improve tests

---
 clang/include/clang/Basic/AttrDocs.td |  2 ++
 clang/test/Sema/uninit-variables.c    | 19 ++++++++++++++
 clang/test/SemaCXX/uninitialized.cpp  | 38 ++++++++++++++++++++++++++-
 3 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index e5ed575db3971b..c1e688d1faa241 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1640,6 +1640,8 @@ initializers exist):
     }, {
       .count = sizeof(buf) / sizeof(*buf),
       // warning: field 'element_size' is not explicitly initialized
+      // (Note that a missing initializer for 'flags' is not diagnosed, because
+      // the field is not marked as requiring explicit initialization.)
     });
   }
 
diff --git a/clang/test/Sema/uninit-variables.c b/clang/test/Sema/uninit-variables.c
index decb4700d07362..bad2e14bd28cb8 100644
--- a/clang/test/Sema/uninit-variables.c
+++ b/clang/test/Sema/uninit-variables.c
@@ -557,8 +557,27 @@ struct with_explicit_field {
   int y [[clang::requires_explicit_initialization]]; // #FIELD_Y
 };
 
+struct with_explicit_array {
+  [[clang::requires_explicit_initialization]] int arr[2]; // #FIELD_ARR
+};
+
+struct with_explicit_flex_array {
+  int x;
+  [[clang::requires_explicit_initialization]] int flex_arr[]; // #FIELD_FLEX_ARR
+};
+
 void aggregate() {
   struct with_explicit_field a; // expected-warning {{field in 'with_explicit_field' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_Y {{'y' declared here}}
   struct with_explicit_field b = {1}; // expected-warning {{field 'y' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_Y {{'y' declared here}}
   (void)(&a != &b);
+
+  struct with_explicit_field c = {1, 2};
+  struct with_explicit_field d = {.y = 3};
+  (void)(&c != &d);
+
+  struct with_explicit_array e = {{1}};  // OK -- part of array is still initialized
+  (void)e;
+
+  struct with_explicit_flex_array f = {2}; // expected-warning {{field 'flex_arr' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_FLEX_ARR {{'flex_arr' declared here}}
+  (void)f;
 }
diff --git a/clang/test/SemaCXX/uninitialized.cpp b/clang/test/SemaCXX/uninitialized.cpp
index 7c9783c3a86e8e..9ded2db24508d6 100644
--- a/clang/test/SemaCXX/uninitialized.cpp
+++ b/clang/test/SemaCXX/uninitialized.cpp
@@ -1473,6 +1473,30 @@ template<typename T> struct Outer {
 };
 Outer<int>::Inner outerinner;
 
+struct Polymorphic { virtual ~Polymorphic() { } };
+
+template<class... Bases>
+struct Inherit : Bases... { // #TYPE_INHERIT
+  int g1; // #FIELD_G1
+};
+
+template<class... Bases>
+struct InheritWithExplicit : Bases... { // #TYPE_INHERIT_WITH_EXPLICIT
+  int g2 [[clang::requires_explicit_initialization]]; // #FIELD_G2
+};
+
+struct Special {};
+
+template<>
+struct Inherit<Special> {
+  int g3 [[clang::requires_explicit_initialization]]; // #FIELD_G3
+};
+
+template<>
+struct InheritWithExplicit<Special> {
+  int g4; // #FIELD_G4
+};
+
 void aggregate() {
   struct NonAgg {
     NonAgg() { }
@@ -1490,7 +1514,7 @@ void aggregate() {
   };
 
   struct C {
-    [[clang::requires_explicit_initialization]] int c1 = 2; // #FIELD_C1
+    [[clang::requires_explicit_initialization]] int c1; // #FIELD_C1
     C() = default;  // Test pre-C++20 aggregates
   };
 
@@ -1573,4 +1597,16 @@ void aggregate() {
   // expected-note@#FIELD_D2 {{'d2' declared here}}
   E e;
   (void)e;
+
+  InheritWithExplicit<> agg;  // expected-warning {{field in 'InheritWithExplicit<>' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_G2 {{'g2' declared here}}
+  (void)agg;
+
+  InheritWithExplicit<Polymorphic> polymorphic;  // expected-warning@#FIELD_G2 {{'requires_explicit_initialization' attribute is ignored in non-aggregate type 'InheritWithExplicit<Polymorphic>'}}
+  (void)polymorphic;
+
+  Inherit<Special> specialized_explicit;  // expected-warning {{field in 'Inherit<Special>' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_G3 {{'g3' declared here}}
+  (void)specialized_explicit;
+
+  InheritWithExplicit<Special> specialized_implicit;  // no-warning
+  (void)specialized_implicit;
 }

>From 31c6dc6ed684b7d29134320cc1c0bab701fe7730 Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Wed, 20 Nov 2024 23:49:15 -0500
Subject: [PATCH 09/12] Add test case for pre-C++17-only aggregates

---
 clang/test/SemaCXX/uninitialized.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/clang/test/SemaCXX/uninitialized.cpp b/clang/test/SemaCXX/uninitialized.cpp
index 9ded2db24508d6..51ccbfb70e9fde 100644
--- a/clang/test/SemaCXX/uninitialized.cpp
+++ b/clang/test/SemaCXX/uninitialized.cpp
@@ -1515,6 +1515,9 @@ void aggregate() {
 
   struct C {
     [[clang::requires_explicit_initialization]] int c1; // #FIELD_C1
+#if 201703L <= __cplusplus && __cplusplus < 202002L
+    // expected-warning@#FIELD_C1 {{explicit initialization of field 'c1' will not be enforced in C++20 and later because 'C' has a user-declared constructor, making the type no longer an aggregate}}
+#endif
     C() = default;  // Test pre-C++20 aggregates
   };
 

>From 672eaced6d114656315780140f105e3177742c41 Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Thu, 21 Nov 2024 17:28:08 -0500
Subject: [PATCH 10/12] Attr.td

---
 clang/include/clang/Basic/Attr.td | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 80fb55a403ac9b..ad65c5005c54d8 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1886,7 +1886,7 @@ def Leaf : InheritableAttr {
 }
 
 def ExplicitInit : InheritableAttr {
-  let Spellings = [Clang<"requires_explicit_initialization", 1>];
+  let Spellings = [Clang<"requires_explicit_initialization">];
   let Subjects = SubjectList<[Field], ErrorDiag>;
   let Documentation = [ExplicitInitDocs];
   let SimpleHandler = 1;

>From f4c31be485ddfbcfb12205e4d0267a4e3cfb047d Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Fri, 22 Nov 2024 09:44:45 -0500
Subject: [PATCH 11/12] Move diagnostic to DiagnosticASTKinds.td

---
 clang/include/clang/Basic/DiagnosticASTKinds.td  | 10 ++++++++++
 clang/include/clang/Basic/DiagnosticSemaKinds.td |  9 ---------
 clang/lib/AST/DeclCXX.cpp                        |  2 +-
 3 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 6a658cf14356f5..095264f6e0c19a 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -390,6 +390,16 @@ def note_constexpr_assumption_failed : Note<
 def err_experimental_clang_interp_failed : Error<
   "the experimental clang interpreter failed to evaluate an expression">;
 
+def warn_attribute_needs_aggregate : Warning<
+  "%0 attribute is ignored in non-aggregate type %1">,
+  InGroup<IgnoredAttributes>;
+
+def warn_cxx20_compat_requires_explicit_init_non_aggregate : Warning<
+  "explicit initialization of field %0 will not be enforced in C++20 and later "
+  "because %1 has a user-declared constructor, making the type no longer an "
+  "aggregate">,
+  DefaultIgnore, InGroup<CXX20Compat>;
+
 def warn_integer_constant_overflow : Warning<
   "overflow in expression; result is %0 with type %1">,
   InGroup<DiagGroup<"integer-overflow">>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 47bd44e1ff0e3a..8f90caff3f62fd 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2318,11 +2318,6 @@ def err_init_list_bad_dest_type : Error<
 def warn_cxx20_compat_aggregate_init_with_ctors : Warning<
   "aggregate initialization of type %0 with user-declared constructors "
   "is incompatible with C++20">, DefaultIgnore, InGroup<CXX20Compat>;
-def warn_cxx20_compat_requires_explicit_init_non_aggregate : Warning<
-  "explicit initialization of field %0 will not be enforced in C++20 and later "
-  "because %1 has a user-declared constructor, making the type no longer an "
-  "aggregate">,
-  DefaultIgnore, InGroup<CXX20Compat>;
 def warn_cxx17_compat_aggregate_init_paren_list : Warning<
   "aggregate initialization of type %0 from a parenthesized list of values "
   "is a C++20 extension">, DefaultIgnore, InGroup<CXX20>;
@@ -3157,10 +3152,6 @@ def warn_attribute_ignored_no_calls_in_stmt: Warning<
   "statement">,
   InGroup<IgnoredAttributes>;
 
-def warn_attribute_needs_aggregate : Warning<
-  "%0 attribute is ignored in non-aggregate type %1">,
-  InGroup<IgnoredAttributes>;
-
 def warn_attribute_ignored_non_function_pointer: Warning<
   "%0 attribute is ignored because %1 is not a function pointer">,
   InGroup<IgnoredAttributes>;
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 99fc884c2ca0dc..b4b5af696d0425 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -29,7 +29,7 @@
 #include "clang/AST/TypeLoc.h"
 #include "clang/AST/UnresolvedSet.h"
 #include "clang/Basic/Diagnostic.h"
-#include "clang/Basic/DiagnosticSema.h"
+#include "clang/Basic/DiagnosticAST.h"
 #include "clang/Basic/IdentifierTable.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/LangOptions.h"

>From 5ed7102d0dc1b31de9cda206b8120f711a6ee9a4 Mon Sep 17 00:00:00 2001
From: higher-performance <higher.performance.github at gmail.com>
Date: Mon, 25 Nov 2024 06:01:33 -0500
Subject: [PATCH 12/12] More fixes

---
 .../include/clang/Basic/DiagnosticASTKinds.td |  4 +-
 clang/test/SemaCXX/uninitialized.cpp          | 52 +++++++++++++++----
 2 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 095264f6e0c19a..7129664a8371a6 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -395,8 +395,8 @@ def warn_attribute_needs_aggregate : Warning<
   InGroup<IgnoredAttributes>;
 
 def warn_cxx20_compat_requires_explicit_init_non_aggregate : Warning<
-  "explicit initialization of field %0 will not be enforced in C++20 and later "
-  "because %1 has a user-declared constructor, making the type no longer an "
+  "explicit initialization of field %1 will not be enforced in C++20 and later "
+  "because %2 has a user-declared constructor, making the type no longer an "
   "aggregate">,
   DefaultIgnore, InGroup<CXX20Compat>;
 
diff --git a/clang/test/SemaCXX/uninitialized.cpp b/clang/test/SemaCXX/uninitialized.cpp
index 51ccbfb70e9fde..95dd488e81a624 100644
--- a/clang/test/SemaCXX/uninitialized.cpp
+++ b/clang/test/SemaCXX/uninitialized.cpp
@@ -1,5 +1,6 @@
-// RUN: %clang_cc1 -fsyntax-only -Wall -Wuninitialized -Wno-unused-value -Wno-unused-lambda-capture -Wno-uninitialized-const-reference -std=c++1z -verify %s
-// RUN: %clang_cc1 -fsyntax-only -Wall -Wuninitialized -Wno-unused-value -Wno-unused-lambda-capture -Wno-uninitialized-const-reference -std=c++1z -verify %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -fsyntax-only -Wall -Wc++20-compat -Wuninitialized -Wno-unused-value -Wno-unused-lambda-capture -Wno-uninitialized-const-reference -std=c++1z -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wall -Wc++20-compat -Wuninitialized -Wno-unused-value -Wno-unused-lambda-capture -Wno-uninitialized-const-reference -std=c++1z -verify %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -fsyntax-only -Wall -Wc++20-compat -Wuninitialized -Wno-unused-value -Wno-unused-lambda-capture -Wno-uninitialized-const-reference -std=c++20 -verify %s
 
 // definitions for std::move
 namespace std {
@@ -1514,10 +1515,11 @@ void aggregate() {
   };
 
   struct C {
-    [[clang::requires_explicit_initialization]] int c1; // #FIELD_C1
-#if 201703L <= __cplusplus && __cplusplus < 202002L
-    // expected-warning@#FIELD_C1 {{explicit initialization of field 'c1' will not be enforced in C++20 and later because 'C' has a user-declared constructor, making the type no longer an aggregate}}
+#if __cplusplus < 202002L
+    // expected-warning at +1 {{explicit initialization of field 'c1' will not be enforced in C++20 and later because 'C' has a user-declared constructor, making the type no longer an aggregate}}
+    [[clang::requires_explicit_initialization]]
 #endif
+    int c1; // #FIELD_C1
     C() = default;  // Test pre-C++20 aggregates
   };
 
@@ -1555,11 +1557,41 @@ void aggregate() {
   delete ptr2;
 
 #if __cplusplus >= 202002L
-  // expected-warning at +2 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
-  // expected-warning {{field 's1' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}}
-  D a({}, 0);
-  (void)a;
-#else
+  // expected-warning at +3 {{field 's1' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  // expected-warning at +2 {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  D a1({}, 0);
+  (void)a1;
+
+  // expected-warning at +3 {{field 's1' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  // expected-warning at +2 {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  D a2(S{}, 0);
+  (void)a2;
+
+  // expected-warning at +2 {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  D a3(S{.s1 = 0}, 0);
+  (void)a3;
+
+  // expected-warning at +3 {{field 's1' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S1 {{'s1' declared here}}
+  // expected-warning at +2 {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  D a4(S(), 0);
+  (void)a4;
+
+  // expected-warning at +2 {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  D a5(S(0), 0);
+  (void)a5;
+
+  // expected-warning at +2 {{field 's4' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_S4 {{'s4' declared here}}
+  // expected-warning at +1 {{field 'd2' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_D2 {{'d2' declared here}}
+  D a6 = {S(0), 0};
+  (void)a6;
+#endif
+
+#if 201103L <= __cplusplus && __cplusplus < 202002L
   C a; // expected-warning {{field in 'C' requires explicit initialization but is not explicitly initialized}} expected-note@#FIELD_C1 {{'c1' declared here}}
   (void)a;
 #endif



More information about the cfe-commits mailing list