-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[clang] Avoid triggering vtable instantiation for C++23 constexpr dtor #102605
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
Conversation
In C++23 anything can be constexpr, including a dtor of a class whose members and bases don't have constexpr dtors. Avoid early triggering of vtable instantiation int this case. Fixes llvm#102293
I'm really in doubts that this is actually correct and useful... |
@llvm/pr-subscribers-clang Author: Mariya Podchishchaeva (Fznamznon) ChangesIn C++23 anything can be constexpr, including a dtor of a class whose members and bases don't have constexpr dtors. Avoid early triggering of vtable instantiation int this case. Fixes #102293 Full diff: https://github.com/llvm/llvm-project/pull/102605.diff 2 Files Affected:
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index b07e555afcaccf..63d2131acdd650 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7042,12 +7042,38 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
}
}
+ bool EffectivelyConstexprDestructor = true;
+ // Avoid triggering vtable instantiation due to a dtor that is not
+ // "effectively constexpr" for better compatibility.
+ if (isa<CXXDestructorDecl>(M)) {
+ auto Check = [](QualType T, auto &&Check) -> bool {
+ const CXXRecordDecl *RD =
+ T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
+ if (!RD || !RD->isCompleteDefinition())
+ return true;
+
+ if (!RD->hasConstexprDestructor())
+ return false;
+
+ for (const CXXBaseSpecifier &B : RD->bases())
+ if (!Check(B.getType(), Check))
+ return false;
+ for (const FieldDecl *FD : RD->fields())
+ if (!Check(FD->getType(), Check))
+ return false;
+ return true;
+ };
+ EffectivelyConstexprDestructor =
+ Check(QualType(Record->getTypeForDecl(), 0), Check);
+ }
+
// Define defaulted constexpr virtual functions that override a base class
// function right away.
// FIXME: We can defer doing this until the vtable is marked as used.
if (CSM != CXXSpecialMemberKind::Invalid && !M->isDeleted() &&
M->isDefaulted() && M->isConstexpr() && M->size_overridden_methods())
- DefineDefaultedFunction(*this, M, M->getLocation());
+ if (EffectivelyConstexprDestructor)
+ DefineDefaultedFunction(*this, M, M->getLocation());
if (!Incomplete)
CheckCompletedMemberFunction(M);
diff --git a/clang/test/SemaCXX/gh102293.cpp b/clang/test/SemaCXX/gh102293.cpp
new file mode 100644
index 00000000000000..30629fc03bf6a9
--- /dev/null
+++ b/clang/test/SemaCXX/gh102293.cpp
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+template <typename T> static void destroy() {
+ T t;
+ ++t;
+}
+
+struct Incomplete;
+
+template <typename = int> struct HasD {
+ ~HasD() { destroy<Incomplete*>(); }
+};
+
+struct HasVT {
+ virtual ~HasVT();
+};
+
+struct S : HasVT {
+ HasD<> v;
+};
+
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General direction looks good!
clang/lib/Sema/SemaDeclCXX.cpp
Outdated
if (EffectivelyConstexprDestructor) | ||
DefineDefaultedFunction(*this, M, M->getLocation()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can merge the two if statements
if (isa<CXXDestructorDecl>(M)) { | ||
auto Check = [](QualType T, auto &&Check) -> bool { | ||
const CXXRecordDecl *RD = | ||
T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl(); | ||
if (!RD || !RD->isCompleteDefinition()) | ||
return true; | ||
|
||
if (!RD->hasConstexprDestructor()) | ||
return false; | ||
|
||
for (const CXXBaseSpecifier &B : RD->bases()) | ||
if (!Check(B.getType(), Check)) | ||
return false; | ||
for (const FieldDecl *FD : RD->fields()) | ||
if (!Check(FD->getType(), Check)) | ||
return false; | ||
return true; | ||
}; | ||
EffectivelyConstexprDestructor = | ||
Check(QualType(Record->getTypeForDecl(), 0), Check); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Record->defaultedDestructorIsConstexpr()
would give the right result even if C++23, so I think we can simplify that to CSM != CXXSpecialMemberKind::Destructor || Record->defaultedDestructorIsConstexpr()
Could you try?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, unfortunately Record->defaultedDestructorIsConstexpr()
returns false for C++23.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because it's not effectively constexpr :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, woops, I meant it returns true
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to this
llvm-project/clang/lib/AST/DeclCXX.cpp
Line 1342 in 52126dc
data().DefaultedDefaultConstructorIsConstexpr = |
I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. That's unfortunate. Oh well, good enough!
// Avoid triggering vtable instantiation due to a dtor that is not | ||
// "effectively constexpr" for better compatibility. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want more comment here (and/or a reference to the issue)
struct S : HasVT { | ||
HasD<> v; | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happen if you do
static_assert((S{}, 1));
(and do we care that it's probably going to complain about the destructor not being defined?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It complains
gh102293.cpp:23:15: error: static assertion expression is not an integral constant expression
23 | static_assert((S{}, 1));
| ^~~~~~~~
gh102293.cpp:23:16: note: non-constexpr function '~HasD' cannot be used in a constant expression
23 | static_assert((S{}, 1));
| ^
gh102293.cpp:23:16: note: in call to 'S{}.~S()'
23 | static_assert((S{}, 1));
| ^
gh102293.cpp:12:3: note: declared here
12 | ~HasD() { destroy<Incomplete*>(); }
| ^
gh102293.cpp:6:5: error: arithmetic on a pointer to an incomplete type 'Incomplete'
6 | ++t;
| ^ ~
gh102293.cpp:12:13: note: in instantiation of function template specialization 'destroy<Incomplete *>' requested here
12 | ~HasD() { destroy<Incomplete*>(); }
| ^
gh102293.cpp:19:8: note: in instantiation of member function 'HasD<>::~HasD' requested here
19 | struct S : HasVT {
| ^
gh102293.cpp:9:8: note: forward declaration of 'Incomplete'
9 | struct Incomplete;
| ^
I suppose expectedly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there NO tests checking that complain?
Does this also fix #92486? |
Yes, it does. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
if (isa<CXXDestructorDecl>(M)) { | ||
auto Check = [](QualType T, auto &&Check) -> bool { | ||
const CXXRecordDecl *RD = | ||
T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl(); | ||
if (!RD || !RD->isCompleteDefinition()) | ||
return true; | ||
|
||
if (!RD->hasConstexprDestructor()) | ||
return false; | ||
|
||
for (const CXXBaseSpecifier &B : RD->bases()) | ||
if (!Check(B.getType(), Check)) | ||
return false; | ||
for (const FieldDecl *FD : RD->fields()) | ||
if (!Check(FD->getType(), Check)) | ||
return false; | ||
return true; | ||
}; | ||
EffectivelyConstexprDestructor = | ||
Check(QualType(Record->getTypeForDecl(), 0), Check); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. That's unfortunate. Oh well, good enough!
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/66/builds/2746 Here is the relevant piece of the build log for the reference:
|
Will this be backported to release/19.x? |
llvm#102605) In C++23 anything can be constexpr, including a dtor of a class whose members and bases don't have constexpr dtors. Avoid early triggering of vtable instantiation int this case. Fixes llvm#102293 (cherry picked from commit d469794)
llvm#102605) In C++23 anything can be constexpr, including a dtor of a class whose members and bases don't have constexpr dtors. Avoid early triggering of vtable instantiation int this case. Fixes llvm#102293 (cherry picked from commit d469794)
hi, this seems to be triggering infinite recursion and stack exhaustion on some invalid code patterns, see #104802 for details. |
In C++23 anything can be constexpr, including a dtor of a class whose members and bases don't have constexpr dtors. Avoid early triggering of vtable instantiation int this case.
Fixes #102293