Skip to content

Commit 4fca7b0

Browse files
authored
[LifetimeSafety] Detect expiry of loans to trivially destructed types (llvm#168855)
Handling Trivially Destructed Types This PR uses `AddLifetime` to handle expiry of loans to trivially destructed types. Example: ```cpp int * trivial_uar(){ int *ptr; int x = 1; ptr = &x; return ptr; } ``` The CFG created now has an Expire Fact for trivially destructed types: ``` Function: trivial_uar Block B2: End of Block Block B1: Issue (0 (Path: x), ToOrigin: 0 (Expr: DeclRefExpr)) OriginFlow (Dest: 1 (Expr: UnaryOperator), Src: 0 (Expr: DeclRefExpr)) Use (2 (Decl: ptr), Write) OriginFlow (Dest: 2 (Decl: ptr), Src: 1 (Expr: UnaryOperator)) Use (2 (Decl: ptr), Read) OriginFlow (Dest: 3 (Expr: ImplicitCastExpr), Src: 2 (Decl: ptr)) Expire (0 (Path: x)) OriginEscapes (3 (Expr: ImplicitCastExpr)) End of Block Block B0: End of Block ``` This Expire Fact issues UAR and UAF warnings. Fixes llvm#162862
1 parent d36e2b6 commit 4fca7b0

File tree

6 files changed

+165
-24
lines changed

6 files changed

+165
-24
lines changed

clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
5050
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
5151

5252
private:
53-
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt);
53+
void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
5454

5555
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
5656

clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ void FactsGenerator::run() {
6363
const CFGElement &Element = Block->Elements[I];
6464
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
6565
Visit(CS->getStmt());
66-
else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
67-
Element.getAs<CFGAutomaticObjDtor>())
68-
handleDestructor(*DtorOpt);
66+
else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
67+
Element.getAs<CFGLifetimeEnds>())
68+
handleLifetimeEnds(*LifetimeEnds);
6969
}
7070
CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
7171
EscapesInCurrentBlock.end());
@@ -229,25 +229,19 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
229229
killAndFlowOrigin(*MTE, *MTE->getSubExpr());
230230
}
231231

232-
void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
233-
/// TODO: Also handle trivial destructors (e.g., for `int`
234-
/// variables) which will never have a CFGAutomaticObjDtor node.
232+
void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
235233
/// TODO: Handle loans to temporaries.
236-
/// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the
237-
/// lifetime ends.
238-
const VarDecl *DestructedVD = DtorOpt.getVarDecl();
239-
if (!DestructedVD)
234+
const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl();
235+
if (!LifetimeEndsVD)
240236
return;
241237
// Iterate through all loans to see if any expire.
242-
/// TODO(opt): Do better than a linear search to find loans associated with
243-
/// 'DestructedVD'.
244-
for (const Loan &L : FactMgr.getLoanMgr().getLoans()) {
245-
const AccessPath &LoanPath = L.Path;
238+
for (const auto &Loan : FactMgr.getLoanMgr().getLoans()) {
239+
const AccessPath &LoanPath = Loan.Path;
246240
// Check if the loan is for a stack variable and if that variable
247241
// is the one being destructed.
248-
if (LoanPath.D == DestructedVD)
242+
if (LoanPath.D == LifetimeEndsVD)
249243
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
250-
L.ID, DtorOpt.getTriggerStmt()->getEndLoc()));
244+
Loan.ID, LifetimeEnds.getTriggerStmt()->getEndLoc()));
251245
}
252246
}
253247

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2992,6 +2992,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
29922992

29932993
bool EnableLifetimeSafetyAnalysis = S.getLangOpts().EnableLifetimeSafety;
29942994

2995+
if (EnableLifetimeSafetyAnalysis)
2996+
AC.getCFGBuildOptions().AddLifetime = true;
2997+
29952998
// Force that certain expressions appear as CFGElements in the CFG. This
29962999
// is used to speed up various analyses.
29973000
// FIXME: This isn't the right factoring. This is here for initial

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ int return_int_val() {
5959
int x = 10;
6060
// CHECK: Block B{{[0-9]+}}:
6161
// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr))
62+
// CHECK: Expire ([[L_X:[0-9]+]] (Path: x))
6263
return x;
6364
}
6465
// CHECK-NEXT: End of Block
@@ -77,7 +78,6 @@ void loan_expires_cpp() {
7778
}
7879

7980

80-
// FIXME: No expire for Trivial Destructors
8181
// CHECK-LABEL: Function: loan_expires_trivial
8282
void loan_expires_trivial() {
8383
int trivial_obj = 1;
@@ -86,11 +86,29 @@ void loan_expires_trivial() {
8686
// CHECK: OriginFlow (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr))
8787
int* pTrivialObj = &trivial_obj;
8888
// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator))
89-
// CHECK-NOT: Expire
89+
// CHECK: Expire ([[L_TRIVIAL_OBJ:[0-9]+]] (Path: trivial_obj))
9090
// CHECK-NEXT: End of Block
91-
// FIXME: Add check for Expire once trivial destructors are handled for expiration.
9291
}
9392

93+
// Trivial Destructors
94+
// CHECK-LABEL: Function: return_int_pointer
95+
int* return_int_pointer() {
96+
int* ptr;
97+
// CHECK: Block B{{[0-9]+}}:
98+
int x = 1;
99+
// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr))
100+
// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
101+
ptr = &x;
102+
// CHECK: Use ([[O_PTR:[0-9]+]] (Decl: ptr), Write)
103+
// CHECK: OriginFlow (Dest: [[O_PTR]] (Decl: ptr), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
104+
// CHECK: Use ([[O_PTR]] (Decl: ptr), Read)
105+
// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR]] (Decl: ptr))
106+
// CHECK: Expire ([[L_X]] (Path: x))
107+
// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
108+
return ptr;
109+
}
110+
// CHECK-NEXT: End of Block
111+
94112
// CHECK-LABEL: Function: conditional
95113
void conditional(bool condition) {
96114
int a = 5;

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

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ struct [[gsl::Pointer()]] View {
1212
void use() const;
1313
};
1414

15+
class TriviallyDestructedClass {
16+
View a, b;
17+
};
18+
1519
//===----------------------------------------------------------------------===//
1620
// Basic Definite Use-After-Free (-W...permissive)
1721
// These are cases where the pointer is guaranteed to be dangling at the use site.
@@ -396,6 +400,24 @@ void loan_from_previous_iteration(MyObj safe, bool condition) {
396400
} // expected-note {{destroyed here}}
397401
}
398402

403+
void trivial_int_uaf() {
404+
int * a;
405+
{
406+
int b = 1;
407+
a = &b; // expected-warning {{object whose reference is captured does not live long enough}}
408+
} // expected-note {{destroyed here}}
409+
(void)*a; // expected-note {{later used here}}
410+
}
411+
412+
void trivial_class_uaf() {
413+
TriviallyDestructedClass* ptr;
414+
{
415+
TriviallyDestructedClass s;
416+
ptr = &s; // expected-warning {{object whose reference is captured does not live long enough}}
417+
} // expected-note {{destroyed here}}
418+
(void)ptr; // expected-note {{later used here}}
419+
}
420+
399421
//===----------------------------------------------------------------------===//
400422
// Basic Definite Use-After-Return (Return-Stack-Address) (-W...permissive)
401423
// These are cases where the pointer is guaranteed to be dangling at the use site.
@@ -493,6 +515,43 @@ MyObj& reference_return_of_local() {
493515
// expected-note@-1 {{returned here}}
494516
}
495517

518+
int* trivial_int_uar() {
519+
int *a;
520+
int b = 1;
521+
a = &b; // expected-warning {{address of stack memory is returned later}}
522+
return a; // expected-note {{returned here}}
523+
}
524+
525+
TriviallyDestructedClass* trivial_class_uar () {
526+
TriviallyDestructedClass *ptr;
527+
TriviallyDestructedClass s;
528+
ptr = &s; // expected-warning {{address of stack memory is returned later}}
529+
return ptr; // expected-note {{returned here}}
530+
}
531+
532+
// FIXME: No lifetime warning for this as no expire facts are generated for parameters
533+
const int& return_parameter(int a) {
534+
return a;
535+
}
536+
537+
// FIXME: No lifetime warning for this as no expire facts are generated for parameters
538+
int* return_pointer_to_parameter(int a) {
539+
return &a;
540+
}
541+
542+
const int& return_reference_to_parameter(int a)
543+
{
544+
const int &b = a;
545+
return b; // expected-warning {{address of stack memory is returned later}}
546+
// expected-note@-1 {{returned here}}
547+
}
548+
549+
const int& get_ref_to_local() {
550+
int a = 42;
551+
return a; // expected-warning {{address of stack memory is returned later}}
552+
// expected-note@-1 {{returned here}}
553+
}
554+
496555
//===----------------------------------------------------------------------===//
497556
// Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined
498557
// These are cases where the diagnostic kind is determined by location
@@ -688,7 +747,8 @@ void lifetimebound_partial_safety(bool cond) {
688747
v.use(); // expected-note {{later used here}}
689748
}
690749

691-
// FIXME: Creating reference from lifetimebound call doesn't propagate loans.
750+
// FIXME: Warning should be on the 'GetObject' call, not the assignment to 'ptr'.
751+
// The loan from the lifetimebound argument is not propagated to the call expression itself.
692752
const MyObj& GetObject(View v [[clang::lifetimebound]]);
693753
void lifetimebound_return_reference() {
694754
View v;
@@ -697,9 +757,9 @@ void lifetimebound_return_reference() {
697757
MyObj obj;
698758
View temp_v = obj;
699759
const MyObj& ref = GetObject(temp_v);
700-
ptr = &ref;
701-
}
702-
(void)*ptr;
760+
ptr = &ref; // expected-warning {{object whose reference is captured does not live long enough}}
761+
} // expected-note {{destroyed here}}
762+
(void)*ptr; // expected-note {{later used here}}
703763
}
704764

705765
// FIXME: No warning for non gsl::Pointer types. Origin tracking is only supported for pointer types.

clang/unittests/Analysis/LifetimeSafetyTest.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class LifetimeTestRunner {
5959
BuildOptions.setAllAlwaysAdd();
6060
BuildOptions.AddImplicitDtors = true;
6161
BuildOptions.AddTemporaryDtors = true;
62+
BuildOptions.AddLifetime = true;
6263

6364
// Run the main analysis.
6465
Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr);
@@ -1307,6 +1308,42 @@ TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) {
13071308
EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1"));
13081309
}
13091310

1311+
TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAF) {
1312+
SetupTest(R"(
1313+
void target() {
1314+
int *ptr;
1315+
{
1316+
int s = 1;
1317+
ptr = &s;
1318+
}
1319+
POINT(p1);
1320+
(void)*ptr;
1321+
}
1322+
)");
1323+
EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1"));
1324+
EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1"));
1325+
}
1326+
1327+
TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAF) {
1328+
SetupTest(R"(
1329+
class S {
1330+
View a, b;
1331+
};
1332+
1333+
void target() {
1334+
S* ptr;
1335+
{
1336+
S s;
1337+
ptr = &s;
1338+
}
1339+
POINT(p1);
1340+
(void)ptr;
1341+
}
1342+
)");
1343+
EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1"));
1344+
EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1"));
1345+
}
1346+
13101347
TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) {
13111348
SetupTest(R"(
13121349
MyObj* target() {
@@ -1506,5 +1543,34 @@ TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) {
15061543
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
15071544
}
15081545

1546+
TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAR) {
1547+
SetupTest(R"(
1548+
int* target() {
1549+
int s = 10;
1550+
int* p = &s;
1551+
POINT(p1);
1552+
return p;
1553+
}
1554+
)");
1555+
EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
1556+
}
1557+
1558+
TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAR) {
1559+
SetupTest(R"(
1560+
class S {
1561+
View a, b;
1562+
};
1563+
1564+
S* target() {
1565+
S *ptr;
1566+
S s;
1567+
ptr = &s;
1568+
POINT(p1);
1569+
return ptr;
1570+
}
1571+
)");
1572+
EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
1573+
}
1574+
15091575
} // anonymous namespace
15101576
} // namespace clang::lifetimes::internal

0 commit comments

Comments
 (0)