Skip to content

Commit fadaf5d

Browse files
usx95Gabor Horvath
authored andcommitted
[LifetimeSafety] Add parameter lifetime tracking in CFG (llvm#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 llvm#169014
1 parent 361f176 commit fadaf5d

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
@@ -1675,6 +1675,12 @@ std::unique_ptr<CFG> CFGBuilder::buildCFG(const Decl *D, Stmt *Statement) {
16751675
assert(Succ == &cfg->getExit());
16761676
Block = nullptr; // the EXIT block is empty. Create all other blocks lazily.
16771677

1678+
// Add parameters to the initial scope to handle their dtos and lifetime ends.
1679+
LocalScope *paramScope = nullptr;
1680+
if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D))
1681+
for (ParmVarDecl *PD : FD->parameters())
1682+
paramScope = addLocalScopeForVarDecl(PD, paramScope);
1683+
16781684
if (BuildOpts.AddImplicitDtors)
16791685
if (const CXXDestructorDecl *DD = dyn_cast_or_null<CXXDestructorDecl>(D))
16801686
addImplicitDtorsForDestructor(DD);
@@ -2255,6 +2261,11 @@ LocalScope* CFGBuilder::addLocalScopeForVarDecl(VarDecl *VD,
22552261
if (!VD->hasLocalStorage())
22562262
return Scope;
22572263

2264+
// Reference parameters are aliases to objects that live elsewhere, so they
2265+
// don't require automatic destruction or lifetime tracking.
2266+
if (isa<ParmVarDecl>(VD) && VD->getType()->isReferenceType())
2267+
return Scope;
2268+
22582269
if (!BuildOpts.AddLifetime && !BuildOpts.AddScopes &&
22592270
!needsAutomaticDestruction(VD)) {
22602271
assert(BuildOpts.AddImplicitDtors);
@@ -5680,8 +5691,15 @@ class StmtPrinterHelper : public PrinterHelper {
56805691
bool handleDecl(const Decl *D, raw_ostream &OS) {
56815692
DeclMapTy::iterator I = DeclMap.find(D);
56825693

5683-
if (I == DeclMap.end())
5694+
if (I == DeclMap.end()) {
5695+
// ParmVarDecls are not declared in the CFG itself, so they do not appear
5696+
// in DeclMap.
5697+
if (auto *PVD = dyn_cast_or_null<ParmVarDecl>(D)) {
5698+
OS << "[Parm: " << PVD->getNameAsString() << "]";
5699+
return true;
5700+
}
56845701
return false;
5702+
}
56855703

56865704
if (currentBlock >= 0 && I->second.first == (unsigned) currentBlock
56875705
&& 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)