Skip to content

Add Clang attribute to ensure that fields are initialized explicitly #102040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4271,6 +4271,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.
Expand Down
13 changes: 12 additions & 1 deletion clang/include/clang/AST/DeclBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,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.

Expand Down Expand Up @@ -1667,6 +1670,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;
Expand All @@ -1681,7 +1692,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.
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -1891,6 +1891,13 @@ def Leaf : InheritableAttr {
let SimpleHandler = 1;
}

def ExplicitInit : InheritableAttr {
let Spellings = [Clang<"requires_explicit_initialization">];
let Subjects = SubjectList<[Field], ErrorDiag>;
let Documentation = [ExplicitInitDocs];
let SimpleHandler = 1;
}

def LifetimeBound : DeclOrTypeAttr {
let Spellings = [Clang<"lifetimebound", 0>];
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
Expand Down
49 changes: 49 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,55 @@ is not specified.
}];
}

def ExplicitInitDocs : Documentation {
let Category = DocCatField;
let Content = [{
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,
for example, the passing of named parameters.
The attribute generates a warning when explicit initializers for such
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, struct Buffer buffer,
struct ArrayIOParams params);

int main() {
unsigned int buf[512];
ReadArray(stdin, {
buf
// warning: field 'length' is not explicitly initialized
}, {
.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.)
});
}

}];
}

def NoUniqueAddressDocs : Documentation {
let Category = DocCatField;
let Content = [{
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 %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>;

def warn_integer_constant_overflow : Warning<
"overflow in expression; result is %0 with type %1">,
InGroup<DiagGroup<"integer-overflow">>;
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ def Trigraphs : DiagGroup<"trigraphs">;
def UndefinedReinterpretCast : DiagGroup<"undefined-reinterpret-cast">;
def ReinterpretBaseClass : DiagGroup<"reinterpret-base-class">;
def Unicode : DiagGroup<"unicode">;
def UninitializedExplicitInit : DiagGroup<"uninitialized-explicit-init">;
def UninitializedMaybe : DiagGroup<"conditional-uninitialized">;
def UninitializedSometimes : DiagGroup<"sometimes-uninitialized">;
def UninitializedStaticSelfInit : DiagGroup<"static-self-init">;
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -2347,6 +2347,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 %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<
Expand Down
8 changes: 5 additions & 3 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anyone have a good intuition for how much this increases the chances of ODR hash collisions? I know we're only stealing one bit, but those bits add up.

return RecordDeclBits.ODRHash;
}

Expand Down
39 changes: 39 additions & 0 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "clang/AST/TypeLoc.h"
#include "clang/AST/UnresolvedSet.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticAST.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/LangOptions.h"
Expand Down Expand Up @@ -457,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;

Expand Down Expand Up @@ -1113,6 +1118,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
} else if (!T.isCXX98PODType(Context))
data().PlainOldData = false;

if (Field->hasAttr<ExplicitInitAttr>())
setHasUninitializedExplicitInitFields(true);

if (T->isReferenceType()) {
if (!Field->hasInClassInitializer())
data().HasUninitializedReferenceMember = true;
Expand Down Expand Up @@ -1372,6 +1380,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (!FieldRec->hasCopyAssignmentWithConstParam())
data().ImplicitCopyAssignmentHasConstParam = false;

if (FieldRec->hasUninitializedExplicitInitFields() &&
FieldRec->isAggregate())
setHasUninitializedExplicitInitFields(true);

if (FieldRec->hasUninitializedReferenceMember() &&
!Field->hasInClassInitializer())
data().HasUninitializedReferenceMember = true;
Expand Down Expand Up @@ -2188,6 +2200,33 @@ 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 (isAggregate() && hasUserDeclaredConstructor() &&
!Context.getLangOpts().CPlusPlus20) {
// Diagnose any aggregate behavior changes in C++20
for (const FieldDecl *FD : fields()) {
if (const auto *AT = FD->getAttr<ExplicitInitAttr>())
Context.getDiagnostics().Report(
AT->getLocation(),
diag::warn_cxx20_compat_requires_explicit_init_non_aggregate)
<< AT << FD << 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 (const FieldDecl *FD : fields()) {
if (const auto *AT = FD->getAttr<ExplicitInitAttr>())
Context.getDiagnostics().Report(AT->getLocation(),
diag::warn_attribute_needs_aggregate)
<< AT << Context.getRecordType(this);
}
setHasUninitializedExplicitInitFields(false);
}
}

bool CXXRecordDecl::mayBeAbstract() const {
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19221,6 +19221,8 @@ 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);
Expand Down
58 changes: 58 additions & 0 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,13 @@ 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.
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -738,6 +745,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)
<< /* Var-in-Record */ 0 << 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
Expand Down Expand Up @@ -4563,6 +4578,14 @@ 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)
<< /* Var-in-Record */ 1 << DestRecordDecl;
emitUninitializedExplicitInitFields(S, DestRecordDecl);
}

// C++11 [dcl.init]p6:
// If a program calls for the default initialization of an object
// of a const-qualified type T, T shall be a class type with a
Expand Down Expand Up @@ -5857,6 +5880,12 @@ static void TryOrBuildParenListInitialization(
} else {
// We've processed all of the args, but there are still members that
// have to be initialized.
if (!VerifyOnly && FD->hasAttr<ExplicitInitAttr>()) {
S.Diag(Kind.getLocation(), diag::warn_field_requires_explicit_init)
<< /* Var-in-Record */ 0 << FD;
S.Diag(FD->getLocation(), diag::note_entity_declared_at) << FD;
}

if (FD->hasInClassInitializer()) {
if (!VerifyOnly) {
// C++ [dcl.init]p16.6.2.2
Expand Down Expand Up @@ -6462,6 +6491,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:
Expand Down Expand Up @@ -7315,6 +7357,22 @@ PerformConstructorInitialization(Sema &S,
if (S.DiagnoseUseOfDecl(Step.Function.FoundDecl, Loc))
return ExprError();

if (Kind.getKind() == InitializationKind::IK_Value &&
Constructor->isImplicit()) {
auto *RD = Step.Type.getCanonicalType()->getAsCXXRecordDecl();
if (RD && RD->isAggregate() && RD->hasUninitializedExplicitInitFields()) {
unsigned I = 0;
for (const FieldDecl *FD : RD->fields()) {
if (I >= ConstructorArgs.size() && FD->hasAttr<ExplicitInitAttr>()) {
S.Diag(Loc, diag::warn_field_requires_explicit_init)
<< /* Var-in-Record */ 0 << FD;
S.Diag(FD->getLocation(), diag::note_entity_declared_at) << FD;
}
++I;
}
}
}

TypeSourceInfo *TSInfo = Entity.getTypeSourceInfo();
if (!TSInfo)
TSInfo = S.Context.getTrivialTypeSourceInfo(Entity.getType(), Loc);
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,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));
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/Serialization/ASTWriterDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,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);
Expand Down Expand Up @@ -2479,7 +2480,8 @@ void ASTWriter::WriteDeclAbbrevs() {
// isNonTrivialToPrimitiveCopy, isNonTrivialToPrimitiveDestroy,
// hasNonTrivialToPrimitiveDefaultInitializeCUnion,
// hasNonTrivialToPrimitiveDestructCUnion,
// hasNonTrivialToPrimitiveCopyCUnion, isParamDestroyedInCallee,
// hasNonTrivialToPrimitiveCopyCUnion,
// hasUninitializedExplicitInitFields, isParamDestroyedInCallee,
// getArgPassingRestrictions
// ODRHash
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 26));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading