-
Notifications
You must be signed in to change notification settings - Fork 15.1k
[Clang] FunctionEffect analysis was missing a CXXBindTemporaryExpr's implicit call to a destructor. #166110
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
…implicit call to a destructor.
|
@llvm/pr-subscribers-clang Author: Doug Wyatt (dougsonos) ChangesThis example is reduced from a discovery: resetting a shared pointer from a nonblocking function is not diagnosed.
Analysis was ignoring the implicit call in the AST to destroy the temporary. Full diff: https://github.com/llvm/llvm-project/pull/166110.diff 2 Files Affected:
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 8590ee831084f..468f157f2e0bd 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1271,7 +1271,15 @@ class Analyzer {
const CXXConstructorDecl *Ctor = Construct->getConstructor();
CallableInfo CI(*Ctor);
followCall(CI, Construct->getLocation());
+ return true;
+ }
+ bool VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *BTE) override {
+ const CXXDestructorDecl* Dtor = BTE->getTemporary()->getDestructor();
+ if (Dtor != nullptr) {
+ CallableInfo CI(*Dtor);
+ followCall(CI, BTE->getBeginLoc());
+ }
return true;
}
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index b26a945843696..4b831c0a6be09 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -354,6 +354,20 @@ struct Unsafe {
Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
};
+// Exercise the case of a temporary with a safe constructor and unsafe destructor.
+void nb23()
+{
+ struct X {
+ int *ptr = nullptr;
+ X() {}
+ ~X() { delete ptr; } // expected-note {{destructor cannot be inferred 'nonblocking' because it allocates or deallocates memory}}
+ };
+
+ auto inner = []() [[clang::nonblocking]] {
+ X(); // expected-warning {{lambda with 'nonblocking' attribute must not call non-'nonblocking' destructor 'nb23()::X::~X'}}
+ };
+}
+
struct DerivedFromUnsafe : public Unsafe {
DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
|
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
|
TODO: Consider We currently look for a destructor on a VarDecl but I suspect that becomes a redundant check with this change. Need to verify. |
Visiting struct S { ~S(); };
void f() {
S s;
[&]() [[clang::nonblocking]] {
[s]{ auto x = &s; }();
[=]{ auto x = &s; }();
}();
}These are all by-value captures, meaning that we need to call
That might no longer be necessary yeah. |
With this patch, here we get two warnings that S's destructor is being called from the nonblocking lambda. |
Oh, nice; add that as a test case then |
|
Added that as test The possible redundancy between |
Can we simply keep both checks? Or does that cause too many duplicate diagnostics? |
Alternatively, can we skip checking the type of the |
|
Yeah, we can keep both checks (and that's how things stand at the moment on the branch). I'm having trouble finding an example where they are redundant. |
Sgtm; we can come back to this if/when someone finds an example that causes duplicate warnings |
|
Aha, I dumped a big AST to JSON, searched for a It may be a little annoying to get two warnings but technically I think there really are two implicit calls to the destructor. (right? the result of |
Only if it returns an xvalue; if it returns a prvalue, it’s directly constructed into |
In this case, the initialiser contains a |
Actually, that doesn’t work too well if someone does weird things like: struct S { ~S(); };
S s{S(S())};Obviously no-one writes code like this, but even if you remove some of the unnecessary parts here, the AST is still sufficiently complex to where checking all possible patterns becomes annoying. |
OK, I'm inclined to think the rare duplicate warnings are tolerable for now (they're at least at different source locations), even if they're not correct. |
Sirraide
left a comment
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
One thing I forgot to mention with the other function effects prs: we should add a release note for them. It doesn’t have to be anything too specific; just something along the lines of ‘Fixed a number of false positives and false negatives around -Wfunction-effects’ is enough imo.
Sirraide
left a comment
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.
(forgot to select ‘approve’ instead of ‘comment’)
|
Thanks! I added a release note with this PR. (There's one more, #166078 ) |
This example is reduced from a discovery: resetting a shared pointer from a nonblocking function is not diagnosed.
shared_ptr<T>::reset()creates a temporaryshared_ptrand swaps it with its current state. The temporaryshared_ptrconstructor is nonblocking but its destructor potentially deallocates memory and is unsafe.Analysis was ignoring the implicit call in the AST to destroy the temporary.