| //===- LifetimeSafetyTest.cpp - Lifetime Safety Tests -*---------- C++-*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Testing/TestAST.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <optional> |
| #include <vector> |
| |
| namespace clang::lifetimes::internal { |
| namespace { |
| |
| using namespace ast_matchers; |
| using ::testing::Not; |
| using ::testing::SizeIs; |
| using ::testing::UnorderedElementsAreArray; |
| |
| // A helper class to run the full lifetime analysis on a piece of code |
| // and provide an interface for querying the results. |
| class LifetimeTestRunner { |
| public: |
| LifetimeTestRunner(llvm::StringRef Code) { |
| std::string FullCode = R"( |
| #define POINT(name) void("__lifetime_test_point_" #name) |
| |
| struct MyObj { ~MyObj() {} int i; }; |
| |
| struct [[gsl::Pointer()]] View { |
| View(const MyObj&); |
| View(); |
| }; |
| )"; |
| FullCode += Code.str(); |
| |
| Inputs = TestInputs(FullCode); |
| Inputs.Language = TestLanguage::Lang_CXX20; |
| AST = std::make_unique<clang::TestAST>(Inputs); |
| ASTCtx = &AST->context(); |
| |
| // Find the target function using AST matchers. |
| auto MatchResult = |
| match(functionDecl(hasName("target")).bind("target"), *ASTCtx); |
| auto *FD = selectFirst<FunctionDecl>("target", MatchResult); |
| if (!FD) { |
| ADD_FAILURE() << "Test case must have a function named 'target'"; |
| return; |
| } |
| AnalysisCtx = std::make_unique<AnalysisDeclContext>(nullptr, FD); |
| CFG::BuildOptions &BuildOptions = AnalysisCtx->getCFGBuildOptions(); |
| BuildOptions.setAllAlwaysAdd(); |
| BuildOptions.AddImplicitDtors = true; |
| BuildOptions.AddTemporaryDtors = true; |
| BuildOptions.AddLifetime = true; |
| |
| // Run the main analysis. |
| Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr); |
| Analysis->run(); |
| |
| AnnotationToPointMap = Analysis->getFactManager().getTestPoints(); |
| } |
| |
| LifetimeSafetyAnalysis &getAnalysis() { return *Analysis; } |
| ASTContext &getASTContext() { return *ASTCtx; } |
| AnalysisDeclContext &getAnalysisContext() { return *AnalysisCtx; } |
| |
| ProgramPoint getProgramPoint(llvm::StringRef Annotation) { |
| auto It = AnnotationToPointMap.find(Annotation); |
| if (It == AnnotationToPointMap.end()) { |
| ADD_FAILURE() << "Annotation '" << Annotation << "' not found."; |
| return nullptr; |
| } |
| return It->second; |
| } |
| |
| private: |
| TestInputs Inputs; |
| std::unique_ptr<TestAST> AST; |
| ASTContext *ASTCtx = nullptr; |
| std::unique_ptr<AnalysisDeclContext> AnalysisCtx; |
| std::unique_ptr<LifetimeSafetyAnalysis> Analysis; |
| llvm::StringMap<ProgramPoint> AnnotationToPointMap; |
| }; |
| |
| // A convenience wrapper that uses the LifetimeSafetyAnalysis public API. |
| class LifetimeTestHelper { |
| public: |
| LifetimeTestHelper(LifetimeTestRunner &Runner) |
| : Runner(Runner), Analysis(Runner.getAnalysis()) {} |
| |
| std::optional<OriginID> getOriginForDecl(llvm::StringRef VarName) { |
| auto *VD = findDecl<ValueDecl>(VarName); |
| if (!VD) |
| return std::nullopt; |
| // This assumes the OriginManager's `get` can find an existing origin. |
| // We might need a `find` method on OriginManager to avoid `getOrCreate` |
| // logic in a const-query context if that becomes an issue. |
| return const_cast<OriginManager &>(Analysis.getFactManager().getOriginMgr()) |
| .get(*VD); |
| } |
| |
| std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) { |
| auto *VD = findDecl<VarDecl>(VarName); |
| if (!VD) { |
| ADD_FAILURE() << "Failed to find VarDecl for '" << VarName << "'"; |
| return {}; |
| } |
| std::vector<LoanID> LID; |
| for (const Loan *L : Analysis.getFactManager().getLoanMgr().getLoans()) |
| if (const auto *BL = dyn_cast<PathLoan>(L)) |
| if (BL->getAccessPath().D == VD) |
| LID.push_back(L->getID()); |
| if (LID.empty()) { |
| ADD_FAILURE() << "Loan for '" << VarName << "' not found."; |
| return {}; |
| } |
| return LID; |
| } |
| |
| // Gets the set of loans that are live at the given program point. A loan is |
| // considered live at point P if there is a live origin which contains this |
| // loan. |
| std::optional<LoanSet> getLiveLoansAtPoint(ProgramPoint P) const { |
| const auto &LiveOriginsAnalysis = Runner.getAnalysis().getLiveOrigins(); |
| const auto &LoanPropagation = Runner.getAnalysis().getLoanPropagation(); |
| |
| LivenessMap LiveOriginsMap = LiveOriginsAnalysis.getLiveOriginsAt(P); |
| |
| LoanSet::Factory F; |
| LoanSet Result = F.getEmptySet(); |
| |
| for (const auto &[OID, LI] : LiveOriginsMap) { |
| LoanSet Loans = LoanPropagation.getLoans(OID, P); |
| Result = clang::lifetimes::internal::utils::join(Result, Loans, F); |
| } |
| |
| if (Result.isEmpty()) |
| return std::nullopt; |
| |
| return Result; |
| } |
| |
| const ExpireFact * |
| getExpireFactFromAllFacts(const llvm::ArrayRef<const Fact *> &FactsInBlock, |
| const LoanID &loanID) { |
| for (const Fact *F : FactsInBlock) { |
| if (auto const *CurrentEF = F->getAs<ExpireFact>()) |
| if (CurrentEF->getLoanID() == loanID) |
| return CurrentEF; |
| } |
| return nullptr; |
| } |
| |
| std::optional<LoanSet> getLoansAtPoint(OriginID OID, |
| llvm::StringRef Annotation) { |
| ProgramPoint PP = Runner.getProgramPoint(Annotation); |
| if (!PP) |
| return std::nullopt; |
| return Analysis.getLoanPropagation().getLoans(OID, PP); |
| } |
| |
| std::optional<std::vector<std::pair<OriginID, LivenessKind>>> |
| getLiveOriginsAtPoint(llvm::StringRef Annotation) { |
| ProgramPoint PP = Runner.getProgramPoint(Annotation); |
| if (!PP) |
| return std::nullopt; |
| std::vector<std::pair<OriginID, LivenessKind>> Result; |
| for (auto &[OID, Info] : Analysis.getLiveOrigins().getLiveOriginsAt(PP)) |
| Result.push_back({OID, Info.Kind}); |
| return Result; |
| } |
| |
| ProgramPoint getProgramPoint(llvm::StringRef Annotation) { |
| return Runner.getProgramPoint(Annotation); |
| } |
| |
| llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) { |
| return Runner.getAnalysis().getFactManager().getBlockContaining(P); |
| } |
| |
| private: |
| template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) { |
| auto &Ctx = Runner.getASTContext(); |
| const auto *TargetFunc = Runner.getAnalysisContext().getDecl(); |
| auto Results = |
| match(valueDecl(hasName(Name), |
| hasAncestor(functionDecl(equalsNode(TargetFunc)))) |
| .bind("v"), |
| Ctx); |
| if (Results.empty()) { |
| ADD_FAILURE() << "Declaration '" << Name << "' not found in AST."; |
| return nullptr; |
| } |
| if (Results.size() > 1) { |
| ADD_FAILURE() << "Multiple declarations found for '" << Name << "'"; |
| return nullptr; |
| } |
| return const_cast<DeclT *>(selectFirst<DeclT>("v", Results)); |
| } |
| |
| LifetimeTestRunner &Runner; |
| LifetimeSafetyAnalysis &Analysis; |
| }; |
| |
| // ========================================================================= // |
| // GTest Matchers & Fixture |
| // ========================================================================= // |
| |
| // A helper class to represent a set of loans, identified by variable names. |
| class LoanSetInfo { |
| public: |
| LoanSetInfo(const std::vector<std::string> &Vars, LifetimeTestHelper &H) |
| : LoanVars(Vars), Helper(H) {} |
| std::vector<std::string> LoanVars; |
| LifetimeTestHelper &Helper; |
| }; |
| |
| // It holds the name of the origin variable and a reference to the helper. |
| class OriginInfo { |
| public: |
| OriginInfo(llvm::StringRef OriginVar, LifetimeTestHelper &Helper) |
| : OriginVar(OriginVar), Helper(Helper) {} |
| llvm::StringRef OriginVar; |
| 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. |
| /// |
| /// This matcher is intended to be used with an \c OriginInfo object. |
| /// |
| /// \param LoanVars A vector of strings, where each string is the name of a |
| /// variable expected to be the source of a loan. |
| /// \param Annotation A string identifying the program point (created with |
| /// POINT()) where the check should be performed. |
| MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") { |
| const OriginInfo &Info = arg; |
| std::optional<OriginID> OIDOpt = Info.Helper.getOriginForDecl(Info.OriginVar); |
| if (!OIDOpt) { |
| *result_listener << "could not find origin for '" << Info.OriginVar.str() |
| << "'"; |
| return false; |
| } |
| |
| std::optional<LoanSet> ActualLoansSetOpt = |
| Info.Helper.getLoansAtPoint(*OIDOpt, Annotation); |
| if (!ActualLoansSetOpt) { |
| *result_listener << "could not get a valid loan set at point '" |
| << Annotation << "'"; |
| return false; |
| } |
| std::vector<LoanID> ActualLoans(ActualLoansSetOpt->begin(), |
| ActualLoansSetOpt->end()); |
| |
| std::vector<LoanID> ExpectedLoans; |
| for (const auto &LoanVar : LoanVars) { |
| std::vector<LoanID> ExpectedLIDs = Info.Helper.getLoansForVar(LoanVar); |
| if (ExpectedLIDs.empty()) { |
| *result_listener << "could not find loan for var '" << LoanVar << "'"; |
| return false; |
| } |
| ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(), |
| ExpectedLIDs.end()); |
| } |
| std::sort(ExpectedLoans.begin(), ExpectedLoans.end()); |
| std::sort(ActualLoans.begin(), ActualLoans.end()); |
| if (ExpectedLoans != ActualLoans) { |
| *result_listener << "Expected: {"; |
| for (const auto &LoanID : ExpectedLoans) { |
| *result_listener << LoanID.Value << ", "; |
| } |
| *result_listener << "} Actual: {"; |
| for (const auto &LoanID : ActualLoans) { |
| *result_listener << LoanID.Value << ", "; |
| } |
| *result_listener << "}"; |
| return false; |
| } |
| |
| return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans), |
| ActualLoans, result_listener); |
| } |
| |
| enum class LivenessKindFilter { Maybe, Must, All }; |
| |
| /// 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<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; |
| } |
| ExpectedLiveOrigins.push_back(*OriginIDOpt); |
| } |
| 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_P2(HasLiveLoanAtExpiryImpl, HelperPtr, Annotation, "") { |
| llvm::StringRef VarName = arg; |
| LifetimeTestHelper &Helper = *HelperPtr; |
| |
| std::vector<LoanID> Loans = Helper.getLoansForVar(VarName); |
| if (Loans.empty()) { |
| *result_listener << "No loans found for variable" << VarName.str(); |
| return false; |
| } |
| |
| ProgramPoint PP = Helper.getProgramPoint(Annotation); |
| llvm::ArrayRef<const Fact *> AllFactsInBlock = Helper.getBlockContaining(PP); |
| |
| bool NoExpireFactLive = false; |
| for (const LoanID CurrentLoanID : Loans) { |
| const ExpireFact *EF = |
| Helper.getExpireFactFromAllFacts(AllFactsInBlock, CurrentLoanID); |
| if (!EF) { |
| NoExpireFactLive = true; |
| continue; |
| } |
| std::optional<LoanSet> LiveLoans = Helper.getLiveLoansAtPoint(EF); |
| if (!LiveLoans.has_value()) { |
| *result_listener << "No Live Loans At Expiry Location."; |
| continue; |
| } |
| if (LiveLoans->contains({CurrentLoanID})) |
| return true; |
| } |
| if (NoExpireFactLive) { |
| *result_listener << "No Expire Fact for loan of " << VarName.str(); |
| return false; |
| } |
| *result_listener << "No loans of " << VarName.str() << " are live"; |
| return false; |
| } |
| |
| 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. |
| class LifetimeAnalysisTest : public ::testing::Test { |
| protected: |
| void SetupTest(llvm::StringRef Code) { |
| Runner = std::make_unique<LifetimeTestRunner>(Code); |
| Helper = std::make_unique<LifetimeTestHelper>(*Runner); |
| } |
| |
| OriginInfo Origin(llvm::StringRef OriginVar) { |
| return OriginInfo(OriginVar, *Helper); |
| } |
| |
| /// 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); |
| } |
| |
| /// A convenience helper for asserting that no loans are expired. |
| LoanSetInfo NoLoans() { return LoansTo({}); } |
| |
| // Factory function that hides the std::vector creation. |
| auto HasLoansTo(std::initializer_list<std::string> LoanVars, |
| const char *Annotation) { |
| return HasLoansToImpl(std::vector<std::string>(LoanVars), Annotation); |
| } |
| |
| auto HasLiveLoanAtExpiry(const char *Annotation) { |
| return HasLiveLoanAtExpiryImpl(Helper.get(), Annotation); |
| } |
| |
| std::unique_ptr<LifetimeTestRunner> Runner; |
| std::unique_ptr<LifetimeTestHelper> Helper; |
| }; |
| |
| // ========================================================================= // |
| // TESTS |
| // ========================================================================= // |
| |
| TEST_F(LifetimeAnalysisTest, SimpleLoanAndOrigin) { |
| SetupTest(R"( |
| void target() { |
| int x; |
| int* p = &x; |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"x"}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, OverwriteOrigin) { |
| SetupTest(R"( |
| void target() { |
| MyObj s1, s2; |
| |
| MyObj* p = &s1; |
| POINT(after_s1); |
| |
| p = &s2; |
| POINT(after_s2); |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_s1")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "after_s2")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ConditionalLoan) { |
| SetupTest(R"( |
| void target(bool cond) { |
| int a, b; |
| int *p = nullptr; |
| if (cond) { |
| p = &a; |
| POINT(after_then); |
| } else { |
| p = &b; |
| POINT(after_else); |
| } |
| POINT(after_if); |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "after_then")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"b"}, "after_else")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"a", "b"}, "after_if")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, PointerChain) { |
| SetupTest(R"( |
| void target() { |
| MyObj y; |
| MyObj* ptr1 = &y; |
| POINT(p1); |
| |
| MyObj* ptr2 = ptr1; |
| POINT(p2); |
| |
| ptr2 = ptr1; |
| POINT(p3); |
| |
| ptr2 = ptr2; // Self assignment |
| POINT(p4); |
| } |
| )"); |
| EXPECT_THAT(Origin("ptr1"), HasLoansTo({"y"}, "p1")); |
| EXPECT_THAT(Origin("ptr2"), HasLoansTo({"y"}, "p2")); |
| EXPECT_THAT(Origin("ptr2"), HasLoansTo({"y"}, "p3")); |
| EXPECT_THAT(Origin("ptr2"), HasLoansTo({"y"}, "p4")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ReassignToNull) { |
| SetupTest(R"( |
| void target() { |
| MyObj s1; |
| MyObj* p = &s1; |
| POINT(before_null); |
| p = nullptr; |
| POINT(after_null); |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "before_null")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({}, "after_null")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ReassignInIf) { |
| SetupTest(R"( |
| void target(bool condition) { |
| MyObj s1, s2; |
| MyObj* p = &s1; |
| POINT(before_if); |
| if (condition) { |
| p = &s2; |
| POINT(after_reassign); |
| } |
| POINT(after_if); |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "before_if")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "after_reassign")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_if")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, AssignInSwitch) { |
| SetupTest(R"( |
| void target(int mode) { |
| MyObj s1, s2, s3; |
| MyObj* p = nullptr; |
| switch (mode) { |
| case 1: |
| p = &s1; |
| POINT(case1); |
| break; |
| case 2: |
| p = &s2; |
| POINT(case2); |
| break; |
| default: |
| p = &s3; |
| POINT(case3); |
| break; |
| } |
| POINT(after_switch); |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "case1")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "case2")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s3"}, "case3")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2", "s3"}, "after_switch")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LoopWithBreak) { |
| SetupTest(R"( |
| void target(int count) { |
| MyObj s1; |
| MyObj s2; |
| MyObj* p = &s1; |
| POINT(before_loop); |
| for (int i = 0; i < count; ++i) { |
| if (i == 5) { |
| p = &s2; |
| POINT(inside_if); |
| break; |
| } |
| POINT(after_if); |
| } |
| POINT(after_loop); |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "before_loop")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "inside_if")); |
| // At the join point after if, s2 cannot make it to p without the if. |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_if")); |
| // At the join point after the loop, p could hold a loan to s1 (if the loop |
| // completed normally) or to s2 (if the loop was broken). |
| EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_loop")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, PointersInACycle) { |
| SetupTest(R"( |
| void target(bool condition) { |
| MyObj v1, v2, v3; |
| MyObj *p1 = &v1, *p2 = &v2, *p3 = &v3; |
| |
| POINT(before_while); |
| while (condition) { |
| MyObj* temp = p1; |
| p1 = p2; |
| p2 = p3; |
| p3 = temp; |
| POINT(in_loop); |
| } |
| POINT(after_loop); |
| } |
| )"); |
| EXPECT_THAT(Origin("p1"), HasLoansTo({"v1"}, "before_while")); |
| EXPECT_THAT(Origin("p2"), HasLoansTo({"v2"}, "before_while")); |
| EXPECT_THAT(Origin("p3"), HasLoansTo({"v3"}, "before_while")); |
| |
| // At the fixed point after the loop, all pointers could point to any of |
| // the three variables. |
| EXPECT_THAT(Origin("p1"), HasLoansTo({"v1", "v2", "v3"}, "after_loop")); |
| EXPECT_THAT(Origin("p2"), HasLoansTo({"v1", "v2", "v3"}, "after_loop")); |
| EXPECT_THAT(Origin("p3"), HasLoansTo({"v1", "v2", "v3"}, "after_loop")); |
| |
| EXPECT_THAT(Origin("temp"), HasLoansTo({"v1", "v2", "v3"}, "in_loop")); |
| // 'temp' is a block-local origin and it's loans are not tracked outside the |
| // block. |
| EXPECT_THAT(Origin("temp"), HasLoansTo({}, "after_loop")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, PointersAndExpirationInACycle) { |
| SetupTest(R"( |
| void target(bool condition) { |
| MyObj v1, v2; |
| MyObj *p1 = &v1, *p2 = &v2; |
| |
| POINT(before_while); |
| while (condition) { |
| POINT(in_loop_before_temp); |
| MyObj temp; |
| p1 = &temp; |
| POINT(in_loop_after_temp); |
| |
| MyObj* q = p1; |
| p1 = p2; |
| p2 = q; |
| } |
| POINT(after_loop); |
| } |
| )"); |
| EXPECT_THAT(Origin("p1"), HasLoansTo({"v1"}, "before_while")); |
| EXPECT_THAT(Origin("p2"), HasLoansTo({"v2"}, "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(Origin("p1"), HasLoansTo({"temp"}, "in_loop_after_temp")); |
| EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "in_loop_after_temp")); |
| |
| EXPECT_THAT(Origin("p1"), HasLoansTo({"v1", "v2", "temp"}, "after_loop")); |
| EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "after_loop")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, InfiniteLoopPrunesEdges) { |
| SetupTest(R"( |
| void target(MyObj out) { |
| MyObj *p = &out; |
| POINT(before_loop); |
| |
| for (;;) { |
| POINT(begin); |
| MyObj in; |
| p = ∈ |
| POINT(end); |
| } |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"out"}, "before_loop")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"in", "out"}, "begin")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"in"}, "end")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, NestedScopes) { |
| SetupTest(R"( |
| void target() { |
| MyObj* p = nullptr; |
| { |
| MyObj outer; |
| p = &outer; |
| POINT(before_inner_scope); |
| { |
| MyObj inner; |
| p = &inner; |
| POINT(inside_inner_scope); |
| } // inner expires |
| POINT(after_inner_scope); |
| } // outer expires |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"outer"}, "before_inner_scope")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "inside_inner_scope")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_inner_scope")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, NoDuplicateLoansForImplicitCastToConst) { |
| SetupTest(R"( |
| void target() { |
| MyObj a; |
| const MyObj* p = &a; |
| const MyObj* q = &a; |
| POINT(at_end); |
| } |
| )"); |
| EXPECT_THAT(Helper->getLoansForVar("a"), SizeIs(2)); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, GslPointerSimpleLoan) { |
| SetupTest(R"( |
| void target() { |
| MyObj a; |
| View x = a; |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, GslPointerConstructFromOwner) { |
| SetupTest(R"( |
| void target() { |
| MyObj al, bl, cl, dl, el, fl; |
| View a = View(al); |
| View b = View{bl}; |
| View c = View(View(View(cl))); |
| View d = View{View(View(dl))}; |
| View e = View{View{View{el}}}; |
| View f = {fl}; |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("a"), HasLoansTo({"al"}, "p1")); |
| EXPECT_THAT(Origin("b"), HasLoansTo({"bl"}, "p1")); |
| EXPECT_THAT(Origin("c"), HasLoansTo({"cl"}, "p1")); |
| EXPECT_THAT(Origin("d"), HasLoansTo({"dl"}, "p1")); |
| EXPECT_THAT(Origin("e"), HasLoansTo({"el"}, "p1")); |
| EXPECT_THAT(Origin("f"), HasLoansTo({"fl"}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, GslPointerConstructFromView) { |
| SetupTest(R"( |
| void target() { |
| MyObj a; |
| View x = View(a); |
| View y = View{x}; |
| View z = View(View(View(y))); |
| View p = View{View(View(x))}; |
| View q = {x}; |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("q"), HasLoansTo({"a"}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, GslPointerInConditionalOperator) { |
| SetupTest(R"( |
| void target(bool cond) { |
| MyObj a, b; |
| View v = cond ? a : b; |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("v"), HasLoansTo({"a", "b"}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ExtraParenthesis) { |
| SetupTest(R"( |
| void target() { |
| MyObj a; |
| View x = ((View((((a)))))); |
| View y = ((View{(((x)))})); |
| View z = ((View(((y))))); |
| View p = ((View{((x))})); |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1")); |
| } |
| |
| // FIXME: Handle temporaries. |
| TEST_F(LifetimeAnalysisTest, ViewFromTemporary) { |
| SetupTest(R"( |
| MyObj temporary(); |
| void target() { |
| View v = temporary(); |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, GslPointerWithConstAndAuto) { |
| SetupTest(R"( |
| void target() { |
| MyObj a; |
| const View v1 = a; |
| auto v2 = v1; |
| const auto& v3 = v2; |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, GslPointerPropagation) { |
| SetupTest(R"( |
| void target() { |
| MyObj a; |
| View x = a; |
| POINT(p1); |
| |
| View y = x; // Propagation via copy-construction |
| POINT(p2); |
| |
| View z; |
| z = x; // Propagation via copy-assignment |
| POINT(p3); |
| } |
| )"); |
| |
| EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p2")); |
| EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p3")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, GslPointerReassignment) { |
| SetupTest(R"( |
| void target() { |
| MyObj safe; |
| View v; |
| v = safe; |
| POINT(p1); |
| { |
| MyObj unsafe; |
| v = unsafe; |
| POINT(p2); |
| } // `unsafe` expires here. |
| POINT(p3); |
| } |
| )"); |
| |
| EXPECT_THAT(Origin("v"), HasLoansTo({"safe"}, "p1")); |
| EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p2")); |
| EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p3")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, GslPointerConversionOperator) { |
| SetupTest(R"( |
| struct String; |
| |
| struct [[gsl::Pointer()]] StringView { |
| StringView() = default; |
| }; |
| |
| struct String { |
| ~String() {} |
| operator StringView() const; |
| }; |
| |
| void target() { |
| String xl, yl; |
| StringView x = xl; |
| StringView y; |
| y = yl; |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("x"), HasLoansTo({"xl"}, "p1")); |
| EXPECT_THAT(Origin("y"), HasLoansTo({"yl"}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundSimple) { |
| SetupTest(R"( |
| View Identity(View v [[clang::lifetimebound]]); |
| void target() { |
| MyObj a, b; |
| View v1 = a; |
| POINT(p1); |
| |
| View v2 = Identity(v1); |
| View v3 = Identity(b); |
| POINT(p2); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); |
| // The origin of v2 should now contain the loan to 'o' from v1. |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2")); |
| EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundMemberFunction) { |
| SetupTest(R"( |
| struct [[gsl::Pointer()]] MyView { |
| MyView(const MyObj& o) {} |
| MyView pass() [[clang::lifetimebound]] { return *this; } |
| }; |
| void target() { |
| MyObj o; |
| MyView v1 = o; |
| POINT(p1); |
| MyView v2 = v1.pass(); |
| POINT(p2); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1")); |
| // The call v1.pass() is bound to 'v1'. The origin of v2 should get the loans |
| // from v1. |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p2")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundMultipleArgs) { |
| SetupTest(R"( |
| View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]); |
| void target() { |
| MyObj o1, o2; |
| View v1 = o1; |
| View v2 = o2; |
| POINT(p1); |
| |
| View v3 = Choose(true, v1, v2); |
| POINT(p2); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1")); |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p2")); |
| // v3 should have loans from both v1 and v2, demonstrating the union of |
| // loans. |
| EXPECT_THAT(Origin("v3"), HasLoansTo({"o1", "o2"}, "p2")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundMixedArgs) { |
| SetupTest(R"( |
| View Choose(bool cond, View a [[clang::lifetimebound]], View b); |
| void target() { |
| MyObj o1, o2; |
| View v1 = o1; |
| View v2 = o2; |
| POINT(p1); |
| |
| View v3 = Choose(true, v1, v2); |
| POINT(p2); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1")); |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p1")); |
| // v3 should only have loans from v1, as v2 is not lifetimebound. |
| EXPECT_THAT(Origin("v3"), HasLoansTo({"o1"}, "p2")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundChainOfViews) { |
| SetupTest(R"( |
| View Identity(View v [[clang::lifetimebound]]); |
| View DoubleIdentity(View v [[clang::lifetimebound]]); |
| |
| void target() { |
| MyObj obj; |
| View v1 = obj; |
| POINT(p1); |
| View v2 = DoubleIdentity(Identity(v1)); |
| POINT(p2); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"obj"}, "p1")); |
| // v2 should inherit the loan from v1 through the chain of calls. |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"obj"}, "p2")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundRawPointerParameter) { |
| SetupTest(R"( |
| View ViewFromPtr(const MyObj* p [[clang::lifetimebound]]); |
| MyObj* PtrFromPtr(const MyObj* p [[clang::lifetimebound]]); |
| MyObj* PtrFromView(View v [[clang::lifetimebound]]); |
| |
| void target() { |
| MyObj a; |
| View v = ViewFromPtr(&a); |
| POINT(p1); |
| |
| MyObj b; |
| MyObj* ptr1 = PtrFromPtr(&b); |
| MyObj* ptr2 = PtrFromPtr(PtrFromPtr(PtrFromPtr(ptr1))); |
| POINT(p2); |
| |
| MyObj c; |
| View v2 = ViewFromPtr(PtrFromView(c)); |
| POINT(p3); |
| } |
| )"); |
| EXPECT_THAT(Origin("v"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("ptr1"), HasLoansTo({"b"}, "p2")); |
| EXPECT_THAT(Origin("ptr2"), HasLoansTo({"b"}, "p2")); |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"c"}, "p3")); |
| } |
| |
| // FIXME: This can be controversial and may be revisited in the future. |
| TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefViewParameter) { |
| SetupTest(R"( |
| View Identity(const View& v [[clang::lifetimebound]]); |
| void target() { |
| MyObj o; |
| View v1 = o; |
| View v2 = Identity(v1); |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefObjParam) { |
| SetupTest(R"( |
| View Identity(const MyObj& o [[clang::lifetimebound]]); |
| void target() { |
| MyObj a; |
| View v1 = Identity(a); |
| POINT(p1); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundReturnReference) { |
| SetupTest(R"( |
| const MyObj& Identity(View v [[clang::lifetimebound]]); |
| void target() { |
| MyObj a; |
| View v1 = a; |
| POINT(p1); |
| |
| View v2 = Identity(v1); |
| |
| const MyObj& b = Identity(v1); |
| View v3 = Identity(b); |
| POINT(p2); |
| |
| MyObj c; |
| View v4 = Identity(c); |
| POINT(p3); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2")); |
| |
| // FIXME: Handle reference types. 'v3' should have loan to 'a' instead of 'b'. |
| EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2")); |
| |
| EXPECT_THAT(Origin("v4"), HasLoansTo({"c"}, "p3")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunction) { |
| SetupTest(R"( |
| template <typename T> |
| const T& Identity(T&& v [[clang::lifetimebound]]); |
| void target() { |
| MyObj a; |
| View v1 = Identity(a); |
| POINT(p1); |
| |
| View v2 = Identity(v1); |
| const View& v3 = Identity(v1); |
| POINT(p2); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2")); |
| EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p2")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateClass) { |
| SetupTest(R"( |
| template<typename T> |
| struct [[gsl::Pointer()]] MyTemplateView { |
| MyTemplateView(const T& o) {} |
| MyTemplateView pass() [[clang::lifetimebound]] { return *this; } |
| }; |
| void target() { |
| MyObj o; |
| MyTemplateView<MyObj> v1 = o; |
| POINT(p1); |
| MyTemplateView<MyObj> v2 = v1.pass(); |
| POINT(p2); |
| } |
| )"); |
| EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1")); |
| EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p2")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) { |
| SetupTest(R"( |
| struct MyOwner { |
| MyObj o; |
| operator View() const [[clang::lifetimebound]]; |
| }; |
| |
| void target() { |
| MyOwner owner; |
| View v = owner; |
| POINT(p1); |
| } |
| )"); |
| 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")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAF) { |
| SetupTest(R"( |
| void target() { |
| int *ptr; |
| { |
| int s = 1; |
| ptr = &s; |
| } |
| POINT(p1); |
| (void)*ptr; |
| } |
| )"); |
| EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1")); |
| EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAF) { |
| SetupTest(R"( |
| class S { |
| View a, b; |
| }; |
| |
| void target() { |
| S* ptr; |
| { |
| S s; |
| ptr = &s; |
| } |
| POINT(p1); |
| (void)ptr; |
| } |
| )"); |
| EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1")); |
| EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) { |
| SetupTest(R"( |
| MyObj* target() { |
| MyObj s; |
| MyObj* p = &s; |
| POINT(p1); |
| return p; |
| } |
| )"); |
| EXPECT_THAT("s", HasLiveLoanAtExpiry("p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, DirectReturn) { |
| SetupTest(R"( |
| MyObj* target() { |
| MyObj s; |
| POINT(P); |
| return &s; |
| } |
| )"); |
| EXPECT_THAT("s", HasLiveLoanAtExpiry("P")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ConditionalAssignUnconditionalReturn) { |
| SetupTest(R"( |
| MyObj* target(bool c) { |
| MyObj s1; |
| MyObj* p = nullptr; |
| if (c) { |
| p = &s1; |
| } |
| POINT(P); |
| return p; |
| } |
| )"); |
| EXPECT_THAT("s1", HasLiveLoanAtExpiry("P")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, MultipleAssignments) { |
| SetupTest(R"( |
| MyObj* target() { |
| MyObj s; |
| MyObj* p1 = &s; |
| MyObj* p2 = &s; |
| POINT(P); |
| return p2; |
| } |
| )"); |
| // Test if atleast one loan to "s" is live; |
| EXPECT_THAT("s", HasLiveLoanAtExpiry("P")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ConditionalAssignBothBranches) { |
| SetupTest(R"( |
| MyObj* target(bool c) { |
| MyObj s1; |
| static MyObj s2; |
| MyObj* p = nullptr; |
| if (c) { |
| p = &s1; |
| } else { |
| p = &s2; |
| } |
| POINT(P); |
| return p; |
| } |
| )"); |
| EXPECT_THAT("s1", HasLiveLoanAtExpiry("P")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ReassignFromSafeToLocalThenReturn) { |
| SetupTest(R"( |
| MyObj* target() { |
| static MyObj safe_obj; |
| MyObj local_obj; |
| MyObj* p = &safe_obj; |
| |
| p = &local_obj; |
| POINT(P); |
| return p; |
| } |
| )"); |
| EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("P")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, PointerChainToLocal) { |
| SetupTest(R"( |
| MyObj* target() { |
| MyObj local_obj; |
| MyObj* p1 = &local_obj; |
| MyObj* p2 = p1; |
| POINT(P); |
| return p2; |
| } |
| )"); |
| EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("P")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, MultipleAssignmentMultipleReturn) { |
| SetupTest(R"( |
| MyObj* target(bool c1, bool c2) { |
| static MyObj global_obj; |
| MyObj local_obj1; |
| MyObj local_obj2; |
| MyObj* p = nullptr; |
| if(c1){ |
| p = &local_obj1; |
| POINT(C1); |
| return p; |
| } |
| else if(c2){ |
| p = &local_obj2; |
| POINT(C2); |
| return p; |
| } |
| p = &global_obj; |
| POINT(C3); |
| return p; |
| } |
| )"); |
| |
| EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("C1")); |
| EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("C2")); |
| |
| EXPECT_THAT("local_obj1", Not(HasLiveLoanAtExpiry("C3"))); |
| EXPECT_THAT("local_obj2", Not(HasLiveLoanAtExpiry("C3"))); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, MultipleAssignmentsSingleReturn) { |
| SetupTest(R"( |
| MyObj* target(bool c1, bool c2) { |
| static MyObj global_obj; |
| MyObj local_obj1; |
| MyObj local_obj2; |
| MyObj* p = nullptr; |
| if(c1){ |
| p = &local_obj1; |
| } |
| else if(c2){ |
| p = &local_obj2; |
| } |
| else{ |
| p = &global_obj; |
| } |
| POINT(P); |
| return p; |
| } |
| )"); |
| EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("P")); |
| EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("P")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, UseAfterScopeThenReturn) { |
| SetupTest(R"( |
| MyObj* target() { |
| MyObj* p; |
| { |
| MyObj local_obj; |
| p = &local_obj; |
| POINT(p1); |
| } |
| POINT(p2); |
| return p; |
| } |
| )"); |
| EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2")); |
| EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2")); |
| |
| EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1")); |
| EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1")); |
| |
| EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("p2")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) { |
| SetupTest(R"( |
| MyObj* target(bool c) { |
| MyObj* p; |
| static MyObj global_obj; |
| { |
| MyObj local_obj; |
| p = &local_obj; |
| if(c){ |
| POINT(p1); |
| return p; |
| } |
| } |
| POINT(p2); |
| return &global_obj; |
| } |
| )"); |
| EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("p1")); |
| |
| EXPECT_THAT(NoOrigins(), AreLiveAt("p2")); |
| |
| EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1")); |
| EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAR) { |
| SetupTest(R"( |
| int* target() { |
| int s = 10; |
| int* p = &s; |
| POINT(p1); |
| return p; |
| } |
| )"); |
| EXPECT_THAT("s", HasLiveLoanAtExpiry("p1")); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAR) { |
| SetupTest(R"( |
| class S { |
| View a, b; |
| }; |
| |
| S* target() { |
| S *ptr; |
| S s; |
| ptr = &s; |
| POINT(p1); |
| return ptr; |
| } |
| )"); |
| EXPECT_THAT("s", HasLiveLoanAtExpiry("p1")); |
| } |
| |
| } // anonymous namespace |
| } // namespace clang::lifetimes::internal |