Skip to content

Commit afa47aa

Browse files
committed
[LifetimeSafety] Track gsl::Pointer types
1 parent a6889af commit afa47aa

File tree

2 files changed

+290
-21
lines changed

2 files changed

+290
-21
lines changed

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 138 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,31 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> {
428428
addAssignOriginFact(*VD, *InitExpr);
429429
}
430430

431+
void VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
432+
if (!isGslPointerType(CCE->getType()))
433+
return;
434+
435+
if (CCE->getNumArgs() > 0 && hasOrigin(CCE->getArg(0)->getType()))
436+
// This is a propagation.
437+
addAssignOriginFact(*CCE, *CCE->getArg(0));
438+
else
439+
// This could be a new borrow.
440+
checkForBorrows(CCE, CCE->getConstructor(),
441+
{CCE->getArgs(), CCE->getNumArgs()});
442+
}
443+
444+
void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
445+
if (!isGslPointerType(MCE->getImplicitObjectArgument()->getType()))
446+
return;
447+
// Specifically for conversion operators, like `std::string_view p = a;`
448+
if (isa<CXXConversionDecl>(MCE->getCalleeDecl())) {
449+
// The argument is the implicit object itself.
450+
checkForBorrows(MCE, MCE->getMethodDecl(),
451+
{MCE->getImplicitObjectArgument()});
452+
}
453+
// Note: A more general VisitCallExpr could also be used here.
454+
}
455+
431456
void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) {
432457
/// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized
433458
/// pointers can use the same type of loan.
@@ -479,29 +504,39 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> {
479504
}
480505

481506
void VisitBinaryOperator(const BinaryOperator *BO) {
482-
if (BO->isAssignmentOp()) {
483-
const Expr *LHSExpr = BO->getLHS();
484-
const Expr *RHSExpr = BO->getRHS();
485-
486-
// We are interested in assignments like `ptr1 = ptr2` or `ptr = &var`
487-
// LHS must be a pointer/reference type that can be an origin.
488-
// RHS must also represent an origin (either another pointer/ref or an
489-
// address-of).
490-
if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr))
491-
if (const auto *VD_LHS =
492-
dyn_cast<ValueDecl>(DRE_LHS->getDecl()->getCanonicalDecl());
493-
VD_LHS && hasOrigin(VD_LHS->getType()))
494-
addAssignOriginFact(*VD_LHS, *RHSExpr);
495-
}
507+
if (BO->isAssignmentOp())
508+
handleAssignment(BO->getLHS(), BO->getRHS());
509+
}
510+
511+
void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
512+
if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2)
513+
handleAssignment(OCE->getArg(0), OCE->getArg(1));
496514
}
497515

498516
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) {
499517
// Check if this is a test point marker. If so, we are done with this
500518
// expression.
501519
if (VisitTestPoint(FCE))
502520
return;
503-
// Visit as normal otherwise.
504-
Base::VisitCXXFunctionalCastExpr(FCE);
521+
if (isGslPointerType(FCE->getType()))
522+
addAssignOriginFact(*FCE, *FCE->getSubExpr());
523+
}
524+
525+
void VisitInitListExpr(const InitListExpr *ILE) {
526+
if (!hasOrigin(ILE->getType()))
527+
return;
528+
// For list initialization with a single element, like `View{...}`, the
529+
// origin of the list itself is the origin of its single element.
530+
if (ILE->getNumInits() == 1)
531+
addAssignOriginFact(*ILE, *ILE->getInit(0));
532+
}
533+
534+
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE) {
535+
if (!hasOrigin(MTE->getType()))
536+
return;
537+
// A temporary object's origin is the same as the origin of the
538+
// expression that initializes it.
539+
addAssignOriginFact(*MTE, *MTE->getSubExpr());
505540
}
506541

507542
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
@@ -527,8 +562,88 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> {
527562
}
528563

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

533648
template <typename Destination, typename Source>
534649
void addAssignOriginFact(const Destination &D, const Source &S) {
@@ -578,10 +693,13 @@ class FactGenerator : public RecursiveASTVisitor<FactGenerator> {
578693
for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
579694
FactGeneratorBlockRAII BlockGenerator(FG, Block);
580695
for (const CFGElement &Element : *Block) {
581-
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
696+
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) {
697+
DEBUG_WITH_TYPE("PrintCFG", llvm::dbgs() << " ================== \n");
698+
DEBUG_WITH_TYPE("PrintCFG", CS->dump());
699+
DEBUG_WITH_TYPE("PrintCFG", CS->getStmt()->dumpColor());
582700
TraverseStmt(const_cast<Stmt *>(CS->getStmt()));
583-
else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
584-
Element.getAs<CFGAutomaticObjDtor>())
701+
} else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
702+
Element.getAs<CFGAutomaticObjDtor>())
585703
FG.handleDestructor(*DtorOpt);
586704
}
587705
}

clang/unittests/Analysis/LifetimeSafetyTest.cpp

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include "clang/ASTMatchers/ASTMatchers.h"
1212
#include "clang/Testing/TestAST.h"
1313
#include "llvm/ADT/StringMap.h"
14-
#include "llvm/Testing/Support/Error.h"
1514
#include "gmock/gmock.h"
1615
#include "gtest/gtest.h"
1716
#include <optional>
@@ -31,7 +30,13 @@ class LifetimeTestRunner {
3130
LifetimeTestRunner(llvm::StringRef Code) {
3231
std::string FullCode = R"(
3332
#define POINT(name) void("__lifetime_test_point_" #name)
33+
3434
struct MyObj { ~MyObj() {} int i; };
35+
36+
struct [[gsl::Pointer()]] View {
37+
View(const MyObj&);
38+
View();
39+
};
3540
)";
3641
FullCode += Code.str();
3742

@@ -741,5 +746,151 @@ TEST_F(LifetimeAnalysisTest, NoDuplicateLoansForImplicitCastToConst) {
741746
EXPECT_THAT(Helper->getLoansForVar("a"), SizeIs(2));
742747
}
743748

749+
TEST_F(LifetimeAnalysisTest, GslPointerSimpleLoan) {
750+
SetupTest(R"(
751+
void target() {
752+
MyObj a;
753+
View x = a;
754+
POINT(p1);
755+
}
756+
)");
757+
EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1"));
758+
}
759+
760+
TEST_F(LifetimeAnalysisTest, GslPointerConstructFromOwner) {
761+
SetupTest(R"(
762+
void target() {
763+
MyObj al, bl, cl, dl, el;
764+
View a = View(al);
765+
View b = View{bl};
766+
View c = View(View(View(cl)));
767+
View d = View{View(View(dl))};
768+
View e = View{View{View{el}}};
769+
POINT(p1);
770+
}
771+
)");
772+
EXPECT_THAT(Origin("a"), HasLoansTo({"al"}, "p1"));
773+
EXPECT_THAT(Origin("b"), HasLoansTo({"bl"}, "p1"));
774+
EXPECT_THAT(Origin("c"), HasLoansTo({"cl"}, "p1"));
775+
EXPECT_THAT(Origin("d"), HasLoansTo({"dl"}, "p1"));
776+
EXPECT_THAT(Origin("e"), HasLoansTo({"el"}, "p1"));
777+
}
778+
779+
TEST_F(LifetimeAnalysisTest, GslPointerConstructFromView) {
780+
SetupTest(R"(
781+
void target() {
782+
MyObj a;
783+
View x = View(a);
784+
View y = View{x};
785+
View z = View(View(View(y)));
786+
View p = View{View(View(x))};
787+
POINT(p1);
788+
}
789+
)");
790+
EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1"));
791+
EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p1"));
792+
EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p1"));
793+
EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1"));
794+
}
795+
796+
// FIXME: Handle loans in ternary operator!
797+
TEST_F(LifetimeAnalysisTest, GslPointerInConditionalOperator) {
798+
SetupTest(R"(
799+
void target(bool cond) {
800+
MyObj a, b;
801+
View v = cond ? a : b;
802+
POINT(p1);
803+
}
804+
)");
805+
EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1"));
806+
}
807+
808+
// FIXME: Handle temporaries.
809+
TEST_F(LifetimeAnalysisTest, ViewFromTemporary) {
810+
SetupTest(R"(
811+
MyObj temporary();
812+
void target() {
813+
View v = temporary();
814+
POINT(p1);
815+
}
816+
)");
817+
EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1"));
818+
}
819+
820+
TEST_F(LifetimeAnalysisTest, GslPointerWithConstAndAuto) {
821+
SetupTest(R"(
822+
void target() {
823+
MyObj a;
824+
const View v1 = a;
825+
auto v2 = v1;
826+
const auto& v3 = v2;
827+
POINT(p1);
828+
}
829+
)");
830+
EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
831+
EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p1"));
832+
EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p1"));
833+
}
834+
835+
TEST_F(LifetimeAnalysisTest, GslPointerPropagation) {
836+
SetupTest(R"(
837+
void target() {
838+
MyObj a;
839+
View x = a;
840+
POINT(p1);
841+
842+
View y = x; // Propagation via copy-construction
843+
POINT(p2);
844+
845+
View z;
846+
z = x; // Propagation via copy-assignment
847+
POINT(p3);
848+
}
849+
)");
850+
851+
EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1"));
852+
EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p2"));
853+
EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p3"));
854+
}
855+
856+
TEST_F(LifetimeAnalysisTest, GslPointerLoanExpiration) {
857+
SetupTest(R"(
858+
void target() {
859+
View x;
860+
{
861+
MyObj a;
862+
x = a;
863+
POINT(before_expiry);
864+
} // `a` is destroyed here.
865+
POINT(after_expiry);
866+
}
867+
)");
868+
869+
EXPECT_THAT(NoLoans(), AreExpiredAt("before_expiry"));
870+
EXPECT_THAT(LoansTo({"a"}), AreExpiredAt("after_expiry"));
871+
}
872+
873+
TEST_F(LifetimeAnalysisTest, GslPointerReassignment) {
874+
SetupTest(R"(
875+
void target() {
876+
MyObj safe;
877+
View v;
878+
v = safe;
879+
POINT(p1);
880+
{
881+
MyObj unsafe;
882+
v = unsafe;
883+
POINT(p2);
884+
} // `unsafe` expires here.
885+
POINT(p3);
886+
}
887+
)");
888+
889+
EXPECT_THAT(Origin("v"), HasLoansTo({"safe"}, "p1"));
890+
EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p2"));
891+
EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p3"));
892+
EXPECT_THAT(LoansTo({"unsafe"}), AreExpiredAt("p3"));
893+
}
894+
744895
} // anonymous namespace
745896
} // namespace clang::lifetimes::internal

0 commit comments

Comments
 (0)