https://github.com/AdityaOP007 created 
https://github.com/llvm/llvm-project/pull/212264

# Fix `lifetime_capture_by` indexing for non-static member functions (#212231)
#212231 
## Summary

This PR fixes an internal compiler crash in Clang's lifetime safety analysis 
when `[[clang::lifetime_capture_by(...)]]` is used on parameters of non-static 
member functions.

The root cause was an inconsistency between the parameter indexing generated 
during semantic analysis and the argument indexing used by 
`FactsGenerator::handleLifetimeCaptureBy()`. While Sema assigns indices 
relative to the complete argument list (including the implicit `this` object), 
the lifetime analysis removed the implicit object before indexing, resulting in 
incorrect parameter lookups and an out-of-bounds access for valid C++ code.

This change aligns the lifetime analysis with Sema's indexing model, 
eliminating the crash while preserving the existing behavior for all supported 
function types.

## Changes

* Replaced the instance-method check with `hasImplicitObjectParameter(FD)` to 
correctly identify functions that have an implicit object parameter.
* Removed the `Args.drop_front()` logic and used the complete argument list 
consistently for lifetime capture lookups.
* Updated attribute processing to iterate over all `LifetimeCaptureByAttr` 
annotations associated with a parameter instead of only the first one.
* Added defensive bounds validation to prevent invalid indexing during error 
recovery without affecting valid code paths.
* Kept the implementation localized to lifetime safety analysis without 
changing AST structures, diagnostics, or ABI.

## Testing

Added regression coverage for:

* Original issue reproducer
* Non-static member functions
* `const` member functions
* Static member functions
* Template member functions
* Overloaded member functions
* Multiple `lifetime_capture_by` annotations
* Reordered parameter declarations
* Namespace and free functions

These tests ensure the original crash is resolved while maintaining consistent 
behavior across supported function categories.

## Expected Result

### Before

```cpp
struct S {
  void cap(int *p [[clang::lifetime_capture_by(c)]], int *&c);
};

void use() {
  S s;
  int i;
  int *c;
  s.cap(&i, c);
}
```

Compiling with:

```bash
clang++ -Wlifetime-safety test.cpp
```

resulted in an internal compiler assertion similar to:

```text
Assertion `Index < Length && "Invalid index!"' failed.
```

### After

The same program compiles successfully under `-Wlifetime-safety` without 
triggering an internal compiler crash. Lifetime capture analysis now correctly 
follows the parameter indexing established during semantic analysis, and all 
existing regression tests continue to pass.

Fixes #212231.


>From 1eee2fd8d6653fe62adc50176136c759e6928814 Mon Sep 17 00:00:00 2001
From: Aditya Prakash Jha <[email protected]>
Date: Mon, 27 Jul 2026 20:14:59 +0530
Subject: [PATCH] clang: fix lifetime_capture_by indexing for member functions

---
 .../LifetimeSafety/FactsGenerator.cpp         | 100 ++++++++++--------
 clang/test/Sema/LifetimeSafety/safety.cpp     |  37 +++++++
 2 files changed, 92 insertions(+), 45 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ac6267dabf48e..65aaa1ed02cad 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -1019,54 +1019,64 @@ void FactsGenerator::handleLifetimeCaptureBy(const 
FunctionDecl *FD,
   // 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 * {
-    const ParmVarDecl *PVD = nullptr;
-    if (IsInstance) {
-      // 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 {
-      if (I < FD->getNumParams())
-        PVD = FD->getParamDecl(I);
+  bool HasImplicitThisParam = hasImplicitObjectParameter(FD);
+  auto processArgAttrs = [&](unsigned I, auto Callback) {
+    if (HasImplicitThisParam) {
+      if (I == 0) {
+        if (TypeSourceInfo *TSI = FD->getTypeSourceInfo()) {
+          AttributedTypeLoc ATL;
+          for (TypeLoc TL = TSI->getTypeLoc();
+               (ATL = TL.getAsAdjusted<AttributedTypeLoc>());
+               TL = ATL.getModifiedLoc())
+            if (auto *A = ATL.getAttrAs<LifetimeCaptureByAttr>())
+              Callback(A);
+        }
+        return;
+      }
+      --I;
     }
-    return PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr;
+    if (I < FD->getNumParams())
+      for (const auto *A :
+           FD->getParamDecl(I)->specific_attrs<LifetimeCaptureByAttr>())
+        Callback(A);
   };
   for (unsigned I = 0; I < Args.size(); ++I) {
-    const LifetimeCaptureByAttr *Attr = getArgCaptureBy(I);
-    if (!Attr)
-      continue;
-    OriginList *CapturedOriginList = getOriginsList(*Args[I]);
-    if (!CapturedOriginList)
-      continue;
-    for (int CapturingArgIdx : Attr->params()) {
-      // FIXME: Add support for capturing to Global/unknown.
-      if (CapturingArgIdx == LifetimeCaptureByAttr::Global ||
-          CapturingArgIdx == LifetimeCaptureByAttr::Unknown ||
-          CapturingArgIdx == LifetimeCaptureByAttr::Invalid)
-        continue;
-      ArrayRef<const Expr *> CallArgs = IsInstance ? Args.drop_front() : Args;
-      const Expr *CapturedByArg =
-          (CapturingArgIdx == LifetimeCaptureByAttr::This)
-              ? Args[0]
-              : CallArgs[CapturingArgIdx];
-      assert(CapturedByArg && "Capturer expression must be valid");
-
-      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));
-    }
+    OriginList *CapturedOriginList = nullptr;
+    processArgAttrs(I, [&](const LifetimeCaptureByAttr *Attr) {
+      if (!Attr)
+        return;
+      if (!CapturedOriginList) {
+        CapturedOriginList = getOriginsList(*Args[I]);
+        if (!CapturedOriginList)
+          return;
+      }
+      for (int CapturingArgIdx : Attr->params()) {
+        // FIXME: Add support for capturing to Global/unknown.
+        if (CapturingArgIdx == LifetimeCaptureByAttr::Global ||
+            CapturingArgIdx == LifetimeCaptureByAttr::Unknown ||
+            CapturingArgIdx == LifetimeCaptureByAttr::Invalid)
+          continue;
+        if (CapturingArgIdx < 0 ||
+            CapturingArgIdx >= static_cast<int>(Args.size()))
+          continue;
+
+        const Expr *CapturedByArg = Args[CapturingArgIdx];
+        assert(CapturedByArg && "Capturer expression must be valid");
+
+        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/LifetimeSafety/safety.cpp 
b/clang/test/Sema/LifetimeSafety/safety.cpp
index f3b042cce74b2..1f2673348780c 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -4272,3 +4272,40 @@ void test_cyclic_cfg(int n) {
   }         // expected-note {{local variable 'a' is destroyed here}}
   v.use();  // expected-note {{later used here}}
 }
+
+namespace member_capture_by_param {
+struct S {
+  void cap(int *p [[clang::lifetime_capture_by(c)]], int *&c);
+  void cap_const(int *p [[clang::lifetime_capture_by(c)]], int *&c) const;
+  static void cap_static(int *p [[clang::lifetime_capture_by(c)]], int *&c);
+
+  template <typename T>
+  void cap_template(T *p [[clang::lifetime_capture_by(c)]], T *&c);
+
+  void cap_overload(int *p [[clang::lifetime_capture_by(c)]], int *&c);
+  void cap_overload(double *p [[clang::lifetime_capture_by(c)]], double *&c);
+
+  void cap_multi(int *p [[clang::lifetime_capture_by(c1, c2)]], int *&c1, int 
*&c2);
+  void cap_reordered(int *&c, int *p [[clang::lifetime_capture_by(c)]]);
+};
+
+namespace ns {
+  void cap_ns(int *p [[clang::lifetime_capture_by(c)]], int *&c);
+}
+
+void test_member_capture_by() {
+  S s;
+  int i;
+  int *c;
+  s.cap(&i, c);
+  s.cap_const(&i, c);
+  S::cap_static(&i, c);
+  s.cap_template(&i, c);
+  s.cap_overload(&i, c);
+  int *c2;
+  s.cap_multi(&i, c, c2);
+  s.cap_reordered(c, &i);
+  ns::cap_ns(&i, c);
+}
+} // namespace member_capture_by_param
+

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to