aboutsummaryrefslogtreecommitdiff
path: root/clang/unittests
diff options
context:
space:
mode:
Diffstat (limited to 'clang/unittests')
-rw-r--r--clang/unittests/AST/DeclPrinterTest.cpp2
-rw-r--r--clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp23
-rw-r--r--clang/unittests/Analysis/LifetimeSafetyTest.cpp501
3 files changed, 285 insertions, 241 deletions
diff --git a/clang/unittests/AST/DeclPrinterTest.cpp b/clang/unittests/AST/DeclPrinterTest.cpp
index 1a1b707..a412a98 100644
--- a/clang/unittests/AST/DeclPrinterTest.cpp
+++ b/clang/unittests/AST/DeclPrinterTest.cpp
@@ -1090,7 +1090,7 @@ TEST(DeclPrinter, TestClassTemplateDecl9) {
"template<typename T> struct Z { };"
"template<template<typename U> class T = Z> struct A { };",
classTemplateDecl(hasName("A")).bind("id"),
- "template <template <typename U> class T> struct A {}"));
+ "template <template <typename U> class T = Z> struct A {}"));
}
TEST(DeclPrinter, TestClassTemplateDecl10) {
diff --git a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
index 4e97174..95f8ae2 100644
--- a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
+++ b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
@@ -1749,6 +1749,13 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByInitToNonConst) {
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
}
+ {
+ const std::string Code = "void f() { int* x = nullptr; int*& b = x; }";
+ auto AST = buildASTFromCodeWithArgs(Code, {});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
+ }
}
TEST(ExprMutationAnalyzerTest, PointeeMutatedByAssignToNonConst) {
@@ -1786,6 +1793,14 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByPassAsArgument) {
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
}
+ {
+ const std::string Code =
+ "void b(int *&); void f() { int* x = nullptr; b(x); }";
+ auto AST = buildASTFromCodeWithArgs(Code, {});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
+ }
}
TEST(ExprMutationAnalyzerTest, PointeeMutatedByPassAsArgumentInConstruct) {
@@ -1884,6 +1899,14 @@ TEST(ExprMutationAnalyzerTest, PointeeMutatedByExplicitCastToNonConst) {
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
}
+ {
+ const std::string Code =
+ "void f() { int* x = nullptr; static_cast<int*&>(x); }";
+ auto AST = buildASTFromCodeWithArgs(Code, {"-Wno-everything"});
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_TRUE(isPointeeMutated(Results, AST.get()));
+ }
}
TEST(ExprMutationAnalyzerTest, PointeeMutatedByConstCastToNonConst) {
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 3821015..169b2d2 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -126,12 +126,12 @@ public:
return Analysis.getLoansAtPoint(OID, PP);
}
- std::optional<std::vector<LoanID>>
- getExpiredLoansAtPoint(llvm::StringRef Annotation) {
+ std::optional<std::vector<std::pair<OriginID, LivenessKind>>>
+ getLiveOriginsAtPoint(llvm::StringRef Annotation) {
ProgramPoint PP = Runner.getProgramPoint(Annotation);
if (!PP)
return std::nullopt;
- return Analysis.getExpiredLoansAtPoint(PP);
+ return Analysis.getLiveOriginsAtPoint(PP);
}
private:
@@ -180,6 +180,15 @@ public:
LifetimeTestHelper &Helper;
};
+// A helper class to represent a set of origins, identified by variable names.
+class OriginsInfo {
+public:
+ OriginsInfo(const std::vector<std::string> &Vars, LifetimeTestHelper &H)
+ : OriginVars(Vars), Helper(H) {}
+ std::vector<std::string> OriginVars;
+ LifetimeTestHelper &Helper;
+};
+
/// Matcher to verify the set of loans held by an origin at a specific
/// program point.
///
@@ -221,14 +230,15 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
std::sort(ExpectedLoans.begin(), ExpectedLoans.end());
std::sort(ActualLoans.begin(), ActualLoans.end());
if (ExpectedLoans != ActualLoans) {
- *result_listener << "Expected: ";
+ *result_listener << "Expected: {";
for (const auto &LoanID : ExpectedLoans) {
*result_listener << LoanID.Value << ", ";
}
- *result_listener << "Actual: ";
+ *result_listener << "} Actual: {";
for (const auto &LoanID : ActualLoans) {
*result_listener << LoanID.Value << ", ";
}
+ *result_listener << "}";
return false;
}
@@ -236,32 +246,71 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
ActualLoans, result_listener);
}
-/// Matcher to verify that the complete set of expired loans at a program point
-/// matches the expected loan set.
-MATCHER_P(AreExpiredAt, Annotation, "") {
- const LoanSetInfo &Info = arg;
- auto &Helper = Info.Helper;
+enum class LivenessKindFilter { Maybe, Must, All };
- auto ActualExpiredSetOpt = Helper.getExpiredLoansAtPoint(Annotation);
- if (!ActualExpiredSetOpt) {
- *result_listener << "could not get a valid expired loan set at point '"
+/// Matcher to verify the complete set of live origins at a program point.
+MATCHER_P2(AreLiveAtImpl, Annotation, ConfFilter, "") {
+ const OriginsInfo &Info = arg;
+ auto &Helper = Info.Helper;
+ auto ActualLiveSetOpt = Helper.getLiveOriginsAtPoint(Annotation);
+ if (!ActualLiveSetOpt) {
+ *result_listener << "could not get a valid live origin set at point '"
<< Annotation << "'";
return false;
}
- std::vector<LoanID> ActualExpiredLoans = *ActualExpiredSetOpt;
- std::vector<LoanID> ExpectedExpiredLoans;
- for (const auto &VarName : Info.LoanVars) {
- auto LoanIDs = Helper.getLoansForVar(VarName);
- if (LoanIDs.empty()) {
- *result_listener << "could not find a loan for variable '" << VarName
+ std::vector<OriginID> ActualLiveOrigins;
+ for (const auto [OID, ActualConfidence] : ActualLiveSetOpt.value()) {
+ if (ConfFilter == LivenessKindFilter::All)
+ ActualLiveOrigins.push_back(OID);
+ if (ActualConfidence == LivenessKind::Maybe &&
+ ConfFilter == LivenessKindFilter::Maybe)
+ ActualLiveOrigins.push_back(OID);
+ if (ActualConfidence == LivenessKind::Must &&
+ ConfFilter == LivenessKindFilter::Must)
+ ActualLiveOrigins.push_back(OID);
+ }
+
+ std::vector<OriginID> ExpectedLiveOrigins;
+ for (const auto &VarName : Info.OriginVars) {
+ auto OriginIDOpt = Helper.getOriginForDecl(VarName);
+ if (!OriginIDOpt) {
+ *result_listener << "could not find an origin for variable '" << VarName
<< "'";
return false;
}
- ExpectedExpiredLoans.insert(ExpectedExpiredLoans.end(), LoanIDs.begin(),
- LoanIDs.end());
+ ExpectedLiveOrigins.push_back(*OriginIDOpt);
}
- return ExplainMatchResult(UnorderedElementsAreArray(ExpectedExpiredLoans),
- ActualExpiredLoans, result_listener);
+ std::sort(ExpectedLiveOrigins.begin(), ExpectedLiveOrigins.end());
+ std::sort(ActualLiveOrigins.begin(), ActualLiveOrigins.end());
+ if (ExpectedLiveOrigins != ActualLiveOrigins) {
+ *result_listener << "Expected: {";
+ for (const auto &OriginID : ExpectedLiveOrigins) {
+ *result_listener << OriginID.Value << ", ";
+ }
+ *result_listener << "} Actual: {";
+ for (const auto &OriginID : ActualLiveOrigins) {
+ *result_listener << OriginID.Value << ", ";
+ }
+ *result_listener << "}";
+ return false;
+ }
+ return true;
+}
+
+MATCHER_P(MustBeLiveAt, Annotation, "") {
+ return ExplainMatchResult(AreLiveAtImpl(Annotation, LivenessKindFilter::Must),
+ arg, result_listener);
+}
+
+MATCHER_P(MaybeLiveAt, Annotation, "") {
+ return ExplainMatchResult(
+ AreLiveAtImpl(Annotation, LivenessKindFilter::Maybe), arg,
+ result_listener);
+}
+
+MATCHER_P(AreLiveAt, Annotation, "") {
+ return ExplainMatchResult(AreLiveAtImpl(Annotation, LivenessKindFilter::All),
+ arg, result_listener);
}
// Base test fixture to manage the runner and helper.
@@ -277,6 +326,13 @@ protected:
}
/// Factory function that hides the std::vector creation.
+ OriginsInfo Origins(std::initializer_list<std::string> OriginVars) {
+ return OriginsInfo({OriginVars}, *Helper);
+ }
+
+ OriginsInfo NoOrigins() { return Origins({}); }
+
+ /// Factory function that hides the std::vector creation.
LoanSetInfo LoansTo(std::initializer_list<std::string> LoanVars) {
return LoanSetInfo({LoanVars}, *Helper);
}
@@ -428,29 +484,6 @@ TEST_F(LifetimeAnalysisTest, AssignInSwitch) {
EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2", "s3"}, "after_switch"));
}
-TEST_F(LifetimeAnalysisTest, LoanInLoop) {
- SetupTest(R"(
- void target(bool condition) {
- MyObj* p = nullptr;
- while (condition) {
- POINT(start_loop);
- MyObj inner;
- p = &inner;
- POINT(end_loop);
- }
- POINT(after_loop);
- }
- )");
- EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "start_loop"));
- EXPECT_THAT(LoansTo({"inner"}), AreExpiredAt("start_loop"));
-
- EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "end_loop"));
- EXPECT_THAT(NoLoans(), AreExpiredAt("end_loop"));
-
- EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_loop"));
- EXPECT_THAT(LoansTo({"inner"}), AreExpiredAt("after_loop"));
-}
-
TEST_F(LifetimeAnalysisTest, LoopWithBreak) {
SetupTest(R"(
void target(int count) {
@@ -528,20 +561,16 @@ TEST_F(LifetimeAnalysisTest, PointersAndExpirationInACycle) {
)");
EXPECT_THAT(Origin("p1"), HasLoansTo({"v1"}, "before_while"));
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2"}, "before_while"));
- EXPECT_THAT(NoLoans(), AreExpiredAt("before_while"));
EXPECT_THAT(Origin("p1"),
HasLoansTo({"v1", "v2", "temp"}, "in_loop_before_temp"));
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "in_loop_before_temp"));
- EXPECT_THAT(LoansTo({"temp"}), AreExpiredAt("in_loop_before_temp"));
EXPECT_THAT(Origin("p1"), HasLoansTo({"temp"}, "in_loop_after_temp"));
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "in_loop_after_temp"));
- EXPECT_THAT(NoLoans(), AreExpiredAt("in_loop_after_temp"));
EXPECT_THAT(Origin("p1"), HasLoansTo({"v1", "v2", "temp"}, "after_loop"));
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "after_loop"));
- EXPECT_THAT(LoansTo({"temp"}), AreExpiredAt("after_loop"));
}
TEST_F(LifetimeAnalysisTest, InfiniteLoopPrunesEdges) {
@@ -585,178 +614,6 @@ TEST_F(LifetimeAnalysisTest, NestedScopes) {
EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_inner_scope"));
}
-TEST_F(LifetimeAnalysisTest, SimpleExpiry) {
- SetupTest(R"(
- void target() {
- MyObj* p = nullptr;
- {
- MyObj s;
- p = &s;
- POINT(before_expiry);
- } // s goes out of scope here
- POINT(after_expiry);
- }
- )");
- EXPECT_THAT(NoLoans(), AreExpiredAt("before_expiry"));
- EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_expiry"));
-}
-
-TEST_F(LifetimeAnalysisTest, NestedExpiry) {
- SetupTest(R"(
- void target() {
- MyObj s1;
- MyObj* p = &s1;
- POINT(before_inner);
- {
- MyObj s2;
- p = &s2;
- POINT(in_inner);
- } // s2 expires
- POINT(after_inner);
- }
- )");
- EXPECT_THAT(NoLoans(), AreExpiredAt("before_inner"));
- EXPECT_THAT(NoLoans(), AreExpiredAt("in_inner"));
- EXPECT_THAT(LoansTo({"s2"}), AreExpiredAt("after_inner"));
-}
-
-TEST_F(LifetimeAnalysisTest, ConditionalExpiry) {
- SetupTest(R"(
- void target(bool cond) {
- MyObj s1;
- MyObj* p = &s1;
- POINT(before_if);
- if (cond) {
- MyObj s2;
- p = &s2;
- POINT(then_block);
- } // s2 expires here
- POINT(after_if);
- }
- )");
- EXPECT_THAT(NoLoans(), AreExpiredAt("before_if"));
- EXPECT_THAT(NoLoans(), AreExpiredAt("then_block"));
- EXPECT_THAT(LoansTo({"s2"}), AreExpiredAt("after_if"));
-}
-
-TEST_F(LifetimeAnalysisTest, LoopExpiry) {
- SetupTest(R"(
- void target() {
- MyObj *p = nullptr;
- for (int i = 0; i < 2; ++i) {
- POINT(start_loop);
- MyObj s;
- p = &s;
- POINT(end_loop);
- } // s expires here on each iteration
- POINT(after_loop);
- }
- )");
- EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("start_loop"));
- EXPECT_THAT(NoLoans(), AreExpiredAt("end_loop"));
- EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_loop"));
-}
-
-TEST_F(LifetimeAnalysisTest, MultipleExpiredLoans) {
- SetupTest(R"(
- void target() {
- MyObj *p1, *p2, *p3;
- {
- MyObj s1;
- p1 = &s1;
- POINT(p1);
- } // s1 expires
- POINT(p2);
- {
- MyObj s2;
- p2 = &s2;
- MyObj s3;
- p3 = &s3;
- POINT(p3);
- } // s2, s3 expire
- POINT(p4);
- }
- )");
- EXPECT_THAT(NoLoans(), AreExpiredAt("p1"));
- EXPECT_THAT(LoansTo({"s1"}), AreExpiredAt("p2"));
- EXPECT_THAT(LoansTo({"s1"}), AreExpiredAt("p3"));
- EXPECT_THAT(LoansTo({"s1", "s2", "s3"}), AreExpiredAt("p4"));
-}
-
-TEST_F(LifetimeAnalysisTest, GotoJumpsOutOfScope) {
- SetupTest(R"(
- void target(bool cond) {
- MyObj *p = nullptr;
- {
- MyObj s;
- p = &s;
- POINT(before_goto);
- if (cond) {
- goto end;
- }
- } // `s` expires here on the path that doesn't jump
- POINT(after_scope);
- end:
- POINT(after_goto);
- }
- )");
- EXPECT_THAT(NoLoans(), AreExpiredAt("before_goto"));
- EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_scope"));
- EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_goto"));
-}
-
-TEST_F(LifetimeAnalysisTest, ContinueInLoop) {
- SetupTest(R"(
- void target(int count) {
- MyObj *p = nullptr;
- MyObj outer;
- p = &outer;
- POINT(before_loop);
-
- for (int i = 0; i < count; ++i) {
- if (i % 2 == 0) {
- MyObj s_even;
- p = &s_even;
- POINT(in_even_iter);
- continue;
- }
- MyObj s_odd;
- p = &s_odd;
- POINT(in_odd_iter);
- }
- POINT(after_loop);
- }
- )");
- EXPECT_THAT(NoLoans(), AreExpiredAt("before_loop"));
- EXPECT_THAT(LoansTo({"s_odd"}), AreExpiredAt("in_even_iter"));
- EXPECT_THAT(LoansTo({"s_even"}), AreExpiredAt("in_odd_iter"));
- EXPECT_THAT(LoansTo({"s_even", "s_odd"}), AreExpiredAt("after_loop"));
-}
-
-TEST_F(LifetimeAnalysisTest, ReassignedPointerThenOriginalExpires) {
- SetupTest(R"(
- void target() {
- MyObj* p = nullptr;
- {
- MyObj s1;
- p = &s1;
- POINT(p_has_s1);
- {
- MyObj s2;
- p = &s2;
- POINT(p_has_s2);
- }
- POINT(p_after_s2_expires);
- } // s1 expires here.
- POINT(p_after_s1_expires);
- }
- )");
- EXPECT_THAT(NoLoans(), AreExpiredAt("p_has_s1"));
- EXPECT_THAT(NoLoans(), AreExpiredAt("p_has_s2"));
- EXPECT_THAT(LoansTo({"s2"}), AreExpiredAt("p_after_s2_expires"));
- EXPECT_THAT(LoansTo({"s1", "s2"}), AreExpiredAt("p_after_s1_expires"));
-}
-
TEST_F(LifetimeAnalysisTest, NoDuplicateLoansForImplicitCastToConst) {
SetupTest(R"(
void target() {
@@ -880,23 +737,6 @@ TEST_F(LifetimeAnalysisTest, GslPointerPropagation) {
EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p3"));
}
-TEST_F(LifetimeAnalysisTest, GslPointerLoanExpiration) {
- SetupTest(R"(
- void target() {
- View x;
- {
- MyObj a;
- x = a;
- POINT(before_expiry);
- } // `a` is destroyed here.
- POINT(after_expiry);
- }
- )");
-
- EXPECT_THAT(NoLoans(), AreExpiredAt("before_expiry"));
- EXPECT_THAT(LoansTo({"a"}), AreExpiredAt("after_expiry"));
-}
-
TEST_F(LifetimeAnalysisTest, GslPointerReassignment) {
SetupTest(R"(
void target() {
@@ -916,7 +756,6 @@ TEST_F(LifetimeAnalysisTest, GslPointerReassignment) {
EXPECT_THAT(Origin("v"), HasLoansTo({"safe"}, "p1"));
EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p2"));
EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p3"));
- EXPECT_THAT(LoansTo({"unsafe"}), AreExpiredAt("p3"));
}
TEST_F(LifetimeAnalysisTest, GslPointerConversionOperator) {
@@ -1174,5 +1013,187 @@ TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) {
)");
EXPECT_THAT(Origin("v"), HasLoansTo({"owner"}, "p1"));
}
+
+TEST_F(LifetimeAnalysisTest, LivenessDeadPointer) {
+ SetupTest(R"(
+ void target() {
+ POINT(p1);
+ MyObj s;
+ MyObj* p = &s;
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessSimpleReturn) {
+ SetupTest(R"(
+ MyObj* target() {
+ MyObj s;
+ MyObj* p = &s;
+ POINT(p1);
+ return p;
+ }
+ )");
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessKilledByReassignment) {
+ SetupTest(R"(
+ MyObj* target() {
+ MyObj s1, s2;
+ MyObj* p = &s1;
+ POINT(p1);
+ p = &s2;
+ POINT(p2);
+ return p;
+ }
+ )");
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessAcrossBranches) {
+ SetupTest(R"(
+ MyObj* target(bool c) {
+ MyObj x, y;
+ MyObj* p = nullptr;
+ POINT(p1);
+ if (c) {
+ p = &x;
+ POINT(p2);
+ } else {
+ p = &y;
+ POINT(p3);
+ }
+ return p;
+ }
+ )");
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p3"));
+ // Before the `if`, the value of `p` (`nullptr`) is always overwritten before.
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessInLoop) {
+ SetupTest(R"(
+ MyObj* target(bool c) {
+ MyObj s1, s2;
+ MyObj* p = &s1;
+ MyObj* q = &s2;
+ POINT(p1);
+ while(c) {
+ POINT(p2);
+
+ p = q;
+ POINT(p3);
+ }
+ POINT(p4);
+ return p;
+ }
+ )");
+
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p4"));
+ EXPECT_THAT(NoOrigins(), MaybeLiveAt("p4"));
+
+ EXPECT_THAT(Origins({"p", "q"}), MaybeLiveAt("p3"));
+
+ EXPECT_THAT(Origins({"q"}), MustBeLiveAt("p2"));
+ EXPECT_THAT(NoOrigins(), MaybeLiveAt("p2"));
+
+ EXPECT_THAT(Origins({"p", "q"}), MaybeLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessInLoopAndIf) {
+ // See https://github.com/llvm/llvm-project/issues/156959.
+ SetupTest(R"(
+ void target(bool cond) {
+ MyObj b;
+ while (cond) {
+ POINT(p1);
+
+ MyObj a;
+ View p = b;
+
+ POINT(p2);
+
+ if (cond) {
+ POINT(p3);
+ p = a;
+ }
+ POINT(p4);
+ (void)p;
+ POINT(p5);
+ }
+ }
+ )");
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p5"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p4"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p3"));
+ EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p2"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessInLoopAndIf2) {
+ SetupTest(R"(
+ void target(MyObj safe, bool condition) {
+ MyObj* p = &safe;
+ MyObj* q = &safe;
+ POINT(p1);
+
+ while (condition) {
+ POINT(p2);
+ MyObj x;
+ p = &x;
+
+ POINT(p3);
+
+ if (condition) {
+ q = p;
+ POINT(p4);
+ }
+
+ POINT(p5);
+ (void)*p;
+ (void)*q;
+ POINT(p6);
+ }
+ }
+ )");
+ EXPECT_THAT(Origins({"q"}), MaybeLiveAt("p6"));
+ EXPECT_THAT(NoOrigins(), MustBeLiveAt("p6"));
+
+ EXPECT_THAT(Origins({"p", "q"}), MustBeLiveAt("p5"));
+
+ EXPECT_THAT(Origins({"p", "q"}), MustBeLiveAt("p4"));
+
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p3"));
+ EXPECT_THAT(Origins({"q"}), MaybeLiveAt("p3"));
+
+ EXPECT_THAT(Origins({"q"}), MaybeLiveAt("p2"));
+ EXPECT_THAT(NoOrigins(), MustBeLiveAt("p2"));
+
+ EXPECT_THAT(Origins({"q"}), MaybeLiveAt("p1"));
+ EXPECT_THAT(NoOrigins(), MustBeLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) {
+ SetupTest(R"(
+ void target(MyObj safe) {
+ MyObj* p = &safe;
+ for (int i = 0; i < 1; ++i) {
+ MyObj s;
+ p = &s;
+ POINT(p1);
+ }
+ POINT(p2);
+ (void)*p;
+ }
+ )");
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
+ EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1"));
+}
+
} // anonymous namespace
} // namespace clang::lifetimes::internal