diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index a1acd8615afdd..cccb25bd41037 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -52,6 +52,8 @@ class FactsGenerator : public ConstStmtVisitor { private: void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds); + void handleTemporaryDtor(const CFGTemporaryDtor &TemporaryDtor); + void handleGSLPointerConstruction(const CXXConstructExpr *CCE); /// Checks if a call-like expression creates a borrow by passing a value to a diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h index e9bccd4773622..6dfe4c8fa6b9c 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H #include "clang/AST/Decl.h" +#include "clang/AST/ExprCXX.h" #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h" #include "llvm/Support/raw_ostream.h" @@ -29,9 +30,25 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) { /// variable. /// TODO: Model access paths of other types, e.g., s.field, heap and globals. struct AccessPath { - const clang::ValueDecl *D; + // An access path can be: + // - ValueDecl * , to represent the storage location corresponding to the + // variable declared in ValueDecl. + // - MaterializeTemporaryExpr * , to represent the storage location of the + // temporary object materialized via this MaterializeTemporaryExpr. + const llvm::PointerUnion + P; + + AccessPath(const clang::ValueDecl *D) : P(D) {} + AccessPath(const clang::MaterializeTemporaryExpr *MTE) : P(MTE) {} + + const clang::ValueDecl *getAsValueDecl() const { + return P.dyn_cast(); + } - AccessPath(const clang::ValueDecl *D) : D(D) {} + const clang::MaterializeTemporaryExpr *getAsMaterializeTemporaryExpr() const { + return P.dyn_cast(); + } }; /// An abstract base class for a single "Loan" which represents lending a diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 2f270b03996f2..fbfed5d5a2cfd 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -41,6 +41,26 @@ static const PathLoan *createLoan(FactManager &FactMgr, return nullptr; } +/// Creates a loan for the storage location of a temporary object. +/// \param MTW The MaterializeTemporaryExpr that represents the temporary +/// binding. \return The new Loan. +static const PathLoan *createLoan(FactManager &FactMgr, + const MaterializeTemporaryExpr *MTE) { + AccessPath Path(MTE); + return FactMgr.getLoanMgr().createLoan(Path, MTE); +} + +/// Try to find a CXXBindTemporaryExpr that descends from MTE, stripping away +/// any implicit casts. +/// \param MTE MaterializeTemporaryExpr whose descendants we are interested in. +/// \return Pointer to descendant CXXBindTemporaryExpr or nullptr when not +/// found. +static const CXXBindTemporaryExpr * +getChildBinding(const MaterializeTemporaryExpr *MTE) { + const Expr *Child = MTE->getSubExpr()->IgnoreImpCasts(); + return dyn_cast(Child); +} + void FactsGenerator::run() { llvm::TimeTraceScope TimeProfile("FactGenerator"); const CFG &Cfg = *AC.getCFG(); @@ -60,6 +80,9 @@ void FactsGenerator::run() { else if (std::optional LifetimeEnds = Element.getAs()) handleLifetimeEnds(*LifetimeEnds); + else if (std::optional TemporaryDtor = + Element.getAs()) + handleTemporaryDtor(*TemporaryDtor); } CurrentBlockFacts.append(EscapesInCurrentBlock.begin(), EscapesInCurrentBlock.end()); @@ -218,9 +241,14 @@ void FactsGenerator::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *MTE) { if (!hasOrigin(MTE)) return; - // A temporary object's origin is the same as the origin of the - // expression that initializes it. - killAndFlowOrigin(*MTE, *MTE->getSubExpr()); + if (getChildBinding(MTE)) { + // Issue a loan to MTE for the storage location represented by MTE. + const Loan *L = createLoan(FactMgr, MTE); + OriginID OID = FactMgr.getOriginMgr().getOrCreate(*MTE); + CurrentBlockFacts.push_back(FactMgr.createFact(L->getID(), OID)); + } else { + killAndFlowOrigin(*MTE, *MTE->getSubExpr()); + } } void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) { @@ -233,13 +261,38 @@ void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) { if (const auto *BL = dyn_cast(Loan)) { // Check if the loan is for a stack variable and if that variable // is the one being destructed. - if (BL->getAccessPath().D == LifetimeEndsVD) + const AccessPath AP = BL->getAccessPath(); + const ValueDecl *Path = AP.getAsValueDecl(); + if (Path == LifetimeEndsVD) CurrentBlockFacts.push_back(FactMgr.createFact( BL->getID(), LifetimeEnds.getTriggerStmt()->getEndLoc())); } } } +void FactsGenerator::handleTemporaryDtor( + const CFGTemporaryDtor &TemporaryDtor) { + const CXXBindTemporaryExpr *BTE = TemporaryDtor.getBindTemporaryExpr(); + if (!BTE) { + return; + } + // Iterate through all loans to see if any expire. + for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) { + if (const auto *BL = dyn_cast(Loan)) { + // Check if the loan is for a temporary materialization and if that storage + // location is the one being destructed. + const AccessPath &AP = BL->getAccessPath(); + const MaterializeTemporaryExpr *Path = AP.getAsMaterializeTemporaryExpr(); + if (!Path) + continue; + if (BTE == getChildBinding(Path)) { + CurrentBlockFacts.push_back(FactMgr.createFact( + BL->getID(), TemporaryDtor.getBindTemporaryExpr()->getEndLoc())); + } + } + } +} + void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) { assert(isGslPointerType(CCE->getType())); if (CCE->getNumArgs() != 1) diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp index fdfdbb40a2a46..7d105e6a96967 100644 --- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp @@ -12,7 +12,16 @@ namespace clang::lifetimes::internal { void PathLoan::dump(llvm::raw_ostream &OS) const { OS << getID() << " (Path: "; - OS << Path.D->getNameAsString() << ")"; + if (const clang::ValueDecl *VD = Path.getAsValueDecl()) { + OS << VD->getNameAsString(); + } else if (const clang::MaterializeTemporaryExpr *MTE = + Path.getAsMaterializeTemporaryExpr()) { + // No nice "name" for the temporary, so deferring to LLVM default + OS << "MaterializeTemporaryExpr at " << MTE; + } else { + llvm_unreachable("access path is not one of any supported types"); + } + OS << ")"; } void PlaceholderLoan::dump(llvm::raw_ostream &OS) const { diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 1191469e23df1..78e82a3f89b0f 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -16,6 +16,9 @@ class TriviallyDestructedClass { View a, b; }; +MyObj temporary(); +void use(View); + //===----------------------------------------------------------------------===// // Basic Definite Use-After-Free (-W...permissive) // These are cases where the pointer is guaranteed to be dangling at the use site. @@ -943,3 +946,10 @@ void parentheses(bool cond) { } // expected-note 4 {{destroyed here}} (void)*p; // expected-note 4 {{later used here}} } + +void use_temporary_after_destruction() { + View a; + a = temporary(); // expected-warning {{object whose reference is captured does not live long enough}} \ + expected-note {{destroyed here}} + use(a); // expected-note {{later used here}} +} diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index fee4e79e27d03..9f585111ba40c 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -9,6 +9,7 @@ #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h" #include "clang/Testing/TestAST.h" #include "llvm/ADT/StringMap.h" #include "gmock/gmock.h" @@ -116,7 +117,7 @@ class LifetimeTestHelper { std::vector LID; for (const Loan *L : Analysis.getFactManager().getLoanMgr().getLoans()) if (const auto *BL = dyn_cast(L)) - if (BL->getAccessPath().D == VD) + if (BL->getAccessPath().getAsValueDecl() == VD) LID.push_back(L->getID()); if (LID.empty()) { ADD_FAILURE() << "Loan for '" << VarName << "' not found."; @@ -125,6 +126,14 @@ class LifetimeTestHelper { return LID; } + bool isLoanToATemporary(LoanID LID) { + const Loan *L = Analysis.getFactManager().getLoanMgr().getLoan(LID); + if (const auto *BL = dyn_cast(L)) { + return BL->getAccessPath().getAsMaterializeTemporaryExpr() != nullptr; + } + return false; + } + // 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. @@ -402,6 +411,35 @@ MATCHER_P(AreLiveAt, Annotation, "") { arg, result_listener); } +MATCHER_P(HasLoanToATemporary, Annotation, "") { + const OriginInfo &Info = arg; + auto &Helper = Info.Helper; + std::optional OIDOpt = Helper.getOriginForDecl(Info.OriginVar); + if (!OIDOpt) { + *result_listener << "could not find origin for '" << Info.OriginVar.str() + << "'"; + return false; + } + + std::optional LoansSetOpt = + Helper.getLoansAtPoint(*OIDOpt, Annotation); + if (!LoansSetOpt) { + *result_listener << "could not get a valid loan set at point '" + << Annotation << "'"; + return false; + } + + std::vector Loans(LoansSetOpt->begin(), LoansSetOpt->end()); + + for (LoanID LID : Loans) { + if (Helper.isLoanToATemporary(LID)) + return true; + } + *result_listener << "could not find loan to a temporary for '" + << Info.OriginVar.str() << "'"; + return false; +} + // Base test fixture to manage the runner and helper. class LifetimeAnalysisTest : public ::testing::Test { protected: @@ -803,16 +841,18 @@ TEST_F(LifetimeAnalysisTest, ExtraParenthesis) { EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1")); } -// FIXME: Handle temporaries. TEST_F(LifetimeAnalysisTest, ViewFromTemporary) { SetupTest(R"( MyObj temporary(); + void use(View); void target() { - View v = temporary(); - POINT(p1); + View a; + a = temporary(); + POINT(p1); + use(a); } )"); - EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1")); + EXPECT_THAT(Origin("a"), HasLoanToATemporary("p1")); } TEST_F(LifetimeAnalysisTest, GslPointerWithConstAndAuto) {