llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-temporal-safety Author: Utkarsh Saxena (usx95) <details> <summary>Changes</summary> Detect dangling field references when stack memory escapes to class fields. This change extends lifetime safety analysis to detect a common class of temporal memory safety bugs where local variables or parameters are stored in class fields but outlive their scope. - Added a new `FieldEscapeFact` class to represent when an origin escapes via assignment to a field - Refactored `OriginEscapesFact` into a base class with specialized subclasses for different escape scenarios - Added detection for stack memory escaping to fields in constructors and member functions - Implemented new diagnostic for dangling field references with appropriate warning messages Importantly, - Added `AddParameterDtors` option to CFG to add parameter dtors and lifetime ends behind an option. In principle, parameters ctors and dtors do not belong in the function context but in the caller context. This becomes incorrect to include in function's CFG when we have inlined CFGs like some analyses in the analyzer (produces double dtors for arguments). Therefore this provides a way to opt-in to know about destructed params on function exits. --- Patch is 34.80 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/177363.diff 19 Files Affected: - (modified) clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h (+48-4) - (modified) clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h (+3-1) - (modified) clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h (+5-1) - (modified) clang/include/clang/Analysis/CFG.h (+5) - (modified) clang/include/clang/Basic/DiagnosticGroups.td (+9-2) - (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+6) - (modified) clang/lib/Analysis/CFG.cpp (+15-6) - (modified) clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp (+1) - (modified) clang/lib/Analysis/LifetimeSafety/Checker.cpp (+12-5) - (modified) clang/lib/Analysis/LifetimeSafety/Facts.cpp (+10-3) - (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+51-17) - (modified) clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp (+7-2) - (modified) clang/lib/Analysis/LifetimeSafety/Origins.cpp (+17-8) - (modified) clang/lib/Sema/AnalysisBasedWarnings.cpp (+12-4) - (modified) clang/test/Analysis/lifetime-cfg-output.cpp (-28) - (modified) clang/test/Analysis/scopes-cfg-output.cpp (-2) - (modified) clang/test/Sema/warn-lifetime-analysis-nocfg.cpp (+16-9) - (added) clang/test/Sema/warn-lifetime-safety-dangling-field.cpp (+151) - (modified) clang/test/Sema/warn-lifetime-safety-dataflow.cpp (+1-1) ``````````diff diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index a66925b7302ca..61a4eea05bcb5 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -136,19 +136,63 @@ class OriginFlowFact : public Fact { const OriginManager &OM) const override; }; +/// Represents that an origin escapes the current scope through various means. +/// This is the base class for different escape scenarios. class OriginEscapesFact : public Fact { OriginID OID; - const Expr *EscapeExpr; public: + /// The way an origin can escape the current scope. + enum class EscapeKind : uint8_t { + Return, /// Escapes via return statement. + Field, /// Escapes via assignment to a field. + // FIXME: Add support for escape to global (dangling global ptr). + } EscKind; + static bool classof(const Fact *F) { return F->getKind() == Kind::OriginEscapes; } - OriginEscapesFact(OriginID OID, const Expr *EscapeExpr) - : Fact(Kind::OriginEscapes), OID(OID), EscapeExpr(EscapeExpr) {} + OriginEscapesFact(OriginID OID, EscapeKind EscKind) + : Fact(Kind::OriginEscapes), OID(OID), EscKind(EscKind) {} OriginID getEscapedOriginID() const { return OID; } - const Expr *getEscapeExpr() const { return EscapeExpr; }; + EscapeKind getEscapeKind() const { return EscKind; } +}; + +/// Represents that an origin escapes via a return statement. +class ReturnEscapeFact : public OriginEscapesFact { + const Expr *ReturnExpr; + +public: + ReturnEscapeFact(OriginID OID, const Expr *ReturnExpr) + : OriginEscapesFact(OID, EscapeKind::Return), ReturnExpr(ReturnExpr) {} + + static bool classof(const Fact *F) { + return F->getKind() == Kind::OriginEscapes && + static_cast<const OriginEscapesFact *>(F)->getEscapeKind() == + EscapeKind::Return; + } + const Expr *getReturnExpr() const { return ReturnExpr; }; + void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const override; +}; + +/// Represents that an origin escapes via assignment to a field. +/// Example: `this->view = local_var;` where local_var outlives the assignment +/// but not the object containing the field. +class FieldEscapeFact : public OriginEscapesFact { + const FieldDecl *FDecl; + +public: + FieldEscapeFact(OriginID OID, const FieldDecl *FDecl) + : OriginEscapesFact(OID, EscapeKind::Field), FDecl(FDecl) {} + + static bool classof(const Fact *F) { + return F->getKind() == Kind::OriginEscapes && + static_cast<const OriginEscapesFact *>(F)->getEscapeKind() == + EscapeKind::Field; + } + const FieldDecl *getFieldDecl() const { return FDecl; }; 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 a47505ee9f159..e4487b0d1dbc7 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -58,10 +58,12 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr); + void handleCXXCtorInitializer(const CXXCtorInitializer *CII); void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds); - void handleTemporaryDtor(const CFGTemporaryDtor &TemporaryDtor); + void handleExitBlock(); + 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/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 9c91355355233..8256d5829dcb9 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -52,10 +52,14 @@ class LifetimeSafetyReporter { Confidence Confidence) {} virtual void reportUseAfterReturn(const Expr *IssueExpr, - const Expr *EscapeExpr, + const Expr *ReturnExpr, SourceLocation ExpiryLoc, Confidence Confidence) {} + virtual void reportDanglingField(const Expr *IssueExpr, + const FieldDecl *Field, + SourceLocation ExpiryLoc) {} + // Suggests lifetime bound annotations for function paramters virtual void suggestAnnotation(SuggestionScope Scope, const ParmVarDecl *ParmToAnnotate, diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index a4bafd4927df0..16efb24e58211 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -1235,6 +1235,11 @@ class CFG { bool AddEHEdges = false; bool AddInitializers = false; bool AddImplicitDtors = false; + // Add dtors for function parameters. In principle, function parameters are + // constructed and destructed in the caller context but analyses could still + // choose to include these in the callee's CFG to represent the dtors run on + // function exit. + bool AddParameterDtors = false; bool AddLifetime = false; bool AddLoopExit = false; bool AddTemporaryDtors = false; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 34624dd3eed3a..c4cae4a30ec4f 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -533,14 +533,21 @@ def Dangling : DiagGroup<"dangling", [DanglingAssignment, DanglingGsl, ReturnStackAddress]>; -def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive">; +def LifetimeSafetyDanglingField : DiagGroup<"lifetime-safety-dangling-field"> { + code Documentation = [{Warning to detect dangling field references.}]; +} + +def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive", + [LifetimeSafetyDanglingField]>; def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict">; + def LifetimeSafety : DiagGroup<"lifetime-safety", [LifetimeSafetyPermissive, LifetimeSafetyStrict]> { code Documentation = [{ - Experimental warnings to detect use-after-free and related temporal safety bugs based on lifetime safety analysis. + Warnings to detect use-after-free and related temporal safety bugs based on lifetime safety analysis. }]; } + def LifetimeSafetyCrossTUSuggestions : DiagGroup<"lifetime-safety-cross-tu-suggestions">; def LifetimeSafetyIntraTUSuggestions diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index a2be7ab3791b9..8b05b0ffdd04d 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10816,9 +10816,15 @@ def warn_lifetime_safety_return_stack_addr_strict InGroup<LifetimeSafetyStrict>, DefaultIgnore; +def warn_lifetime_safety_dangling_field + : Warning<"address of stack memory escapes to a field">, + InGroup<LifetimeSafetyDanglingField>, + 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">; +def note_lifetime_safety_dangling_field_here: Note<"this field dangles">; def warn_lifetime_safety_intra_tu_suggestion : Warning<"parameter in intra-TU function should be marked " diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index a9c7baa00543c..fdca00a0f196f 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -1667,12 +1667,21 @@ std::unique_ptr<CFG> CFGBuilder::buildCFG(const Decl *D, Stmt *Statement) { assert(Succ == &cfg->getExit()); Block = nullptr; // the EXIT block is empty. Create all other blocks lazily. - // Add parameters to the initial scope to handle their dtos and lifetime ends. - LocalScope *paramScope = nullptr; - if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D)) - for (ParmVarDecl *PD : FD->parameters()) - paramScope = addLocalScopeForVarDecl(PD, paramScope); - + if (BuildOpts.AddParameterDtors) { + // Add parameters to the initial scope to handle their dtos and lifetime + // ends. + LocalScope *paramScope = nullptr; + if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D)) + for (ParmVarDecl *PD : FD->parameters()) { + paramScope = addLocalScopeForVarDecl(PD, paramScope); + } + if (auto *C = dyn_cast<CompoundStmt>(Statement)) + if (C->body_empty() || !isa<ReturnStmt>(*C->body_rbegin())) + // If the body ends with a ReturnStmt, the dtors will be added in + // VisitReturnStmt. + addAutomaticObjHandling(ScopePos, LocalScope::const_iterator(), + Statement); + } if (BuildOpts.AddImplicitDtors) if (const CXXDestructorDecl *DD = dyn_cast_or_null<CXXDestructorDecl>(D)) addImplicitDtorsForDestructor(DD); diff --git a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp index 6c4847c7c23fb..c47d7b5f9c3d6 100644 --- a/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp +++ b/clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp @@ -159,6 +159,7 @@ llvm::Expected<AdornedCFG> AdornedCFG::build(const Decl &D, Stmt &S, Options.PruneTriviallyFalseEdges = true; Options.AddImplicitDtors = true; Options.AddTemporaryDtors = true; + Options.AddParameterDtors = true; Options.AddInitializers = true; Options.AddCXXDefaultInitExprInCtors = true; Options.AddLifetime = true; diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index f7383126fac38..9ae17168d9b93 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -91,7 +91,8 @@ class LifetimeChecker { const ParmVarDecl *PVD = PL->getParmVarDecl(); if (PVD->hasAttr<LifetimeBoundAttr>()) continue; - AnnotationWarningsMap.try_emplace(PVD, OEF->getEscapeExpr()); + if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) + AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr()); } } } @@ -154,10 +155,16 @@ class LifetimeChecker { Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc, Confidence); else if (const auto *OEF = - CausingFact.dyn_cast<const OriginEscapesFact *>()) - Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(), - ExpiryLoc, Confidence); - else + CausingFact.dyn_cast<const OriginEscapesFact *>()) { + if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF)) + Reporter->reportUseAfterReturn(IssueExpr, RetEscape->getReturnExpr(), + ExpiryLoc, Confidence); + else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF)) + Reporter->reportDanglingField(IssueExpr, FieldEscape->getFieldDecl(), + ExpiryLoc); + else + llvm_unreachable("Unhandled OriginEscapesFact type"); + } else llvm_unreachable("Unhandled CausingFact type"); } } diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 2673ce5ba354b..1fc72aa0a4259 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -45,11 +45,18 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &, OS << "\n"; } -void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { +void ReturnEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const { OS << "OriginEscapes ("; OM.dump(getEscapedOriginID(), OS); - OS << ")\n"; + OS << ", via Return)\n"; +} + +void FieldEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const { + OS << "OriginEscapes ("; + OM.dump(getEscapedOriginID(), OS); + OS << ", via Field)\n"; } void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &, diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index d5990597e1614..47c919b7d139d 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -10,6 +10,7 @@ #include <string> #include "clang/AST/OperationKinds.h" +#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" #include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" @@ -107,6 +108,9 @@ void FactsGenerator::run() { const CFGElement &Element = Block->Elements[I]; if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) Visit(CS->getStmt()); + else if (std::optional<CFGInitializer> Initializer = + Element.getAs<CFGInitializer>()) + handleCXXCtorInitializer(Initializer->getInitializer()); else if (std::optional<CFGLifetimeEnds> LifetimeEnds = Element.getAs<CFGLifetimeEnds>()) handleLifetimeEnds(*LifetimeEnds); @@ -114,6 +118,9 @@ void FactsGenerator::run() { Element.getAs<CFGTemporaryDtor>()) handleTemporaryDtor(*TemporaryDtor); } + if (Block == &Cfg.getExit()) + handleExitBlock(); + CurrentBlockFacts.append(EscapesInCurrentBlock.begin(), EscapesInCurrentBlock.end()); FactMgr.addBlockFacts(Block, CurrentBlockFacts); @@ -180,6 +187,13 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) { } } +void FactsGenerator::handleCXXCtorInitializer(const CXXCtorInitializer *CII) { + // Flows origins from the initializer expression to the field. + // Example: `MyObj(std::string s) : view(s) {}` + if (const FieldDecl *FD = CII->getAnyMember()) + killAndFlowOrigin(*FD, *CII->getInit()); +} + void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) { // Specifically for conversion operators, // like `std::string_view p = std::string{};` @@ -316,31 +330,42 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) { if (const Expr *RetExpr = RS->getRetValue()) { if (OriginList *List = getOriginsList(*RetExpr)) for (OriginList *L = List; L != nullptr; L = L->peelOuterOrigin()) - EscapesInCurrentBlock.push_back(FactMgr.createFact<OriginEscapesFact>( + EscapesInCurrentBlock.push_back(FactMgr.createFact<ReturnEscapeFact>( L->getOuterOriginID(), RetExpr)); } } void FactsGenerator::handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr) { - if (const auto *DRE_LHS = - dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) { - OriginList *LHSList = getOriginsList(*DRE_LHS); - assert(LHSList && "LHS is a DRE and should have an origin list"); - OriginList *RHSList = getOriginsList(*RHSExpr); - - // For operator= with reference parameters (e.g., - // `View& operator=(const View&)`), the RHS argument stays an lvalue, - // unlike built-in assignment where LValueToRValue cast strips the outer - // lvalue origin. Strip it manually to get the actual value origins being - // assigned. - RHSList = getRValueOrigins(RHSExpr, RHSList); + LHSExpr = LHSExpr->IgnoreParenImpCasts(); + OriginList *LHSList = nullptr; - markUseAsWrite(DRE_LHS); - // Kill the old loans of the destination origin and flow the new loans - // from the source origin. - flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true); + if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) { + LHSList = getOriginsList(*DRE_LHS); + assert(LHSList && "LHS is a DRE and should have an origin list"); + } + // Handle assignment to member fields (e.g., `this->view = s` or `view = s`). + // This enables detection of dangling fields when local values escape to + // fields. + if (const auto *ME_LHS = dyn_cast<MemberExpr>(LHSExpr)) { + LHSList = getOriginsList(*ME_LHS); + assert(LHSList && "LHS is a MemberExpr and should have an origin list"); } + if (!LHSList) + return; + OriginList *RHSList = getOriginsList(*RHSExpr); + // For operator= with reference parameters (e.g., + // `View& operator=(const View&)`), the RHS argument stays an lvalue, + // unlike built-in assignment where LValueToRValue cast strips the outer + // lvalue origin. Strip it manually to get the actual value origins being + // assigned. + RHSList = getRValueOrigins(RHSExpr, RHSList); + + if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) + markUseAsWrite(DRE_LHS); + // Kill the old loans of the destination origin and flow the new loans + // from the source origin. + flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true); } void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) { @@ -463,6 +488,15 @@ void FactsGenerator::handleTemporaryDtor( } } +void FactsGenerator::handleExitBlock() { + // Creates FieldEscapeFacts for all field origins that remain live at exit. + for (const Origin &O : FactMgr.getOriginMgr().getOrigins()) + if (O.getDecl()) + if (auto *FD = dyn_cast<FieldDecl>(O.getDecl())) + EscapesInCurrentBlock.push_back( + FactMgr.createFact<FieldEscapeFact>(O.ID, FD)); +} + void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) { assert(isGslPointerType(CCE->getType())); if (CCE->getNumArgs() != 1) diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp index 862dca256280f..f210fb4d752d4 100644 --- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp @@ -8,6 +8,7 @@ #include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h" #include "Dataflow.h" +#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" #include "llvm/Support/ErrorHandling.h" namespace clang::lifetimes::internal { @@ -56,8 +57,12 @@ struct Lattice { static SourceLocation GetFactLoc(CausingFactType F) { if (const auto *UF = F.dyn_cast<const UseFact *>()) return UF->getUseExpr()->getExprLoc(); - if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>()) - return OEF->getEscapeExpr()->getExprLoc(); + if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>()) { + if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) + return ReturnEsc->getReturnExpr()->getExprLoc(); + if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF)) + return FieldEsc->getFieldDecl()->getLocation(); + } llvm_unreachable("unhandled causing fact in PointerUnion"); } diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp index ca933f612eb08..6062b3d42ebe2 100644 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp @@ -9,6 +9,7 @@ #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/Expr.h" @@ -138,25 +139,33 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) { QualType Type = E->getType(); - // Special handling for DeclRefExpr to share origins with th... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/177363 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
