https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/158489
>From 076766a917cd3c327a584dd868418d69ddecfcd5 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Sun, 14 Sep 2025 14:46:45 +0000 Subject: [PATCH] lifetime-analysis-lifetimebound --- .../clang/Analysis/Analyses/LifetimeSafety.h | 11 +- clang/lib/Analysis/LifetimeSafety.cpp | 195 +++++++++---- .../test/Analysis/LifetimeSafety/benchmark.py | 2 +- .../Sema/warn-lifetime-safety-dataflow.cpp | 155 +++++------ clang/test/Sema/warn-lifetime-safety.cpp | 149 ++++++++++ .../unittests/Analysis/LifetimeSafetyTest.cpp | 257 +++++++++++++++++- 6 files changed, 636 insertions(+), 133 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h index 7e1bfc903083e..512cb76cd6349 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h @@ -75,13 +75,14 @@ template <typename Tag> struct ID { } }; -template <typename Tag> -inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) { - return OS << ID.Value; -} - using LoanID = ID<struct LoanTag>; using OriginID = ID<struct OriginTag>; +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) { + return OS << ID.Value; +} +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) { + return OS << ID.Value; +} // Using LLVM's immutable collections is efficient for dataflow analysis // as it avoids deep copies during state transitions. diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp index d016c6f12e82e..f63e95189e8f3 100644 --- a/clang/lib/Analysis/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety.cpp @@ -212,8 +212,10 @@ class Fact { /// A loan expires as its underlying storage is freed (e.g., variable goes /// out of scope). Expire, + /// The loan set of an origin is cleared. + KillOrigin, /// An origin is propagated from a source to a destination (e.g., p = q). - AssignOrigin, + OriginFlow, /// An origin escapes the function by flowing into the return value. ReturnOfOrigin, /// An origin is used (eg. dereferencing a pointer). @@ -285,22 +287,24 @@ class ExpireFact : public Fact { } }; -class AssignOriginFact : public Fact { +class OriginFlowFact : public Fact { OriginID OIDDest; OriginID OIDSrc; public: static bool classof(const Fact *F) { - return F->getKind() == Kind::AssignOrigin; + return F->getKind() == Kind::OriginFlow; } - AssignOriginFact(OriginID OIDDest, OriginID OIDSrc) - : Fact(Kind::AssignOrigin), OIDDest(OIDDest), OIDSrc(OIDSrc) {} + OriginFlowFact(OriginID OIDDest, OriginID OIDSrc) + : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc) {} + OriginID getDestOriginID() const { return OIDDest; } OriginID getSrcOriginID() const { return OIDSrc; } + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const override { - OS << "AssignOrigin (Dest: "; + OS << "OriginFlow (Dest: "; OM.dump(getDestOriginID(), OS); OS << ", Src: "; OM.dump(getSrcOriginID(), OS); @@ -353,6 +357,23 @@ class UseFact : public Fact { } }; +class KillOriginFact : public Fact { + OriginID OID; + +public: + static bool classof(const Fact *F) { + return F->getKind() == Kind::KillOrigin; + } + KillOriginFact(OriginID OID) : Fact(Kind::KillOrigin), OID(OID) {} + OriginID getOriginID() const { return OID; } + + void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const override { + OS << "KillOrigin ("; + OM.dump(getOriginID(), OS); + OS << ")\n"; + } +}; /// A dummy-fact used to mark a specific point in the code for testing. /// It is generated by recognizing a `void("__lifetime_test_point_...")` cast. class TestPointFact : public Fact { @@ -454,7 +475,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { if (const auto *VD = dyn_cast<VarDecl>(D)) if (hasOrigin(VD)) if (const Expr *InitExpr = VD->getInit()) - addAssignOriginFact(*VD, *InitExpr); + addOriginFlowFact(*VD, *InitExpr); } void VisitDeclRefExpr(const DeclRefExpr *DRE) { @@ -492,9 +513,23 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { isa<CXXConversionDecl>(MCE->getCalleeDecl())) { // The argument is the implicit object itself. handleFunctionCall(MCE, MCE->getMethodDecl(), - {MCE->getImplicitObjectArgument()}); + {MCE->getImplicitObjectArgument()}, + /*IsGslConstruction=*/true); } - // FIXME: A more general VisitCallExpr could also be used here. + if (const CXXMethodDecl *Method = MCE->getMethodDecl()) { + // Construct the argument list, with the implicit 'this' object as the + // first argument. + llvm::SmallVector<const Expr *, 4> Args; + Args.push_back(MCE->getImplicitObjectArgument()); + Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs()); + + handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false); + } + } + + void VisitCallExpr(const CallExpr *CE) { + handleFunctionCall(CE, CE->getDirectCallee(), + {CE->getArgs(), CE->getNumArgs()}); } void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) { @@ -508,7 +543,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { return; // An ImplicitCastExpr node itself gets an origin, which flows from the // origin of its sub-expression (after stripping its own parens/casts). - addAssignOriginFact(*ICE, *ICE->getSubExpr()); + addOriginFlowFact(*ICE, *ICE->getSubExpr()); } void VisitUnaryOperator(const UnaryOperator *UO) { @@ -522,7 +557,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { // its sub-expression (x). This fact will cause the dataflow analysis // to propagate any loans held by the sub-expression's origin to the // origin of this UnaryOperator expression. - addAssignOriginFact(*UO, *SubExpr); + addOriginFlowFact(*UO, *SubExpr); } } @@ -542,8 +577,15 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { } void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) { - if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) + // Assignment operators have special "kill-then-propagate" semantics + // and are handled separately. + if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) { handleAssignment(OCE->getArg(0), OCE->getArg(1)); + return; + } + handleFunctionCall(OCE, OCE->getDirectCallee(), + {OCE->getArgs(), OCE->getNumArgs()}, + /*IsGslConstruction=*/false); } void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) { @@ -552,7 +594,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { if (handleTestPoint(FCE)) return; if (isGslPointerType(FCE->getType())) - addAssignOriginFact(*FCE, *FCE->getSubExpr()); + addOriginFlowFact(*FCE, *FCE->getSubExpr()); } void VisitInitListExpr(const InitListExpr *ILE) { @@ -561,7 +603,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { // For list initialization with a single element, like `View{...}`, the // origin of the list itself is the origin of its single element. if (ILE->getNumInits() == 1) - addAssignOriginFact(*ILE, *ILE->getInit(0)); + addOriginFlowFact(*ILE, *ILE->getInit(0)); } void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE) { @@ -569,7 +611,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { return; // A temporary object's origin is the same as the origin of the // expression that initializes it. - addAssignOriginFact(*MTE, *MTE->getSubExpr()); + addOriginFlowFact(*MTE, *MTE->getSubExpr()); } void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) { @@ -624,34 +666,68 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { if (CCE->getNumArgs() != 1) return; if (hasOrigin(CCE->getArg(0))) - addAssignOriginFact(*CCE, *CCE->getArg(0)); + addOriginFlowFact(*CCE, *CCE->getArg(0)); else // This could be a new borrow. handleFunctionCall(CCE, CCE->getConstructor(), - {CCE->getArgs(), CCE->getNumArgs()}); + {CCE->getArgs(), CCE->getNumArgs()}, + /*IsGslConstruction=*/true); + } + static const FunctionDecl * + getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) { + return FD != nullptr ? FD->getMostRecentDecl() : nullptr; } + static const CXXMethodDecl * + getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD) { + const FunctionDecl *FD = CMD; + return cast_if_present<CXXMethodDecl>( + getDeclWithMergedLifetimeBoundAttrs(FD)); + } + static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) { + FD = getDeclWithMergedLifetimeBoundAttrs(FD); + const TypeSourceInfo *TSI = FD->getTypeSourceInfo(); + if (!TSI) + return false; + // Don't declare this variable in the second operand of the for-statement; + // GCC miscompiles that by ending its lifetime before evaluating the + // third operand. See gcc.gnu.org/PR86769. + AttributedTypeLoc ATL; + for (TypeLoc TL = TSI->getTypeLoc(); + (ATL = TL.getAsAdjusted<AttributedTypeLoc>()); + TL = ATL.getModifiedLoc()) { + if (ATL.getAttrAs<LifetimeBoundAttr>()) + return true; + } + return false; + } /// Checks if a call-like expression creates a borrow by passing a value to a /// reference parameter, creating an IssueFact if it does. void handleFunctionCall(const Expr *Call, const FunctionDecl *FD, - ArrayRef<const Expr *> Args) { - if (!FD) + ArrayRef<const Expr *> Args, + bool IsGslConstruction = false) { + // Ignore functions returning values with no origin. + if (!FD || !hasOrigin(Call)) return; - // TODO: Handle more than one arguments. - for (unsigned I = 0; I <= 0 /*Args.size()*/; ++I) { - const Expr *ArgExpr = Args[I]; - - // Propagate origins for CXX this. - if (FD->isCXXClassMember() && I == 0) { - addAssignOriginFact(*Call, *ArgExpr); - continue; - } - // The parameter is a pointer, reference, or gsl::Pointer. - // This is a borrow. We propagate the origin from the argument expression - // at the call site to the parameter declaration in the callee. - if (hasOrigin(ArgExpr)) - addAssignOriginFact(*Call, *ArgExpr); - } + auto IsArgLifetimeBound = [FD](unsigned I) -> bool { + const ParmVarDecl *PVD = nullptr; + if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); + Method && Method->isInstance()) { + if (I == 0) + // For the 'this' argument, the attribute is on the method itself. + return implicitObjectParamIsLifetimeBound(Method); + if ((I - 1) < Method->getNumParams()) + // For explicit arguments, find the corresponding parameter + // declaration. + PVD = Method->getParamDecl(I - 1); + } else if (I < FD->getNumParams()) + // For free functions or static methods. + PVD = FD->getParamDecl(I); + return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false; + }; + for (unsigned I = 0; I < Args.size(); ++I) + if (IsGslConstruction || IsArgLifetimeBound(I)) + addOriginFlowFact(*Call, *Args[I]); } /// Creates a loan for the storage path of a given declaration reference. @@ -668,11 +744,16 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { } template <typename Destination, typename Source> - void addAssignOriginFact(const Destination &D, const Source &S) { + void addOriginFlowFact(const Destination &D, const Source &S) { OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D); OriginID SrcOID = FactMgr.getOriginMgr().get(S); CurrentBlockFacts.push_back( - FactMgr.createFact<AssignOriginFact>(DestOID, SrcOID)); + FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID)); + } + + void killOrigin(const ValueDecl *D) { + OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(*D); + CurrentBlockFacts.push_back(FactMgr.createFact<KillOriginFact>(DestOID)); } /// Checks if the expression is a `void("__lifetime_test_point_...")` cast. @@ -703,12 +784,12 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) { markUseAsWrite(DRE_LHS); - if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) - // We are interested in assignments like `ptr1 = ptr2` or `ptr = &var`. - // LHS must be a pointer/reference type that can be an origin. RHS must - // also represent an origin (either another pointer/ref or an - // address-of). - addAssignOriginFact(*VD_LHS, *RHSExpr); + if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) { + // Kill the old loans of the destination origin and flow the new loans + // from the source origin. + killOrigin(VD_LHS); + addOriginFlowFact(*VD_LHS, *RHSExpr); + } } } @@ -882,8 +963,10 @@ class DataflowAnalysis { return D->transfer(In, *F->getAs<IssueFact>()); case Fact::Kind::Expire: return D->transfer(In, *F->getAs<ExpireFact>()); - case Fact::Kind::AssignOrigin: - return D->transfer(In, *F->getAs<AssignOriginFact>()); + case Fact::Kind::OriginFlow: + return D->transfer(In, *F->getAs<OriginFlowFact>()); + case Fact::Kind::KillOrigin: + return D->transfer(In, *F->getAs<KillOriginFact>()); case Fact::Kind::ReturnOfOrigin: return D->transfer(In, *F->getAs<ReturnOfOriginFact>()); case Fact::Kind::Use: @@ -897,7 +980,8 @@ class DataflowAnalysis { public: Lattice transfer(Lattice In, const IssueFact &) { return In; } Lattice transfer(Lattice In, const ExpireFact &) { return In; } - Lattice transfer(Lattice In, const AssignOriginFact &) { return In; } + Lattice transfer(Lattice In, const OriginFlowFact &) { return In; } + Lattice transfer(Lattice In, const KillOriginFact &) { return In; } Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; } Lattice transfer(Lattice In, const UseFact &) { return In; } Lattice transfer(Lattice In, const TestPointFact &) { return In; } @@ -1049,14 +1133,27 @@ class LoanPropagationAnalysis LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID))); } - /// The destination origin's loan set is replaced by the source's. - /// This implicitly "resets" the old loans of the destination. - Lattice transfer(Lattice In, const AssignOriginFact &F) { + /// A flow from source to destination adds the source's loans to the + /// destination's, without clearing the destination's existing loans. + Lattice transfer(Lattice In, const OriginFlowFact &F) { OriginID DestOID = F.getDestOriginID(); OriginID SrcOID = F.getSrcOriginID(); + + LoanSet DestLoans = getLoans(In, DestOID); LoanSet SrcLoans = getLoans(In, SrcOID); + LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory); + return LoanPropagationLattice( - OriginLoanMapFactory.add(In.Origins, DestOID, SrcLoans)); + OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans)); + } + + /// Clears the loan set of the specified origin. This is used on the + /// left-hand side of an assignment to invalidate the variable's old lifetime. + Lattice transfer(Lattice In, const KillOriginFact &F) { + OriginID OID = F.getOriginID(); + // Replace the origin's loan set with an empty set. + return LoanPropagationLattice(OriginLoanMapFactory.add( + In.Origins, OID, LoanSetFactory.getEmptySet())); } LoanSet getLoans(OriginID OID, ProgramPoint P) { diff --git a/clang/test/Analysis/LifetimeSafety/benchmark.py b/clang/test/Analysis/LifetimeSafety/benchmark.py index 2373f9984eecd..d2e5f0b2122a3 100644 --- a/clang/test/Analysis/LifetimeSafety/benchmark.py +++ b/clang/test/Analysis/LifetimeSafety/benchmark.py @@ -340,7 +340,7 @@ def run_single_test( "name": "cycle", "title": "Pointer Cycle in Loop", "generator_func": generate_cpp_cycle_test, - "n_values": [25, 50, 75, 100], + "n_values": [50, 75, 100, 200, 300], }, { "name": "merge", diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp index 7dac27506fb6b..910b2df73b2d5 100644 --- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp +++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp @@ -12,12 +12,12 @@ MyObj* return_local_addr() { MyObj x {10}; // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr)) MyObj* p = &x; -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator)) return p; // CHECK: Use ([[O_P]] (Decl: p), Read) -// CHECK: AssignOrigin (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p)) +// 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)) } @@ -29,26 +29,26 @@ MyObj* assign_and_return_local_addr() { MyObj y{20}; // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_Y:[0-9]+]] (Path: y), ToOrigin: [[O_DRE_Y:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_Y]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_Y]] (Expr: DeclRefExpr)) MyObj* ptr1 = &y; -// CHECK: AssignOrigin (Dest: [[O_PTR1:[0-9]+]] (Decl: ptr1), Src: [[O_ADDR_Y]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_PTR1:[0-9]+]] (Decl: ptr1), Src: [[O_ADDR_Y]] (Expr: UnaryOperator)) MyObj* ptr2 = ptr1; // CHECK: Use ([[O_PTR1]] (Decl: ptr1), Read) -// CHECK: AssignOrigin (Dest: [[O_PTR1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1)) -// CHECK: AssignOrigin (Dest: [[O_PTR2:[0-9]+]] (Decl: ptr2), Src: [[O_PTR1_RVAL]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_PTR1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1)) +// CHECK: OriginFlow (Dest: [[O_PTR2:[0-9]+]] (Decl: ptr2), Src: [[O_PTR1_RVAL]] (Expr: ImplicitCastExpr)) ptr2 = ptr1; // CHECK: Use ([[O_PTR1]] (Decl: ptr1), Read) -// CHECK: AssignOrigin (Dest: [[O_PTR1_RVAL_2:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1)) +// CHECK: OriginFlow (Dest: [[O_PTR1_RVAL_2:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1)) // CHECK: Use ({{[0-9]+}} (Decl: ptr2), Write) -// CHECK: AssignOrigin (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR1_RVAL_2]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR1_RVAL_2]] (Expr: ImplicitCastExpr)) ptr2 = ptr2; // Self assignment. // CHECK: Use ([[O_PTR2]] (Decl: ptr2), Read) -// CHECK: AssignOrigin (Dest: [[O_PTR2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2)) +// CHECK: OriginFlow (Dest: [[O_PTR2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2)) // CHECK: Use ([[O_PTR2]] (Decl: ptr2), Write) -// CHECK: AssignOrigin (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR2_RVAL]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR2_RVAL]] (Expr: ImplicitCastExpr)) return ptr2; // CHECK: Use ([[O_PTR2]] (Decl: ptr2), Read) -// CHECK: AssignOrigin (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2)) +// 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)) } @@ -70,9 +70,9 @@ void loan_expires_cpp() { MyObj obj{1}; // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_OBJ:[0-9]+]] (Path: obj), ToOrigin: [[O_DRE_OBJ:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OBJ]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OBJ]] (Expr: DeclRefExpr)) MyObj* pObj = &obj; -// CHECK: AssignOrigin (Dest: {{[0-9]+}} (Decl: pObj), Src: [[O_ADDR_OBJ]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pObj), Src: [[O_ADDR_OBJ]] (Expr: UnaryOperator)) // CHECK: Expire ([[L_OBJ]] (Path: obj)) } @@ -83,9 +83,9 @@ void loan_expires_trivial() { int trivial_obj = 1; // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_TRIVIAL_OBJ:[0-9]+]] (Path: trivial_obj), ToOrigin: [[O_DRE_TRIVIAL:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr)) int* pTrivialObj = &trivial_obj; -// CHECK: AssignOrigin (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator)) // CHECK-NOT: Expire // CHECK-NEXT: End of Block // FIXME: Add check for Expire once trivial destructors are handled for expiration. @@ -100,20 +100,20 @@ void conditional(bool condition) { if (condition) // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_A]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_A]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] (Expr: UnaryOperator)) p = &a; else // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_B:[0-9]+]] (Path: b), ToOrigin: [[O_DRE_B:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_B:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_B]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_B]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_B:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_B]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_B]] (Expr: UnaryOperator)) p = &b; // CHECK: Block B{{[0-9]+}}: int *q = p; // CHECK: Use ([[O_P]] (Decl: p), Read) -// CHECK: AssignOrigin (Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p)) -// CHECK: AssignOrigin (Dest: [[O_Q:[0-9]+]] (Decl: q), Src: [[O_P_RVAL]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p)) +// CHECK: OriginFlow (Dest: [[O_Q:[0-9]+]] (Decl: q), Src: [[O_P_RVAL]] (Expr: ImplicitCastExpr)) } @@ -128,36 +128,36 @@ void pointers_in_a_cycle(bool condition) { MyObj* p3 = &v3; // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_V1:[0-9]+]] (Path: v1), ToOrigin: [[O_DRE_V1:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_V1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_V1]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P1:[0-9]+]] (Decl: p1), Src: [[O_ADDR_V1]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_V1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_V1]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P1:[0-9]+]] (Decl: p1), Src: [[O_ADDR_V1]] (Expr: UnaryOperator)) // CHECK: Issue ([[L_V2:[0-9]+]] (Path: v2), ToOrigin: [[O_DRE_V2:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_V2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_V2]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P2:[0-9]+]] (Decl: p2), Src: [[O_ADDR_V2]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_V2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_V2]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P2:[0-9]+]] (Decl: p2), Src: [[O_ADDR_V2]] (Expr: UnaryOperator)) // CHECK: Issue ([[L_V3:[0-9]+]] (Path: v3), ToOrigin: [[O_DRE_V3:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_V3:[0-g]+]] (Expr: UnaryOperator), Src: [[O_DRE_V3]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P3:[0-9]+]] (Decl: p3), Src: [[O_ADDR_V3]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_V3:[0-g]+]] (Expr: UnaryOperator), Src: [[O_DRE_V3]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P3:[0-9]+]] (Decl: p3), Src: [[O_ADDR_V3]] (Expr: UnaryOperator)) while (condition) { // CHECK: Block B{{[0-9]+}}: MyObj* temp = p1; // CHECK: Use ([[O_P1]] (Decl: p1), Read) -// CHECK: AssignOrigin (Dest: [[O_P1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P1]] (Decl: p1)) -// CHECK: AssignOrigin (Dest: [[O_TEMP:[0-9]+]] (Decl: temp), Src: [[O_P1_RVAL]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_P1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P1]] (Decl: p1)) +// CHECK: OriginFlow (Dest: [[O_TEMP:[0-9]+]] (Decl: temp), Src: [[O_P1_RVAL]] (Expr: ImplicitCastExpr)) p1 = p2; // CHECK: Use ([[O_P2:[0-9]+]] (Decl: p2), Read) -// CHECK: AssignOrigin (Dest: [[O_P2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P2]] (Decl: p2)) +// CHECK: OriginFlow (Dest: [[O_P2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P2]] (Decl: p2)) // CHECK: Use ({{[0-9]+}} (Decl: p1), Write) -// CHECK: AssignOrigin (Dest: [[O_P1]] (Decl: p1), Src: [[O_P2_RVAL]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_P1]] (Decl: p1), Src: [[O_P2_RVAL]] (Expr: ImplicitCastExpr)) p2 = p3; // CHECK: Use ([[O_P3:[0-9]+]] (Decl: p3), Read) -// CHECK: AssignOrigin (Dest: [[O_P3_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P3]] (Decl: p3)) +// CHECK: OriginFlow (Dest: [[O_P3_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P3]] (Decl: p3)) // CHECK: Use ({{[0-9]+}} (Decl: p2), Write) -// CHECK: AssignOrigin (Dest: [[O_P2]] (Decl: p2), Src: [[O_P3_RVAL]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_P2]] (Decl: p2), Src: [[O_P3_RVAL]] (Expr: ImplicitCastExpr)) p3 = temp; // CHECK: Use ([[O_TEMP]] (Decl: temp), Read) -// CHECK: AssignOrigin (Dest: [[O_TEMP_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_TEMP]] (Decl: temp)) +// CHECK: OriginFlow (Dest: [[O_TEMP_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_TEMP]] (Decl: temp)) // CHECK: Use ({{[0-9]+}} (Decl: p3), Write) -// CHECK: AssignOrigin (Dest: [[O_P3]] (Decl: p3), Src: [[O_TEMP_RVAL]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_P3]] (Decl: p3), Src: [[O_TEMP_RVAL]] (Expr: ImplicitCastExpr)) } } @@ -168,13 +168,13 @@ void overwrite_origin() { // CHECK: Block B{{[0-9]+}}: MyObj* p = &s1; // CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) p = &s2; // CHECK: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr)) // CHECK: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator)) // CHECK: Expire ([[L_S2]] (Path: s2)) // CHECK: Expire ([[L_S1]] (Path: s1)) } @@ -185,12 +185,12 @@ void reassign_to_null() { // CHECK: Block B{{[0-9]+}}: MyObj* p = &s1; // CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) p = nullptr; -// CHECK: AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: {{[0-9]+}} (Expr: CXXNullPtrLiteralExpr)) +// CHECK: OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: {{[0-9]+}} (Expr: CXXNullPtrLiteralExpr)) // CHECK: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr)) // CHECK: Expire ([[L_S1]] (Path: s1)) } // FIXME: Have a better representation for nullptr than just an empty origin. @@ -204,15 +204,15 @@ void reassign_in_if(bool condition) { MyObj* p = &s1; // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) if (condition) { // CHECK: Block B{{[0-9]+}}: p = &s2; // CHECK: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr)) // CHECK: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator)) } // CHECK: Block B{{[0-9]+}}: // CHECK: Expire ([[L_S2]] (Path: s2)) @@ -227,32 +227,32 @@ void assign_in_switch(int mode) { MyObj s3; MyObj* p = nullptr; // CHECK: Block B{{[0-9]+}}: -// CHECK: AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr)) switch (mode) { case 1: // CHECK-DAG: Block B{{[0-9]+}}: p = &s1; // CHECK-DAG: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK-DAG: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) +// CHECK-DAG: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) // CHECK-DAG: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK-DAG: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) +// CHECK-DAG: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) break; case 2: // CHECK-DAG: Block B{{[0-9]+}}: p = &s2; // CHECK-DAG: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK-DAG: AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr)) +// CHECK-DAG: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr)) // CHECK-DAG: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK-DAG: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator)) +// CHECK-DAG: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator)) break; default: // CHECK: Block B{{[0-9]+}}: p = &s3; // CHECK: Issue ([[L_S3:[0-9]+]] (Path: s3), ToOrigin: [[O_DRE_S3:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_S3:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S3]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_S3:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S3]] (Expr: DeclRefExpr)) // CHECK: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S3]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S3]] (Expr: UnaryOperator)) break; } // CHECK: Block B{{[0-9]+}}: @@ -265,16 +265,16 @@ void assign_in_switch(int mode) { void loan_in_loop(bool condition) { MyObj* p = nullptr; // CHECK: Block B{{[0-9]+}}: -// CHECK: AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr)) while (condition) { MyObj inner; // CHECK: Block B{{[0-9]+}}: p = &inner; // CHECK: Issue ([[L_INNER:[0-9]+]] (Path: inner), ToOrigin: [[O_DRE_INNER:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr)) // CHECK: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator)) // CHECK: Expire ([[L_INNER]] (Path: inner)) } } @@ -286,16 +286,17 @@ void loop_with_break(int count) { MyObj* p = &s1; // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator)) for (int i = 0; i < count; ++i) { if (i == 5) { // CHECK: Block B{{[0-9]+}}: p = &s2; // CHECK: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr)) // CHECK: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator)) +// CHECK: KillOrigin ([[O_P]] (Decl: p)) +// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator)) break; } } @@ -308,22 +309,24 @@ void loop_with_break(int count) { void nested_scopes() { MyObj* p = nullptr; // CHECK: Block B{{[0-9]+}}: -// CHECK: AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr)) { MyObj outer; p = &outer; // CHECK: Issue ([[L_OUTER:[0-9]+]] (Path: outer), ToOrigin: [[O_DRE_OUTER:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_OUTER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OUTER]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_OUTER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OUTER]] (Expr: DeclRefExpr)) // CHECK: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_OUTER]] (Expr: UnaryOperator)) +// CHECK: KillOrigin ([[O_P]] (Decl: p)) +// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_OUTER]] (Expr: UnaryOperator)) { MyObj inner; p = &inner; // CHECK: Issue ([[L_INNER:[0-9]+]] (Path: inner), ToOrigin: [[O_DRE_INNER:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr)) // CHECK: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator)) +// CHECK: KillOrigin ([[O_P]] (Decl: p)) +// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator)) } // CHECK: Expire ([[L_INNER]] (Path: inner)) } @@ -336,14 +339,14 @@ void pointer_indirection() { int *p = &a; // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_A]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_A]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] (Expr: UnaryOperator)) int **pp = &p; // Note: No facts are generated for &p because the subexpression is a pointer type, // which is not yet supported by the origin model. This is expected. int *q = *pp; // CHECK: Use ([[O_PP:[0-9]+]] (Decl: pp), Read) -// CHECK: AssignOrigin (Dest: {{[0-9]+}} (Decl: q), Src: {{[0-9]+}} (Expr: ImplicitCastExpr)) +// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: q), Src: {{[0-9]+}} (Expr: ImplicitCastExpr)) } // CHECK-LABEL: Function: ternary_operator @@ -360,7 +363,7 @@ void ternary_operator() { // CHECK: Block B{{[0-9]+}}: // CHECK: Use ({{[0-9]+}} (Decl: p), Write) -// CHECK: AssignOrigin (Dest: {{[0-9]+}} (Decl: p), Src: {{[0-9]+}} (Expr: ConditionalOperator)) +// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: p), Src: {{[0-9]+}} (Expr: ConditionalOperator)) } // CHECK-LABEL: Function: test_use_facts @@ -371,9 +374,9 @@ void test_use_facts() { // CHECK: Block B{{[0-9]+}}: p = &x; // CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr)) -// CHECK: AssignOrigin (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr)) // CHECK: Use ([[O_P:[0-9]+]] (Decl: p), Write) -// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator)) +// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator)) (void)*p; // CHECK: Use ([[O_P]] (Decl: p), Read) usePointer(p); diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index bc8a5f3f7150f..0cfd69b68ec55 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -371,3 +371,152 @@ void no_error_if_dangle_then_rescue_gsl() { v = safe; // 'v' is "rescued" before use by reassigning to a valid object. v.use(); // This is safe. } + + +//===----------------------------------------------------------------------===// +// Lifetimebound Attribute Tests +//===----------------------------------------------------------------------===// + +View Identity(View v [[clang::lifetimebound]]); +View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]); +MyObj* GetPointer(const MyObj& obj [[clang::lifetimebound]]); + +struct [[gsl::Pointer()]] LifetimeBoundView { + LifetimeBoundView(); + LifetimeBoundView(const MyObj& obj [[clang::lifetimebound]]); + LifetimeBoundView pass() [[clang::lifetimebound]] { return *this; } + operator View() const [[clang::lifetimebound]]; +}; + +void lifetimebound_simple_function() { + View v; + { + MyObj obj; + v = Identity(obj); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + v.use(); // expected-note {{later used here}} +} + +void lifetimebound_multiple_args_definite() { + View v; + { + MyObj obj1, obj2; + v = Choose(true, + obj1, // expected-warning {{object whose reference is captured does not live long enough}} + obj2); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note 2 {{destroyed here}} + v.use(); // expected-note 2 {{later used here}} +} + +void lifetimebound_multiple_args_potential(bool cond) { + MyObj safe; + View v = safe; + { + MyObj obj1; + if (cond) { + MyObj obj2; + v = Choose(true, + obj1, // expected-warning {{object whose reference is captured may not live long enough}} + obj2); // expected-warning {{object whose reference is captured may not live long enough}} + } // expected-note {{destroyed here}} + } // expected-note {{destroyed here}} + v.use(); // expected-note 2 {{later used here}} +} + +View SelectFirst(View a [[clang::lifetimebound]], View b); +void lifetimebound_mixed_args() { + View v; + { + MyObj obj1, obj2; + v = SelectFirst(obj1, // expected-warning {{object whose reference is captured does not live long enough}} + obj2); + } // expected-note {{destroyed here}} + v.use(); // expected-note {{later used here}} +} + +void lifetimebound_member_function() { + LifetimeBoundView lbv, lbv2; + { + MyObj obj; + lbv = obj; // expected-warning {{object whose reference is captured does not live long enough}} + lbv2 = lbv.pass(); + } // expected-note {{destroyed here}} + View v = lbv2; // expected-note {{later used here}} + v.use(); +} + +void lifetimebound_conversion_operator() { + View v; + { + MyObj obj; + LifetimeBoundView lbv = obj; // expected-warning {{object whose reference is captured does not live long enough}} + v = lbv; // Conversion operator is lifetimebound + } // expected-note {{destroyed here}} + v.use(); // expected-note {{later used here}} +} + +void lifetimebound_chained_calls() { + View v; + { + MyObj obj; + v = Identity(Identity(Identity(obj))); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + v.use(); // expected-note {{later used here}} +} + +void lifetimebound_with_pointers() { + MyObj* ptr; + { + MyObj obj; + ptr = GetPointer(obj); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)*ptr; // expected-note {{later used here}} +} + +void lifetimebound_no_error_safe_usage() { + MyObj obj; + View v1 = Identity(obj); // No warning - obj lives long enough + View v2 = Choose(true, v1, Identity(obj)); // No warning - all args are safe + v2.use(); // Safe usage +} + +void lifetimebound_partial_safety(bool cond) { + MyObj safe_obj; + View v = safe_obj; + + if (cond) { + MyObj temp_obj; + v = Choose(true, + safe_obj, + temp_obj); // expected-warning {{object whose reference is captured may not live long enough}} + } // expected-note {{destroyed here}} + v.use(); // expected-note {{later used here}} +} + +// FIXME: Creating reference from lifetimebound call doesn't propagate loans. +const MyObj& GetObject(View v [[clang::lifetimebound]]); +void lifetimebound_return_reference() { + View v; + const MyObj* ptr; + { + MyObj obj; + View temp_v = obj; + const MyObj& ref = GetObject(temp_v); + ptr = &ref; + } + (void)*ptr; +} + +// FIXME: No warning for non gsl::Pointer types. Origin tracking is only supported for pointer types. +struct LifetimeBoundCtor { + LifetimeBoundCtor(); + LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]); +}; +void lifetimebound_ctor() { + LifetimeBoundCtor v; + { + MyObj obj; + v = obj; + } + (void)v; +} \ No newline at end of file diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index bff5378c0a8a9..3821015f07fb1 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -68,6 +68,7 @@ class LifetimeTestRunner { LifetimeSafetyAnalysis &getAnalysis() { return *Analysis; } ASTContext &getASTContext() { return *ASTCtx; } + AnalysisDeclContext &getAnalysisContext() { return *AnalysisCtx; } ProgramPoint getProgramPoint(llvm::StringRef Annotation) { auto It = AnnotationToPointMap.find(Annotation); @@ -106,7 +107,7 @@ class LifetimeTestHelper { std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) { auto *VD = findDecl<VarDecl>(VarName); if (!VD) { - ADD_FAILURE() << "No VarDecl found for '" << VarName << "'"; + ADD_FAILURE() << "Failed to find VarDecl for '" << VarName << "'"; return {}; } std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD); @@ -136,11 +137,20 @@ class LifetimeTestHelper { private: template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) { auto &Ctx = Runner.getASTContext(); - auto Results = match(valueDecl(hasName(Name)).bind("v"), Ctx); + const auto *TargetFunc = Runner.getAnalysisContext().getDecl(); + auto Results = + match(valueDecl(hasName(Name), + hasAncestor(functionDecl(equalsNode(TargetFunc)))) + .bind("v"), + Ctx); if (Results.empty()) { ADD_FAILURE() << "Declaration '" << Name << "' not found in AST."; return nullptr; } + if (Results.size() > 1) { + ADD_FAILURE() << "Multiple declarations found for '" << Name << "'"; + return nullptr; + } return const_cast<DeclT *>(selectFirst<DeclT>("v", Results)); } @@ -208,6 +218,19 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") { ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(), ExpectedLIDs.end()); } + std::sort(ExpectedLoans.begin(), ExpectedLoans.end()); + std::sort(ActualLoans.begin(), ActualLoans.end()); + if (ExpectedLoans != ActualLoans) { + *result_listener << "Expected: "; + for (const auto &LoanID : ExpectedLoans) { + *result_listener << LoanID.Value << ", "; + } + *result_listener << "Actual: "; + for (const auto &LoanID : ActualLoans) { + *result_listener << LoanID.Value << ", "; + } + return false; + } return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans), ActualLoans, result_listener); @@ -921,5 +944,235 @@ TEST_F(LifetimeAnalysisTest, GslPointerConversionOperator) { EXPECT_THAT(Origin("y"), HasLoansTo({"yl"}, "p1")); } +TEST_F(LifetimeAnalysisTest, LifetimeboundSimple) { + SetupTest(R"( + View Identity(View v [[clang::lifetimebound]]); + void target() { + MyObj a, b; + View v1 = a; + POINT(p1); + + View v2 = Identity(v1); + View v3 = Identity(b); + POINT(p2); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); + // The origin of v2 should now contain the loan to 'o' from v1. + EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2")); + EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundMemberFunction) { + SetupTest(R"( + struct [[gsl::Pointer()]] MyView { + MyView(const MyObj& o) {} + MyView pass() [[clang::lifetimebound]] { return *this; } + }; + void target() { + MyObj o; + MyView v1 = o; + POINT(p1); + MyView v2 = v1.pass(); + POINT(p2); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1")); + // The call v1.pass() is bound to 'v1'. The origin of v2 should get the loans + // from v1. + EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p2")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundMultipleArgs) { + SetupTest(R"( + View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]); + void target() { + MyObj o1, o2; + View v1 = o1; + View v2 = o2; + POINT(p1); + + View v3 = Choose(true, v1, v2); + POINT(p2); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1")); + EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p2")); + // v3 should have loans from both v1 and v2, demonstrating the union of + // loans. + EXPECT_THAT(Origin("v3"), HasLoansTo({"o1", "o2"}, "p2")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundMixedArgs) { + SetupTest(R"( + View Choose(bool cond, View a [[clang::lifetimebound]], View b); + void target() { + MyObj o1, o2; + View v1 = o1; + View v2 = o2; + POINT(p1); + + View v3 = Choose(true, v1, v2); + POINT(p2); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1")); + EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p1")); + // v3 should only have loans from v1, as v2 is not lifetimebound. + EXPECT_THAT(Origin("v3"), HasLoansTo({"o1"}, "p2")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundChainOfViews) { + SetupTest(R"( + View Identity(View v [[clang::lifetimebound]]); + View DoubleIdentity(View v [[clang::lifetimebound]]); + + void target() { + MyObj obj; + View v1 = obj; + POINT(p1); + View v2 = DoubleIdentity(Identity(v1)); + POINT(p2); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"obj"}, "p1")); + // v2 should inherit the loan from v1 through the chain of calls. + EXPECT_THAT(Origin("v2"), HasLoansTo({"obj"}, "p2")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundRawPointerParameter) { + SetupTest(R"( + View ViewFromPtr(const MyObj* p [[clang::lifetimebound]]); + MyObj* PtrFromPtr(const MyObj* p [[clang::lifetimebound]]); + MyObj* PtrFromView(View v [[clang::lifetimebound]]); + + void target() { + MyObj a; + View v = ViewFromPtr(&a); + POINT(p1); + + MyObj b; + MyObj* ptr1 = PtrFromPtr(&b); + MyObj* ptr2 = PtrFromPtr(PtrFromPtr(PtrFromPtr(ptr1))); + POINT(p2); + + MyObj c; + View v2 = ViewFromPtr(PtrFromView(c)); + POINT(p3); + } + )"); + EXPECT_THAT(Origin("v"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("ptr1"), HasLoansTo({"b"}, "p2")); + EXPECT_THAT(Origin("ptr2"), HasLoansTo({"b"}, "p2")); + EXPECT_THAT(Origin("v2"), HasLoansTo({"c"}, "p3")); +} + +// FIXME: This can be controversial and may be revisited in the future. +TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefViewParameter) { + SetupTest(R"( + View Identity(const View& v [[clang::lifetimebound]]); + void target() { + MyObj o; + View v1 = o; + View v2 = Identity(v1); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefObjParam) { + SetupTest(R"( + View Identity(const MyObj& o [[clang::lifetimebound]]); + void target() { + MyObj a; + View v1 = Identity(a); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundReturnReference) { + SetupTest(R"( + const MyObj& Identity(View v [[clang::lifetimebound]]); + void target() { + MyObj a; + View v1 = a; + POINT(p1); + + View v2 = Identity(v1); + + const MyObj& b = Identity(v1); + View v3 = Identity(b); + POINT(p2); + + MyObj c; + View v4 = Identity(c); + POINT(p3); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2")); + + // FIXME: Handle reference types. 'v3' should have loan to 'a' instead of 'b'. + EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2")); + + EXPECT_THAT(Origin("v4"), HasLoansTo({"c"}, "p3")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunction) { + SetupTest(R"( + template <typename T> + const T& Identity(T&& v [[clang::lifetimebound]]); + void target() { + MyObj a; + View v1 = Identity(a); + POINT(p1); + + View v2 = Identity(v1); + const View& v3 = Identity(v1); + POINT(p2); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2")); + EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p2")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateClass) { + SetupTest(R"( + template<typename T> + struct [[gsl::Pointer()]] MyTemplateView { + MyTemplateView(const T& o) {} + MyTemplateView pass() [[clang::lifetimebound]] { return *this; } + }; + void target() { + MyObj o; + MyTemplateView<MyObj> v1 = o; + POINT(p1); + MyTemplateView<MyObj> v2 = v1.pass(); + POINT(p2); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1")); + EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p2")); +} + +TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) { + SetupTest(R"( + struct MyOwner { + MyObj o; + operator View() const [[clang::lifetimebound]]; + }; + + void target() { + MyOwner owner; + View v = owner; + POINT(p1); + } + )"); + EXPECT_THAT(Origin("v"), HasLoansTo({"owner"}, "p1")); +} } // anonymous namespace } // namespace clang::lifetimes::internal _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits