https://github.com/NeKon69 updated 
https://github.com/llvm/llvm-project/pull/203270

>From 090873f16b4aa1cf87f5ab6205e16a0acf536161 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Thu, 11 Jun 2026 16:21:03 +0300
Subject: [PATCH 01/11] [LifeitimeSafety] Support C Language in LifetimeSafety

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |   1 +
 .../LifetimeSafety/FactsGenerator.cpp         |  24 ++-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |   3 +-
 clang/test/Sema/warn-lifetime-safety-c.c      | 152 ++++++++++++++++++
 4 files changed, 178 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/Sema/warn-lifetime-safety-c.c

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 2c961bd305fac..6e7e199a61b80 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -160,6 +160,7 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   // exempting it from the check.
   llvm::DenseMap<const Expr *, UseFact *> UseFacts;
   const CFGBlock *CurrentBlock;
+  bool IsCMode = false;
 };
 
 } // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 9fbfaf8ae606b..c9fa4abedd518 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -106,6 +106,8 @@ void FactsGenerator::run() {
   llvm::TimeTraceScope TimeProfile("FactGenerator");
   const CFG &Cfg = *AC.getCFG();
   llvm::SmallVector<Fact *> PlaceholderLoanFacts = issuePlaceholderLoans();
+  IsCMode = !AC.getASTContext().getLangOpts().CPlusPlus &&
+            !AC.getASTContext().getLangOpts().ObjC;
   // Iterate through the CFG blocks in reverse post-order to ensure that
   // initializations and destructions are processed in the correct sequence.
   for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
@@ -324,6 +326,10 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
       flow(Dest, Src, /*Kill=*/true);
     return;
   case CK_ArrayToPointerDecay:
+    // va_arg(ap, array_type) is UB and does not provide addressable array
+    // storage to model.
+    if (isa<VAArgExpr>(SubExpr))
+      return;
     assert(Src && "Array expression should have origins as it is GL value");
     CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
         Dest->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true));
@@ -347,6 +353,12 @@ void FactsGenerator::VisitUnaryOperator(const 
UnaryOperator *UO) {
   switch (UO->getOpcode()) {
   case UO_AddrOf: {
     const Expr *SubExpr = UO->getSubExpr();
+    // In C, function addresses do not need lifetime tracking. Also skip
+    // address-of on void expressions: GNU C permits them, but void itself has
+    // no origins to track.
+    if (IsCMode && (SubExpr->getType()->isFunctionType() ||
+                    SubExpr->getType()->isVoidType()))
+      return;
     // The origin of an address-of expression (e.g., &x) is the origin of
     // its sub-expression (x). This fact will cause the dataflow analysis
     // to propagate any loans held by the sub-expression's origin to the
@@ -392,6 +404,7 @@ void FactsGenerator::handleAssignment(const Expr 
*TargetExpr,
   }
   if (!LHSList)
     return;
+
   OriginList *RHSList = getOriginsList(*RHSExpr);
   // For operator= with reference parameters (e.g.,
   // `View& operator=(const View&)`), the RHS argument stays an lvalue,
@@ -435,7 +448,12 @@ void FactsGenerator::handleAssignment(const Expr 
*TargetExpr,
   // Kill the old loans of the destination origin and flow the new loans
   // from the source origin.
   flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
-  killAndFlowOrigin(*TargetExpr, *LHSExpr);
+
+  // In C, assignment expressions are not GLValues, so the assignment result 
has
+  // the assigned value origins, not the LHS storage origin.
+  if (IsCMode)
+    LHSList = getRValueOrigins(LHSExpr, LHSList);
+  flow(getOriginsList(*TargetExpr), LHSList, /*Kill=*/true);
 }
 
 void FactsGenerator::handlePointerArithmetic(const BinaryOperator *BO) {
@@ -622,6 +640,10 @@ void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) 
{
 }
 
 void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
+  // Some C subscripts do not refer to addressable storage with origins, such 
as
+  // GNU void-pointer subscripts and vector element extraction from rvalues.
+  if (IsCMode && !ASE->isGLValue())
+    return;
   assert(ASE->isGLValue() && "Array subscript should be a GL value");
   OriginList *Dst = getOriginsList(*ASE);
   assert(Dst && "Array subscript should have origins as it is a GL value");
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 207ce373339f3..1a3319f3e8726 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3164,7 +3164,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
 
   // TODO: Enable lifetime safety analysis for other languages once it is
   // stable.
-  if (EnableLifetimeSafetyAnalysis && S.getLangOpts().CPlusPlus) {
+  if (EnableLifetimeSafetyAnalysis &&
+      (S.getLangOpts().CPlusPlus || !S.getLangOpts().ObjC)) {
     if (AC.getCFG()) {
       lifetimes::LifetimeSafetySemaHelperImpl LifetimeSafetySemaHelper(S);
       lifetimes::runLifetimeSafetyAnalysis(AC, &LifetimeSafetySemaHelper,
diff --git a/clang/test/Sema/warn-lifetime-safety-c.c 
b/clang/test/Sema/warn-lifetime-safety-c.c
new file mode 100644
index 0000000000000..9c809d4351bb5
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-c.c
@@ -0,0 +1,152 @@
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify %s
+
+int *identity(int *p __attribute__((lifetimebound))) { return p; }
+
+struct PointerField {
+  int *ptr;
+};
+
+struct IntField {
+  int field;
+};
+
+void simple_case(void) {
+  int *p;
+  {
+    int i;
+    p = &i; // expected-warning {{local variable 'i' does not live long 
enough}}
+  }         // expected-note {{destroyed here}}
+  (void)*p; // expected-note {{later used here}}
+}
+
+void chained_assignment(void) {
+  int *p, *q, *r;
+  {
+    int i;
+    p = q = r = &i; // expected-warning {{local variable 'i' does not live 
long enough}}
+  }                 // expected-note {{destroyed here}}
+  (void)*p;         // expected-note {{later used here}}
+}
+
+void conditional_branch(int cond) {
+  int safe;
+  int *p = &safe;
+  if (cond) {
+    int i;
+    p = &i; // expected-warning {{local variable 'i' does not live long 
enough}}
+  }         // expected-note {{destroyed here}}
+  (void)*p; // expected-note {{later used here}}
+}
+
+void loop_with_break(int cond) {
+  int safe;
+  int *p = &safe;
+  for (int n = 0; n != 10; ++n) {
+    if (cond) {
+      int i;
+      p = &i; // expected-warning {{local variable 'i' does not live long 
enough}}
+      break;  // expected-note {{destroyed here}}
+    }
+  }
+  (void)*p; // expected-note {{later used here}}
+}
+
+int *return_stack_address(void) {
+  int i;
+  int *p = &i; // expected-warning {{stack memory associated with local 
variable 'i' is returned}}
+  return p;    // expected-note {{returned here}}
+}
+
+void lifetimebound_call(void) {
+  int *p;
+  {
+    int i;
+    p = identity(&i); // expected-warning {{local variable 'i' does not live 
long enough}}
+  }                   // expected-note {{destroyed here}}
+  (void)*p;           // expected-note {{later used here}}
+}
+
+void struct_pointer_field(void) {
+  int *p;
+  {
+    int i;
+    struct PointerField holder;
+    // FIXME: Track origins stored in struct pointer fields.
+    holder.ptr = &i;
+    p = holder.ptr;
+  }
+  (void)*p;
+}
+
+void struct_address_of_field(void) {
+  int *p;
+  {
+    struct IntField holder;
+    p = &holder.field; // expected-warning {{local variable 'holder' does not 
live long enough}}
+  }                    // expected-note {{destroyed here}}
+  (void)*p;            // expected-note {{later used here}}
+}
+
+void conditional_operator_lifetimebound(int cond) {
+  int *p;
+  {
+    int a, b;
+    p = identity(cond ? &a    // expected-warning {{local variable 'a' does 
not live long enough}}
+                      : &b);  // expected-warning {{local variable 'b' does 
not live long enough}}
+  }                           // expected-note 2 {{destroyed here}}
+  (void)*p;                   // expected-note 2 {{later used here}}
+}
+
+union IntOrPtr {
+  int i;
+  int *p;
+};
+
+void union_member(void) {
+  int *p;
+  {
+    union IntOrPtr u;
+    p = &u.i; // expected-warning {{local variable 'u' does not live long 
enough}}
+  }           // expected-note {{destroyed here}}
+  (void)*p;   // expected-note {{later used here}}
+}
+
+struct AnonymousUnion {
+  union {
+    int i;
+    float f;
+  };
+};
+
+void anonymous_union_member(void) {
+  int *p;
+  {
+    struct AnonymousUnion u;
+    p = &u.i; // expected-warning {{local variable 'u' does not live long 
enough}}
+  }           // expected-note {{destroyed here}}
+  (void)*p;   // expected-note {{later used here}}
+}
+
+void function_address_regression(void) {
+  extern void function_address_target(void);
+  char *p = (char *)&function_address_target;
+  (void)p;
+}
+
+int void_pointer_subscript_regression(void *bytes) {
+  return &bytes[0] == &bytes[1];
+}
+
+typedef __attribute__((vector_size(16))) int v4i32;
+v4i32 (*vector_factory)(int);
+
+int vector_subscript_regression(void) {
+  return (*vector_factory)(0)[0];
+}
+
+void va_arg_array_regression(int n, ...) {
+  __builtin_va_list ap;
+  __builtin_va_start(ap, n);
+  int *p = __builtin_va_arg(ap, int[4]); // expected-warning {{second argument 
to 'va_arg' is of array type 'int[4]'}}
+  (void)p;
+}

>From 40333b19971775d51de9fe20f85750cf7d73f41d Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Thu, 11 Jun 2026 20:10:49 +0300
Subject: [PATCH 02/11] apply a few suggestions

---
 .../Analyses/LifetimeSafety/FactsGenerator.h       |  4 +++-
 .../lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 14 ++++++--------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 6e7e199a61b80..a021c09dcd34b 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -29,7 +29,9 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
 
 public:
   FactsGenerator(FactManager &FactMgr, AnalysisDeclContext &AC)
-      : FactMgr(FactMgr), AC(AC) {}
+      : FactMgr(FactMgr), AC(AC),
+        IsCMode(!AC.getASTContext().getLangOpts().CPlusPlus &&
+                !AC.getASTContext().getLangOpts().ObjC) {}
 
   void run();
 
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index c9fa4abedd518..5b2847539e147 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -106,8 +106,6 @@ void FactsGenerator::run() {
   llvm::TimeTraceScope TimeProfile("FactGenerator");
   const CFG &Cfg = *AC.getCFG();
   llvm::SmallVector<Fact *> PlaceholderLoanFacts = issuePlaceholderLoans();
-  IsCMode = !AC.getASTContext().getLangOpts().CPlusPlus &&
-            !AC.getASTContext().getLangOpts().ObjC;
   // Iterate through the CFG blocks in reverse post-order to ensure that
   // initializations and destructions are processed in the correct sequence.
   for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
@@ -353,11 +351,12 @@ void FactsGenerator::VisitUnaryOperator(const 
UnaryOperator *UO) {
   switch (UO->getOpcode()) {
   case UO_AddrOf: {
     const Expr *SubExpr = UO->getSubExpr();
-    // In C, function addresses do not need lifetime tracking. Also skip
-    // address-of on void expressions: GNU C permits them, but void itself has
-    // no origins to track.
-    if (IsCMode && (SubExpr->getType()->isFunctionType() ||
-                    SubExpr->getType()->isVoidType()))
+    // Function addresses do not need lifetime tracking.
+    if (SubExpr->getType()->isFunctionType())
+      return;
+    // Skip address-of on void expressions: GNU C permits them, but void itself
+    // has no origins to track.
+    if (IsCMode && SubExpr->getType()->isVoidType())
       return;
     // The origin of an address-of expression (e.g., &x) is the origin of
     // its sub-expression (x). This fact will cause the dataflow analysis
@@ -404,7 +403,6 @@ void FactsGenerator::handleAssignment(const Expr 
*TargetExpr,
   }
   if (!LHSList)
     return;
-
   OriginList *RHSList = getOriginsList(*RHSExpr);
   // For operator= with reference parameters (e.g.,
   // `View& operator=(const View&)`), the RHS argument stays an lvalue,

>From 2c23110dcb4577c0938802b088f8602b911d60f0 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Thu, 11 Jun 2026 20:58:59 +0300
Subject: [PATCH 03/11] add a few other edge cases

---
 clang/test/Sema/warn-lifetime-safety-c.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/clang/test/Sema/warn-lifetime-safety-c.c 
b/clang/test/Sema/warn-lifetime-safety-c.c
index 9c809d4351bb5..0bc07a28d1674 100644
--- a/clang/test/Sema/warn-lifetime-safety-c.c
+++ b/clang/test/Sema/warn-lifetime-safety-c.c
@@ -150,3 +150,17 @@ void va_arg_array_regression(int n, ...) {
   int *p = __builtin_va_arg(ap, int[4]); // expected-warning {{second argument 
to 'va_arg' is of array type 'int[4]'}}
   (void)p;
 }
+
+// FIXME: We miss the origins of void* after dereference, so we miss to warn 
here.
+void *void_pointer_dereference(void) {
+  int value;
+  void *bytes = &value;
+  return &*bytes;
+}
+
+// FIXME: Atomics are not modeled yet.
+int *atomic_pointer_declref(void) {
+  int value;
+  _Atomic(int *) p = &value;
+  return p;
+}

>From df62a05de23a949340f689777fbabad3f2ee6096 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 12 Jun 2026 14:47:40 +0300
Subject: [PATCH 04/11] move test to the new folder

---
 .../Sema/{warn-lifetime-safety-c.c => LifetimeSafety/safety-c.c}  | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename clang/test/Sema/{warn-lifetime-safety-c.c => LifetimeSafety/safety-c.c} 
(100%)

diff --git a/clang/test/Sema/warn-lifetime-safety-c.c 
b/clang/test/Sema/LifetimeSafety/safety-c.c
similarity index 100%
rename from clang/test/Sema/warn-lifetime-safety-c.c
rename to clang/test/Sema/LifetimeSafety/safety-c.c

>From dfd3385634f159502dc86ea6096d0df7375b6e93 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 12 Jun 2026 18:01:12 +0300
Subject: [PATCH 05/11] refactor

---
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 2 ++
 clang/lib/Sema/AnalysisBasedWarnings.cpp             | 6 ++----
 clang/lib/Sema/SemaLifetimeSafety.h                  | 3 +++
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 5b2847539e147..ebe99ff0dd4b9 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -358,6 +358,8 @@ void FactsGenerator::VisitUnaryOperator(const UnaryOperator 
*UO) {
     // has no origins to track.
     if (IsCMode && SubExpr->getType()->isVoidType())
       return;
+    assert(!SubExpr->getType()->isVoidType() &&
+           "Taking address of void is not valid in C++");
     // The origin of an address-of expression (e.g., &x) is the origin of
     // its sub-expression (x). This fact will cause the dataflow analysis
     // to propagate any loans held by the sub-expression's origin to the
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 1a3319f3e8726..f3d8c0a675426 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2999,8 +2999,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
     }
   }
 
-  if (S.getLangOpts().CPlusPlus &&
-      S.getLangOpts().EnableLifetimeSafetyTUAnalysis)
+  if (S.getLangOpts().EnableLifetimeSafetyTUAnalysis && !S.getLangOpts().ObjC)
     LifetimeSafetyTUAnalysis(S, TU, LSStats);
 }
 
@@ -3164,8 +3163,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
 
   // TODO: Enable lifetime safety analysis for other languages once it is
   // stable.
-  if (EnableLifetimeSafetyAnalysis &&
-      (S.getLangOpts().CPlusPlus || !S.getLangOpts().ObjC)) {
+  if (EnableLifetimeSafetyAnalysis) {
     if (AC.getCFG()) {
       lifetimes::LifetimeSafetySemaHelperImpl LifetimeSafetySemaHelper(S);
       lifetimes::runLifetimeSafetyAnalysis(AC, &LifetimeSafetySemaHelper,
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index 6da4953dea56d..204e5c8d388cb 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -27,6 +27,9 @@ namespace clang::lifetimes {
 inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) {
   if (S.getLangOpts().DebugRunLifetimeSafety)
     return true;
+  // TODO: Enable ObjectiveC later when we know it's stable enough.
+  if (S.getLangOpts().ObjC)
+    return false;
   DiagnosticsEngine &Diags = S.getDiagnostics();
   constexpr unsigned DiagIDs[] = {
       diag::warn_lifetime_safety_use_after_scope,

>From 83d366d122d6aa942510331bb2a36622c7031143 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 12 Jun 2026 19:45:25 +0300
Subject: [PATCH 06/11] refactor, enable standard attribute spelling for C23,
 add test

---
 clang/include/clang/Basic/Attr.td         | 2 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp  | 2 +-
 clang/lib/Sema/SemaLifetimeSafety.h       | 3 +++
 clang/test/Sema/LifetimeSafety/safety-c.c | 7 +++++++
 4 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index a1e02e1478df1..dbf7d2276f84f 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2129,7 +2129,7 @@ def ExplicitInit : InheritableAttr {
 }
 
 def LifetimeBound : DeclOrTypeAttr {
-  let Spellings = [Clang<"lifetimebound", 0>];
+  let Spellings = [Clang<"lifetimebound", 1>];
   let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
   let Documentation = [LifetimeBoundDocs];
   let SimpleHandler = 1;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index f3d8c0a675426..02d22e4f7b4f7 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2999,7 +2999,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
     }
   }
 
-  if (S.getLangOpts().EnableLifetimeSafetyTUAnalysis && !S.getLangOpts().ObjC)
+  if (lifetimes::IsLifetimeSafetyEnabled(S, TU))
     LifetimeSafetyTUAnalysis(S, TU, LSStats);
 }
 
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index 204e5c8d388cb..0b3a0875f49a2 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -30,6 +30,9 @@ inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) {
   // TODO: Enable ObjectiveC later when we know it's stable enough.
   if (S.getLangOpts().ObjC)
     return false;
+  if (isa<TranslationUnitDecl>(D) &&
+      S.getLangOpts().EnableLifetimeSafetyTUAnalysis)
+    return true;
   DiagnosticsEngine &Diags = S.getDiagnostics();
   constexpr unsigned DiagIDs[] = {
       diag::warn_lifetime_safety_use_after_scope,
diff --git a/clang/test/Sema/LifetimeSafety/safety-c.c 
b/clang/test/Sema/LifetimeSafety/safety-c.c
index 0bc07a28d1674..c890545ba8627 100644
--- a/clang/test/Sema/LifetimeSafety/safety-c.c
+++ b/clang/test/Sema/LifetimeSafety/safety-c.c
@@ -151,6 +151,13 @@ void va_arg_array_regression(int n, ...) {
   (void)p;
 }
 
+void va_arg_function_regression(int n, ...) {
+  __builtin_va_list ap;
+  __builtin_va_start(ap, n);
+  int (*p)(void) = __builtin_va_arg(ap, int(void)); // expected-error {{second 
argument to 'va_arg' is of non-POD type 'int (void)'}}
+  (void)p;
+}
+
 // FIXME: We miss the origins of void* after dereference, so we miss to warn 
here.
 void *void_pointer_dereference(void) {
   int value;

>From 07c2329edaa0104f281b330e9534f18283fa8098 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 12 Jun 2026 20:16:56 +0300
Subject: [PATCH 07/11] fix

---
 clang/lib/Sema/SemaLifetimeSafety.h | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index 0b3a0875f49a2..59e315450e91f 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -30,9 +30,8 @@ inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) {
   // TODO: Enable ObjectiveC later when we know it's stable enough.
   if (S.getLangOpts().ObjC)
     return false;
-  if (isa<TranslationUnitDecl>(D) &&
-      S.getLangOpts().EnableLifetimeSafetyTUAnalysis)
-    return true;
+  if (isa<TranslationUnitDecl>(D))
+    return S.getLangOpts().EnableLifetimeSafetyTUAnalysis;
   DiagnosticsEngine &Diags = S.getDiagnostics();
   constexpr unsigned DiagIDs[] = {
       diag::warn_lifetime_safety_use_after_scope,

>From ebc4a9ac2c6f7c8a491604c299489932452eb1d2 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 12 Jun 2026 20:48:40 +0300
Subject: [PATCH 08/11] add missing note..

---
 clang/test/Sema/LifetimeSafety/safety-c.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/test/Sema/LifetimeSafety/safety-c.c 
b/clang/test/Sema/LifetimeSafety/safety-c.c
index c890545ba8627..c1da3a1118ea1 100644
--- a/clang/test/Sema/LifetimeSafety/safety-c.c
+++ b/clang/test/Sema/LifetimeSafety/safety-c.c
@@ -61,7 +61,8 @@ void lifetimebound_call(void) {
   int *p;
   {
     int i;
-    p = identity(&i); // expected-warning {{local variable 'i' does not live 
long enough}}
+    p = identity(&i); // expected-warning {{local variable 'i' does not live 
long enough}} \
+                      // expected-note {{expression aliases the storage of 
local variable 'i'}}
   }                   // expected-note {{destroyed here}}
   (void)*p;           // expected-note {{later used here}}
 }

>From 203a17731d1e769de964291729a7a64c2717582e Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Sat, 13 Jun 2026 12:17:03 +0300
Subject: [PATCH 09/11] update lifetime_capture_by add semantic tests for C23
 spelling

---
 clang/include/clang/Basic/Attr.td          |  2 +-
 clang/test/Sema/attr-lifetime-capture-by.c | 28 ++++++++++++++++++++++
 clang/test/Sema/attr-lifetimebound.c       | 21 ++++++++++++++++
 3 files changed, 50 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/Sema/attr-lifetime-capture-by.c
 create mode 100644 clang/test/Sema/attr-lifetimebound.c

diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index dbf7d2276f84f..ae446553d9742 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2136,7 +2136,7 @@ def LifetimeBound : DeclOrTypeAttr {
 }
 
 def LifetimeCaptureBy : DeclOrTypeAttr {
-  let Spellings = [Clang<"lifetime_capture_by", 0>];
+  let Spellings = [Clang<"lifetime_capture_by", 1>];
   let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
   let Args = [VariadicParamOrParamIdxArgument<"Params">];
   let Documentation = [LifetimeCaptureByDocs];
diff --git a/clang/test/Sema/attr-lifetime-capture-by.c 
b/clang/test/Sema/attr-lifetime-capture-by.c
new file mode 100644
index 0000000000000..f847a5bbca7c5
--- /dev/null
+++ b/clang/test/Sema/attr-lifetime-capture-by.c
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -std=c2x -verify %s
+
+struct S {
+  int *x;
+};
+
+void capture(int *x [[clang::lifetime_capture_by(s, t)]],
+             struct S *s,
+             struct S *t);
+void capture_gnu(int *x __attribute__((lifetime_capture_by(s, t))),
+                 struct S *s,
+                 struct S *t);
+
+[[clang::lifetime_capture_by(s)]] // expected-error 
{{'clang::lifetime_capture_by' attribute only applies to parameters and 
implicit object parameters}}
+void attr_on_function(int *x);
+void attr_on_function_gnu(int *x) __attribute__((lifetime_capture_by(s))); // 
expected-error {{'lifetime_capture_by' attribute only applies to parameters and 
implicit object parameters}}
+
+void invalid_args(int *x1 [[clang::lifetime_capture_by(12345 + 12)]], // 
expected-error {{'lifetime_capture_by' attribute argument '12345 + 12' is not a 
known function parameter; must be a function parameter, 'this', 'global' or 
'unknown'}}
+                  int *x2 [[clang::lifetime_capture_by(no_such_param)]], // 
expected-error {{'lifetime_capture_by' attribute argument 'no_such_param' is 
not a known function parameter; must be a function parameter, 'this', 'global' 
or 'unknown'}}
+                  int *x3 [[clang::lifetime_capture_by("no_such_param")]], // 
expected-error {{'lifetime_capture_by' attribute argument '"no_such_param"' is 
not a known function parameter; must be a function parameter, 'this', 'global' 
or 'unknown'}}
+                  int *x4 [[clang::lifetime_capture_by()]], // expected-error 
{{'lifetime_capture_by' attribute specifies no capturing entity}}
+                  int *x5 [[clang::lifetime_capture_by(x5)]]); // 
expected-error {{'lifetime_capture_by' argument references itself}}
+
+void invalid_args_gnu(int *x1 __attribute__((lifetime_capture_by(12345 + 
12))), // expected-error {{'lifetime_capture_by' attribute argument '12345 + 
12' is not a known function parameter; must be a function parameter, 'this', 
'global' or 'unknown'}}
+                      int *x2 
__attribute__((lifetime_capture_by(no_such_param))), // expected-error 
{{'lifetime_capture_by' attribute argument 'no_such_param' is not a known 
function parameter; must be a function parameter, 'this', 'global' or 
'unknown'}}
+                      int *x3 
__attribute__((lifetime_capture_by("no_such_param"))), // expected-error 
{{'lifetime_capture_by' attribute argument '"no_such_param"' is not a known 
function parameter; must be a function parameter, 'this', 'global' or 
'unknown'}}
+                      int *x4 __attribute__((lifetime_capture_by())), // 
expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}}
+                      int *x5 __attribute__((lifetime_capture_by(x5)))); // 
expected-error {{'lifetime_capture_by' argument references itself}}
diff --git a/clang/test/Sema/attr-lifetimebound.c 
b/clang/test/Sema/attr-lifetimebound.c
new file mode 100644
index 0000000000000..6292fe90bdf68
--- /dev/null
+++ b/clang/test/Sema/attr-lifetimebound.c
@@ -0,0 +1,21 @@
+// RUN: %clang_cc1 -std=c2x -verify %s
+
+int *ptr_param(int *param [[clang::lifetimebound]]);
+int *ptr_param_gnu(int *param __attribute__((lifetimebound)));
+int *ptr_param_redecl(int *param);
+int *ptr_param_redecl(int *param [[clang::lifetimebound]]);
+int *ptr_param_redecl(int *param) { return param; }
+int *ptr_param_redecl_gnu(int *param);
+int *ptr_param_redecl_gnu(int *param __attribute__((lifetimebound)));
+int *ptr_param_redecl_gnu(int *param) { return param; }
+
+void void_return(int *param [[clang::lifetimebound]]); // expected-error 
{{'lifetimebound' attribute cannot be applied to a parameter of a function that 
returns void; did you mean 'lifetime_capture_by(X)'}}
+void void_return_gnu(int *param __attribute__((lifetimebound))); // 
expected-error {{'lifetimebound' attribute cannot be applied to a parameter of 
a function that returns void; did you mean 'lifetime_capture_by(X)'}}
+
+int *attr_with_arg(int *param [[clang::lifetimebound(1)]]); // expected-error 
{{'clang::lifetimebound' attribute takes no arguments}}
+int *attr_with_arg_gnu(int *param __attribute__((lifetimebound(1)))); // 
expected-error {{'lifetimebound' attribute takes no arguments}}
+
+int attr_on_var [[clang::lifetimebound]]; // expected-error 
{{'clang::lifetimebound' attribute only applies to parameters and implicit 
object parameters}}
+int attr_on_var_gnu __attribute__((lifetimebound)); // expected-error 
{{'lifetimebound' attribute only applies to parameters and implicit object 
parameters}}
+int * [[clang::lifetimebound]] attr_on_pointee; // expected-error 
{{'clang::lifetimebound' attribute only applies to parameters and implicit 
object parameters}}
+int (*func_ptr)(int) [[clang::lifetimebound]]; // expected-error 
{{'clang::lifetimebound' attribute only applies to parameters and implicit 
object parameters}}

>From 32bd4e41cbac2ba8a48053e4860c2fcbd6b9a1fc Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Sat, 13 Jun 2026 12:43:41 +0300
Subject: [PATCH 10/11] sync

---
 clang/test/Sema/LifetimeSafety/safety-c.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/Sema/LifetimeSafety/safety-c.c 
b/clang/test/Sema/LifetimeSafety/safety-c.c
index c1da3a1118ea1..ba400a98b24ba 100644
--- a/clang/test/Sema/LifetimeSafety/safety-c.c
+++ b/clang/test/Sema/LifetimeSafety/safety-c.c
@@ -62,7 +62,7 @@ void lifetimebound_call(void) {
   {
     int i;
     p = identity(&i); // expected-warning {{local variable 'i' does not live 
long enough}} \
-                      // expected-note {{expression aliases the storage of 
local variable 'i'}}
+                      // expected-note {{result of call to 'identity' aliases 
the storage of local variable 'i'}}
   }                   // expected-note {{destroyed here}}
   (void)*p;           // expected-note {{later used here}}
 }

>From 0d4e6b716c989e6cde078b66d0170f8cd30c09b9 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Sat, 13 Jun 2026 17:01:10 +0300
Subject: [PATCH 11/11] fix crash

---
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 2 +-
 clang/test/Sema/LifetimeSafety/safety-c.c            | 6 ++++++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ebe99ff0dd4b9..2bfdbdf71b70b 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -326,7 +326,7 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
   case CK_ArrayToPointerDecay:
     // va_arg(ap, array_type) is UB and does not provide addressable array
     // storage to model.
-    if (isa<VAArgExpr>(SubExpr))
+    if (isa<VAArgExpr>(SubExpr->IgnoreParens()))
       return;
     assert(Src && "Array expression should have origins as it is GL value");
     CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
diff --git a/clang/test/Sema/LifetimeSafety/safety-c.c 
b/clang/test/Sema/LifetimeSafety/safety-c.c
index ba400a98b24ba..1f862edb01def 100644
--- a/clang/test/Sema/LifetimeSafety/safety-c.c
+++ b/clang/test/Sema/LifetimeSafety/safety-c.c
@@ -152,6 +152,12 @@ void va_arg_array_regression(int n, ...) {
   (void)p;
 }
 
+void take(int* q);
+void va_arg_array_paren_regression(int n, ...) {
+  __builtin_va_list ap;
+  take((__builtin_va_arg(ap, int[4]))); // expected-warning {{second argument 
to 'va_arg' is of array type 'int[4]'}}
+}
+
 void va_arg_function_regression(int n, ...) {
   __builtin_va_list ap;
   __builtin_va_start(ap, n);

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

Reply via email to