Skip to content

[dataflow] CXXForRangeStmt should extend flow condition #80989

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

paulsemel
Copy link
Contributor

This is needed in order to correctly assume implicit iterator validity in the iterator checker.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:dataflow Clang Dataflow Analysis framework - https://clang.llvm.org/docs/DataFlowAnalysisIntro.html clang:analysis labels Feb 7, 2024
@llvmbot
Copy link
Member

llvmbot commented Feb 7, 2024

@llvm/pr-subscribers-clang-analysis

@llvm/pr-subscribers-clang

Author: Paul Semel (paulsemel)

Changes

This is needed in order to correctly assume implicit iterator validity in the iterator checker.


Full diff: https://github.com/llvm/llvm-project/pull/80989.diff

1 Files Affected:

  • (modified) clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp (+6-3)
diff --git a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp
index 4c88c46142d64d..f02c65094113ec 100644
--- a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp
+++ b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp
@@ -103,9 +103,12 @@ class TerminatorVisitor
     return {nullptr, false};
   }
 
-  TerminatorVisitorRetTy VisitCXXForRangeStmt(const CXXForRangeStmt *) {
-    // Don't do anything special for CXXForRangeStmt, because the condition
-    // (being implicitly generated) isn't visible from the loop body.
+  TerminatorVisitorRetTy VisitCXXForRangeStmt(const CXXForRangeStmt *S) {
+    // Even though the condition isn't visible from the loop body, analysis
+    // might depend on the implicit implicit statements implied by the loop.
+    auto *Cond = S->getCond();
+    if (Cond != nullptr)
+      return extendFlowCondition(*Cond);
     return {nullptr, false};
   }
 

Copy link
Contributor

@martinboehme martinboehme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test that fails without this fix?

This is needed in order to correctly assume implicit iterator validity
in the iterator checker.
@paulsemel paulsemel force-pushed the extend-flow-condition-for-for-based-loop branch from b227aa8 to 9cbe0ef Compare March 18, 2024 15:32
Comment on lines +1714 to +1732
TEST_F(TopTest, ForRangeStmtHasFlowCondition) {
std::string Code = R"(
#include <array>
void target(bool Foo) {
std::array<int, 5> t;
for (auto& i : t) {
(void)0;
/*[[p1]]*/
}
}
)";
runDataflow(Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
const AnalysisOutputs &AO) {
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1"));
const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
ASSERT_TRUE(Env1.proves(Env1.arena().makeAtomRef(Env1.getFlowConditionToken())));
});
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this test work @martinboehme ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments above -- I'm not sure that the test, if it fails, means that we need a flow condition for the range-based for. (User code will never be able to "observe" this flow condition, so I'm not sure why we need it.)

I still don't understand what the underlying purpose is that you need this flow condition for in the iterator check. Can you expand? (I want to make sure we're not dealing with an XY problem.)

@paulsemel paulsemel requested a review from martinboehme March 18, 2024 15:34
Copy link

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff 1b87ebce924e507cbc27c2e0dc623941d16388c9 9cbe0ef60f0154cbbefaa7d3092d65e4a4ab4d2a -- clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
View the diff from clang-format here.
diff --git a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
index b940fb8767..cd6d23c9c4 100644
--- a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
@@ -1722,13 +1722,15 @@ TEST_F(TopTest, ForRangeStmtHasFlowCondition) {
       }
     }
   )";
-  runDataflow(Code,
-              [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
-                 const AnalysisOutputs &AO) {
-                  ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1"));
-                  const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
-                  ASSERT_TRUE(Env1.proves(Env1.arena().makeAtomRef(Env1.getFlowConditionToken())));
-              });
+  runDataflow(
+      Code,
+      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+         const AnalysisOutputs &AO) {
+        ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1"));
+        const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
+        ASSERT_TRUE(Env1.proves(
+            Env1.arena().makeAtomRef(Env1.getFlowConditionToken())));
+      });
 }
 
 } // namespace

Comment on lines +1716 to +1723
#include <array>
void target(bool Foo) {
std::array<int, 5> t;
for (auto& i : t) {
(void)0;
/*[[p1]]*/
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <array>
void target(bool Foo) {
std::array<int, 5> t;
for (auto& i : t) {
(void)0;
/*[[p1]]*/
}
}
#include <initializer_list>
void target() {
for (auto& i : {1, 2}) {
(void)0;
/*[[p1]]*/
}
}

Try to keep code snippets as simple as possible:

  • I don't think Foo is needed here?
  • An initializer_list is probably the simplest thing you can use to iterate over (it doesn't matter specifically what you're iterating over, right?)

Comment on lines +1728 to +1729
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1"));
const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1"));
const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
  • A lot of tests still do the ASSERT_THAT(Results.keys(), UnorderedElementsAre(...));, but it's unnecessary -- getEnvironmentAtAnnotation() itself asserts that the annotation exists.

  • When there is only one environment you want to query, the usual names are Env and p (will need to be changed in the code snippet too, obviously).

ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1"));
const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
ASSERT_TRUE(Env1.proves(Env1.arena().makeAtomRef(Env1.getFlowConditionToken())));
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this fail without the other changes in this PR?

If it does, this looks to me more like a bug in DataflowEnvironment; this assertion should generally be true for any environment, whether or not we extended the flow condition. (In that sense, the name of the test isn't accurate, because we would expect this to pass even if the flow condition was not extended.)

const AnalysisOutputs &AO) {
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1"));
const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
ASSERT_TRUE(Env1.proves(Env1.arena().makeAtomRef(Env1.getFlowConditionToken())));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ASSERT_TRUE(Env1.proves(Env1.arena().makeAtomRef(Env1.getFlowConditionToken())));
EXPECT_TRUE(Env1.proves(Env1.arena().makeAtomRef(Env1.getFlowConditionToken())));

ASSERT_ aborts the test if the check fails and should generally only be used if it's not meaningful to execute the rest of the test in this case. Otherwise, use EXPECT_ so that you can continue executing the rest of the test.

It obviously doesn't make a difference in this case, but a) it's good style to default to EXPECT_ anyway, and b) people might add more checks later, and they might forget to change the ASSERT_ to an EXPECT_.

Comment on lines +1714 to +1732
TEST_F(TopTest, ForRangeStmtHasFlowCondition) {
std::string Code = R"(
#include <array>
void target(bool Foo) {
std::array<int, 5> t;
for (auto& i : t) {
(void)0;
/*[[p1]]*/
}
}
)";
runDataflow(Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
const AnalysisOutputs &AO) {
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1"));
const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
ASSERT_TRUE(Env1.proves(Env1.arena().makeAtomRef(Env1.getFlowConditionToken())));
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments above -- I'm not sure that the test, if it fails, means that we need a flow condition for the range-based for. (User code will never be able to "observe" this flow condition, so I'm not sure why we need it.)

I still don't understand what the underlying purpose is that you need this flow condition for in the iterator check. Can you expand? (I want to make sure we're not dealing with an XY problem.)

@martinboehme
Copy link
Contributor

Note code formatting failure in CI. (Use git clang-format to fix.)

@ymand
Copy link
Collaborator

ymand commented Mar 19, 2024

Just a note: I've siginfiicantly simplified the code that you're modifying in this PR (#84499), so expect some merge conflicts next time you pull from main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:analysis clang:dataflow Clang Dataflow Analysis framework - https://clang.llvm.org/docs/DataFlowAnalysisIntro.html clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants