Skip to content

Commit fa5312a

Browse files
committed
[LifetimeSafety] Track gsl::Pointer types
1 parent 5c910de commit fa5312a

File tree

3 files changed

+386
-19
lines changed

3 files changed

+386
-19
lines changed

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 120 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,31 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> {
433433

434434
void VisitDeclRefExpr(const DeclRefExpr *DRE) { handleUse(DRE); }
435435

436+
void VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
437+
if (!isGslPointerType(CCE->getType()))
438+
return;
439+
440+
if (CCE->getNumArgs() > 0 && hasOrigin(CCE->getArg(0)->getType()))
441+
// This is a propagation.
442+
addAssignOriginFact(*CCE, *CCE->getArg(0));
443+
else
444+
// This could be a new borrow.
445+
checkForBorrows(CCE, CCE->getConstructor(),
446+
{CCE->getArgs(), CCE->getNumArgs()});
447+
}
448+
449+
void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
450+
if (!isGslPointerType(MCE->getImplicitObjectArgument()->getType()))
451+
return;
452+
// Specifically for conversion operators, like `std::string_view p = a;`
453+
if (isa<CXXConversionDecl>(MCE->getCalleeDecl())) {
454+
// The argument is the implicit object itself.
455+
checkForBorrows(MCE, MCE->getMethodDecl(),
456+
{MCE->getImplicitObjectArgument()});
457+
}
458+
// Note: A more general VisitCallExpr could also be used here.
459+
}
460+
436461
void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) {
437462
/// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized
438463
/// pointers can use the same type of loan.
@@ -492,10 +517,27 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> {
492517
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) {
493518
// Check if this is a test point marker. If so, we are done with this
494519
// expression.
495-
if (VisitTestPoint(FCE))
520+
if (handleTestPoint(FCE))
496521
return;
497-
// Visit as normal otherwise.
498-
Base::VisitCXXFunctionalCastExpr(FCE);
522+
if (isGslPointerType(FCE->getType()))
523+
addAssignOriginFact(*FCE, *FCE->getSubExpr());
524+
}
525+
526+
void VisitInitListExpr(const InitListExpr *ILE) {
527+
if (!hasOrigin(ILE->getType()))
528+
return;
529+
// For list initialization with a single element, like `View{...}`, the
530+
// origin of the list itself is the origin of its single element.
531+
if (ILE->getNumInits() == 1)
532+
addAssignOriginFact(*ILE, *ILE->getInit(0));
533+
}
534+
535+
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE) {
536+
if (!hasOrigin(MTE->getType()))
537+
return;
538+
// A temporary object's origin is the same as the origin of the
539+
// expression that initializes it.
540+
addAssignOriginFact(*MTE, *MTE->getSubExpr());
499541
}
500542

501543
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
@@ -521,8 +563,75 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> {
521563
}
522564

523565
private:
566+
static bool isGslPointerType(QualType QT) {
567+
if (const auto *RD = QT->getAsCXXRecordDecl()) {
568+
// We need to check the template definition for specializations.
569+
if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
570+
return CTSD->getSpecializedTemplate()
571+
->getTemplatedDecl()
572+
->hasAttr<PointerAttr>();
573+
return RD->hasAttr<PointerAttr>();
574+
}
575+
return false;
576+
}
577+
524578
// Check if a type has an origin.
525-
bool hasOrigin(QualType QT) { return QT->isPointerOrReferenceType(); }
579+
static bool hasOrigin(QualType QT) {
580+
if (QT->isFunctionPointerType())
581+
return false;
582+
return QT->isPointerOrReferenceType() || isGslPointerType(QT);
583+
}
584+
585+
/// Checks if a call-like expression creates a borrow by passing a local
586+
/// value to a reference parameter, creating an IssueFact if it does.
587+
void checkForBorrows(const Expr *Call, const FunctionDecl *FD,
588+
ArrayRef<const Expr *> Args) {
589+
if (!FD)
590+
return;
591+
592+
for (unsigned I = 0; I < Args.size(); ++I) {
593+
if (I >= FD->getNumParams())
594+
break;
595+
596+
const ParmVarDecl *Param = FD->getParamDecl(I);
597+
const Expr *Arg = Args[I];
598+
599+
// This is the core condition for a new borrow: a value type (no origin)
600+
// is passed to a reference parameter.
601+
if (Param->getType()->isReferenceType() && !hasOrigin(Arg->getType())) {
602+
if (const Loan *L = createLoanFrom(Arg, Call)) {
603+
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*Call);
604+
CurrentBlockFacts.push_back(
605+
FactMgr.createFact<IssueFact>(L->ID, OID));
606+
// For view creation, we assume the first borrow is the significant
607+
// one.
608+
return;
609+
}
610+
}
611+
}
612+
}
613+
614+
/// Attempts to create a loan by analyzing the source expression of a borrow.
615+
/// This method is the single point for creating loans, allowing for future
616+
/// expansion to handle temporaries, field members, etc.
617+
/// \param SourceExpr The expression representing the object being borrowed
618+
/// from.
619+
/// \param IssueExpr The expression that triggers the borrow (e.g., a
620+
/// constructor call).
621+
/// \return The new Loan on success, nullptr on failure.
622+
const Loan *createLoanFrom(const Expr *SourceExpr, const Expr *IssueExpr) {
623+
// For now, we only handle direct borrows from local variables.
624+
// In the future, this can be extended to handle MaterializeTemporaryExpr,
625+
// etc.
626+
if (const auto *DRE =
627+
dyn_cast<DeclRefExpr>(SourceExpr->IgnoreParenImpCasts())) {
628+
if (const auto *VD = dyn_cast<ValueDecl>(DRE->getDecl())) {
629+
AccessPath Path(VD);
630+
return &FactMgr.getLoanMgr().addLoan(Path, IssueExpr);
631+
}
632+
}
633+
return nullptr;
634+
}
526635

527636
template <typename Destination, typename Source>
528637
void addAssignOriginFact(const Destination &D, const Source &S) {
@@ -534,7 +643,7 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> {
534643

535644
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
536645
/// If so, creates a `TestPointFact` and returns true.
537-
bool VisitTestPoint(const CXXFunctionalCastExpr *FCE) {
646+
bool handleTestPoint(const CXXFunctionalCastExpr *FCE) {
538647
if (!FCE->getType()->isVoidType())
539648
return false;
540649

@@ -612,10 +721,13 @@ class FactGenerator : public RecursiveASTVisitor<FactGenerator> {
612721
for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
613722
FactGeneratorBlockRAII BlockGenerator(FG, Block);
614723
for (const CFGElement &Element : *Block) {
615-
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
724+
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) {
725+
DEBUG_WITH_TYPE("PrintCFG", llvm::dbgs() << " ================== \n");
726+
DEBUG_WITH_TYPE("PrintCFG", CS->dump());
727+
DEBUG_WITH_TYPE("PrintCFG", CS->getStmt()->dumpColor());
616728
TraverseStmt(const_cast<Stmt *>(CS->getStmt()));
617-
else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
618-
Element.getAs<CFGAutomaticObjDtor>())
729+
} else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
730+
Element.getAs<CFGAutomaticObjDtor>())
619731
FG.handleDestructor(*DtorOpt);
620732
}
621733
}

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

Lines changed: 110 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ struct MyObj {
66
MyObj operator+(MyObj);
77
};
88

9+
struct [[gsl::Pointer()]] View {
10+
View(const MyObj&); // Borrows from MyObj
11+
View();
12+
void use() const;
13+
};
14+
915
//===----------------------------------------------------------------------===//
1016
// Basic Definite Use-After-Free (-W...permissive)
1117
// These are cases where the pointer is guaranteed to be dangling at the use site.
@@ -20,12 +26,31 @@ void definite_simple_case() {
2026
(void)*p; // expected-note {{later used here}}
2127
}
2228

29+
void definite_simple_case_gsl() {
30+
View v;
31+
{
32+
MyObj s;
33+
v = s; // expected-warning {{object whose reference is captured does not live long enough}}
34+
} // expected-note {{destroyed here}}
35+
v.use(); // expected-note {{later used here}}
36+
}
37+
2338
void no_use_no_error() {
2439
MyObj* p;
2540
{
2641
MyObj s;
2742
p = &s;
2843
}
44+
// 'p' is dangling here, but since it is never used, no warning is issued.
45+
}
46+
47+
void no_use_no_error_gsl() {
48+
View v;
49+
{
50+
MyObj s;
51+
v = s;
52+
}
53+
// 'v' is dangling here, but since it is never used, no warning is issued.
2954
}
3055

3156
void definite_pointer_chain() {
@@ -39,6 +64,16 @@ void definite_pointer_chain() {
3964
(void)*q; // expected-note {{later used here}}
4065
}
4166

67+
void definite_propagation_gsl() {
68+
View v1, v2;
69+
{
70+
MyObj s;
71+
v1 = s; // expected-warning {{object whose reference is captured does not live long enough}}
72+
v2 = v1;
73+
} // expected-note {{destroyed here}}
74+
v2.use(); // expected-note {{later used here}}
75+
}
76+
4277
void definite_multiple_uses_one_warning() {
4378
MyObj* p;
4479
{
@@ -78,6 +113,19 @@ void definite_single_pointer_multiple_loans(bool cond) {
78113
(void)*p; // expected-note 2 {{later used here}}
79114
}
80115

116+
void definite_single_pointer_multiple_loans_gsl(bool cond) {
117+
View v;
118+
if (cond){
119+
MyObj s;
120+
v = s; // expected-warning {{object whose reference is captured does not live long enough}}
121+
} // expected-note {{destroyed here}}
122+
else {
123+
MyObj t;
124+
v = t; // expected-warning {{object whose reference is captured does not live long enough}}
125+
} // expected-note {{destroyed here}}
126+
v.use(); // expected-note 2 {{later used here}}
127+
}
128+
81129

82130
//===----------------------------------------------------------------------===//
83131
// Potential (Maybe) Use-After-Free (-W...strict)
@@ -94,18 +142,14 @@ void potential_if_branch(bool cond) {
94142
(void)*p; // expected-note {{later used here}}
95143
}
96144

97-
// If all paths lead to a dangle, it becomes a definite error.
98-
void potential_becomes_definite(bool cond) {
99-
MyObj* p;
145+
void potential_if_branch_gsl(bool cond) {
146+
MyObj safe;
147+
View v = safe;
100148
if (cond) {
101-
MyObj temp1;
102-
p = &temp1; // expected-warning {{does not live long enough}}
103-
} // expected-note {{destroyed here}}
104-
else {
105-
MyObj temp2;
106-
p = &temp2; // expected-warning {{does not live long enough}}
149+
MyObj temp;
150+
v = temp; // expected-warning {{object whose reference is captured may not live long enough}}
107151
} // expected-note {{destroyed here}}
108-
(void)*p; // expected-note 2 {{later used here}}
152+
v.use(); // expected-note {{later used here}}
109153
}
110154

111155
void definite_potential_together(bool cond) {
@@ -159,6 +203,16 @@ void potential_for_loop_use_after_loop_body(MyObj safe) {
159203
(void)*p; // expected-note {{later used here}}
160204
}
161205

206+
void potential_for_loop_gsl() {
207+
MyObj safe;
208+
View v = safe;
209+
for (int i = 0; i < 1; ++i) {
210+
MyObj s;
211+
v = s; // expected-warning {{object whose reference is captured may not live long enough}}
212+
} // expected-note {{destroyed here}}
213+
v.use(); // expected-note {{later used here}}
214+
}
215+
162216
void potential_for_loop_use_before_loop_body(MyObj safe) {
163217
MyObj* p = &safe;
164218
for (int i = 0; i < 1; ++i) {
@@ -182,6 +236,19 @@ void potential_loop_with_break(bool cond) {
182236
(void)*p; // expected-note {{later used here}}
183237
}
184238

239+
void potential_loop_with_break_gsl(bool cond) {
240+
MyObj safe;
241+
View v = safe;
242+
for (int i = 0; i < 10; ++i) {
243+
if (cond) {
244+
MyObj temp;
245+
v = temp; // expected-warning {{object whose reference is captured may not live long enough}}
246+
break; // expected-note {{destroyed here}}
247+
}
248+
}
249+
v.use(); // expected-note {{later used here}}
250+
}
251+
185252
void potential_multiple_expiry_of_same_loan(bool cond) {
186253
// Choose the last expiry location for the loan.
187254
MyObj safe;
@@ -258,6 +325,28 @@ void definite_switch(int mode) {
258325
(void)*p; // expected-note 3 {{later used here}}
259326
}
260327

328+
void definite_switch_gsl(int mode) {
329+
View v;
330+
switch (mode) {
331+
case 1: {
332+
MyObj temp1;
333+
v = temp1; // expected-warning {{object whose reference is captured does not live long enough}}
334+
break; // expected-note {{destroyed here}}
335+
}
336+
case 2: {
337+
MyObj temp2;
338+
v = temp2; // expected-warning {{object whose reference is captured does not live long enough}}
339+
break; // expected-note {{destroyed here}}
340+
}
341+
default: {
342+
MyObj temp3;
343+
v = temp3; // expected-warning {{object whose reference is captured does not live long enough}}
344+
break; // expected-note {{destroyed here}}
345+
}
346+
}
347+
v.use(); // expected-note 3 {{later used here}}
348+
}
349+
261350
//===----------------------------------------------------------------------===//
262351
// No-Error Cases
263352
//===----------------------------------------------------------------------===//
@@ -271,3 +360,14 @@ void no_error_if_dangle_then_rescue() {
271360
p = &safe; // p is "rescued" before use.
272361
(void)*p; // This is safe.
273362
}
363+
364+
void no_error_if_dangle_then_rescue_gsl() {
365+
MyObj safe;
366+
View v;
367+
{
368+
MyObj temp;
369+
v = temp; // 'v' is temporarily dangling.
370+
}
371+
v = safe; // 'v' is "rescued" before use by reassigning to a valid object.
372+
v.use(); // This is safe.
373+
}

0 commit comments

Comments
 (0)