[clang] [LifetimeSafety] Add parameter lifetime tracking in CFG (PR #169320)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Nov 24 04:19:07 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-temporal-safety
Author: Utkarsh Saxena (usx95)
<details>
<summary>Changes</summary>
This PR enhances the CFG builder to properly handle function parameters in lifetime analysis:
1. Added code to include parameters in the initial scope during CFG construction for both `FunctionDecl` and `BlockDecl` types
2. Added a special case to skip reference parameters, as they don't need automatic destruction
3. Fixed several test cases that were previously marked as "FIXME" due to missing parameter lifetime tracking
Previously, Clang's lifetime analysis was not properly tracking the lifetime of function parameters, causing it to miss important use-after-return bugs when parameter values were returned by reference or address. This change ensures that parameters are properly tracked in the CFG, allowing the analyzer to correctly identify when stack memory associated with parameters is returned.
Fixes https://github.com/llvm/llvm-project/issues/169014
---
Full diff: https://github.com/llvm/llvm-project/pull/169320.diff
3 Files Affected:
- (modified) clang/lib/Analysis/CFG.cpp (+10)
- (modified) clang/test/Analysis/scopes-cfg-output.cpp (+2)
- (modified) clang/test/Sema/warn-lifetime-safety.cpp (+14-6)
``````````diff
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index cdde849b0e026..c052af5e96b21 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -1666,6 +1666,13 @@ std::unique_ptr<CFG> CFGBuilder::buildCFG(const Decl *D, Stmt *Statement) {
assert(Succ == &cfg->getExit());
Block = nullptr; // the EXIT block is empty. Create all other blocks lazily.
+ // Add parameters to the initial scope so to handle their dtos and lifetime
+ // ends.
+ LocalScope *paramScope = nullptr;
+ if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D))
+ for (ParmVarDecl *PD : FD->parameters())
+ paramScope = addLocalScopeForVarDecl(PD, paramScope);
+
if (BuildOpts.AddImplicitDtors)
if (const CXXDestructorDecl *DD = dyn_cast_or_null<CXXDestructorDecl>(D))
addImplicitDtorsForDestructor(DD);
@@ -2246,6 +2253,9 @@ LocalScope* CFGBuilder::addLocalScopeForVarDecl(VarDecl *VD,
if (!VD->hasLocalStorage())
return Scope;
+ if (isa<ParmVarDecl>(VD) && VD->getType()->isReferenceType())
+ return Scope;
+
if (!BuildOpts.AddLifetime && !BuildOpts.AddScopes &&
!needsAutomaticDestruction(VD)) {
assert(BuildOpts.AddImplicitDtors);
diff --git a/clang/test/Analysis/scopes-cfg-output.cpp b/clang/test/Analysis/scopes-cfg-output.cpp
index 6ed6f3638f75b..9c75492c33a42 100644
--- a/clang/test/Analysis/scopes-cfg-output.cpp
+++ b/clang/test/Analysis/scopes-cfg-output.cpp
@@ -1437,12 +1437,14 @@ void test_cleanup_functions() {
// CHECK-NEXT: 4: return;
// CHECK-NEXT: 5: CleanupFunction (cleanup_int)
// CHECK-NEXT: 6: CFGScopeEnd(i)
+// CHECK-NEXT: 7: CFGScopeEnd(m)
// CHECK-NEXT: Preds (1): B3
// CHECK-NEXT: Succs (1): B0
// CHECK: [B2]
// CHECK-NEXT: 1: return;
// CHECK-NEXT: 2: CleanupFunction (cleanup_int)
// CHECK-NEXT: 3: CFGScopeEnd(i)
+// CHECK-NEXT: 4: CFGScopeEnd(m)
// CHECK-NEXT: Preds (1): B3
// CHECK-NEXT: Succs (1): B0
// CHECK: [B3]
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index e80a05860389c..c08543c27778f 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -529,14 +529,14 @@ TriviallyDestructedClass* trivial_class_uar () {
return ptr; // expected-note {{returned here}}
}
-// FIXME: No lifetime warning for this as no expire facts are generated for parameters
const int& return_parameter(int a) {
- return a;
+ return a; // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 {{returned here}}
}
-// FIXME: No lifetime warning for this as no expire facts are generated for parameters
int* return_pointer_to_parameter(int a) {
- return &a;
+ return &a; // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 {{returned here}}
}
const int& return_reference_to_parameter(int a)
@@ -788,9 +788,17 @@ const MyObj& lifetimebound_return_ref_to_local() {
// expected-note at -1 {{returned here}}
}
-// FIXME: Fails to diagnose UAR when a reference to a by-value param escapes via the return value.
View lifetimebound_return_of_by_value_param(MyObj stack_param) {
- return Identity(stack_param);
+ return Identity(stack_param); // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 {{returned here}}
+}
+
+void LambdaUARParam() {
+ auto lambda = [](MyObj stack_param) {
+ return Identity(stack_param); // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 {{returned here}}
+ };
+ lambda(MyObj{});
}
// FIXME: Fails to diagnose UAF when a reference to a by-value param escapes via an out-param.
``````````
</details>
https://github.com/llvm/llvm-project/pull/169320
More information about the cfe-commits
mailing list