-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang][Sema] Add checks for validity of default ctor's class #78898
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
[clang][Sema] Add checks for validity of default ctor's class #78898
Conversation
Fixes llvm#10518 Fixes llvm#67914 Fixes llvm#78388 Also addresses the second example in llvm#49103 This patch is based on suggestion from @cor3ntin in llvm#67914 (comment)
@llvm/pr-subscribers-libcxx @llvm/pr-subscribers-clang Author: Vlad Serebrennikov (Endilll) ChangesFixes #10518 This patch is based on suggestion from @cor3ntin in #67914 (comment) Full diff: https://github.com/llvm/llvm-project/pull/78898.diff 2 Files Affected:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 8bb26fcae18d6b..5971bda21a5e25 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -1013,6 +1013,11 @@ Bug Fixes to C++ Support
- Fix a false-positive ODR violation for different definitions for `std::align_val_t`.
Fixes (`#76638 <https://github.com/llvm/llvm-project/issues/76638>`_)
+- Fix crash when calling the constructor of an invalid class.
+ Fixes (`#10518 <https://github.com/llvm/llvm-project/issues/10518>`_),
+ (`#67914 <https://github.com/llvm/llvm-project/issues/10518>`_),
+ and (`#78388 <https://github.com/llvm/llvm-project/issues/78388>`_)
+
- Remove recorded `#pragma once` state for headers included in named modules.
Fixes (`#77995 <https://github.com/llvm/llvm-project/issues/77995>`_)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index df5bd55e7c2836..634af573480b45 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -5990,6 +5990,10 @@ void Sema::ActOnDefaultCtorInitializers(Decl *CDtorDecl) {
if (CXXConstructorDecl *Constructor
= dyn_cast<CXXConstructorDecl>(CDtorDecl)) {
+ if (CXXRecordDecl *ClassDecl = Constructor->getParent();
+ !ClassDecl || ClassDecl->isInvalidDecl()) {
+ return;
+ }
SetCtorInitializers(Constructor, /*AnyErrors=*/false);
DiagnoseUninitializedFields(*this, Constructor);
}
@@ -14030,6 +14034,9 @@ void Sema::DefineImplicitDefaultConstructor(SourceLocation CurrentLocation,
CXXRecordDecl *ClassDecl = Constructor->getParent();
assert(ClassDecl && "DefineImplicitDefaultConstructor - invalid constructor");
+ if (ClassDecl->isInvalidDecl()) {
+ return;
+ }
SynthesizedFunctionScope Scope(*this, Constructor);
|
I decided to not include tests, because our testing infrastructure is not ready to test that Clang doesn't crash without overspecifying the tests using |
Could you elaborate a bit more on that? What is the exact problem with the testing infrastructure? Can we just add a separate test with the cases from the issues, perhaps without |
While I think it's possible to craft a general solution out of what we have today, it seems too brittle and intricate. It's also possible to use a simpler solution that works only for e.g. assertion failures. I don't think either of the above are good options, especially when considered at scale. While it's a discussion for another day, we have hundreds (if not thousands) of regression test cases in our bug tracker labeled Ideal solution seems to be along the following lines:
|
So, there is no way to consistently check on all platforms that we didn't crash when an error diagnostic was issued (does clang return non-zero when there is error diagnostic?), is that a right understanding? |
Yes. On Linux and Windows
I tried my best to explain that it's not an issues of prettiness. Complicated solution that seems to be possible to craft today feels too brittle, requiring reader to understand how various parts are fit together, not to say I'm not sure myself if something won't slip between piping behavior, If you can point me to existing corpus of crash tests that are done correctly, I'll be happy to follow. So far I saw handful of crash tests here and there across out test suite of various quality: from tests that do not account for segmentation faults (https://github.com/llvm/llvm-project/blob/88d1de5ec64210686d93a90529583505635d257d/clang/test/Parser/objc-diag-width.mm is going to fire in this case even though it's not intended to) to borderline useless tests (https://github.com/llvm/llvm-project/blob/60963272c5c83890910fd97f8d941180b6006fca/clang/test/Sema/crash-deduction-guide-access.cpp is going to pass if clang crashes or error diagnostic is produced, because in both cases exit code is not zero). |
Well, ok, perhaps checking that there is actually no crash in clang can be tricky. But what I meant is, since there is no good way to check exit code, why can't we add |
Because we have more than a couple of tests that should ensure we don't crash. Many of them don't make much sense, e.g. automatically reduced ones (even if identifiers are kept intact; #67914 is not a bad example). Which makes it harder for people altering diagnostic behavior to reason about such tests when they fail to pass CI. It also makes contributors to have more "fun" like @AaronBallman had no so long ago, updating tons of tests because of his VLA-related change. Overspecifying a significant corpus of hard-to-reason-about tests leads us to a slippery slope of a applying changes blindly and developing scripts akin to |
Now imagine all these come in without tests because there is no good way to add a test. I see your point, but again, not having a good way to make a test shouldn't free people from adding tests. Instead we should add tests and work on improving test system.
I would rather say it is a specific example. VLAs are an extension in C++ for which we choose how to deal with it. Most of the incoming changes are supported by the standards, so we shouldn't ideally expect many drastic changes in diagnostics like this one. |
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.
Yeah, these changes really need to come with test coverage. We allow landing changes with no test coverage when it's an NFC change or when the testing would require heroic efforts. Otherwise, we expect tests that are as good as you can reasonably make them (sometimes that means going with a unit test instead of a lit test, sometimes it requires creativity in lit, etc). You don't have to fix the testing system first, it's okay to land tests in an unideal state with a FIXME comment, for example.
In terms of the code changes, they look mostly reasonable (some minor nits for coding style conformance) and should be something we can test. We used to crash, so the test is demonstrating that we no longer crash (and that means verifying some particular set of diagnostics are now generated instead or that some IR is now generated, etc). Such a test would be good to have a comment explaining that the code used to crash with a mention or link to the github issue(s).
if (CXXRecordDecl *ClassDecl = Constructor->getParent(); | ||
!ClassDecl || ClassDecl->isInvalidDecl()) { | ||
return; | ||
} |
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.
if (CXXRecordDecl *ClassDecl = Constructor->getParent(); | |
!ClassDecl || ClassDecl->isInvalidDecl()) { | |
return; | |
} | |
if (CXXRecordDecl *ClassDecl = Constructor->getParent(); | |
!ClassDecl || ClassDecl->isInvalidDecl()) | |
return; |
if (ClassDecl->isInvalidDecl()) { | ||
return; | ||
} |
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.
if (ClassDecl->isInvalidDecl()) { | |
return; | |
} | |
if (ClassDecl->isInvalidDecl()) | |
return; |
So my takeaways here are:
Is this correct? |
Yup, that's it exactly! |
// RUN: %clang_cc1 -verify -std=c++98 %s | ||
// RUN: %clang_cc1 -verify -std=c++11 %s | ||
// RUN: %clang_cc1 -verify -std=c++14 %s | ||
// RUN: %clang_cc1 -verify -std=c++17 %s | ||
// RUN: %clang_cc1 -verify -std=c++20 %s | ||
// RUN: %clang_cc1 -verify -std=c++23 %s | ||
// RUN: %clang_cc1 -verify -std=c++2c %s |
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.
We could do 11 and 2c if you want, i don't think it's useful to test all language modes for these tests (ditto the other ones)
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.
Since newer language modes do not necessarily subsume older ones (think dynamic exception specification), and have disabled code paths, I'm not sure we want to be smart about which language modes to test. If we're going to be smart, then yes, 98, 11, and 2c sounds like a good compromise.
…n-sequence-perform
…n-sequence-perform
@philnik said that static asserts are the only interesting part of the test
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.
The libc++ changes LGTM.
Fixes #10518
Fixes #67914
Fixes #78388
Also addresses the second example in #49103
This patch is based on suggestion from @cor3ntin in #67914 (comment)