@@ -20,6 +20,7 @@ namespace clang::lifetimes::internal {
20
20
namespace {
21
21
22
22
using namespace ast_matchers ;
23
+ using ::testing::Not;
23
24
using ::testing::UnorderedElementsAreArray;
24
25
25
26
// A helper class to run the full lifetime analysis on a piece of code
@@ -45,7 +46,10 @@ class LifetimeTestRunner {
45
46
return ;
46
47
}
47
48
AnalysisCtx = std::make_unique<AnalysisDeclContext>(nullptr , FD);
48
- AnalysisCtx->getCFGBuildOptions ().setAllAlwaysAdd ();
49
+ CFG::BuildOptions &BuildOptions = AnalysisCtx->getCFGBuildOptions ();
50
+ BuildOptions.setAllAlwaysAdd ();
51
+ BuildOptions.AddImplicitDtors = true ;
52
+ BuildOptions.AddTemporaryDtors = true ;
49
53
50
54
// Run the main analysis.
51
55
Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx);
@@ -115,6 +119,13 @@ class LifetimeTestHelper {
115
119
return Analysis.getLoansAtPoint (OID, PP);
116
120
}
117
121
122
+ std::optional<LoanSet> getExpiredLoansAtPoint (llvm::StringRef Annotation) {
123
+ ProgramPoint PP = Runner.getProgramPoint (Annotation);
124
+ if (!PP)
125
+ return std::nullopt;
126
+ return Analysis.getExpiredLoansAtPoint (PP);
127
+ }
128
+
118
129
private:
119
130
template <typename DeclT> DeclT *findDecl (llvm::StringRef Name) {
120
131
auto &Ctx = Runner.getASTContext ();
@@ -134,6 +145,15 @@ class LifetimeTestHelper {
134
145
// GTest Matchers & Fixture
135
146
// ========================================================================= //
136
147
148
+ // A helper class to represent the subject of a check, e.g., "the loan to 'x'".
149
+ class LoanInfo {
150
+ public:
151
+ LoanInfo (llvm::StringRef LoanVar, LifetimeTestHelper &Helper)
152
+ : LoanVar(LoanVar), Helper(Helper) {}
153
+ llvm::StringRef LoanVar;
154
+ LifetimeTestHelper &Helper;
155
+ };
156
+
137
157
// It holds the name of the origin variable and a reference to the helper.
138
158
class OriginInfo {
139
159
public:
@@ -185,6 +205,33 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
185
205
ActualLoans, result_listener);
186
206
}
187
207
208
+ // / Matcher to verify if a loan to a specific variable has expired at a given
209
+ // program point.
210
+ MATCHER_P (IsExpiredAt, Annotation, " " ) {
211
+ const LoanInfo &Info = arg;
212
+ std::optional<LoanID> TargetLoanIDOpt =
213
+ Info.Helper .getLoanForVar (Info.LoanVar );
214
+ if (!TargetLoanIDOpt) {
215
+ *result_listener << " could not find a loan for variable '"
216
+ << Info.LoanVar .str () << " '" ;
217
+ return false ;
218
+ }
219
+
220
+ std::optional<LoanSet> ExpiredLoansSetOpt =
221
+ Info.Helper .getExpiredLoansAtPoint (Annotation);
222
+ if (!ExpiredLoansSetOpt) {
223
+ *result_listener << " could not get a valid expired loan set at point '"
224
+ << Annotation << " '" ;
225
+ return false ;
226
+ }
227
+
228
+ if (ExpiredLoansSetOpt->contains (*TargetLoanIDOpt))
229
+ return true ;
230
+
231
+ *result_listener << " was expected to be expired, but was not" ;
232
+ return false ;
233
+ }
234
+
188
235
// Base test fixture to manage the runner and helper.
189
236
class LifetimeAnalysisTest : public ::testing::Test {
190
237
protected:
@@ -197,6 +244,10 @@ class LifetimeAnalysisTest : public ::testing::Test {
197
244
return OriginInfo (OriginVar, *Helper);
198
245
}
199
246
247
+ LoanInfo LoanTo (llvm::StringRef LoanVar) {
248
+ return LoanInfo (LoanVar, *Helper);
249
+ }
250
+
200
251
// Factory function that hides the std::vector creation.
201
252
auto HasLoansTo (std::initializer_list<std::string> LoanVars,
202
253
const char *Annotation) {
@@ -435,5 +486,119 @@ TEST_F(LifetimeAnalysisTest, NestedScopes) {
435
486
EXPECT_THAT (Origin (" p" ), HasLoansTo ({" inner" }, " after_inner_scope" ));
436
487
}
437
488
489
+ // ========================================================================= //
490
+ // Loan Expiration Tests
491
+ // ========================================================================= //
492
+
493
+ TEST_F (LifetimeAnalysisTest, SimpleExpiry) {
494
+ SetupTest (R"(
495
+ void target() {
496
+ MyObj* p = nullptr;
497
+ {
498
+ MyObj s;
499
+ p = &s;
500
+ POINT(before_expiry);
501
+ } // s goes out of scope here
502
+ POINT(after_expiry);
503
+ }
504
+ )" );
505
+ EXPECT_THAT (LoanTo (" s" ), Not (IsExpiredAt (" before_expiry" )));
506
+ EXPECT_THAT (LoanTo (" s" ), IsExpiredAt (" after_expiry" ));
507
+ }
508
+
509
+ TEST_F (LifetimeAnalysisTest, NestedExpiry) {
510
+ SetupTest (R"(
511
+ void target() {
512
+ MyObj s1;
513
+ MyObj* p = &s1;
514
+ POINT(before_inner);
515
+ {
516
+ MyObj s2;
517
+ p = &s2;
518
+ POINT(in_inner);
519
+ } // s2 expires
520
+ POINT(after_inner);
521
+ }
522
+ )" );
523
+ EXPECT_THAT (LoanTo (" s1" ), Not (IsExpiredAt (" before_inner" )));
524
+ EXPECT_THAT (LoanTo (" s2" ), Not (IsExpiredAt (" in_inner" )));
525
+ EXPECT_THAT (LoanTo (" s1" ), Not (IsExpiredAt (" after_inner" )));
526
+ EXPECT_THAT (LoanTo (" s2" ), IsExpiredAt (" after_inner" ));
527
+ }
528
+
529
+ TEST_F (LifetimeAnalysisTest, ConditionalExpiry) {
530
+ SetupTest (R"(
531
+ void target(bool cond) {
532
+ MyObj s1;
533
+ MyObj* p = &s1;
534
+ POINT(before_if);
535
+ if (cond) {
536
+ MyObj s2;
537
+ p = &s2;
538
+ POINT(then_block);
539
+ } // s2 expires here
540
+ POINT(after_if);
541
+ }
542
+ )" );
543
+ EXPECT_THAT (LoanTo (" s1" ), Not (IsExpiredAt (" before_if" )));
544
+ EXPECT_THAT (LoanTo (" s2" ), Not (IsExpiredAt (" then_block" )));
545
+ // After the if-statement, the loan to s2 (created in the 'then' branch)
546
+ // will have expired.
547
+ EXPECT_THAT (LoanTo (" s2" ), IsExpiredAt (" after_if" ));
548
+ EXPECT_THAT (LoanTo (" s1" ), Not (IsExpiredAt (" after_if" )));
549
+ }
550
+
551
+ TEST_F (LifetimeAnalysisTest, LoopExpiry) {
552
+ SetupTest (R"(
553
+ void target() {
554
+ MyObj *p = nullptr;
555
+ for (int i = 0; i < 2; ++i) {
556
+ MyObj s;
557
+ p = &s;
558
+ POINT(in_loop);
559
+ } // s expires here on each iteration
560
+ POINT(after_loop);
561
+ }
562
+ )" );
563
+ // Inside the loop, before the scope of 's' ends, its loan is not expired.
564
+ EXPECT_THAT (LoanTo (" s" ), Not (IsExpiredAt (" in_loop" )));
565
+ // After the loop finishes, the loan to 's' from the last iteration has
566
+ // expired.
567
+ EXPECT_THAT (LoanTo (" s" ), IsExpiredAt (" after_loop" ));
568
+ }
569
+
570
+ TEST_F (LifetimeAnalysisTest, MultipleExpiredLoans) {
571
+ SetupTest (R"(
572
+ void target() {
573
+ MyObj *p1, *p2, *p3;
574
+ {
575
+ MyObj s1;
576
+ p1 = &s1;
577
+ POINT(p1);
578
+ } // s1 expires
579
+ POINT(p2);
580
+ {
581
+ MyObj s2;
582
+ p2 = &s2;
583
+ MyObj s3;
584
+ p3 = &s3;
585
+ POINT(p3);
586
+ } // s2, s3 expire
587
+ POINT(p4);
588
+ }
589
+ )" );
590
+ EXPECT_THAT (LoanTo (" s1" ), Not (IsExpiredAt (" p1" )));
591
+
592
+ EXPECT_THAT (LoanTo (" s1" ), IsExpiredAt (" p2" ));
593
+
594
+ EXPECT_THAT (LoanTo (" s1" ), IsExpiredAt (" p3" ));
595
+ EXPECT_THAT (LoanTo (" s2" ), Not (IsExpiredAt (" p3" )));
596
+ EXPECT_THAT (LoanTo (" s3" ), Not (IsExpiredAt (" p3" )));
597
+
598
+ EXPECT_THAT (LoanTo (" s1" ), IsExpiredAt (" p4" ));
599
+ EXPECT_THAT (LoanTo (" s2" ), IsExpiredAt (" p4" ));
600
+ EXPECT_THAT (LoanTo (" s3" ), IsExpiredAt (" p4" ));
601
+ }
602
+
438
603
} // anonymous namespace
439
604
} // namespace clang::lifetimes::internal
0 commit comments