Skip to content

Commit 4822f49

Browse files
authored
[LifetimeSafety] Add parameter lifetime tracking in CFG (#169320)
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 #169014
1 parent d5aa686 commit 4822f49

File tree

5 files changed

+109
-9
lines changed

5 files changed

+109
-9
lines changed

clang/lib/Analysis/CFG.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,12 @@ std::unique_ptr<CFG> CFGBuilder::buildCFG(const Decl *D, Stmt *Statement) {
16661666
assert(Succ == &cfg->getExit());
16671667
Block = nullptr; // the EXIT block is empty. Create all other blocks lazily.
16681668

1669+
// Add parameters to the initial scope to handle their dtos and lifetime ends.
1670+
LocalScope *paramScope = nullptr;
1671+
if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D))
1672+
for (ParmVarDecl *PD : FD->parameters())
1673+
paramScope = addLocalScopeForVarDecl(PD, paramScope);
1674+
16691675
if (BuildOpts.AddImplicitDtors)
16701676
if (const CXXDestructorDecl *DD = dyn_cast_or_null<CXXDestructorDecl>(D))
16711677
addImplicitDtorsForDestructor(DD);
@@ -2246,6 +2252,11 @@ LocalScope* CFGBuilder::addLocalScopeForVarDecl(VarDecl *VD,
22462252
if (!VD->hasLocalStorage())
22472253
return Scope;
22482254

2255+
// Reference parameters are aliases to objects that live elsewhere, so they
2256+
// don't require automatic destruction or lifetime tracking.
2257+
if (isa<ParmVarDecl>(VD) && VD->getType()->isReferenceType())
2258+
return Scope;
2259+
22492260
if (!BuildOpts.AddLifetime && !BuildOpts.AddScopes &&
22502261
!needsAutomaticDestruction(VD)) {
22512262
assert(BuildOpts.AddImplicitDtors);
@@ -5616,8 +5627,15 @@ class StmtPrinterHelper : public PrinterHelper {
56165627
bool handleDecl(const Decl *D, raw_ostream &OS) {
56175628
DeclMapTy::iterator I = DeclMap.find(D);
56185629

5619-
if (I == DeclMap.end())
5630+
if (I == DeclMap.end()) {
5631+
// ParmVarDecls are not declared in the CFG itself, so they do not appear
5632+
// in DeclMap.
5633+
if (auto *PVD = dyn_cast_or_null<ParmVarDecl>(D)) {
5634+
OS << "[Parm: " << PVD->getNameAsString() << "]";
5635+
return true;
5636+
}
56205637
return false;
5638+
}
56215639

56225640
if (currentBlock >= 0 && I->second.first == (unsigned) currentBlock
56235641
&& I->second.second == currStmt) {

clang/test/Analysis/lifetime-cfg-output.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,3 +935,31 @@ int backpatched_goto() {
935935
goto label;
936936
i++;
937937
}
938+
939+
// CHECK: [B2 (ENTRY)]
940+
// CHECK-NEXT: Succs (1): B1
941+
// CHECK: [B1]
942+
// CHECK-NEXT: 1: a
943+
// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, LValueToRValue, int)
944+
// CHECK-NEXT: 3: b
945+
// CHECK-NEXT: 4: [B1.3] (ImplicitCastExpr, LValueToRValue, int)
946+
// CHECK-NEXT: 5: [B1.2] + [B1.4]
947+
// CHECK-NEXT: 6: c
948+
// CHECK-NEXT: 7: [B1.6] (ImplicitCastExpr, LValueToRValue, int)
949+
// CHECK-NEXT: 8: [B1.5] + [B1.7]
950+
// CHECK-NEXT: 9: int res = a + b + c;
951+
// CHECK-NEXT: 10: res
952+
// CHECK-NEXT: 11: [B1.10] (ImplicitCastExpr, LValueToRValue, int)
953+
// CHECK-NEXT: 12: return [B1.11];
954+
// CHECK-NEXT: 13: [B1.9] (Lifetime ends)
955+
// CHECK-NEXT: 14: [Parm: c] (Lifetime ends)
956+
// CHECK-NEXT: 15: [Parm: b] (Lifetime ends)
957+
// CHECK-NEXT: 16: [Parm: a] (Lifetime ends)
958+
// CHECK-NEXT: Preds (1): B2
959+
// CHECK-NEXT: Succs (1): B0
960+
// CHECK: [B0 (EXIT)]
961+
// CHECK-NEXT: Preds (1): B1
962+
int test_param_scope_end_order(int a, int b, int c) {
963+
int res = a + b + c;
964+
return res;
965+
}

clang/test/Analysis/scopes-cfg-output.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,12 +1437,14 @@ void test_cleanup_functions() {
14371437
// CHECK-NEXT: 4: return;
14381438
// CHECK-NEXT: 5: CleanupFunction (cleanup_int)
14391439
// CHECK-NEXT: 6: CFGScopeEnd(i)
1440+
// CHECK-NEXT: 7: CFGScopeEnd(m)
14401441
// CHECK-NEXT: Preds (1): B3
14411442
// CHECK-NEXT: Succs (1): B0
14421443
// CHECK: [B2]
14431444
// CHECK-NEXT: 1: return;
14441445
// CHECK-NEXT: 2: CleanupFunction (cleanup_int)
14451446
// CHECK-NEXT: 3: CFGScopeEnd(i)
1447+
// CHECK-NEXT: 4: CFGScopeEnd(m)
14461448
// CHECK-NEXT: Preds (1): B3
14471449
// CHECK-NEXT: Succs (1): B0
14481450
// CHECK: [B3]

clang/test/Sema/warn-lifetime-safety.cpp

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -529,14 +529,14 @@ TriviallyDestructedClass* trivial_class_uar () {
529529
return ptr; // expected-note {{returned here}}
530530
}
531531

532-
// FIXME: No lifetime warning for this as no expire facts are generated for parameters
533532
const int& return_parameter(int a) {
534-
return a;
533+
return a; // expected-warning {{address of stack memory is returned later}}
534+
// expected-note@-1 {{returned here}}
535535
}
536536

537-
// FIXME: No lifetime warning for this as no expire facts are generated for parameters
538537
int* return_pointer_to_parameter(int a) {
539-
return &a;
538+
return &a; // expected-warning {{address of stack memory is returned later}}
539+
// expected-note@-1 {{returned here}}
540540
}
541541

542542
const int& return_reference_to_parameter(int a)
@@ -788,9 +788,52 @@ const MyObj& lifetimebound_return_ref_to_local() {
788788
// expected-note@-1 {{returned here}}
789789
}
790790

791-
// FIXME: Fails to diagnose UAR when a reference to a by-value param escapes via the return value.
792-
View lifetimebound_return_of_by_value_param(MyObj stack_param) {
793-
return Identity(stack_param);
791+
View lifetimebound_return_by_value_param(MyObj stack_param) {
792+
return Identity(stack_param); // expected-warning {{address of stack memory is returned later}}
793+
// expected-note@-1 {{returned here}}
794+
}
795+
796+
View lifetimebound_return_by_value_multiple_param(int cond, MyObj a, MyObj b, MyObj c) {
797+
if (cond == 1)
798+
return Identity(a); // expected-warning {{address of stack memory is returned later}}
799+
// expected-note@-1 {{returned here}}
800+
if (cond == 2)
801+
return Identity(b); // expected-warning {{address of stack memory is returned later}}
802+
// expected-note@-1 {{returned here}}
803+
return Identity(c); // expected-warning {{address of stack memory is returned later}}
804+
// expected-note@-1 {{returned here}}
805+
}
806+
807+
template<class T>
808+
View lifetimebound_return_by_value_param_template(T t) {
809+
return Identity(t); // expected-warning {{address of stack memory is returned later}}
810+
// expected-note@-1 {{returned here}}
811+
}
812+
void use_lifetimebound_return_by_value_param_template() {
813+
lifetimebound_return_by_value_param_template(MyObj{}); // expected-note {{in instantiation of}}
814+
}
815+
816+
void lambda_uar_param() {
817+
auto lambda = [](MyObj stack_param) {
818+
return Identity(stack_param); // expected-warning {{address of stack memory is returned later}}
819+
// expected-note@-1 {{returned here}}
820+
};
821+
lambda(MyObj{});
822+
}
823+
824+
// FIXME: This should be detected. We see correct destructors but origin flow breaks somewhere.
825+
namespace VariadicTemplatedParamsUAR{
826+
827+
template<typename... Args>
828+
View Max(Args... args [[clang::lifetimebound]]);
829+
830+
template<typename... Args>
831+
View lifetimebound_return_of_variadic_param(Args... args) {
832+
return Max(args...);
833+
}
834+
void test_variadic() {
835+
lifetimebound_return_of_variadic_param(MyObj{1}, MyObj{2}, MyObj{3});
836+
}
794837
}
795838

796839
// FIXME: Fails to diagnose UAF when a reference to a by-value param escapes via an out-param.

clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,18 @@ recordState(Elements=8, Branches=2, Joins=1)
149149
enterElement(return b ? p : q;)
150150
transfer()
151151
recordState(Elements=9, Branches=2, Joins=1)
152+
enterElement([Parm: q] (Lifetime ends))
153+
transfer()
154+
recordState(Elements=10, Branches=2, Joins=1)
155+
enterElement([Parm: p] (Lifetime ends))
156+
transfer()
157+
recordState(Elements=11, Branches=2, Joins=1)
158+
enterElement([Parm: b] (Lifetime ends))
159+
transfer()
160+
recordState(Elements=12, Branches=2, Joins=1)
152161
153162
enterBlock(0, false)
154-
recordState(Elements=9, Branches=2, Joins=1)
163+
recordState(Elements=12, Branches=2, Joins=1)
155164
156165
endAnalysis()
157166
)");

0 commit comments

Comments
 (0)