Skip to content

Commit 32fe6eb

Browse files
committed
[clang][dataflow] Strengthen pointer comparison.
- Instead of comparing the identity of the `PointerValue`s, compare the underlying `StorageLocation`s. - If the `StorageLocation`s are different, return a definite "false" as the result of the comparison. Before, if the `PointerValue`s were different, the best we could do was to return an atom (because the `StorageLocation`s might still be the same). On an internal codebase, this change reduces SAT solver timeouts by over 20% and "maximum iterations reached" errors by over 50%. In addition, it obviously improves the precision of the analysis. @Xazax-hun inspired me to think about this with his [comments](#73860 (review)) on a different PR. The one thing where the new code currently does the wrong thing is when comparing the addresses of different members of a union. By the language standard, all members of a union should have the same address, but we currently model them with different `StorageLocation`s, and so with this change, we will return false when comparing the addreses. I propose that this is acceptable because is unlikely to affect the behavior of real-world code in meaningful ways. With this change, the test TransferTest.DifferentReferenceLocInJoin started to fail because the code under test no longer set up the desired state where a variable of reference type is mapped to two different storage locations in environments being joined. Rather than trying to modify this test to set up the test condition again, I have chosen to replace the test with an equivalent test in DataflowEnvironmentTest.cpp that sets up the test condition directly; because this test is more direct, it will also be less brittle in the face of future changes.
1 parent 6e761f3 commit 32fe6eb

File tree

3 files changed

+107
-44
lines changed

3 files changed

+107
-44
lines changed

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ static BoolValue &evaluateBooleanEquality(const Expr &LHS, const Expr &RHS,
6060
if (auto *RHSBool = dyn_cast_or_null<BoolValue>(RHSValue))
6161
return Env.makeIff(*LHSBool, *RHSBool);
6262

63+
if (auto *LHSPtr = dyn_cast_or_null<PointerValue>(LHSValue))
64+
if (auto *RHSPtr = dyn_cast_or_null<PointerValue>(RHSValue))
65+
return Env.getBoolLiteralValue(&LHSPtr->getPointeeLoc() ==
66+
&RHSPtr->getPointeeLoc());
67+
6368
return Env.makeAtomicBoolValue();
6469
}
6570

clang/unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namespace {
2424

2525
using namespace clang;
2626
using namespace dataflow;
27+
using ::clang::dataflow::test::findValueDecl;
2728
using ::clang::dataflow::test::getFieldValue;
2829
using ::testing::Contains;
2930
using ::testing::IsNull;
@@ -152,6 +153,47 @@ TEST_F(EnvironmentTest, JoinRecords) {
152153
}
153154
}
154155

156+
TEST_F(EnvironmentTest, DifferentReferenceLocInJoin) {
157+
// This tests the case where the storage location for a reference-type
158+
// variable is different for two states being joined. We used to believe this
159+
// could not happen and therefore had an assertion disallowing this; this test
160+
// exists to demonstrate that we can handle this condition without a failing
161+
// assertion. See also the discussion here:
162+
// https://discourse.llvm.org/t/70086/6
163+
164+
using namespace ast_matchers;
165+
166+
std::string Code = R"cc(
167+
void f(int &ref) {}
168+
)cc";
169+
170+
auto Unit =
171+
tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});
172+
auto &Context = Unit->getASTContext();
173+
174+
ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
175+
176+
const ValueDecl *Ref = findValueDecl(Context, "ref");
177+
178+
Environment Env1(DAContext);
179+
StorageLocation &Loc1 = Env1.createStorageLocation(Context.IntTy);
180+
Env1.setStorageLocation(*Ref, Loc1);
181+
182+
Environment Env2(DAContext);
183+
StorageLocation &Loc2 = Env2.createStorageLocation(Context.IntTy);
184+
Env2.setStorageLocation(*Ref, Loc2);
185+
186+
EXPECT_NE(&Loc1, &Loc2);
187+
188+
Environment::ValueModel Model;
189+
Environment EnvJoined = Environment::join(Env1, Env2, Model);
190+
191+
// Joining environments with different storage locations for the same
192+
// declaration results in the declaration being removed from the joined
193+
// environment.
194+
EXPECT_EQ(EnvJoined.getStorageLocation(*Ref), nullptr);
195+
}
196+
155197
TEST_F(EnvironmentTest, InitGlobalVarsFun) {
156198
using namespace ast_matchers;
157199

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3893,6 +3893,66 @@ TEST(TransferTest, BooleanInequality) {
38933893
});
38943894
}
38953895

3896+
TEST(TransferTest, PointerEquality) {
3897+
std::string Code = R"(
3898+
void target() {
3899+
int i = 0;
3900+
int i_other = 0;
3901+
int *p1 = &i;
3902+
int *p2 = &i;
3903+
int *p_other = &i_other;
3904+
int *null = nullptr;
3905+
3906+
bool p1_eq_p1 = (p1 == p1);
3907+
bool p1_eq_p2 = (p1 == p2);
3908+
bool p1_eq_p_other = (p1 == p_other);
3909+
3910+
bool p1_eq_null = (p1 == null);
3911+
bool p1_eq_nullptr = (p1 == nullptr);
3912+
bool null_eq_nullptr = (null == nullptr);
3913+
bool nullptr_eq_nullptr = (nullptr == nullptr);
3914+
3915+
// We won't duplicate all of the tests above with `!=`, as we know that
3916+
// the implementation simply negates the result of the `==` comparison.
3917+
// Instaed, just spot-check one case.
3918+
bool p1_ne_p_other = (p1 != p_other);
3919+
3920+
(void)0; // [[p]]
3921+
}
3922+
)";
3923+
runDataflow(
3924+
Code,
3925+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
3926+
ASTContext &ASTCtx) {
3927+
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
3928+
3929+
// Check the we have indeed set things up so that `p1` and `p2` have
3930+
// different pointer values.
3931+
EXPECT_NE(&getValueForDecl<PointerValue>(ASTCtx, Env, "p1"),
3932+
&getValueForDecl<PointerValue>(ASTCtx, Env, "p2"));
3933+
3934+
EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_p1"),
3935+
&Env.getBoolLiteralValue(true));
3936+
EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_p2"),
3937+
&Env.getBoolLiteralValue(true));
3938+
EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_p_other"),
3939+
&Env.getBoolLiteralValue(false));
3940+
3941+
EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_null"),
3942+
&Env.getBoolLiteralValue(false));
3943+
EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_eq_nullptr"),
3944+
&Env.getBoolLiteralValue(false));
3945+
EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "null_eq_nullptr"),
3946+
&Env.getBoolLiteralValue(true));
3947+
EXPECT_EQ(
3948+
&getValueForDecl<BoolValue>(ASTCtx, Env, "nullptr_eq_nullptr"),
3949+
&Env.getBoolLiteralValue(true));
3950+
3951+
EXPECT_EQ(&getValueForDecl<BoolValue>(ASTCtx, Env, "p1_ne_p_other"),
3952+
&Env.getBoolLiteralValue(true));
3953+
});
3954+
}
3955+
38963956
TEST(TransferTest, IntegerLiteralEquality) {
38973957
std::string Code = R"(
38983958
void target() {
@@ -6315,48 +6375,4 @@ TEST(TransferTest, LambdaCaptureThis) {
63156375
});
63166376
}
63176377

6318-
TEST(TransferTest, DifferentReferenceLocInJoin) {
6319-
// This test triggers a case where the storage location for a reference-type
6320-
// variable is different for two states being joined. We used to believe this
6321-
// could not happen and therefore had an assertion disallowing this; this test
6322-
// exists to demonstrate that we can handle this condition without a failing
6323-
// assertion. See also the discussion here:
6324-
// https://discourse.llvm.org/t/70086/6
6325-
std::string Code = R"(
6326-
namespace std {
6327-
template <class T> struct initializer_list {
6328-
const T* begin();
6329-
const T* end();
6330-
};
6331-
}
6332-
6333-
void target(char* p, char* end) {
6334-
while (p != end) {
6335-
if (*p == ' ') {
6336-
p++;
6337-
continue;
6338-
}
6339-
6340-
auto && range = {1, 2};
6341-
for (auto b = range.begin(), e = range.end(); b != e; ++b) {
6342-
}
6343-
(void)0;
6344-
// [[p]]
6345-
}
6346-
}
6347-
)";
6348-
runDataflow(
6349-
Code,
6350-
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
6351-
ASTContext &ASTCtx) {
6352-
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
6353-
6354-
// Joining environments with different storage locations for the same
6355-
// declaration results in the declaration being removed from the joined
6356-
// environment.
6357-
const ValueDecl *VD = findValueDecl(ASTCtx, "range");
6358-
ASSERT_EQ(Env.getStorageLocation(*VD), nullptr);
6359-
});
6360-
}
6361-
63626378
} // namespace

0 commit comments

Comments
 (0)