[clang] [LifetimeSafety] Support C Language in LifetimeSafety (PR #203270)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 12 08:01:29 PDT 2026
https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/203270
>From 090873f16b4aa1cf87f5ab6205e16a0acf536161 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 11 Jun 2026 16:21:03 +0300
Subject: [PATCH 1/5] [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 <nobodqwe at gmail.com>
Date: Thu, 11 Jun 2026 20:10:49 +0300
Subject: [PATCH 2/5] 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 <nobodqwe at gmail.com>
Date: Thu, 11 Jun 2026 20:58:59 +0300
Subject: [PATCH 3/5] 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 <nobodqwe at gmail.com>
Date: Fri, 12 Jun 2026 14:47:40 +0300
Subject: [PATCH 4/5] 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 <nobodqwe at gmail.com>
Date: Fri, 12 Jun 2026 18:01:12 +0300
Subject: [PATCH 5/5] 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,
More information about the cfe-commits
mailing list