-
Notifications
You must be signed in to change notification settings - Fork 14.6k
[clang][dataflow] Handle CXXInheritedCtorInitExpr in ResultObjectVisitor. #99616
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
…tor. `CXXInheritedCtorInitExpr` is another of the node kinds that should be considered an "original initializer". An assertion failure in `assert(Children.size() == 1)` happens without this fix.
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write If you have received no comments on your PR for a week, you can request a review If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-clang-analysis @llvm/pr-subscribers-clang Author: Pasquale Riello (Pask00) Changes
Full diff: https://github.com/llvm/llvm-project/pull/99616.diff 2 Files Affected:
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index f734168e647bd..3307981ce7e0f 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -416,7 +416,7 @@ class ResultObjectVisitor : public AnalysisASTVisitor<ResultObjectVisitor> {
// below them can initialize the same object (or part of it).
if (isa<CXXConstructExpr>(E) || isa<CallExpr>(E) || isa<LambdaExpr>(E) ||
isa<CXXDefaultArgExpr>(E) || isa<CXXStdInitializerListExpr>(E) ||
- isa<AtomicExpr>(E) ||
+ isa<AtomicExpr>(E) || isa<CXXInheritedCtorInitExpr>(E) ||
// We treat `BuiltinBitCastExpr` as an "original initializer" too as
// it may not even be casting from a record type -- and even if it is,
// the two objects are in general of unrelated type.
diff --git a/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp b/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp
index a4ac597bb06d6..8481026f7c1fc 100644
--- a/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp
@@ -442,6 +442,46 @@ TEST_F(EnvironmentTest, CXXDefaultInitExprResultObjIsWrappedExprResultObj) {
&Env.getResultObjectLocation(*DefaultInit->getExpr()));
}
+TEST_F(EnvironmentTest, ResultObjectLocationForInheritedCtorInitExpr) {
+ using namespace ast_matchers;
+
+ std::string Code = R"(
+ struct Base {
+ Base(int b) {}
+ };
+ struct Derived : Base {
+ using Base::Base;
+ };
+
+ Derived d = Derived(0);
+ )";
+
+ auto Unit =
+ tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++20"});
+ auto &Context = Unit->getASTContext();
+
+ ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ auto Results =
+ match(cxxConstructorDecl(
+ hasAnyConstructorInitializer(cxxCtorInitializer(
+ withInitializer(expr().bind("inherited_ctor_init_expr")))))
+ .bind("ctor"),
+ Context);
+ const auto *Constructor = selectFirst<CXXConstructorDecl>("ctor", Results);
+ const auto *InheritedCtorInit =
+ selectFirst<CXXInheritedCtorInitExpr>("inherited_ctor_init_expr", Results);
+
+ // Verify that `inherited_ctor_init_expr` has no children.
+ ASSERT_EQ(InheritedCtorInit->child_begin(), InheritedCtorInit->child_end());
+
+ Environment Env(DAContext, *Constructor);
+ Env.initialize();
+
+ RecordStorageLocation &Loc = Env.getResultObjectLocation(*InheritedCtorInit);
+ ASSERT_NE(&Loc, nullptr);
+}
+
TEST_F(EnvironmentTest, Stmt) {
using namespace ast_matchers;
|
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.
Thanks for taking this on!
Looks good apart from the fact that the test should be in a different file (and done slightly differently, see detailed comment in the code).
@@ -442,6 +442,46 @@ TEST_F(EnvironmentTest, CXXDefaultInitExprResultObjIsWrappedExprResultObj) { | |||
&Env.getResultObjectLocation(*DefaultInit->getExpr())); | |||
} | |||
|
|||
TEST_F(EnvironmentTest, ResultObjectLocationForInheritedCtorInitExpr) { |
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 tend to put tests for getResultObjectLocation()
in TransferTest. See an example for AtomicExpr
here, which you can also use as a model for a test for CXXInheritedCtorInitExpr
.
Doing this in TransferTest.cpp has the benefit that the test is smaller, plus you can make the assertion stronger (see the test for AtomicExpr
linked above which checks the exact value returned by getResultObjectLocation()
instead of just testing that it is non-null).
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.
Thanks for looking at this Martin!
I tried doing that, but in that way I can't have access to the right Environment
.
The problem is that the specific part of the AST I am interested to (a CXXConstructorDecl
) is implicitly "generated" (because it is an inherited constructor), so I can't put an annotation inside of it.
I may be completely wrong, but I think that in this case the only way is to explicitly initialize the environment as it is usually done in EnvironmentTest.
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 problem is that the specific part of the AST I am interested to (a CXXConstructorDecl) is implicitly "generated" (because it is an inherited constructor), so I can't put an annotation inside of it.
Ah, thanks for the explanation, I understand now. Yes, agree that you can't do this in the way we do other tests for getResultObjectLocation()
in TransferTest.cpp.
Can you add a comment to the test that explains this (and points out that most of the tests for getResultObjectLocation()
are in TransferTest.cpp)? I'd like to avoid other tests for getResultObjectLocation()
getting added next to this test just because it looks like the right place for them.
@@ -442,6 +442,46 @@ TEST_F(EnvironmentTest, CXXDefaultInitExprResultObjIsWrappedExprResultObj) { | |||
&Env.getResultObjectLocation(*DefaultInit->getExpr())); | |||
} | |||
|
|||
TEST_F(EnvironmentTest, ResultObjectLocationForInheritedCtorInitExpr) { |
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 problem is that the specific part of the AST I am interested to (a CXXConstructorDecl) is implicitly "generated" (because it is an inherited constructor), so I can't put an annotation inside of it.
Ah, thanks for the explanation, I understand now. Yes, agree that you can't do this in the way we do other tests for getResultObjectLocation()
in TransferTest.cpp.
Can you add a comment to the test that explains this (and points out that most of the tests for getResultObjectLocation()
are in TransferTest.cpp)? I'd like to avoid other tests for getResultObjectLocation()
getting added next to this test just because it looks like the right place for them.
auto Results = | ||
match(cxxConstructorDecl( | ||
hasAnyConstructorInitializer(cxxCtorInitializer( | ||
withInitializer(expr().bind("inherited_ctor_init_expr"))))) |
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.
withInitializer(expr().bind("inherited_ctor_init_expr"))))) | |
withInitializer(cxxInheritedCtorInitExpr().bind("inherited_ctor_init_expr"))))) |
to make this more explicit?
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.
That would be perfect, but actually there are no matchers for constructor initializer expressions.
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.
Sorry, I specifically checked ASTMatchers.h for this and could have sworn I found this, but on looking again, I see that it doesn't exist.
clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp
Outdated
Show resolved
Hide resolved
clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp
Outdated
Show resolved
Hide resolved
clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp
Outdated
Show resolved
Hide resolved
clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp
Outdated
Show resolved
Hide resolved
clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp
Outdated
Show resolved
Hide resolved
Co-authored-by: martinboehme <mboehme@google.com>
I have a high-level question not directly related to the patch. We have probably even talked about it at a conference a few years ago but I don't remember 😅
I briefly looked at the implementation and I suspect that you folks might be reinventing __ |
@Pask00 Congratulations on having your first Pull Request (PR) merged into the LLVM Project! Your changes will be combined with recent changes from other authors, then tested Please check whether problems have been caused by your change specifically, as How to do this, and the rest of the post-merge process, is covered in detail here. If your change does cause a problem, it may be reverted, or you can revert it yourself. If you don't get any reports, no action is required from you. Your changes are working as expected, well done! |
I don't recall -- maybe you talked to someone else?
Thanks for the info -- I wasn't aware of Agree that we should use |
The initial motivation was here: https://github.com/llvm/llvm-project/blob/llvmorg-19-init/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp#L130 - this is the place where the clang static analyzer's path-sensitive engine looks at the Additionally, it allows us to keep track of that storage location for the purposes of future events (see the next function at https://github.com/llvm/llvm-project/blob/llvmorg-19-init/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp#L408 where we write the evaluated
where the CFG looks like a double diamond: we may construct one of the two objects, we may destroy one of the two objects, but we merge in the middle in order to call
Thanks to Manuel Klimek's hard work, the magic CFG terminator at the beginning of the bottom diamond (which corresponds to the runtime branch responsible for conditionally invoking the destructor) remembers a So, yeah, the construction context helps us discover facts about the program such as "if I made a tiny conference talk about this (the second half of https://www.youtube.com/watch?v=4n3l-ZcDJNY&t=692s). The interesting part is probably where I enumerate all the ~30 cases I've found, that produce substantially different AST, which probably require a different I'm very open to changes in
|
@haoNoQ Thank you for going to the trouble to explain all of this in detail!
I haven't looked in detail yet, but I think all of the information we need is there. The main thing I'm wondering is whether we can make it so there are fewer case distinctions that the code in As far as I can tell, the various "intermediate" classes in the class hierarchy look as if they provide a mechanism for this. I.e. instead of distinguishing between all of the possible leaf classes, we would merely handle If you agree that this works, then the main question to me is how we make sure that we make sure we update our code if more cases are added to |
CXXInheritedCtorInitExpr
is another of the node kinds that should be considered an "original initializer". An assertion failure inassert(Children.size() == 1)
happens without this fix.