Only overwrite destination points-to-set if we have a single object on the LHS.
Adds multiple tests; of these, Overwrite_MultipleDestinations fails before this
change.
PiperOrigin-RevId: 453662886
diff --git a/lifetime_analysis/lifetime_analysis.cc b/lifetime_analysis/lifetime_analysis.cc
index 212f9ce..9a9f002 100644
--- a/lifetime_analysis/lifetime_analysis.cc
+++ b/lifetime_analysis/lifetime_analysis.cc
@@ -417,11 +417,18 @@
if (!op->getLHS()->getType()->isPointerType()) break;
ObjectSet rhs_points_to = points_to_map_.GetExprObjectSet(op->getRHS());
for (Object pointer : lhs_points_to) {
- if (object_repository_.GetObjectValueType(pointer) ==
- ObjectRepository::ObjectValueType::kMultiValued) {
- points_to_map_.ExtendPointerPointsToSet(pointer, rhs_points_to);
- } else {
+ // We can overwrite (instead of extend) the destination points-to-set
+ // only in very specific circumstances:
+ // - We need to know unambiguously what the LHS refers to, so that we
+ // know we're definitely writing to a particular object, and
+ // - That destination object needs to be "single-valued" (it can't be
+ // an array, for example).
+ if (lhs_points_to.size() == 1 &&
+ object_repository_.GetObjectValueType(pointer) ==
+ ObjectRepository::ObjectValueType::kSingleValued) {
points_to_map_.SetPointerPointsToSet(pointer, rhs_points_to);
+ } else {
+ points_to_map_.ExtendPointerPointsToSet(pointer, rhs_points_to);
}
}
break;
diff --git a/lifetime_analysis/test/basic.cc b/lifetime_analysis/test/basic.cc
index d442510..d9f2f84 100644
--- a/lifetime_analysis/test/basic.cc
+++ b/lifetime_analysis/test/basic.cc
@@ -456,6 +456,54 @@
LifetimesAre({{"f", "a -> a"}, {"target", "a -> a"}}));
}
+TEST_F(LifetimeAnalysisTest, Overwrite_SingleDestination) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* a, int* b) {
+ int** pp = &b;
+ // There is only one thing that `pp` can be pointing at, so the analysis
+ // should conclude that `b` is being overwritten with `a`.
+ *pp = a;
+ return b;
+ }
+ )"),
+ LifetimesAre({{"target", "a, b -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, Overwrite_SingleDestinationVariant) {
+ EXPECT_THAT(GetLifetimes(R"(
+ // Similar to above, but potentially leave `pp` uninitialized.
+ int* target(int* a, int* b) {
+ int** pp;
+ if (*a > 0) {
+ pp = &b;
+ }
+ // If `pp` is uninitialized, the following is UB, so the analysis can
+ // assume that `pp` was initialized to point to `b`.
+ // This particular test function is pretty terrible style, but it seems
+ // plausible that similar situations can come up in more reasonable code.
+ *pp = a;
+ return b;
+ }
+ )"),
+ LifetimesAre({{"target", "a, b -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, Overwrite_MultipleDestinations) {
+ EXPECT_THAT(GetLifetimes(R"(
+ // This is a regression test. The analysis used to conclude falsely that `b`
+ // was unconditionally being overwritten with `a` in the assignment and was
+ // therefore producing the wrong lifetimes "a, b -> a".
+ int* target(int* a, int* b) {
+ int** pp = *a > 0? &a : &b;
+ // The analysis should understand that the following assignment _might_
+ // overwrite `b` with `a` but does not necessarily do so.
+ *pp = a;
+ return b;
+ }
+ )"),
+ LifetimesAre({{"target", "a, a -> a"}}));
+}
+
} // namespace
} // namespace lifetimes
} // namespace tidy