Skip to content
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

[Sema] Instantiate destructors for initialized members #128866

Merged
merged 7 commits into from
Mar 13, 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
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ Bug Fixes to C++ Support
not in the last position.
- Clang now correctly parses ``if constexpr`` expressions in immediate function context. (#GH123524)
- Fixed an assertion failure affecting code that uses C++23 "deducing this". (#GH130272)
- Clang now properly instantiates destructors for initialized members within non-delegating constructors. (#GH93251)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -5451,7 +5451,8 @@ class Sema final : public SemaBase {
/// destructor is referenced.
void MarkVirtualBaseDestructorsReferenced(
SourceLocation Location, CXXRecordDecl *ClassDecl,
llvm::SmallPtrSetImpl<const RecordType *> *DirectVirtualBases = nullptr);
llvm::SmallPtrSetImpl<const CXXRecordDecl *> *DirectVirtualBases =
nullptr);

/// Do semantic checks to allow the complete destructor variant to be emitted
/// when the destructor is defined in another translation unit. In the Itanium
Expand Down
225 changes: 121 additions & 104 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5289,6 +5289,100 @@ Sema::SetDelegatingInitializer(CXXConstructorDecl *Constructor,
return false;
}

static CXXDestructorDecl *LookupDestructorIfRelevant(Sema &S,
CXXRecordDecl *Class) {
if (Class->isInvalidDecl())
return nullptr;
if (Class->hasIrrelevantDestructor())
return nullptr;

// Dtor might still be missing, e.g because it's invalid.
return S.LookupDestructor(Class);
}

static void MarkFieldDestructorReferenced(Sema &S, SourceLocation Location,
FieldDecl *Field) {
if (Field->isInvalidDecl())
return;

// Don't destroy incomplete or zero-length arrays.
if (isIncompleteOrZeroLengthArrayType(S.Context, Field->getType()))
return;

QualType FieldType = S.Context.getBaseElementType(Field->getType());

auto *FieldClassDecl = FieldType->getAsCXXRecordDecl();
if (!FieldClassDecl)
return;

// The destructor for an implicit anonymous union member is never invoked.
if (FieldClassDecl->isUnion() && FieldClassDecl->isAnonymousStructOrUnion())
return;

auto *Dtor = LookupDestructorIfRelevant(S, FieldClassDecl);
if (!Dtor)
return;

S.CheckDestructorAccess(Field->getLocation(), Dtor,
S.PDiag(diag::err_access_dtor_field)
<< Field->getDeclName() << FieldType);

S.MarkFunctionReferenced(Location, Dtor);
S.DiagnoseUseOfDecl(Dtor, Location);
}

static void MarkBaseDestructorsReferenced(Sema &S, SourceLocation Location,
CXXRecordDecl *ClassDecl) {
if (ClassDecl->isDependentContext())
return;

// We only potentially invoke the destructors of potentially constructed
// subobjects.
bool VisitVirtualBases = !ClassDecl->isAbstract();

// If the destructor exists and has already been marked used in the MS ABI,
// then virtual base destructors have already been checked and marked used.
// Skip checking them again to avoid duplicate diagnostics.
if (S.Context.getTargetInfo().getCXXABI().isMicrosoft()) {
CXXDestructorDecl *Dtor = ClassDecl->getDestructor();
if (Dtor && Dtor->isUsed())
VisitVirtualBases = false;
}

llvm::SmallPtrSet<const CXXRecordDecl *, 8> DirectVirtualBases;

// Bases.
for (const auto &Base : ClassDecl->bases()) {
auto *BaseClassDecl = Base.getType()->getAsCXXRecordDecl();
if (!BaseClassDecl)
continue;

// Remember direct virtual bases.
if (Base.isVirtual()) {
if (!VisitVirtualBases)
continue;
DirectVirtualBases.insert(BaseClassDecl);
}

auto *Dtor = LookupDestructorIfRelevant(S, BaseClassDecl);
if (!Dtor)
continue;

// FIXME: caret should be on the start of the class name
S.CheckDestructorAccess(Base.getBeginLoc(), Dtor,
S.PDiag(diag::err_access_dtor_base)
<< Base.getType() << Base.getSourceRange(),
S.Context.getTypeDeclType(ClassDecl));

S.MarkFunctionReferenced(Location, Dtor);
S.DiagnoseUseOfDecl(Dtor, Location);
}

if (VisitVirtualBases)
S.MarkVirtualBaseDestructorsReferenced(Location, ClassDecl,
&DirectVirtualBases);
}

bool Sema::SetCtorInitializers(CXXConstructorDecl *Constructor, bool AnyErrors,
ArrayRef<CXXCtorInitializer *> Initializers) {
if (Constructor->isDependentContext()) {
Expand Down Expand Up @@ -5456,10 +5550,24 @@ bool Sema::SetCtorInitializers(CXXConstructorDecl *Constructor, bool AnyErrors,
NumInitializers * sizeof(CXXCtorInitializer*));
Constructor->setCtorInitializers(baseOrMemberInitializers);

SourceLocation Location = Constructor->getLocation();

// Constructors implicitly reference the base and member
// destructors.
MarkBaseAndMemberDestructorsReferenced(Constructor->getLocation(),
Constructor->getParent());

for (CXXCtorInitializer *Initializer : Info.AllToInit) {
FieldDecl *Field = Initializer->getAnyMember();
if (!Field)
continue;

// C++ [class.base.init]p12:
// In a non-delegating constructor, the destructor for each
// potentially constructed subobject of class type is potentially
// invoked.
MarkFieldDestructorReferenced(*this, Location, Field);
}

MarkBaseDestructorsReferenced(*this, Location, Constructor->getParent());
}

return HadError;
Expand Down Expand Up @@ -5764,9 +5872,8 @@ void Sema::ActOnMemInitializers(Decl *ConstructorDecl,
DiagnoseUninitializedFields(*this, Constructor);
}

void
Sema::MarkBaseAndMemberDestructorsReferenced(SourceLocation Location,
CXXRecordDecl *ClassDecl) {
void Sema::MarkBaseAndMemberDestructorsReferenced(SourceLocation Location,
CXXRecordDecl *ClassDecl) {
// Ignore dependent contexts. Also ignore unions, since their members never
// have destructors implicitly called.
if (ClassDecl->isDependentContext() || ClassDecl->isUnion())
Expand All @@ -5779,119 +5886,29 @@ Sema::MarkBaseAndMemberDestructorsReferenced(SourceLocation Location,

// Non-static data members.
for (auto *Field : ClassDecl->fields()) {
if (Field->isInvalidDecl())
continue;

// Don't destroy incomplete or zero-length arrays.
if (isIncompleteOrZeroLengthArrayType(Context, Field->getType()))
continue;

QualType FieldType = Context.getBaseElementType(Field->getType());

const RecordType* RT = FieldType->getAs<RecordType>();
if (!RT)
continue;

CXXRecordDecl *FieldClassDecl = cast<CXXRecordDecl>(RT->getDecl());
if (FieldClassDecl->isInvalidDecl())
continue;
if (FieldClassDecl->hasIrrelevantDestructor())
continue;
// The destructor for an implicit anonymous union member is never invoked.
if (FieldClassDecl->isUnion() && FieldClassDecl->isAnonymousStructOrUnion())
continue;

CXXDestructorDecl *Dtor = LookupDestructor(FieldClassDecl);
// Dtor might still be missing, e.g because it's invalid.
if (!Dtor)
continue;
CheckDestructorAccess(Field->getLocation(), Dtor,
PDiag(diag::err_access_dtor_field)
<< Field->getDeclName()
<< FieldType);

MarkFunctionReferenced(Location, Dtor);
DiagnoseUseOfDecl(Dtor, Location);
}

// We only potentially invoke the destructors of potentially constructed
// subobjects.
bool VisitVirtualBases = !ClassDecl->isAbstract();

// If the destructor exists and has already been marked used in the MS ABI,
// then virtual base destructors have already been checked and marked used.
// Skip checking them again to avoid duplicate diagnostics.
if (Context.getTargetInfo().getCXXABI().isMicrosoft()) {
CXXDestructorDecl *Dtor = ClassDecl->getDestructor();
if (Dtor && Dtor->isUsed())
VisitVirtualBases = false;
MarkFieldDestructorReferenced(*this, Location, Field);
}

llvm::SmallPtrSet<const RecordType *, 8> DirectVirtualBases;

// Bases.
for (const auto &Base : ClassDecl->bases()) {
const RecordType *RT = Base.getType()->getAs<RecordType>();
if (!RT)
continue;

// Remember direct virtual bases.
if (Base.isVirtual()) {
if (!VisitVirtualBases)
continue;
DirectVirtualBases.insert(RT);
}

CXXRecordDecl *BaseClassDecl = cast<CXXRecordDecl>(RT->getDecl());
// If our base class is invalid, we probably can't get its dtor anyway.
if (BaseClassDecl->isInvalidDecl())
continue;
if (BaseClassDecl->hasIrrelevantDestructor())
continue;

CXXDestructorDecl *Dtor = LookupDestructor(BaseClassDecl);
// Dtor might still be missing, e.g because it's invalid.
if (!Dtor)
continue;

// FIXME: caret should be on the start of the class name
CheckDestructorAccess(Base.getBeginLoc(), Dtor,
PDiag(diag::err_access_dtor_base)
<< Base.getType() << Base.getSourceRange(),
Context.getTypeDeclType(ClassDecl));

MarkFunctionReferenced(Location, Dtor);
DiagnoseUseOfDecl(Dtor, Location);
}

if (VisitVirtualBases)
MarkVirtualBaseDestructorsReferenced(Location, ClassDecl,
&DirectVirtualBases);
MarkBaseDestructorsReferenced(*this, Location, ClassDecl);
}

void Sema::MarkVirtualBaseDestructorsReferenced(
SourceLocation Location, CXXRecordDecl *ClassDecl,
llvm::SmallPtrSetImpl<const RecordType *> *DirectVirtualBases) {
llvm::SmallPtrSetImpl<const CXXRecordDecl *> *DirectVirtualBases) {
// Virtual bases.
for (const auto &VBase : ClassDecl->vbases()) {
// Bases are always records in a well-formed non-dependent class.
const RecordType *RT = VBase.getType()->castAs<RecordType>();

// Ignore already visited direct virtual bases.
if (DirectVirtualBases && DirectVirtualBases->count(RT))
auto *BaseClassDecl = VBase.getType()->getAsCXXRecordDecl();
if (!BaseClassDecl)
continue;

CXXRecordDecl *BaseClassDecl = cast<CXXRecordDecl>(RT->getDecl());
// If our base class is invalid, we probably can't get its dtor anyway.
if (BaseClassDecl->isInvalidDecl())
continue;
if (BaseClassDecl->hasIrrelevantDestructor())
// Ignore already visited direct virtual bases.
if (DirectVirtualBases && DirectVirtualBases->count(BaseClassDecl))
continue;

CXXDestructorDecl *Dtor = LookupDestructor(BaseClassDecl);
// Dtor might still be missing, e.g because it's invalid.
auto *Dtor = LookupDestructorIfRelevant(*this, BaseClassDecl);
if (!Dtor)
continue;

if (CheckDestructorAccess(
ClassDecl->getLocation(), Dtor,
PDiag(diag::err_access_dtor_vbase)
Expand Down
6 changes: 3 additions & 3 deletions clang/test/SemaCXX/cxx0x-nontrivial-union.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,18 @@ namespace disabled_dtor {
union disable_dtor {
T val;
template<typename...U>
disable_dtor(U &&...u) : val(forward<U>(u)...) {}
disable_dtor(U &&...u) : val(forward<U>(u)...) {} // expected-error {{attempt to use a deleted function}}
~disable_dtor() {}
};

struct deleted_dtor {
deleted_dtor(int n, char c) : n(n), c(c) {}
int n;
char c;
~deleted_dtor() = delete;
~deleted_dtor() = delete; // expected-note {{'~deleted_dtor' has been explicitly marked deleted here}}
};

disable_dtor<deleted_dtor> dd(4, 'x');
disable_dtor<deleted_dtor> dd(4, 'x'); // expected-note {{in instantiation of function template specialization 'disabled_dtor::disable_dtor<disabled_dtor::deleted_dtor>::disable_dtor<int, char>' requested here}}
}

namespace optional {
Expand Down
50 changes: 50 additions & 0 deletions clang/test/SemaCXX/union-member-destructor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s

namespace t1 {
template <class T> struct VSX {
~VSX() { static_assert(sizeof(T) != 4, ""); } // expected-error {{static assertion failed due to requirement 'sizeof(int) != 4':}} \
// expected-note {{expression evaluates to '4 != 4'}}
};
struct VS {
union {
VSX<int> _Tail;
};
~VS() { }
VS(short);
VS();
};
VS::VS() : VS(0) { } // delegating constructors should not produce errors
VS::VS(short) : _Tail() { } // expected-note {{in instantiation of member function 't1::VSX<int>::~VSX' requested here}}
}


namespace t2 {
template <class T> struct VSX {
~VSX() { static_assert(sizeof(T) != 4, ""); } // expected-error {{static assertion failed due to requirement 'sizeof(int) != 4':}} \
// expected-note {{expression evaluates to '4 != 4'}}
};
struct VS {
union {
struct {
VSX<int> _Tail;
};
};
~VS() { }
VS(short);
};
VS::VS(short) : _Tail() { } // expected-note {{in instantiation of member function 't2::VSX<int>::~VSX' requested here}}
}


namespace t3 {
template <class T> struct VSX {
~VSX() { static_assert(sizeof(T) != 4, ""); } // expected-error {{static assertion failed due to requirement 'sizeof(int) != 4':}} \
// expected-note {{expression evaluates to '4 != 4'}}
};
union VS {
VSX<int> _Tail;
~VS() { }
VS(short);
};
VS::VS(short) : _Tail() { } // expected-note {{in instantiation of member function 't3::VSX<int>::~VSX' requested here}}
}