https://github.com/kashika0112 updated https://github.com/llvm/llvm-project/pull/165370
>From 2ab23aa98033c9f14cc6910786b6041c125b0726 Mon Sep 17 00:00:00 2001 From: Kashika Akhouri <[email protected]> Date: Tue, 28 Oct 2025 10:59:48 +0000 Subject: [PATCH 1/2] Adding use-after-return in Lifetime Analysis --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 19 + .../Analyses/LifetimeSafety/FactsGenerator.h | 1 + .../Analyses/LifetimeSafety/LifetimeSafety.h | 5 + .../clang/Basic/DiagnosticSemaKinds.td | 1 + clang/lib/Analysis/LifetimeSafety/Checker.cpp | 77 +++- clang/lib/Analysis/LifetimeSafety/Dataflow.h | 3 + clang/lib/Analysis/LifetimeSafety/Facts.cpp | 8 + .../LifetimeSafety/FactsGenerator.cpp | 6 +- clang/lib/Sema/AnalysisBasedWarnings.cpp | 415 +++++++++--------- 9 files changed, 329 insertions(+), 206 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 6a90aeb01e638..712e1d42e2966 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -44,6 +44,8 @@ class Fact { Use, /// A marker for a specific point in the code, for testing. TestPoint, + OriginEscapes, + // An origin that stores a loan escapes the function. }; private: @@ -142,6 +144,23 @@ class ReturnOfOriginFact : public Fact { const OriginManager &OM) const override; }; +class OriginEscapesFact : public Fact { + OriginID OID; + const Stmt *EscapeSource; + +public: + static bool classof(const Fact *F) { + return F->getKind() == Kind::OriginEscapes; + } + + OriginEscapesFact(OriginID OID, const Stmt *Source) + : Fact(Kind::OriginEscapes), OID(OID), EscapeSource(Source) {} + OriginID getEscapedOriginID() const { return OID; } + const Stmt *getEscapeSource() const { return EscapeSource; } + void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const override; +}; + class UseFact : public Fact { const Expr *UseExpr; // True if this use is a write operation (e.g., left-hand side of assignment). diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 5e58abee2bbb3..ec3c130bb6c66 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -93,6 +93,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { FactManager &FactMgr; AnalysisDeclContext &AC; llvm::SmallVector<Fact *> CurrentBlockFacts; + llvm::SmallVector<Fact *> EscapesInCurrentBlock; // To distinguish between reads and writes for use-after-free checks, this map // stores the `UseFact` for each `DeclRefExpr`. We initially identify all // `DeclRefExpr`s as "read" uses. When an assignment is processed, the use diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 91ffbb169f947..f5b1b3d9b6564 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -42,6 +42,11 @@ class LifetimeSafetyReporter { virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr, SourceLocation FreeLoc, Confidence Confidence) {} + + virtual void reportUseAfterReturn(const Expr *IssueExpr, + const Stmt *EscapeStmt, + SourceLocation ExpiryLoc, + Confidence Confidence) {} }; /// The main entry point for the analysis. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 5ff4cc4b833d9..6ea5e34cab291 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10730,6 +10730,7 @@ def warn_lifetime_safety_loan_expires_strict : Warning< InGroup<LifetimeSafetyStrict>, DefaultIgnore; def note_lifetime_safety_used_here : Note<"later used here">; def note_lifetime_safety_destroyed_here : Note<"destroyed here">; +def note_lifetime_safety_returned_here : Note<"returned here">; // For non-floating point, expressions of the form x == x or x != x // should result in a warning, since these always evaluate to a constant. diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index c443c3a5d4f9b..ae5c62ef04d72 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -21,6 +21,7 @@ #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Basic/SourceLocation.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/TimeProfiler.h" @@ -61,10 +62,23 @@ class LifetimeChecker { AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter) : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM), Reporter(Reporter) { - for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) - for (const Fact *F : FactMgr.getFacts(B)) - if (const auto *EF = F->getAs<ExpireFact>()) + for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) { + llvm::SmallVector<const ExpireFact *> BlockExpires; + llvm::SmallVector<const OriginEscapesFact *> BlockEscapes; + for (const Fact *F : FactMgr.getFacts(B)) { + if (const auto *EF = F->getAs<ExpireFact>()) { checkExpiry(EF); + BlockExpires.push_back(EF); + } else if (const auto *OEF = F->getAs<OriginEscapesFact>()) { + BlockEscapes.push_back(OEF); + } + } + if (Reporter) { + for (const OriginEscapesFact *OEF : BlockEscapes) { + checkEscape(OEF, BlockExpires); + } + } + } issuePendingWarnings(); } @@ -106,6 +120,63 @@ class LifetimeChecker { /*ConfidenceLevel=*/CurConfidence}; } + void checkEscape(const OriginEscapesFact *OEF, + llvm::ArrayRef<const ExpireFact *> BlockExpires) { + + if (!Reporter) { + return; + } + + OriginID returnedOID = OEF->getEscapedOriginID(); + ProgramPoint PP = OEF; + + LoanSet HeldLoans = LoanPropagation.getLoans(returnedOID, PP); + if (HeldLoans.isEmpty()) { + return; + } + + llvm::SmallSet<LoanID, 4> ExpiredLoansInBlock; + llvm::DenseMap<LoanID, SourceLocation> ExpiryLocs; + + for (const ExpireFact *EF : BlockExpires) { + ExpiredLoansInBlock.insert(EF->getLoanID()); + ExpiryLocs[EF->getLoanID()] = EF->getExpiryLoc(); + } + + bool hasExpiredDependency = false; + bool allHeldLoansExpired = true; + LoanID exampleExpiredLoan = LoanID(); + + for (LoanID heldLoan : HeldLoans) { + if (ExpiredLoansInBlock.count(heldLoan)) { + hasExpiredDependency = true; + if (exampleExpiredLoan.Value == LoanID().Value) { + exampleExpiredLoan = heldLoan; + } + } else { + allHeldLoansExpired = false; + } + } + + if (!hasExpiredDependency) { + return; + } + + Confidence FinalConfidence; + if (allHeldLoansExpired) { + FinalConfidence = Confidence::Definite; + } else { + FinalConfidence = Confidence::Maybe; + } + + const Loan &L = FactMgr.getLoanMgr().getLoan(exampleExpiredLoan); + SourceLocation ExpiryLoc = ExpiryLocs[exampleExpiredLoan]; + const Stmt *EscapeSource = OEF->getEscapeSource(); + + Reporter->reportUseAfterReturn(L.IssueExpr, EscapeSource, ExpiryLoc, + FinalConfidence); + } + void issuePendingWarnings() { if (!Reporter) return; diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h index 2f7bcb6e5dc81..37720ffa03618 100644 --- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h +++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h @@ -168,6 +168,8 @@ class DataflowAnalysis { return D->transfer(In, *F->getAs<OriginFlowFact>()); case Fact::Kind::ReturnOfOrigin: return D->transfer(In, *F->getAs<ReturnOfOriginFact>()); + case Fact::Kind::OriginEscapes: + return D->transfer(In, *F->getAs<OriginEscapesFact>()); case Fact::Kind::Use: return D->transfer(In, *F->getAs<UseFact>()); case Fact::Kind::TestPoint: @@ -181,6 +183,7 @@ class DataflowAnalysis { Lattice transfer(Lattice In, const ExpireFact &) { return In; } Lattice transfer(Lattice In, const OriginFlowFact &) { return In; } Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; } + Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; } Lattice transfer(Lattice In, const UseFact &) { return In; } Lattice transfer(Lattice In, const TestPointFact &) { return In; } }; diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 1aea64f864367..29c75959ba0fe 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -50,6 +50,14 @@ void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, OS << ")\n"; } +void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const { + OS << "OriginEscapes ("; + OM.dump(getEscapedOriginID(), OS); + OS << ")\n"; +} + + void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const { OS << "Use ("; diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 9b68de107e314..762ed07eb5bb8 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -58,6 +58,7 @@ void FactsGenerator::run() { // initializations and destructions are processed in the correct sequence. for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) { CurrentBlockFacts.clear(); + EscapesInCurrentBlock.clear(); for (unsigned I = 0; I < Block->size(); ++I) { const CFGElement &Element = Block->Elements[I]; if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) @@ -66,6 +67,8 @@ void FactsGenerator::run() { Element.getAs<CFGAutomaticObjDtor>()) handleDestructor(*DtorOpt); } + CurrentBlockFacts.append(EscapesInCurrentBlock.begin(), + EscapesInCurrentBlock.end()); FactMgr.addBlockFacts(Block, CurrentBlockFacts); } } @@ -166,7 +169,8 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) { if (const Expr *RetExpr = RS->getRetValue()) { if (hasOrigin(RetExpr)) { OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr); - CurrentBlockFacts.push_back(FactMgr.createFact<ReturnOfOriginFact>(OID)); + EscapesInCurrentBlock.push_back( + FactMgr.createFact<OriginEscapesFact>(OID, RS)); } } } diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 140b709dbb651..879793575e0b4 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -65,61 +65,61 @@ using namespace clang; //===----------------------------------------------------------------------===// namespace { - class UnreachableCodeHandler : public reachable_code::Callback { - Sema &S; - SourceRange PreviousSilenceableCondVal; - - public: - UnreachableCodeHandler(Sema &s) : S(s) {} - - void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L, - SourceRange SilenceableCondVal, SourceRange R1, - SourceRange R2, bool HasFallThroughAttr) override { - // If the diagnosed code is `[[fallthrough]];` and - // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never - // be executed` warning to avoid generating diagnostic twice - if (HasFallThroughAttr && - !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr, - SourceLocation())) - return; +class UnreachableCodeHandler : public reachable_code::Callback { + Sema &S; + SourceRange PreviousSilenceableCondVal; - // Avoid reporting multiple unreachable code diagnostics that are - // triggered by the same conditional value. +public: + UnreachableCodeHandler(Sema &s) : S(s) {} + + void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L, + SourceRange SilenceableCondVal, SourceRange R1, + SourceRange R2, bool HasFallThroughAttr) override { + // If the diagnosed code is `[[fallthrough]];` and + // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never + // be executed` warning to avoid generating diagnostic twice + if (HasFallThroughAttr && + !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr, + SourceLocation())) + return; + + // Avoid reporting multiple unreachable code diagnostics that are + // triggered by the same conditional value. if (PreviousSilenceableCondVal.isValid() && SilenceableCondVal.isValid() && - PreviousSilenceableCondVal == SilenceableCondVal) - return; - PreviousSilenceableCondVal = SilenceableCondVal; + PreviousSilenceableCondVal == SilenceableCondVal) + return; + PreviousSilenceableCondVal = SilenceableCondVal; - unsigned diag = diag::warn_unreachable; - switch (UK) { - case reachable_code::UK_Break: - diag = diag::warn_unreachable_break; - break; - case reachable_code::UK_Return: - diag = diag::warn_unreachable_return; - break; - case reachable_code::UK_Loop_Increment: - diag = diag::warn_unreachable_loop_increment; - break; - case reachable_code::UK_Other: - break; - } + unsigned diag = diag::warn_unreachable; + switch (UK) { + case reachable_code::UK_Break: + diag = diag::warn_unreachable_break; + break; + case reachable_code::UK_Return: + diag = diag::warn_unreachable_return; + break; + case reachable_code::UK_Loop_Increment: + diag = diag::warn_unreachable_loop_increment; + break; + case reachable_code::UK_Other: + break; + } - S.Diag(L, diag) << R1 << R2; + S.Diag(L, diag) << R1 << R2; - SourceLocation Open = SilenceableCondVal.getBegin(); - if (Open.isValid()) { - SourceLocation Close = SilenceableCondVal.getEnd(); - Close = S.getLocForEndOfToken(Close); - if (Close.isValid()) { - S.Diag(Open, diag::note_unreachable_silence) + SourceLocation Open = SilenceableCondVal.getBegin(); + if (Open.isValid()) { + SourceLocation Close = SilenceableCondVal.getEnd(); + Close = S.getLocForEndOfToken(Close); + if (Close.isValid()) { + S.Diag(Open, diag::note_unreachable_silence) << FixItHint::CreateInsertion(Open, "/* DISABLES CODE */ (") << FixItHint::CreateInsertion(Close, ")"); - } } } - }; + } +}; } // anonymous namespace /// CheckUnreachable - Check for unreachable code. @@ -388,9 +388,9 @@ static void checkThrowInNonThrowingFunc(Sema &S, const FunctionDecl *FD, if (BodyCFG->getExit().pred_empty()) return; visitReachableThrows(BodyCFG, [&](const CXXThrowExpr *Throw, CFGBlock &Block) { - if (throwEscapes(S, Throw, Block, BodyCFG)) - EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD); - }); + if (throwEscapes(S, Throw, Block, BodyCFG)) + EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD); + }); } static bool isNoexcept(const FunctionDecl *FD) { @@ -803,7 +803,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body, } else if (isa<BlockDecl>(D)) { if (const FunctionType *FT = - BlockType->getPointeeType()->getAs<FunctionType>()) { + BlockType->getPointeeType()->getAs<FunctionType>()) { if (FT->getReturnType()->isVoidType()) ReturnsVoid = true; if (FT->getNoReturnAttr()) @@ -815,7 +815,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body, // Short circuit for compilation speed. if (CD.checkDiagnostics(Diags, ReturnsVoid, HasNoReturn)) - return; + return; SourceLocation LBrace = Body->getBeginLoc(), RBrace = Body->getEndLoc(); // cpu_dispatch functions permit empty function bodies for ICC compatibility. @@ -892,7 +892,7 @@ class ContainsReference : public ConstEvaluatedExprVisitor<ContainsReference> { typedef ConstEvaluatedExprVisitor<ContainsReference> Inherited; ContainsReference(ASTContext &Context, const DeclRefExpr *Needle) - : Inherited(Context), FoundReference(false), Needle(Needle) {} + : Inherited(Context), FoundReference(false), Needle(Needle) {} void VisitExpr(const Expr *E) { // Stop evaluating if we already have a reference. @@ -1016,7 +1016,7 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use, int RemoveDiagKind = -1; const char *FixitStr = S.getLangOpts().CPlusPlus ? (I->Output ? "true" : "false") - : (I->Output ? "1" : "0"); + : (I->Output ? "1" : "0"); FixItHint Fixit1, Fixit2; switch (Term ? Term->getStmtClass() : Stmt::DeclStmtClass) { @@ -1124,7 +1124,7 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use, << IsCapturedByBlock << User->getSourceRange(); if (RemoveDiagKind != -1) S.Diag(Fixit1.RemoveRange.getBegin(), diag::note_uninit_fixit_remove_cond) - << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2; + << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2; Diagnosed = true; } @@ -1294,32 +1294,32 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor { // Don't care about other unreachable statements. } } - // If there are no unreachable statements, this may be a special - // case in CFG: - // case X: { - // A a; // A has a destructor. - // break; - // } - // // <<<< This place is represented by a 'hanging' CFG block. - // case Y: - continue; + // If there are no unreachable statements, this may be a special + // case in CFG: + // case X: { + // A a; // A has a destructor. + // break; + // } + // // <<<< This place is represented by a 'hanging' CFG block. + // case Y: + continue; } - const Stmt *LastStmt = getLastStmt(*P); - if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) { - markFallthroughVisited(AS); - ++AnnotatedCnt; - continue; // Fallthrough annotation, good. - } + const Stmt *LastStmt = getLastStmt(*P); + if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) { + markFallthroughVisited(AS); + ++AnnotatedCnt; + continue; // Fallthrough annotation, good. + } - if (!LastStmt) { // This block contains no executable statements. - // Traverse its predecessors. - std::copy(P->pred_begin(), P->pred_end(), - std::back_inserter(BlockQueue)); - continue; - } + if (!LastStmt) { // This block contains no executable statements. + // Traverse its predecessors. + std::copy(P->pred_begin(), P->pred_end(), + std::back_inserter(BlockQueue)); + continue; + } - ++UnannotatedCnt; + ++UnannotatedCnt; } return !!UnannotatedCnt; } @@ -1335,48 +1335,48 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor { return true; } - // We don't want to traverse local type declarations. We analyze their - // methods separately. - bool TraverseDecl(Decl *D) override { return true; } + // We don't want to traverse local type declarations. We analyze their + // methods separately. + bool TraverseDecl(Decl *D) override { return true; } - // We analyze lambda bodies separately. Skip them here. - bool TraverseLambdaExpr(LambdaExpr *LE) override { - // Traverse the captures, but not the body. - for (const auto C : zip(LE->captures(), LE->capture_inits())) - TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C)); - return true; - } + // We analyze lambda bodies separately. Skip them here. + bool TraverseLambdaExpr(LambdaExpr *LE) override { + // Traverse the captures, but not the body. + for (const auto C : zip(LE->captures(), LE->capture_inits())) + TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C)); + return true; + } - private: +private: - static const AttributedStmt *asFallThroughAttr(const Stmt *S) { - if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) { - if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs())) - return AS; - } - return nullptr; + static const AttributedStmt *asFallThroughAttr(const Stmt *S) { + if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) { + if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs())) + return AS; } + return nullptr; + } - static const Stmt *getLastStmt(const CFGBlock &B) { - if (const Stmt *Term = B.getTerminatorStmt()) - return Term; - for (const CFGElement &Elem : llvm::reverse(B)) - if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>()) - return CS->getStmt(); - // Workaround to detect a statement thrown out by CFGBuilder: - // case X: {} case Y: - // case X: ; case Y: - if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel())) - if (!isa<SwitchCase>(SW->getSubStmt())) - return SW->getSubStmt(); + static const Stmt *getLastStmt(const CFGBlock &B) { + if (const Stmt *Term = B.getTerminatorStmt()) + return Term; + for (const CFGElement &Elem : llvm::reverse(B)) + if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>()) + return CS->getStmt(); + // Workaround to detect a statement thrown out by CFGBuilder: + // case X: {} case Y: + // case X: ; case Y: + if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel())) + if (!isa<SwitchCase>(SW->getSubStmt())) + return SW->getSubStmt(); - return nullptr; - } + return nullptr; + } - bool FoundSwitchStatements; - AttrStmts FallthroughStmts; - Sema &S; - llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks; + bool FoundSwitchStatements; + AttrStmts FallthroughStmts; + Sema &S; + llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks; }; } // anonymous namespace @@ -1384,7 +1384,7 @@ static StringRef getFallthroughAttrSpelling(Preprocessor &PP, SourceLocation Loc) { TokenValue FallthroughTokens[] = { tok::l_square, tok::l_square, - PP.getIdentifierInfo("fallthrough"), + PP.getIdentifierInfo("fallthrough"), tok::r_square, tok::r_square }; @@ -1513,7 +1513,7 @@ static void diagnoseRepeatedUseOfWeak(Sema &S, typedef sema::FunctionScopeInfo::WeakObjectUseMap WeakObjectUseMap; typedef sema::FunctionScopeInfo::WeakUseVector WeakUseVector; typedef std::pair<const Stmt *, WeakObjectUseMap::const_iterator> - StmtUsesPair; + StmtUsesPair; ASTContext &Ctx = S.getASTContext(); @@ -1977,7 +1977,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { : getNotes(); } - public: +public: ThreadSafetyReporter(Sema &S, SourceLocation FL, SourceLocation FEL) : S(S), FunLocation(FL), FunEndLocation(FEL), CurrentFunction(nullptr), Verbose(false) {} @@ -2075,18 +2075,18 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { bool ReentrancyMismatch) override { unsigned DiagID = 0; switch (LEK) { - case LEK_LockedSomePredecessors: - DiagID = diag::warn_lock_some_predecessors; - break; - case LEK_LockedSomeLoopIterations: - DiagID = diag::warn_expecting_lock_held_on_loop; - break; - case LEK_LockedAtEndOfFunction: - DiagID = diag::warn_no_unlock; - break; - case LEK_NotLockedAtEndOfFunction: - DiagID = diag::warn_expecting_locked; - break; + case LEK_LockedSomePredecessors: + DiagID = diag::warn_lock_some_predecessors; + break; + case LEK_LockedSomeLoopIterations: + DiagID = diag::warn_expecting_lock_held_on_loop; + break; + case LEK_LockedAtEndOfFunction: + DiagID = diag::warn_no_unlock; + break; + case LEK_NotLockedAtEndOfFunction: + DiagID = diag::warn_expecting_locked; + break; } if (LocEndOfScope.isInvalid()) LocEndOfScope = FunEndLocation; @@ -2132,7 +2132,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { break; } PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) - << D << getLockKindFromAccessKind(AK)); + << D << getLockKindFromAccessKind(AK)); Warnings.emplace_back(std::move(Warning), getNotes()); } @@ -2143,39 +2143,39 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { unsigned DiagID = 0; if (PossibleMatch) { switch (POK) { - case POK_VarAccess: - DiagID = diag::warn_variable_requires_lock_precise; - break; - case POK_VarDereference: - DiagID = diag::warn_var_deref_requires_lock_precise; - break; - case POK_FunctionCall: - DiagID = diag::warn_fun_requires_lock_precise; - break; - case POK_PassByRef: - DiagID = diag::warn_guarded_pass_by_reference; - break; - case POK_PtPassByRef: - DiagID = diag::warn_pt_guarded_pass_by_reference; - break; - case POK_ReturnByRef: - DiagID = diag::warn_guarded_return_by_reference; - break; - case POK_PtReturnByRef: - DiagID = diag::warn_pt_guarded_return_by_reference; - break; - case POK_PassPointer: - DiagID = diag::warn_guarded_pass_pointer; - break; - case POK_PtPassPointer: - DiagID = diag::warn_pt_guarded_pass_pointer; - break; - case POK_ReturnPointer: - DiagID = diag::warn_guarded_return_pointer; - break; - case POK_PtReturnPointer: - DiagID = diag::warn_pt_guarded_return_pointer; - break; + case POK_VarAccess: + DiagID = diag::warn_variable_requires_lock_precise; + break; + case POK_VarDereference: + DiagID = diag::warn_var_deref_requires_lock_precise; + break; + case POK_FunctionCall: + DiagID = diag::warn_fun_requires_lock_precise; + break; + case POK_PassByRef: + DiagID = diag::warn_guarded_pass_by_reference; + break; + case POK_PtPassByRef: + DiagID = diag::warn_pt_guarded_pass_by_reference; + break; + case POK_ReturnByRef: + DiagID = diag::warn_guarded_return_by_reference; + break; + case POK_PtReturnByRef: + DiagID = diag::warn_pt_guarded_return_by_reference; + break; + case POK_PassPointer: + DiagID = diag::warn_guarded_pass_pointer; + break; + case POK_PtPassPointer: + DiagID = diag::warn_pt_guarded_pass_pointer; + break; + case POK_ReturnPointer: + DiagID = diag::warn_guarded_return_pointer; + break; + case POK_PtReturnPointer: + DiagID = diag::warn_pt_guarded_return_pointer; + break; } PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind << D @@ -2191,39 +2191,39 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { Warnings.emplace_back(std::move(Warning), getNotes(Note)); } else { switch (POK) { - case POK_VarAccess: - DiagID = diag::warn_variable_requires_lock; - break; - case POK_VarDereference: - DiagID = diag::warn_var_deref_requires_lock; - break; - case POK_FunctionCall: - DiagID = diag::warn_fun_requires_lock; - break; - case POK_PassByRef: - DiagID = diag::warn_guarded_pass_by_reference; - break; - case POK_PtPassByRef: - DiagID = diag::warn_pt_guarded_pass_by_reference; - break; - case POK_ReturnByRef: - DiagID = diag::warn_guarded_return_by_reference; - break; - case POK_PtReturnByRef: - DiagID = diag::warn_pt_guarded_return_by_reference; - break; - case POK_PassPointer: - DiagID = diag::warn_guarded_pass_pointer; - break; - case POK_PtPassPointer: - DiagID = diag::warn_pt_guarded_pass_pointer; - break; - case POK_ReturnPointer: - DiagID = diag::warn_guarded_return_pointer; - break; - case POK_PtReturnPointer: - DiagID = diag::warn_pt_guarded_return_pointer; - break; + case POK_VarAccess: + DiagID = diag::warn_variable_requires_lock; + break; + case POK_VarDereference: + DiagID = diag::warn_var_deref_requires_lock; + break; + case POK_FunctionCall: + DiagID = diag::warn_fun_requires_lock; + break; + case POK_PassByRef: + DiagID = diag::warn_guarded_pass_by_reference; + break; + case POK_PtPassByRef: + DiagID = diag::warn_pt_guarded_pass_by_reference; + break; + case POK_ReturnByRef: + DiagID = diag::warn_guarded_return_by_reference; + break; + case POK_PtReturnByRef: + DiagID = diag::warn_pt_guarded_return_by_reference; + break; + case POK_PassPointer: + DiagID = diag::warn_guarded_pass_pointer; + break; + case POK_PtPassPointer: + DiagID = diag::warn_pt_guarded_pass_pointer; + break; + case POK_ReturnPointer: + DiagID = diag::warn_guarded_return_pointer; + break; + case POK_PtReturnPointer: + DiagID = diag::warn_pt_guarded_return_pointer; + break; } PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind << D @@ -2241,7 +2241,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { SourceLocation Loc) override { PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_acquire_requires_negative_cap) - << Kind << LockName << Neg); + << Kind << LockName << Neg); Warnings.emplace_back(std::move(Warning), getNotes()); } @@ -2423,7 +2423,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler { public: UnsafeBufferUsageReporter(Sema &S, bool SuggestSuggestions) - : S(S), SuggestSuggestions(SuggestSuggestions) {} + : S(S), SuggestSuggestions(SuggestSuggestions) {} void handleUnsafeOperation(const Stmt *Operation, bool IsRelatedToDecl, ASTContext &Ctx) override { @@ -2808,6 +2808,17 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter { << UseExpr->getEndLoc(); } + void reportUseAfterReturn(const Expr *IssueExpr, const Stmt *EscapeStmt, + SourceLocation ExpiryLoc, Confidence C) override { + S.Diag(IssueExpr->getExprLoc(), + C == Confidence::Definite + ? diag::warn_lifetime_safety_loan_expires_permissive + : diag::warn_lifetime_safety_loan_expires_strict) + << IssueExpr->getEndLoc(); + + S.Diag(EscapeStmt->getBeginLoc(), diag::note_lifetime_safety_returned_here); + } + private: Sema &S; }; @@ -2815,7 +2826,7 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter { } // namespace clang::lifetimes void clang::sema::AnalysisBasedWarnings::IssueWarnings( - TranslationUnitDecl *TU) { + TranslationUnitDecl *TU) { if (!TU) return; // This is unexpected, give up quietly. @@ -2928,13 +2939,13 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( AC.getCFGBuildOptions().setAllAlwaysAdd(); } else { AC.getCFGBuildOptions() - .setAlwaysAdd(Stmt::BinaryOperatorClass) - .setAlwaysAdd(Stmt::CompoundAssignOperatorClass) - .setAlwaysAdd(Stmt::BlockExprClass) - .setAlwaysAdd(Stmt::CStyleCastExprClass) - .setAlwaysAdd(Stmt::DeclRefExprClass) - .setAlwaysAdd(Stmt::ImplicitCastExprClass) - .setAlwaysAdd(Stmt::UnaryOperatorClass); + .setAlwaysAdd(Stmt::BinaryOperatorClass) + .setAlwaysAdd(Stmt::CompoundAssignOperatorClass) + .setAlwaysAdd(Stmt::BlockExprClass) + .setAlwaysAdd(Stmt::CStyleCastExprClass) + .setAlwaysAdd(Stmt::DeclRefExprClass) + .setAlwaysAdd(Stmt::ImplicitCastExprClass) + .setAlwaysAdd(Stmt::UnaryOperatorClass); } // Install the logical handler. >From 0ba83e2a940795f3427f7141c20822853c51e63a Mon Sep 17 00:00:00 2001 From: Kashika Akhouri <[email protected]> Date: Tue, 28 Oct 2025 10:59:48 +0000 Subject: [PATCH 2/2] Adding use-after-return in Lifetime Analysis --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 26 +- .../Analyses/LifetimeSafety/FactsGenerator.h | 2 + .../Analyses/LifetimeSafety/LifetimeSafety.h | 2 +- .../Analyses/LifetimeSafety/LiveOrigins.h | 11 +- .../clang/Basic/DiagnosticSemaKinds.td | 10 + clang/lib/Analysis/LifetimeSafety/Checker.cpp | 91 ++----- clang/lib/Analysis/LifetimeSafety/Dataflow.h | 3 - clang/lib/Analysis/LifetimeSafety/Facts.cpp | 7 - .../LifetimeSafety/FactsGenerator.cpp | 5 +- .../Analysis/LifetimeSafety/LiveOrigins.cpp | 48 +++- clang/lib/Sema/AnalysisBasedWarnings.cpp | 8 +- .../Sema/warn-lifetime-safety-dataflow.cpp | 4 +- clang/test/Sema/warn-lifetime-safety.cpp | 118 +++++++++ .../unittests/Analysis/LifetimeSafetyTest.cpp | 237 ++++++++++++++++++ 14 files changed, 446 insertions(+), 126 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 712e1d42e2966..1b87eee4a8d17 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -39,13 +39,11 @@ class Fact { /// loan set. OriginFlow, /// An origin escapes the function by flowing into the return value. - ReturnOfOrigin, - /// An origin is used (eg. appears as l-value expression like DeclRefExpr). Use, /// A marker for a specific point in the code, for testing. TestPoint, + // Indicates that an origin escapes the function scope (e.g., via return). OriginEscapes, - // An origin that stores a loan escapes the function. }; private: @@ -130,33 +128,19 @@ class OriginFlowFact : public Fact { const OriginManager &OM) const override; }; -class ReturnOfOriginFact : public Fact { - OriginID OID; - -public: - static bool classof(const Fact *F) { - return F->getKind() == Kind::ReturnOfOrigin; - } - - ReturnOfOriginFact(OriginID OID) : Fact(Kind::ReturnOfOrigin), OID(OID) {} - OriginID getReturnedOriginID() const { return OID; } - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; -}; - class OriginEscapesFact : public Fact { OriginID OID; - const Stmt *EscapeSource; + const Expr *EscapeExpr; public: static bool classof(const Fact *F) { return F->getKind() == Kind::OriginEscapes; } - OriginEscapesFact(OriginID OID, const Stmt *Source) - : Fact(Kind::OriginEscapes), OID(OID), EscapeSource(Source) {} + OriginEscapesFact(OriginID OID, const Expr *EscapeExpr) + : Fact(Kind::OriginEscapes), OID(OID), EscapeExpr(EscapeExpr) {} OriginID getEscapedOriginID() const { return OID; } - const Stmt *getEscapeSource() const { return EscapeSource; } + const Expr *getEscapeExpr() const { return EscapeExpr; }; void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const override; }; diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index ec3c130bb6c66..21d160493289e 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -93,6 +93,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { FactManager &FactMgr; AnalysisDeclContext &AC; llvm::SmallVector<Fact *> CurrentBlockFacts; + // Collect origins that escape the function in this block. These are handled + // at the end of the block to ensure `OEFs` appear after `Expire` facts. llvm::SmallVector<Fact *> EscapesInCurrentBlock; // To distinguish between reads and writes for use-after-free checks, this map // stores the `UseFact` for each `DeclRefExpr`. We initially identify all diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index f5b1b3d9b6564..b34a7f18b5809 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -44,7 +44,7 @@ class LifetimeSafetyReporter { Confidence Confidence) {} virtual void reportUseAfterReturn(const Expr *IssueExpr, - const Stmt *EscapeStmt, + const Expr *EscapeExpr, SourceLocation ExpiryLoc, Confidence Confidence) {} }; diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h index c4f5f0e9ae46c..dae97501c0b53 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h @@ -43,7 +43,7 @@ struct LivenessInfo { /// multiple uses along different paths, this will point to the use appearing /// earlier in the translation unit. /// This is 'null' when the origin is not live. - const UseFact *CausingUseFact; + const Fact *CausingFact; /// The kind of liveness of the origin. /// `Must`: The origin is live on all control-flow paths from the current @@ -56,17 +56,16 @@ struct LivenessInfo { /// while `Maybe`-be-alive suggests a potential one on some paths. LivenessKind Kind; - LivenessInfo() : CausingUseFact(nullptr), Kind(LivenessKind::Dead) {} - LivenessInfo(const UseFact *UF, LivenessKind K) - : CausingUseFact(UF), Kind(K) {} + LivenessInfo() : CausingFact(nullptr), Kind(LivenessKind::Dead) {} + LivenessInfo(const Fact *CF, LivenessKind K) : CausingFact(CF), Kind(K) {} bool operator==(const LivenessInfo &Other) const { - return CausingUseFact == Other.CausingUseFact && Kind == Other.Kind; + return CausingFact == Other.CausingFact && Kind == Other.Kind; } bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); } void Profile(llvm::FoldingSetNodeID &IDBuilder) const { - IDBuilder.AddPointer(CausingUseFact); + IDBuilder.AddPointer(CausingFact); IDBuilder.Add(Kind); } }; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6ea5e34cab291..4eb7263715f4a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10728,6 +10728,16 @@ def warn_lifetime_safety_loan_expires_permissive : Warning< def warn_lifetime_safety_loan_expires_strict : Warning< "object whose reference is captured may not live long enough">, InGroup<LifetimeSafetyStrict>, DefaultIgnore; + +def warn_lifetime_safety_return_stack_addr_permissive + : Warning<"returning reference to stack allocated object">, + InGroup<LifetimeSafetyPermissive>, + DefaultIgnore; +def warn_lifetime_safety_return_stack_addr_strict + : Warning<"may be returning reference to stack allocated object">, + InGroup<LifetimeSafetyStrict>, + DefaultIgnore; + def note_lifetime_safety_used_here : Note<"later used here">; def note_lifetime_safety_destroyed_here : Note<"destroyed here">; def note_lifetime_safety_returned_here : Note<"returned here">; diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index ae5c62ef04d72..64ca5439cc264 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -44,7 +44,7 @@ namespace { /// Struct to store the complete context for a potential lifetime violation. struct PendingWarning { SourceLocation ExpiryLoc; // Where the loan expired. - const Expr *UseExpr; // Where the origin holding this loan was used. + const Fact *CausingFact; // If the use is a UseFact or OriginEscapeFact Confidence ConfidenceLevel; }; @@ -64,25 +64,17 @@ class LifetimeChecker { Reporter(Reporter) { for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) { llvm::SmallVector<const ExpireFact *> BlockExpires; - llvm::SmallVector<const OriginEscapesFact *> BlockEscapes; for (const Fact *F : FactMgr.getFacts(B)) { if (const auto *EF = F->getAs<ExpireFact>()) { checkExpiry(EF); BlockExpires.push_back(EF); - } else if (const auto *OEF = F->getAs<OriginEscapesFact>()) { - BlockEscapes.push_back(OEF); - } - } - if (Reporter) { - for (const OriginEscapesFact *OEF : BlockEscapes) { - checkEscape(OEF, BlockExpires); } } } issuePendingWarnings(); } - /// Checks for use-after-free errors when a loan expires. + /// Checks for use-after-free & use-after-return errors when a loan expires. /// /// This method examines all live origins at the expiry point and determines /// if any of them hold the expiring loan. If so, it creates a pending @@ -97,7 +89,8 @@ class LifetimeChecker { LoanID ExpiredLoan = EF->getLoanID(); LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF); Confidence CurConfidence = Confidence::None; - const UseFact *BadUse = nullptr; + const Fact *BestCausingFact = nullptr; + for (auto &[OID, LiveInfo] : Origins) { LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF); if (!HeldLoans.contains(ExpiredLoan)) @@ -106,85 +99,37 @@ class LifetimeChecker { Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind); if (CurConfidence < NewConfidence) { CurConfidence = NewConfidence; - BadUse = LiveInfo.CausingUseFact; + BestCausingFact = LiveInfo.CausingFact; } } - if (!BadUse) + if (!BestCausingFact) return; // We have a use-after-free. Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel; if (LastConf >= CurConfidence) return; FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(), - /*UseExpr=*/BadUse->getUseExpr(), + /*BestCausingFact*/ BestCausingFact, /*ConfidenceLevel=*/CurConfidence}; } - void checkEscape(const OriginEscapesFact *OEF, - llvm::ArrayRef<const ExpireFact *> BlockExpires) { - - if (!Reporter) { - return; - } - - OriginID returnedOID = OEF->getEscapedOriginID(); - ProgramPoint PP = OEF; - - LoanSet HeldLoans = LoanPropagation.getLoans(returnedOID, PP); - if (HeldLoans.isEmpty()) { - return; - } - - llvm::SmallSet<LoanID, 4> ExpiredLoansInBlock; - llvm::DenseMap<LoanID, SourceLocation> ExpiryLocs; - - for (const ExpireFact *EF : BlockExpires) { - ExpiredLoansInBlock.insert(EF->getLoanID()); - ExpiryLocs[EF->getLoanID()] = EF->getExpiryLoc(); - } - - bool hasExpiredDependency = false; - bool allHeldLoansExpired = true; - LoanID exampleExpiredLoan = LoanID(); - - for (LoanID heldLoan : HeldLoans) { - if (ExpiredLoansInBlock.count(heldLoan)) { - hasExpiredDependency = true; - if (exampleExpiredLoan.Value == LoanID().Value) { - exampleExpiredLoan = heldLoan; - } - } else { - allHeldLoansExpired = false; - } - } - - if (!hasExpiredDependency) { - return; - } - - Confidence FinalConfidence; - if (allHeldLoansExpired) { - FinalConfidence = Confidence::Definite; - } else { - FinalConfidence = Confidence::Maybe; - } - - const Loan &L = FactMgr.getLoanMgr().getLoan(exampleExpiredLoan); - SourceLocation ExpiryLoc = ExpiryLocs[exampleExpiredLoan]; - const Stmt *EscapeSource = OEF->getEscapeSource(); - - Reporter->reportUseAfterReturn(L.IssueExpr, EscapeSource, ExpiryLoc, - FinalConfidence); - } - void issuePendingWarnings() { if (!Reporter) return; for (const auto &[LID, Warning] : FinalWarningsMap) { const Loan &L = FactMgr.getLoanMgr().getLoan(LID); const Expr *IssueExpr = L.IssueExpr; - Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr, - Warning.ExpiryLoc, Warning.ConfidenceLevel); + const Fact *CausingFact = Warning.CausingFact; + Confidence Confidence = Warning.ConfidenceLevel; + SourceLocation ExpiryLoc = Warning.ExpiryLoc; + + if (const auto *UF = CausingFact->getAs<UseFact>()) { + Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc, + Confidence); + } else if (const auto *OEF = CausingFact->getAs<OriginEscapesFact>()) { + Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(), + ExpiryLoc, Confidence); + } } } }; diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h index 37720ffa03618..23ef9dbcdcb86 100644 --- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h +++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h @@ -166,8 +166,6 @@ class DataflowAnalysis { return D->transfer(In, *F->getAs<ExpireFact>()); case Fact::Kind::OriginFlow: return D->transfer(In, *F->getAs<OriginFlowFact>()); - case Fact::Kind::ReturnOfOrigin: - return D->transfer(In, *F->getAs<ReturnOfOriginFact>()); case Fact::Kind::OriginEscapes: return D->transfer(In, *F->getAs<OriginEscapesFact>()); case Fact::Kind::Use: @@ -182,7 +180,6 @@ class DataflowAnalysis { Lattice transfer(Lattice In, const IssueFact &) { return In; } Lattice transfer(Lattice In, const ExpireFact &) { return In; } Lattice transfer(Lattice In, const OriginFlowFact &) { return In; } - Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; } Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; } Lattice transfer(Lattice In, const UseFact &) { return In; } Lattice transfer(Lattice In, const TestPointFact &) { return In; } diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 29c75959ba0fe..45fce4bcf8ba3 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -43,13 +43,6 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &, OS << ")\n"; } -void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { - OS << "ReturnOfOrigin ("; - OM.dump(getReturnedOriginID(), OS); - OS << ")\n"; -} - void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const { OS << "OriginEscapes ("; diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 762ed07eb5bb8..72a1b52786d54 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -59,6 +59,7 @@ void FactsGenerator::run() { for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) { CurrentBlockFacts.clear(); EscapesInCurrentBlock.clear(); + EscapesInCurrentBlock.clear(); for (unsigned I = 0; I < Block->size(); ++I) { const CFGElement &Element = Block->Elements[I]; if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) @@ -67,6 +68,8 @@ void FactsGenerator::run() { Element.getAs<CFGAutomaticObjDtor>()) handleDestructor(*DtorOpt); } + CurrentBlockFacts.append(EscapesInCurrentBlock.begin(), + EscapesInCurrentBlock.end()); CurrentBlockFacts.append(EscapesInCurrentBlock.begin(), EscapesInCurrentBlock.end()); FactMgr.addBlockFacts(Block, CurrentBlockFacts); @@ -170,7 +173,7 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) { if (hasOrigin(RetExpr)) { OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr); EscapesInCurrentBlock.push_back( - FactMgr.createFact<OriginEscapesFact>(OID, RS)); + FactMgr.createFact<OriginEscapesFact>(OID, RetExpr)); } } } diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp index cddb3f3ce4c1c..02a079184723c 100644 --- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp @@ -53,6 +53,16 @@ struct Lattice { } }; +static SourceLocation GetFactLoc(const Fact &F) { + if (const auto *UF = F.getAs<UseFact>()) { + return UF->getUseExpr()->getExprLoc(); + } + if (const auto *OEF = F.getAs<OriginEscapesFact>()) { + return OEF->getEscapeExpr()->getExprLoc(); + } + return SourceLocation(); // Invalid SourceLocation +} + /// The analysis that tracks which origins are live, with granular information /// about the causing use fact and confidence level. This is a backward /// analysis. @@ -74,11 +84,25 @@ class AnalysisImpl /// one. Lattice join(Lattice L1, Lattice L2) const { LivenessMap Merged = L1.LiveOrigins; - // Take the earliest UseFact to make the join hermetic and commutative. - auto CombineUseFact = [](const UseFact &A, - const UseFact &B) -> const UseFact * { - return A.getUseExpr()->getExprLoc() < B.getUseExpr()->getExprLoc() ? &A - : &B; + // Take the earliest Fact to make the join hermetic and commutative. + auto CombineCausingFact = [](const Fact &A, const Fact &B) -> const Fact * { + SourceLocation LocA = GetFactLoc(A); + SourceLocation LocB = GetFactLoc(B); + + bool aValid = LocA.isValid(); + bool bValid = LocB.isValid(); + + if (aValid && bValid) { + if (LocA < LocB) + return &A; + if (LocB < LocA) + return &B; + } else if (aValid) { + return &A; + } else if (bValid) { + return &B; + } + return &A < &B ? &A : &B; }; auto CombineLivenessKind = [](LivenessKind K1, LivenessKind K2) -> LivenessKind { @@ -93,11 +117,11 @@ class AnalysisImpl const LivenessInfo *L2) -> LivenessInfo { assert((L1 || L2) && "unexpectedly merging 2 empty sets"); if (!L1) - return LivenessInfo(L2->CausingUseFact, LivenessKind::Maybe); + return LivenessInfo(L2->CausingFact, LivenessKind::Maybe); if (!L2) - return LivenessInfo(L1->CausingUseFact, LivenessKind::Maybe); + return LivenessInfo(L1->CausingFact, LivenessKind::Maybe); return LivenessInfo( - CombineUseFact(*L1->CausingUseFact, *L2->CausingUseFact), + CombineCausingFact(*L1->CausingFact, *L2->CausingFact), CombineLivenessKind(L1->Kind, L2->Kind)); }; return Lattice(utils::join( @@ -120,6 +144,14 @@ class AnalysisImpl LivenessInfo(&UF, LivenessKind::Must))); } + // A return operation makes the origin live with definite confidence, as it + /// dominates this program point. + Lattice transfer(Lattice In, const OriginEscapesFact &OEF) { + OriginID OID = OEF.getEscapedOriginID(); + return Lattice(Factory.add(In.LiveOrigins, OID, + LivenessInfo(&OEF, LivenessKind::Must))); + } + /// Issuing a new loan to an origin kills its liveness. Lattice transfer(Lattice In, const IssueFact &IF) { return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID())); diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 879793575e0b4..08b621f39441c 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2808,15 +2808,15 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter { << UseExpr->getEndLoc(); } - void reportUseAfterReturn(const Expr *IssueExpr, const Stmt *EscapeStmt, + void reportUseAfterReturn(const Expr *IssueExpr, const Expr *EscapeExpr, SourceLocation ExpiryLoc, Confidence C) override { S.Diag(IssueExpr->getExprLoc(), C == Confidence::Definite - ? diag::warn_lifetime_safety_loan_expires_permissive - : diag::warn_lifetime_safety_loan_expires_strict) + ? diag::warn_lifetime_safety_return_stack_addr_permissive + : diag::warn_lifetime_safety_return_stack_addr_strict) << IssueExpr->getEndLoc(); - S.Diag(EscapeStmt->getBeginLoc(), diag::note_lifetime_safety_returned_here); + S.Diag(EscapeExpr->getBeginLoc(), diag::note_lifetime_safety_returned_here); } private: diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp index 31148b990d6bd..32796c2ae43c4 100644 --- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp +++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp @@ -18,8 +18,8 @@ MyObj* return_local_addr() { return p; // CHECK: Use ([[O_P]] (Decl: p), Read) // CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p)) -// CHECK: ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr)) // CHECK: Expire ([[L_X]] (Path: x)) +// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr)) } @@ -49,8 +49,8 @@ MyObj* assign_and_return_local_addr() { return ptr2; // CHECK: Use ([[O_PTR2]] (Decl: ptr2), Read) // CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2)) -// CHECK: ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr)) // CHECK: Expire ([[L_Y]] (Path: y)) +// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr)) } // Return of Non-Pointer Type diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 4f234f0ac6e2d..55bf6253a8c82 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -396,6 +396,118 @@ void loan_from_previous_iteration(MyObj safe, bool condition) { } // expected-note {{destroyed here}} } +//===----------------------------------------------------------------------===// +// Basic Definite Use-After-Return (Return-Stack-Address) (-W...permissive) +// These are cases where the pointer is guaranteed to be dangling at the use site. +//===----------------------------------------------------------------------===// + +MyObj* simple_return_stack_address(){ + MyObj s; + MyObj* p = &s; // expected-warning {{returning reference to stack allocated object}} + return p; // expected-note {{returned here}} +} + +MyObj* conditional_assign_unconditional_return(MyObj safe, bool c){ + MyObj s; + MyObj* p = &safe; + if(c){ + p = &s; // expected-warning {{returning reference to stack allocated object}} + } + return p; // expected-note {{returned here}} +} + +MyObj* conditional_assign_both_branches(MyObj safe, bool c){ + + MyObj s; + MyObj* p = nullptr; + if (c) { + p = &s; // expected-warning {{returning reference to stack allocated object}} + } else { + p = &safe; + } + return p; // expected-note {{returned here}} + +} + +MyObj* reassign_safe_to_local(MyObj safe){ + MyObj local; + MyObj* p = &safe; + + p = &local; // expected-warning {{returning reference to stack allocated object}} + return p; // expected-note {{returned here}} +} + +MyObj* pointer_chain_to_local(){ + MyObj local; + MyObj* p1 = &local; // expected-warning {{returning reference to stack allocated object}} + + MyObj* p2 = p1; + + return p2; // expected-note {{returned here}} +} + +MyObj* multiple_assign_multiple_return(MyObj safe, bool c1, bool c2){ + MyObj local1; + MyObj local2; + MyObj* p = nullptr; + if(c1){ + p = &local1; // expected-warning {{returning reference to stack allocated object}} + return p; // expected-note {{returned here}} + } + else if(c2){ + p = &local2; // expected-warning {{returning reference to stack allocated object}} + return p; // expected-note {{returned here}} + } + p = &safe; + return p; +} + +MyObj* multiple_assign_single_return(MyObj safe, bool c1, bool c2){ + MyObj local1; + MyObj local2; + MyObj* p = nullptr; + if(c1){ + p = &local1; // expected-warning {{returning reference to stack allocated object}} + } + else if(c2){ + p = &local2; // expected-warning {{returning reference to stack allocated object}} + } + else{ + p = &safe; + } + + return p; // expected-note {{returned here}} // expected-note {{returned here}} +} + +//===----------------------------------------------------------------------===// +// Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined +// These are cases where the diagnostic kind is determined by location +//===----------------------------------------------------------------------===// + +MyObj* uaf_before_uar(){ + MyObj* p; + { + MyObj local_obj; + p = &local_obj; // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + return p; // expected-note {{later used here}} +} + +MyObj* uar_before_uaf(MyObj safe, bool c){ + MyObj* p; + { + MyObj local_obj; + p = &local_obj; // expected-warning {{returning reference to stack allocated object}} + if(c){ + return p; // expected-note {{returned here}} + } + + } + (void)*p; + p = &safe; + return p; +} + //===----------------------------------------------------------------------===// // No-Error Cases //===----------------------------------------------------------------------===// @@ -434,6 +546,12 @@ void no_error_loan_from_current_iteration(bool cond) { } } +MyObj* safe_return(MyObj safe){ + MyObj local; + MyObj *p = &local; + p = &safe; // p has been reassigned + return p; // This is safe +} //===----------------------------------------------------------------------===// // Lifetimebound Attribute Tests diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 0c051847f4d47..5370cde779a8a 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -1202,5 +1202,242 @@ TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) { EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1")); } +TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) { + SetupTest(R"( + MyObj* target() { + MyObj s; + MyObj* p = &s; + POINT(p1); + return p; + } + )"); + EXPECT_THAT(Origin("p"), HasLoansTo({"s"}, "p1")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, ConditionalAssignUnconditionalReturn) { + SetupTest(R"( + MyObj* target(bool c) { + MyObj s1; + MyObj* p = nullptr; + POINT(before_if); + if (c) { + p = &s1; + POINT(after_assign); + } + POINT(after_if); + return p; + } + )"); + EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_if")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_if")); + + EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_assign")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign")); + + EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_if")); + EXPECT_THAT(Origins({"p"}), MaybeLiveAt("before_if")); +} + +TEST_F(LifetimeAnalysisTest, ConditionalAssignBothBranches) { + SetupTest(R"( + MyObj* target(bool c) { + MyObj s1; + static MyObj s2; + MyObj* p = nullptr; + POINT(before_if); + if (c) { + p = &s1; + POINT(after_assign_if); + } else { + p = &s2; + POINT(after_assign_else); + } + POINT(after_if); + return p; + } + )"); + EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_if")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_if")); + + EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_assign_if")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign_if")); + + EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "after_assign_else")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign_else")); + + EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_if")); + EXPECT_THAT(NoOrigins(), AreLiveAt("before_if")); +} + +TEST_F(LifetimeAnalysisTest, ReassignFromSafeToLocalThenReturn) { + SetupTest(R"( + MyObj* target() { + static MyObj safe_obj; + MyObj local_obj; + MyObj* p = &safe_obj; + POINT(p1); + + p = &local_obj; + POINT(p2); + return p; + } + )"); + + EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2")); + + EXPECT_THAT(Origin("p"), HasLoansTo({"safe_obj"}, "p1")); + EXPECT_THAT(NoOrigins(), AreLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, PointerChainToLocal) { + SetupTest(R"( + MyObj* target() { + MyObj local_obj; + MyObj* p1 = &local_obj; + POINT(p_p1); + MyObj* p2 = p1; + POINT(p_p2); + return p2; + } + )"); + EXPECT_THAT(Origin("p2"), HasLoansTo({"local_obj"}, "p_p2")); + EXPECT_THAT(Origins({"p2"}), MustBeLiveAt("p_p2")); + EXPECT_THAT(Origins({"p1"}), testing::Not(AreLiveAt("p_p2"))); + + EXPECT_THAT(Origin("p1"), HasLoansTo({"local_obj"}, "p_p1")); + EXPECT_THAT(Origins({"p1"}), MustBeLiveAt("p_p1")); +} + +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; + POINT(before_cond); + if(c1){ + p = &local_obj1; + POINT(after_c1); + return p; + } + else if(c2){ + p = &local_obj2; + POINT(after_c2); + return p; + } + p = &global_obj; + POINT(after_cond); + return p; + } + )"); + // Expect two permissive warnings here in each block + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_cond")); + EXPECT_THAT(Origin("p"), HasLoansTo({"global_obj"}, "after_cond")); + + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c2")); + EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj2"}, "after_c2")); + + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c1")); + EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj1"}, "after_c1")); + + EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_cond")); + EXPECT_THAT(NoOrigins(), AreLiveAt("before_cond")); +} + +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; + POINT(before_cond); + if(c1){ + p = &local_obj1; + POINT(after_c1); + } + else if(c2){ + p = &local_obj2; + POINT(after_c2); + } + else{ + p = &global_obj; + POINT(after_else); + } + + POINT(after_cond); + return p; + } + )"); + // Expect permissive warning at return statement with both local_obj1 and + // local_obj2 as culprit loans + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_cond")); + EXPECT_THAT( + Origin("p"), + HasLoansTo({"global_obj", "local_obj2", "local_obj1"}, "after_cond")); + + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_else")); + EXPECT_THAT(Origin("p"), HasLoansTo({"global_obj"}, "after_else")); + + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c2")); + EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj2"}, "after_c2")); + + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c1")); + EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj1"}, "after_c1")); + + EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_cond")); + EXPECT_THAT(NoOrigins(), AreLiveAt("before_cond")); +} + +TEST_F(LifetimeAnalysisTest, UseAfterScopeThenReturn) { + SetupTest(R"( + MyObj* target() { + MyObj* p; + { + MyObj local_obj; + p = &local_obj; + POINT(p1); + } + POINT(p2); + return p; + } + )"); + // Expect a Use-after-scope warning because `p` in `return p` is a use even + // before return + 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")); +} + +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 a return stack address warning as return is before use after scope + EXPECT_THAT(NoOrigins(), AreLiveAt("p2")); + + EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1")); + EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1")); +} + } // anonymous namespace } // namespace clang::lifetimes::internal _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
