https://github.com/kashika0112 updated https://github.com/llvm/llvm-project/pull/196884
>From caf2b8db8a05245b2d085f9f26519cfeec8eeffd Mon Sep 17 00:00:00 2001 From: Kashika Akhouri <[email protected]> Date: Mon, 11 May 2026 06:40:43 +0000 Subject: [PATCH 1/6] Add support for lifetime capture_by --- .../Analyses/LifetimeSafety/FactsGenerator.h | 5 ++ .../LifetimeSafety/FactsGenerator.cpp | 69 ++++++++++++++++++ clang/test/Sema/warn-lifetime-safety.cpp | 73 +++++++++++++++++++ 3 files changed, 147 insertions(+) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 766742e98101a..af545d5e9386c 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -91,6 +91,11 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { void handleMovedArgsInCall(const FunctionDecl *FD, ArrayRef<const Expr *> Args); + // Detects [[clang::lifetime_capture_by(X)]] annotations on a function call to + // create flow facts from captured arguments to the capturer + void handleLifetimeCaptureBy(const FunctionDecl *FD, + ArrayRef<const Expr *> Args); + /// Checks if a call-like expression creates a borrow by passing a value to a /// reference parameter, creating an IssueFact if it does. /// \param IsGslConstruction True if this is a GSL construction where all diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index dc80d2783fc03..2908a9f1dd924 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -864,6 +864,74 @@ void FactsGenerator::handleImplicitObjectFieldUses(const Expr *Call, }); } +void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, + ArrayRef<const Expr *> Args) { + bool isInstance = false; + if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); + Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) { + isInstance = true; + } + auto getArgCaptureBy = [FD, + isInstance](unsigned I) -> LifetimeCaptureByAttr * { + const ParmVarDecl *PVD = nullptr; + // FIXME: Add support for capture_by on function declarations + if (isInstance) { + if (I > 0 && I - 1 < FD->getNumParams()) { + PVD = FD->getParamDecl(I - 1); + } + } else { + if (I < FD->getNumParams()) { + PVD = FD->getParamDecl(I); + } + } + return PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr; + }; + if (Args.empty()) + return; + for (unsigned I = 0; I < Args.size(); ++I) { + const LifetimeCaptureByAttr *Attr = getArgCaptureBy(I); + if (!Attr) + continue; + OriginList *CapturedOriginList = getOriginsList(*Args[I]); + if (!CapturedOriginList) + continue; + if (isGslPointerType(Args[I]->getType())) { + assert(!Args[I]->isGLValue() || CapturedOriginList->getLength() >= 2); + CapturedOriginList = getRValueOrigins(Args[I], CapturedOriginList); + } + if (!CapturedOriginList) + continue; + for (int CapturedByIdx : Attr->params()) { + if (CapturedByIdx == LifetimeCaptureByAttr::Global) { + for (OriginList *L = CapturedOriginList; L != nullptr; + L = L->peelOuterOrigin()) + EscapesInCurrentBlock.push_back(FactMgr.createFact<GlobalEscapeFact>( + L->getOuterOriginID(), nullptr)); + continue; + } + if (CapturedByIdx == LifetimeCaptureByAttr::Unknown || + CapturedByIdx == LifetimeCaptureByAttr::Invalid) + continue; + unsigned CapturedByArgIdx = + (CapturedByIdx == LifetimeCaptureByAttr::This) + ? 0 + : (unsigned)CapturedByIdx + (isInstance ? 1 : 0); + if (CapturedByArgIdx >= Args.size()) + continue; + OriginList *CapturedByOriginList = + getOriginsList(*Args[CapturedByArgIdx]); + if (CapturedByOriginList) { + OriginList *Dest = + (isGslPointerType(Args[CapturedByArgIdx]->getType()) || + isGslOwnerType(Args[CapturedByArgIdx]->getType())) + ? CapturedByOriginList->peelOuterOrigin() + : CapturedByOriginList; + flow(Dest, CapturedOriginList, /*Kill=*/false); + } + } + } +} + void FactsGenerator::handleFunctionCall(const Expr *Call, const FunctionDecl *FD, ArrayRef<const Expr *> Args, @@ -880,6 +948,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, handleDestructiveCall(Call, FD, Args); handleMovedArgsInCall(FD, Args); handleImplicitObjectFieldUses(Call, FD); + handleLifetimeCaptureBy(FD, Args); if (!CallList) return; auto IsArgLifetimeBound = [FD](unsigned I) -> bool { diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 30b450c333fbd..aaef28a85f507 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -3284,3 +3284,76 @@ void assign_non_capturing_to_function_ref(function_ref &r) { } } // namespace GH126600 + +//===----------------------------------------------------------------------===// +// lifetime-capture-by +//===----------------------------------------------------------------------===// + +void setCaptureBy(View& in, View out [[clang::lifetime_capture_by(in)]]); + +void use_after_free_capture_by() { + View res; + { + MyObj a; + setCaptureBy(res, a); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)res; // expected-note {{later used here}} +} + +View use_after_return_capture_by() { + MyObj a; + View res; + setCaptureBy(res, a); // expected-warning {{address of stack memory is returned later}} + return res; // expected-note {{returned here}} + +} + +void transitive_capture() { + View v1, v2; + { + MyObj local; + setCaptureBy(v1, local); // expected-warning {{object whose reference is captured does not live long enough}} + setCaptureBy(v2, v1); + } // expected-note {{destroyed here}} + (void)v2; // expected-note {{later used here}} +} + +struct [[gsl::Pointer]] MyContainer { + View stored; + void set(View s [[clang::lifetime_capture_by(this)]]); +}; + +void member_capture() { + MyContainer c; + { + MyObj local; + c.set(local); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)c.stored; // expected-note {{later used here}} +} + +void captureTwo(View& into, + View a [[clang::lifetime_capture_by(into)]], + View b [[clang::lifetime_capture_by(into)]]); + +void multiple_captures() { + View res; + MyObj val1; + { + MyObj val2; + captureTwo(res, val1, val2); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)res; // expected-note {{later used here}} +} + +void multiple_local_captures() { + View res; + { + MyObj val1; + MyObj val2; + captureTwo(res, val1, val2); // expected-warning 2 {{object whose reference is captured does not live long enough}} + } // expected-note 2 {{destroyed here}} + (void)res; // expected-note 2 {{later used here}} +} + + >From 4311c1f4a8ed347bca632eca058880e72b074fca Mon Sep 17 00:00:00 2001 From: Kashika Akhouri <[email protected]> Date: Thu, 21 May 2026 10:09:28 +0000 Subject: [PATCH 2/6] Modify origin flow logic and add more tests --- .../LifetimeSafety/FactsGenerator.cpp | 65 ++++++++----------- clang/test/Sema/warn-lifetime-safety.cpp | 51 ++++++++++++++- 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 2908a9f1dd924..e126fcd6101a3 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -866,28 +866,27 @@ void FactsGenerator::handleImplicitObjectFieldUses(const Expr *Call, void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, ArrayRef<const Expr *> Args) { - bool isInstance = false; - if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); - Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) { - isInstance = true; - } + if (Args.empty()) + return; + // FIXME: Add support for capture_by on constructors. + if (isa<CXXConstructorDecl>(FD)) + return; + const auto *Method = dyn_cast<CXXMethodDecl>(FD); + bool IsInstance = + Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD); auto getArgCaptureBy = [FD, - isInstance](unsigned I) -> LifetimeCaptureByAttr * { + IsInstance](unsigned I) -> LifetimeCaptureByAttr * { const ParmVarDecl *PVD = nullptr; - // FIXME: Add support for capture_by on function declarations - if (isInstance) { - if (I > 0 && I - 1 < FD->getNumParams()) { + if (IsInstance) { + // FIXME: Add support for capture_by on function declarations + if (I > 0 && I - 1 < FD->getNumParams()) PVD = FD->getParamDecl(I - 1); - } } else { - if (I < FD->getNumParams()) { + if (I < FD->getNumParams()) PVD = FD->getParamDecl(I); - } } return PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr; }; - if (Args.empty()) - return; for (unsigned I = 0; I < Args.size(); ++I) { const LifetimeCaptureByAttr *Attr = getArgCaptureBy(I); if (!Attr) @@ -902,32 +901,24 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, if (!CapturedOriginList) continue; for (int CapturedByIdx : Attr->params()) { - if (CapturedByIdx == LifetimeCaptureByAttr::Global) { - for (OriginList *L = CapturedOriginList; L != nullptr; - L = L->peelOuterOrigin()) - EscapesInCurrentBlock.push_back(FactMgr.createFact<GlobalEscapeFact>( - L->getOuterOriginID(), nullptr)); - continue; - } - if (CapturedByIdx == LifetimeCaptureByAttr::Unknown || + // FIXME: Add support for capturing to Global/unknown. + if (CapturedByIdx == LifetimeCaptureByAttr::Global || + CapturedByIdx == LifetimeCaptureByAttr::Unknown || CapturedByIdx == LifetimeCaptureByAttr::Invalid) continue; - unsigned CapturedByArgIdx = - (CapturedByIdx == LifetimeCaptureByAttr::This) - ? 0 - : (unsigned)CapturedByIdx + (isInstance ? 1 : 0); - if (CapturedByArgIdx >= Args.size()) + ArrayRef<const Expr *> CallArgs = IsInstance ? Args.slice(1) : Args; + const Expr *CapturedByArg = (CapturedByIdx == LifetimeCaptureByAttr::This) + ? Args[0] + : CallArgs[CapturedByIdx]; + assert(CapturedByArg && "Capturer expression must be valid"); + + OriginList *CapturedByOriginList = getOriginsList(*CapturedByArg); + OriginList *Dest = getRValueOrigins(CapturedByArg, CapturedByOriginList); + if (!Dest) continue; - OriginList *CapturedByOriginList = - getOriginsList(*Args[CapturedByArgIdx]); - if (CapturedByOriginList) { - OriginList *Dest = - (isGslPointerType(Args[CapturedByArgIdx]->getType()) || - isGslOwnerType(Args[CapturedByArgIdx]->getType())) - ? CapturedByOriginList->peelOuterOrigin() - : CapturedByOriginList; - flow(Dest, CapturedOriginList, /*Kill=*/false); - } + CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( + Dest->getOuterOriginID(), CapturedOriginList->getOuterOriginID(), + /*KillDest=*/false)); } } } diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index aaef28a85f507..fb581f1d00f1b 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -3289,7 +3289,7 @@ void assign_non_capturing_to_function_ref(function_ref &r) { // lifetime-capture-by //===----------------------------------------------------------------------===// -void setCaptureBy(View& in, View out [[clang::lifetime_capture_by(in)]]); +void setCaptureBy(View& res, View in [[clang::lifetime_capture_by(res)]]); void use_after_free_capture_by() { View res; @@ -3318,6 +3318,40 @@ void transitive_capture() { (void)v2; // expected-note {{later used here}} } +void set1(View& res, const MyObj& in [[clang::lifetime_capture_by(res)]]); + +void test_reference_to_view() { + View v; + { + MyObj local; + set1(v, local); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)v; // expected-note {{later used here}} +} + +// FIXME: Add special handling for multi-level pointers and lvalue expressions which are not DeclRefExpr. +void set2(MyObj** res, const MyObj& in [[clang::lifetime_capture_by(res)]]); + +void test_pointer_to_pointer() { + MyObj *ptr = nullptr; + { + MyObj local; + set2(&ptr, local); + } + (void)ptr; +} + +void set3(MyObj*& res, const MyObj& in [[clang::lifetime_capture_by(res)]]); + +void test_reference_to_pointer() { + MyObj *ptr = nullptr; + { + MyObj local; + set3(ptr, local); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)ptr; // expected-note {{later used here}} +} + struct [[gsl::Pointer]] MyContainer { View stored; void set(View s [[clang::lifetime_capture_by(this)]]); @@ -3332,6 +3366,21 @@ void member_capture() { (void)c.stored; // expected-note {{later used here}} } +// FIXME: Add support for simple containers without annotations. +struct SimpleContainer { + View stored; + void set(View s [[clang::lifetime_capture_by(this)]]); +}; + +void member_capture_simple_container() { + SimpleContainer c; + { + MyObj local; + c.set(local); + } + (void)c.stored; +} + void captureTwo(View& into, View a [[clang::lifetime_capture_by(into)]], View b [[clang::lifetime_capture_by(into)]]); >From 947bb147c418b32ea5995e4eb7894b812033e96d Mon Sep 17 00:00:00 2001 From: Kashika Akhouri <[email protected]> Date: Thu, 21 May 2026 10:28:42 +0000 Subject: [PATCH 3/6] Nit change --- .../clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index af545d5e9386c..f3eadc16473a4 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -91,7 +91,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { void handleMovedArgsInCall(const FunctionDecl *FD, ArrayRef<const Expr *> Args); - // Detects [[clang::lifetime_capture_by(X)]] annotations on a function call to + // Handles [[clang::lifetime_capture_by(X)]] annotations on a function call to // create flow facts from captured arguments to the capturer void handleLifetimeCaptureBy(const FunctionDecl *FD, ArrayRef<const Expr *> Args); >From e01e7507f39788e1d557e8e0fdd18be283e0e942 Mon Sep 17 00:00:00 2001 From: Kashika Akhouri <[email protected]> Date: Fri, 29 May 2026 06:22:07 +0000 Subject: [PATCH 4/6] Add another fix me test --- clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 2 +- clang/test/Sema/warn-lifetime-safety.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 165162ed7a13f..a207e516c545f 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -882,7 +882,7 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, IsInstance](unsigned I) -> LifetimeCaptureByAttr * { const ParmVarDecl *PVD = nullptr; if (IsInstance) { - // FIXME: Add support for capture_by on function declarations + // FIXME: Add support for I == 0 i.e. capture_by on function declarations if (I > 0 && I - 1 < FD->getNumParams()) PVD = FD->getParamDecl(I - 1); } else { diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 51d29b0587835..ecc6018973da3 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -3433,6 +3433,14 @@ void test_pointer_to_pointer() { (void)ptr; } +void test_pointer_to_pointer_2(MyObj **ptr) { + { + MyObj local; + set2(ptr, local); + } + (void)ptr; +} + void set3(MyObj*& res, const MyObj& in [[clang::lifetime_capture_by(res)]]); void test_reference_to_pointer() { >From 7d24be4d1b79469cca1fd58fb6fba94e5c0cd62b Mon Sep 17 00:00:00 2001 From: Kashika Akhouri <[email protected]> Date: Fri, 29 May 2026 06:54:57 +0000 Subject: [PATCH 5/6] Update diagnostics messages --- .../Analysis/LifetimeSafety/FactsGenerator.cpp | 2 +- clang/test/Sema/warn-lifetime-safety.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 5327dacfdfeea..028bcc50f12c4 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -920,7 +920,7 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, CapturedByIdx == LifetimeCaptureByAttr::Unknown || CapturedByIdx == LifetimeCaptureByAttr::Invalid) continue; - ArrayRef<const Expr *> CallArgs = IsInstance ? Args.slice(1) : Args; + ArrayRef<const Expr *> CallArgs = IsInstance ? Args.drop_front() : Args; const Expr *CapturedByArg = (CapturedByIdx == LifetimeCaptureByAttr::This) ? Args[0] : CallArgs[CapturedByIdx]; diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 1676e2c8e4c5c..ffc12f101a914 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -3431,7 +3431,7 @@ void use_after_free_capture_by() { View res; { MyObj a; - setCaptureBy(res, a); // expected-warning {{object whose reference is captured does not live long enough}} + setCaptureBy(res, a); // expected-warning {{local variable 'a' does not live long enough}} } // expected-note {{destroyed here}} (void)res; // expected-note {{later used here}} } @@ -3439,7 +3439,7 @@ void use_after_free_capture_by() { View use_after_return_capture_by() { MyObj a; View res; - setCaptureBy(res, a); // expected-warning {{address of stack memory is returned later}} + setCaptureBy(res, a); // expected-warning {{stack memory associated with local variable 'a' is returned}} return res; // expected-note {{returned here}} } @@ -3448,7 +3448,7 @@ void transitive_capture() { View v1, v2; { MyObj local; - setCaptureBy(v1, local); // expected-warning {{object whose reference is captured does not live long enough}} + setCaptureBy(v1, local); // expected-warning {{local variable 'local' does not live long enough}} setCaptureBy(v2, v1); } // expected-note {{destroyed here}} (void)v2; // expected-note {{later used here}} @@ -3460,7 +3460,7 @@ void test_reference_to_view() { View v; { MyObj local; - set1(v, local); // expected-warning {{object whose reference is captured does not live long enough}} + set1(v, local); // expected-warning {{local variable 'local' does not live long enough}} } // expected-note {{destroyed here}} (void)v; // expected-note {{later used here}} } @@ -3491,7 +3491,7 @@ void test_reference_to_pointer() { MyObj *ptr = nullptr; { MyObj local; - set3(ptr, local); // expected-warning {{object whose reference is captured does not live long enough}} + set3(ptr, local); // expected-warning {{local variable 'local' does not live long enough}} } // expected-note {{destroyed here}} (void)ptr; // expected-note {{later used here}} } @@ -3505,7 +3505,7 @@ void member_capture() { MyContainer c; { MyObj local; - c.set(local); // expected-warning {{object whose reference is captured does not live long enough}} + c.set(local); // expected-warning {{local variable 'local' does not live long enough}} } // expected-note {{destroyed here}} (void)c.stored; // expected-note {{later used here}} } @@ -3534,7 +3534,7 @@ void multiple_captures() { MyObj val1; { MyObj val2; - captureTwo(res, val1, val2); // expected-warning {{object whose reference is captured does not live long enough}} + captureTwo(res, val1, val2); // expected-warning {{local variable 'val2' does not live long enough}} } // expected-note {{destroyed here}} (void)res; // expected-note {{later used here}} } @@ -3544,7 +3544,7 @@ void multiple_local_captures() { { MyObj val1; MyObj val2; - captureTwo(res, val1, val2); // expected-warning 2 {{object whose reference is captured does not live long enough}} + captureTwo(res, val1, val2); // expected-warning {{local variable 'val1' does not live long enough}} // expected-warning {{local variable 'val2' does not live long enough}} } // expected-note 2 {{destroyed here}} (void)res; // expected-note 2 {{later used here}} } >From 4c62d8d27cb45facf15472f92441f59e14137a69 Mon Sep 17 00:00:00 2001 From: Kashika Akhouri <[email protected]> Date: Fri, 29 May 2026 13:01:16 +0000 Subject: [PATCH 6/6] Adding design comments and more tests --- .../LifetimeSafety/FactsGenerator.cpp | 27 ++++++++-------- clang/test/Sema/warn-lifetime-safety.cpp | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 028bcc50f12c4..17617fc92a7e8 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -908,28 +908,29 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, OriginList *CapturedOriginList = getOriginsList(*Args[I]); if (!CapturedOriginList) continue; - if (isGslPointerType(Args[I]->getType())) { - assert(!Args[I]->isGLValue() || CapturedOriginList->getLength() >= 2); - CapturedOriginList = getRValueOrigins(Args[I], CapturedOriginList); - } if (!CapturedOriginList) continue; - for (int CapturedByIdx : Attr->params()) { + for (int CapturingArgIdx : Attr->params()) { // FIXME: Add support for capturing to Global/unknown. - if (CapturedByIdx == LifetimeCaptureByAttr::Global || - CapturedByIdx == LifetimeCaptureByAttr::Unknown || - CapturedByIdx == LifetimeCaptureByAttr::Invalid) + if (CapturingArgIdx == LifetimeCaptureByAttr::Global || + CapturingArgIdx == LifetimeCaptureByAttr::Unknown || + CapturingArgIdx == LifetimeCaptureByAttr::Invalid) continue; ArrayRef<const Expr *> CallArgs = IsInstance ? Args.drop_front() : Args; - const Expr *CapturedByArg = (CapturedByIdx == LifetimeCaptureByAttr::This) - ? Args[0] - : CallArgs[CapturedByIdx]; + const Expr *CapturedByArg = + (CapturingArgIdx == LifetimeCaptureByAttr::This) + ? Args[0] + : CallArgs[CapturingArgIdx]; assert(CapturedByArg && "Capturer expression must be valid"); - OriginList *CapturedByOriginList = getOriginsList(*CapturedByArg); - OriginList *Dest = getRValueOrigins(CapturedByArg, CapturedByOriginList); + OriginList *CapturingOriginList = getOriginsList(*CapturedByArg); + OriginList *Dest = getRValueOrigins(CapturedByArg, CapturingOriginList); if (!Dest) continue; + // KillDest=false because we cannot know if previous captures are being + // replaced or accumulated. Multiple successive captures into the same + // destination must all be tracked, so captured lifetimes are always + // merged. CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( Dest->getOuterOriginID(), CapturedOriginList->getOuterOriginID(), /*KillDest=*/false)); diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index ffc12f101a914..8209ce7dda73b 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -3548,3 +3548,35 @@ void multiple_local_captures() { } // expected-note 2 {{destroyed here}} (void)res; // expected-note 2 {{later used here}} } + +void captureIntoTwo(View& v1, View& v2, + View s [[clang::lifetime_capture_by(v1, v2)]]); + +void captured_by_multiple_params() { + View v1, v2; + { + MyObj local; + captureIntoTwo(v1, v2, local); // expected-warning {{local variable 'local' does not live long enough}} + } // expected-note {{destroyed here}} + (void)v1; // expected-note {{later used here}} +} + +void captured_by_multiple_params_2() { + View v1, v2; + { + MyObj local; + captureIntoTwo(v1, v2, local); // expected-warning {{local variable 'local' does not live long enough}} + } // expected-note {{destroyed here}} + (void)v2; // expected-note {{later used here}} +} + +void capturing_multiple_locals() { + View v; + { + MyObj local1; + setCaptureBy(v, local1); // expected-warning{{local variable 'local1' does not live long enough}} + MyObj local2; + setCaptureBy(v, local2); // expected-warning{{local variable 'local2' does not live long enough}} + } // expected-note 2 {{destroyed here}} + (void)v; // expected-note 2 {{later used here}} +} \ No newline at end of file _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
