Skip to content

Commit 031309f

Browse files
committed
lifetime-analysis-lifetimebound
1 parent 0775d9e commit 031309f

File tree

6 files changed

+639
-134
lines changed

6 files changed

+639
-134
lines changed

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,14 @@ template <typename Tag> struct ID {
7575
}
7676
};
7777

78-
template <typename Tag>
79-
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
80-
return OS << ID.Value;
81-
}
82-
8378
using LoanID = ID<struct LoanTag>;
8479
using OriginID = ID<struct OriginTag>;
80+
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
81+
return OS << ID.Value;
82+
}
83+
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) {
84+
return OS << ID.Value;
85+
}
8586

8687
// Using LLVM's immutable collections is efficient for dataflow analysis
8788
// as it avoids deep copies during state transitions.

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 149 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,10 @@ class Fact {
212212
/// A loan expires as its underlying storage is freed (e.g., variable goes
213213
/// out of scope).
214214
Expire,
215+
/// The loan set of an origin is cleared.
216+
KillOrigin,
215217
/// An origin is propagated from a source to a destination (e.g., p = q).
216-
AssignOrigin,
218+
OriginFlow,
217219
/// An origin escapes the function by flowing into the return value.
218220
ReturnOfOrigin,
219221
/// An origin is used (eg. dereferencing a pointer).
@@ -285,22 +287,24 @@ class ExpireFact : public Fact {
285287
}
286288
};
287289

288-
class AssignOriginFact : public Fact {
290+
class OriginFlowFact : public Fact {
289291
OriginID OIDDest;
290292
OriginID OIDSrc;
291293

292294
public:
293295
static bool classof(const Fact *F) {
294-
return F->getKind() == Kind::AssignOrigin;
296+
return F->getKind() == Kind::OriginFlow;
295297
}
296298

297-
AssignOriginFact(OriginID OIDDest, OriginID OIDSrc)
298-
: Fact(Kind::AssignOrigin), OIDDest(OIDDest), OIDSrc(OIDSrc) {}
299+
OriginFlowFact(OriginID OIDDest, OriginID OIDSrc)
300+
: Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc) {}
301+
299302
OriginID getDestOriginID() const { return OIDDest; }
300303
OriginID getSrcOriginID() const { return OIDSrc; }
304+
301305
void dump(llvm::raw_ostream &OS, const LoanManager &,
302306
const OriginManager &OM) const override {
303-
OS << "AssignOrigin (Dest: ";
307+
OS << "OriginFlow (Dest: ";
304308
OM.dump(getDestOriginID(), OS);
305309
OS << ", Src: ";
306310
OM.dump(getSrcOriginID(), OS);
@@ -353,6 +357,23 @@ class UseFact : public Fact {
353357
}
354358
};
355359

360+
class KillOriginFact : public Fact {
361+
OriginID OID;
362+
363+
public:
364+
static bool classof(const Fact *F) {
365+
return F->getKind() == Kind::KillOrigin;
366+
}
367+
KillOriginFact(OriginID OID) : Fact(Kind::KillOrigin), OID(OID) {}
368+
OriginID getOriginID() const { return OID; }
369+
370+
void dump(llvm::raw_ostream &OS, const LoanManager &,
371+
const OriginManager &OM) const override {
372+
OS << "KillOrigin (";
373+
OM.dump(getOriginID(), OS);
374+
OS << ")\n";
375+
}
376+
};
356377
/// A dummy-fact used to mark a specific point in the code for testing.
357378
/// It is generated by recognizing a `void("__lifetime_test_point_...")` cast.
358379
class TestPointFact : public Fact {
@@ -453,8 +474,10 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
453474
for (const Decl *D : DS->decls())
454475
if (const auto *VD = dyn_cast<VarDecl>(D))
455476
if (hasOrigin(VD))
456-
if (const Expr *InitExpr = VD->getInit())
457-
addAssignOriginFact(*VD, *InitExpr);
477+
if (const Expr *InitExpr = VD->getInit()) {
478+
killOrigin(VD);
479+
addOriginFlowFact(*VD, *InitExpr);
480+
}
458481
}
459482

460483
void VisitDeclRefExpr(const DeclRefExpr *DRE) {
@@ -492,9 +515,23 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
492515
isa<CXXConversionDecl>(MCE->getCalleeDecl())) {
493516
// The argument is the implicit object itself.
494517
handleFunctionCall(MCE, MCE->getMethodDecl(),
495-
{MCE->getImplicitObjectArgument()});
518+
{MCE->getImplicitObjectArgument()},
519+
/*IsGslConstruction=*/true);
496520
}
497-
// FIXME: A more general VisitCallExpr could also be used here.
521+
if (const CXXMethodDecl *Method = MCE->getMethodDecl()) {
522+
// Construct the argument list, with the implicit 'this' object as the
523+
// first argument.
524+
llvm::SmallVector<const Expr *, 4> Args;
525+
Args.push_back(MCE->getImplicitObjectArgument());
526+
Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs());
527+
528+
handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false);
529+
}
530+
}
531+
532+
void VisitCallExpr(const CallExpr *CE) {
533+
handleFunctionCall(CE, CE->getDirectCallee(),
534+
{CE->getArgs(), CE->getNumArgs()});
498535
}
499536

500537
void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) {
@@ -508,7 +545,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
508545
return;
509546
// An ImplicitCastExpr node itself gets an origin, which flows from the
510547
// origin of its sub-expression (after stripping its own parens/casts).
511-
addAssignOriginFact(*ICE, *ICE->getSubExpr());
548+
addOriginFlowFact(*ICE, *ICE->getSubExpr());
512549
}
513550

514551
void VisitUnaryOperator(const UnaryOperator *UO) {
@@ -522,7 +559,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
522559
// its sub-expression (x). This fact will cause the dataflow analysis
523560
// to propagate any loans held by the sub-expression's origin to the
524561
// origin of this UnaryOperator expression.
525-
addAssignOriginFact(*UO, *SubExpr);
562+
addOriginFlowFact(*UO, *SubExpr);
526563
}
527564
}
528565

@@ -542,8 +579,15 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
542579
}
543580

544581
void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
545-
if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2)
582+
// Assignment operators have special "kill-then-propagate" semantics
583+
// and are handled separately.
584+
if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) {
546585
handleAssignment(OCE->getArg(0), OCE->getArg(1));
586+
return;
587+
}
588+
handleFunctionCall(OCE, OCE->getDirectCallee(),
589+
{OCE->getArgs(), OCE->getNumArgs()},
590+
/*IsGslConstruction=*/false);
547591
}
548592

549593
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) {
@@ -552,7 +596,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
552596
if (handleTestPoint(FCE))
553597
return;
554598
if (isGslPointerType(FCE->getType()))
555-
addAssignOriginFact(*FCE, *FCE->getSubExpr());
599+
addOriginFlowFact(*FCE, *FCE->getSubExpr());
556600
}
557601

558602
void VisitInitListExpr(const InitListExpr *ILE) {
@@ -561,15 +605,15 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
561605
// For list initialization with a single element, like `View{...}`, the
562606
// origin of the list itself is the origin of its single element.
563607
if (ILE->getNumInits() == 1)
564-
addAssignOriginFact(*ILE, *ILE->getInit(0));
608+
addOriginFlowFact(*ILE, *ILE->getInit(0));
565609
}
566610

567611
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE) {
568612
if (!hasOrigin(MTE))
569613
return;
570614
// A temporary object's origin is the same as the origin of the
571615
// expression that initializes it.
572-
addAssignOriginFact(*MTE, *MTE->getSubExpr());
616+
addOriginFlowFact(*MTE, *MTE->getSubExpr());
573617
}
574618

575619
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
@@ -624,34 +668,68 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
624668
if (CCE->getNumArgs() != 1)
625669
return;
626670
if (hasOrigin(CCE->getArg(0)))
627-
addAssignOriginFact(*CCE, *CCE->getArg(0));
671+
addOriginFlowFact(*CCE, *CCE->getArg(0));
628672
else
629673
// This could be a new borrow.
630674
handleFunctionCall(CCE, CCE->getConstructor(),
631-
{CCE->getArgs(), CCE->getNumArgs()});
675+
{CCE->getArgs(), CCE->getNumArgs()},
676+
/*IsGslConstruction=*/true);
677+
}
678+
static const FunctionDecl *
679+
getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
680+
return FD != nullptr ? FD->getMostRecentDecl() : nullptr;
632681
}
633682

683+
static const CXXMethodDecl *
684+
getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD) {
685+
const FunctionDecl *FD = CMD;
686+
return cast_if_present<CXXMethodDecl>(
687+
getDeclWithMergedLifetimeBoundAttrs(FD));
688+
}
689+
static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
690+
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
691+
const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
692+
if (!TSI)
693+
return false;
694+
// Don't declare this variable in the second operand of the for-statement;
695+
// GCC miscompiles that by ending its lifetime before evaluating the
696+
// third operand. See gcc.gnu.org/PR86769.
697+
AttributedTypeLoc ATL;
698+
for (TypeLoc TL = TSI->getTypeLoc();
699+
(ATL = TL.getAsAdjusted<AttributedTypeLoc>());
700+
TL = ATL.getModifiedLoc()) {
701+
if (ATL.getAttrAs<LifetimeBoundAttr>())
702+
return true;
703+
}
704+
return false;
705+
}
634706
/// Checks if a call-like expression creates a borrow by passing a value to a
635707
/// reference parameter, creating an IssueFact if it does.
636708
void handleFunctionCall(const Expr *Call, const FunctionDecl *FD,
637-
ArrayRef<const Expr *> Args) {
638-
if (!FD)
709+
ArrayRef<const Expr *> Args,
710+
bool IsGslConstruction = false) {
711+
// Ignore functions returning values with no origin.
712+
if (!FD || !hasOrigin(Call))
639713
return;
640-
// TODO: Handle more than one arguments.
641-
for (unsigned I = 0; I <= 0 /*Args.size()*/; ++I) {
642-
const Expr *ArgExpr = Args[I];
643-
644-
// Propagate origins for CXX this.
645-
if (FD->isCXXClassMember() && I == 0) {
646-
addAssignOriginFact(*Call, *ArgExpr);
647-
continue;
648-
}
649-
// The parameter is a pointer, reference, or gsl::Pointer.
650-
// This is a borrow. We propagate the origin from the argument expression
651-
// at the call site to the parameter declaration in the callee.
652-
if (hasOrigin(ArgExpr))
653-
addAssignOriginFact(*Call, *ArgExpr);
654-
}
714+
auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
715+
const ParmVarDecl *PVD = nullptr;
716+
if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
717+
Method && Method->isInstance()) {
718+
if (I == 0)
719+
// For the 'this' argument, the attribute is on the method itself.
720+
return implicitObjectParamIsLifetimeBound(Method);
721+
if ((I - 1) < Method->getNumParams())
722+
// For explicit arguments, find the corresponding parameter
723+
// declaration.
724+
PVD = Method->getParamDecl(I - 1);
725+
} else if (I < FD->getNumParams())
726+
// For free functions or static methods.
727+
PVD = FD->getParamDecl(I);
728+
return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false;
729+
};
730+
for (unsigned I = 0; I < Args.size(); ++I)
731+
if (IsGslConstruction || IsArgLifetimeBound(I))
732+
addOriginFlowFact(*Call, *Args[I]);
655733
}
656734

657735
/// Creates a loan for the storage path of a given declaration reference.
@@ -668,11 +746,16 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
668746
}
669747

670748
template <typename Destination, typename Source>
671-
void addAssignOriginFact(const Destination &D, const Source &S) {
749+
void addOriginFlowFact(const Destination &D, const Source &S) {
672750
OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
673751
OriginID SrcOID = FactMgr.getOriginMgr().get(S);
674752
CurrentBlockFacts.push_back(
675-
FactMgr.createFact<AssignOriginFact>(DestOID, SrcOID));
753+
FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID));
754+
}
755+
756+
void killOrigin(const ValueDecl *D) {
757+
OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(*D);
758+
CurrentBlockFacts.push_back(FactMgr.createFact<KillOriginFact>(DestOID));
676759
}
677760

678761
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
@@ -703,12 +786,12 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
703786
if (const auto *DRE_LHS =
704787
dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
705788
markUseAsWrite(DRE_LHS);
706-
if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl()))
707-
// We are interested in assignments like `ptr1 = ptr2` or `ptr = &var`.
708-
// LHS must be a pointer/reference type that can be an origin. RHS must
709-
// also represent an origin (either another pointer/ref or an
710-
// address-of).
711-
addAssignOriginFact(*VD_LHS, *RHSExpr);
789+
if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) {
790+
// Kill the old loans of the destination origin and flow the new loans
791+
// from the source origin.
792+
killOrigin(VD_LHS);
793+
addOriginFlowFact(*VD_LHS, *RHSExpr);
794+
}
712795
}
713796
}
714797

@@ -882,8 +965,10 @@ class DataflowAnalysis {
882965
return D->transfer(In, *F->getAs<IssueFact>());
883966
case Fact::Kind::Expire:
884967
return D->transfer(In, *F->getAs<ExpireFact>());
885-
case Fact::Kind::AssignOrigin:
886-
return D->transfer(In, *F->getAs<AssignOriginFact>());
968+
case Fact::Kind::OriginFlow:
969+
return D->transfer(In, *F->getAs<OriginFlowFact>());
970+
case Fact::Kind::KillOrigin:
971+
return D->transfer(In, *F->getAs<KillOriginFact>());
887972
case Fact::Kind::ReturnOfOrigin:
888973
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
889974
case Fact::Kind::Use:
@@ -897,7 +982,8 @@ class DataflowAnalysis {
897982
public:
898983
Lattice transfer(Lattice In, const IssueFact &) { return In; }
899984
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
900-
Lattice transfer(Lattice In, const AssignOriginFact &) { return In; }
985+
Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
986+
Lattice transfer(Lattice In, const KillOriginFact &) { return In; }
901987
Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
902988
Lattice transfer(Lattice In, const UseFact &) { return In; }
903989
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
@@ -1049,14 +1135,27 @@ class LoanPropagationAnalysis
10491135
LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID)));
10501136
}
10511137

1052-
/// The destination origin's loan set is replaced by the source's.
1053-
/// This implicitly "resets" the old loans of the destination.
1054-
Lattice transfer(Lattice In, const AssignOriginFact &F) {
1138+
/// A flow from source to destination adds the source's loans to the
1139+
/// destination's, without clearing the destination's existing loans.
1140+
Lattice transfer(Lattice In, const OriginFlowFact &F) {
10551141
OriginID DestOID = F.getDestOriginID();
10561142
OriginID SrcOID = F.getSrcOriginID();
1143+
1144+
LoanSet DestLoans = getLoans(In, DestOID);
10571145
LoanSet SrcLoans = getLoans(In, SrcOID);
1146+
LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
1147+
10581148
return LoanPropagationLattice(
1059-
OriginLoanMapFactory.add(In.Origins, DestOID, SrcLoans));
1149+
OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans));
1150+
}
1151+
1152+
/// Clears the loan set of the specified origin. This is used on the
1153+
/// left-hand side of an assignment to invalidate the variable's old lifetime.
1154+
Lattice transfer(Lattice In, const KillOriginFact &F) {
1155+
OriginID OID = F.getOriginID();
1156+
// Replace the origin's loan set with an empty set.
1157+
return LoanPropagationLattice(OriginLoanMapFactory.add(
1158+
In.Origins, OID, LoanSetFactory.getEmptySet()));
10601159
}
10611160

10621161
LoanSet getLoans(OriginID OID, ProgramPoint P) {

clang/test/Analysis/LifetimeSafety/benchmark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ def run_single_test(
340340
"name": "cycle",
341341
"title": "Pointer Cycle in Loop",
342342
"generator_func": generate_cpp_cycle_test,
343-
"n_values": [25, 50, 75, 100],
343+
"n_values": [50, 75, 100, 200, 300],
344344
},
345345
{
346346
"name": "merge",

0 commit comments

Comments
 (0)