Skip to content

Commit 651cb0b

Browse files
committed
Implicit lifetimebound for std namespace
1 parent 96bad25 commit 651cb0b

File tree

7 files changed

+294
-63
lines changed

7 files changed

+294
-63
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
4848
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE);
4949
void VisitInitListExpr(const InitListExpr *ILE);
5050
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
51+
void VisitExprWithCleanups(const ExprWithCleanups *EC);
5152

5253
private:
5354
OriginList *getOriginsList(const ValueDecl &D);

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD);
3838
/// method or because it's a normal assignment operator.
3939
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
4040

41+
// Returns true if the implicit object argument (this) of a method call should
42+
// be tracked for GSL lifetime analysis. This applies to STL methods that return
43+
// pointers or references that depend on the lifetime of the object, such as
44+
// container iterators (begin, end), data accessors (c_str, data, get), or
45+
// element accessors (operator[], operator*, front, back, at).
46+
bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee);
47+
48+
// Returns true if the first argument of a free function should be tracked for
49+
// GSL lifetime analysis. This applies to STL free functions that take a pointer
50+
// to a GSL Owner or Pointer and return a pointer or reference that depends on
51+
// the lifetime of the argument, such as std::begin, std::data, std::get, or
52+
// std::any_cast.
53+
bool shouldTrackFirstArgument(const FunctionDecl *FD);
54+
4155
// Tells whether the type is annotated with [[gsl::Pointer]].
4256
bool isGslPointerType(QualType QT);
4357
// Tells whether the type is annotated with [[gsl::Owner]].

clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
1515
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
1616
#include "llvm/Support/Casting.h"
17+
#include "llvm/Support/Debug.h"
1718
#include "llvm/Support/Signals.h"
1819
#include "llvm/Support/TimeProfiler.h"
1920

@@ -328,6 +329,12 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
328329
}
329330
}
330331

332+
void FactsGenerator::VisitExprWithCleanups(const ExprWithCleanups *EC) {
333+
if (hasOrigins(EC)) {
334+
killAndFlowOrigin(*EC, *EC->getSubExpr());
335+
}
336+
}
337+
331338
void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
332339
/// TODO: Handle loans to temporaries.
333340
const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl();
@@ -387,11 +394,14 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
387394
Method && Method->isInstance()) {
388395
if (I == 0)
389396
// For the 'this' argument, the attribute is on the method itself.
390-
return implicitObjectParamIsLifetimeBound(Method);
397+
return implicitObjectParamIsLifetimeBound(Method) ||
398+
shouldTrackImplicitObjectArg(Method);
391399
if ((I - 1) < Method->getNumParams())
392400
// For explicit arguments, find the corresponding parameter
393401
// declaration.
394402
PVD = Method->getParamDecl(I - 1);
403+
} else if (I == 0 && shouldTrackFirstArgument(FD)) {
404+
return true;
395405
} else if (I < FD->getNumParams()) {
396406
// For free functions or static methods.
397407
PVD = FD->getParamDecl(I);

clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,88 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
7171
return isNormalAssignmentOperator(FD);
7272
}
7373

74+
// Decl::isInStdNamespace will return false for iterators in some STL
75+
// implementations due to them being defined in a namespace outside of the std
76+
// namespace.
77+
static bool isInStlNamespace(const Decl *D) {
78+
const DeclContext *DC = D->getDeclContext();
79+
if (!DC)
80+
return false;
81+
if (const auto *ND = dyn_cast<NamespaceDecl>(DC))
82+
if (const IdentifierInfo *II = ND->getIdentifier()) {
83+
StringRef Name = II->getName();
84+
if (Name.size() >= 2 && Name.front() == '_' &&
85+
(Name[1] == '_' || isUppercase(Name[1])))
86+
return true;
87+
}
88+
89+
return DC->isStdNamespace();
90+
}
91+
92+
static bool isPointerLikeType(QualType QT) {
93+
return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
94+
}
95+
96+
bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
97+
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
98+
if (isGslPointerType(Conv->getConversionType()) &&
99+
Callee->getParent()->hasAttr<OwnerAttr>())
100+
return true;
101+
if (!isInStlNamespace(Callee->getParent()))
102+
return false;
103+
if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
104+
!isGslOwnerType(Callee->getFunctionObjectParameterType()))
105+
return false;
106+
if (isPointerLikeType(Callee->getReturnType())) {
107+
if (!Callee->getIdentifier())
108+
return false;
109+
return llvm::StringSwitch<bool>(Callee->getName())
110+
.Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
111+
.Cases({"end", "rend", "cend", "crend"}, true)
112+
.Cases({"c_str", "data", "get"}, true)
113+
// Map and set types.
114+
.Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true)
115+
.Default(false);
116+
}
117+
if (Callee->getReturnType()->isReferenceType()) {
118+
if (!Callee->getIdentifier()) {
119+
auto OO = Callee->getOverloadedOperator();
120+
if (!Callee->getParent()->hasAttr<OwnerAttr>())
121+
return false;
122+
return OO == OverloadedOperatorKind::OO_Subscript ||
123+
OO == OverloadedOperatorKind::OO_Star;
124+
}
125+
return llvm::StringSwitch<bool>(Callee->getName())
126+
.Cases({"front", "back", "at", "top", "value"}, true)
127+
.Default(false);
128+
}
129+
return false;
130+
}
131+
132+
bool shouldTrackFirstArgument(const FunctionDecl *FD) {
133+
if (!FD->getIdentifier() || FD->getNumParams() != 1)
134+
return false;
135+
const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
136+
if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
137+
return false;
138+
if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
139+
return false;
140+
if (FD->getReturnType()->isPointerType() ||
141+
isGslPointerType(FD->getReturnType())) {
142+
return llvm::StringSwitch<bool>(FD->getName())
143+
.Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
144+
.Cases({"end", "rend", "cend", "crend"}, true)
145+
.Case("data", true)
146+
.Default(false);
147+
}
148+
if (FD->getReturnType()->isReferenceType()) {
149+
return llvm::StringSwitch<bool>(FD->getName())
150+
.Cases({"get", "any_cast"}, true)
151+
.Default(false);
152+
}
153+
return false;
154+
}
155+
74156
template <typename T> static bool isRecordWithAttr(QualType Type) {
75157
auto *RD = Type->getAsCXXRecordDecl();
76158
if (!RD)

clang/lib/Analysis/LifetimeSafety/Origins.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "clang/AST/Attr.h"
1212
#include "clang/AST/DeclCXX.h"
1313
#include "clang/AST/DeclTemplate.h"
14+
#include "clang/AST/ExprCXX.h"
1415
#include "clang/AST/TypeBase.h"
1516
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
1617

@@ -79,6 +80,8 @@ OriginList *OriginManager::getOrCreateList(const ValueDecl *D) {
7980
OriginList *OriginManager::getOrCreateList(const Expr *E, size_t Depth) {
8081
if (auto *ParenIgnored = E->IgnoreParens(); ParenIgnored != E)
8182
return getOrCreateList(ParenIgnored);
83+
if (auto *EC = dyn_cast<ExprWithCleanups>(E))
84+
return getOrCreateList(EC->getSubExpr(), Depth);
8285

8386
if (!hasOrigins(E))
8487
return nullptr;

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 2 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -320,66 +320,6 @@ static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
320320
return false;
321321
}
322322

323-
static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
324-
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
325-
if (isGslPointerType(Conv->getConversionType()) &&
326-
Callee->getParent()->hasAttr<OwnerAttr>())
327-
return true;
328-
if (!isInStlNamespace(Callee->getParent()))
329-
return false;
330-
if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
331-
!isGslOwnerType(Callee->getFunctionObjectParameterType()))
332-
return false;
333-
if (isPointerLikeType(Callee->getReturnType())) {
334-
if (!Callee->getIdentifier())
335-
return false;
336-
return llvm::StringSwitch<bool>(Callee->getName())
337-
.Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
338-
.Cases({"end", "rend", "cend", "crend"}, true)
339-
.Cases({"c_str", "data", "get"}, true)
340-
// Map and set types.
341-
.Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true)
342-
.Default(false);
343-
}
344-
if (Callee->getReturnType()->isReferenceType()) {
345-
if (!Callee->getIdentifier()) {
346-
auto OO = Callee->getOverloadedOperator();
347-
if (!Callee->getParent()->hasAttr<OwnerAttr>())
348-
return false;
349-
return OO == OverloadedOperatorKind::OO_Subscript ||
350-
OO == OverloadedOperatorKind::OO_Star;
351-
}
352-
return llvm::StringSwitch<bool>(Callee->getName())
353-
.Cases({"front", "back", "at", "top", "value"}, true)
354-
.Default(false);
355-
}
356-
return false;
357-
}
358-
359-
static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
360-
if (!FD->getIdentifier() || FD->getNumParams() != 1)
361-
return false;
362-
const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
363-
if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
364-
return false;
365-
if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
366-
return false;
367-
if (FD->getReturnType()->isPointerType() ||
368-
isGslPointerType(FD->getReturnType())) {
369-
return llvm::StringSwitch<bool>(FD->getName())
370-
.Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
371-
.Cases({"end", "rend", "cend", "crend"}, true)
372-
.Case("data", true)
373-
.Default(false);
374-
}
375-
if (FD->getReturnType()->isReferenceType()) {
376-
return llvm::StringSwitch<bool>(FD->getName())
377-
.Cases({"get", "any_cast"}, true)
378-
.Default(false);
379-
}
380-
return false;
381-
}
382-
383323
// Returns true if the given constructor is a copy-like constructor, such as
384324
// `Ctor(Owner<U>&&)` or `Ctor(const Owner<U>&)`.
385325
static bool isCopyLikeConstructor(const CXXConstructorDecl *Ctor) {
@@ -564,7 +504,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
564504
VisitLifetimeBoundArg(Callee, ObjectArg);
565505
else if (EnableGSLAnalysis) {
566506
if (auto *CME = dyn_cast<CXXMethodDecl>(Callee);
567-
CME && shouldTrackImplicitObjectArg(CME))
507+
CME && lifetimes::shouldTrackImplicitObjectArg(CME))
568508
VisitGSLPointerArg(Callee, ObjectArg);
569509
}
570510
}
@@ -605,7 +545,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
605545
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
606546
else if (EnableGSLAnalysis && I == 0) {
607547
// Perform GSL analysis for the first argument
608-
if (shouldTrackFirstArgument(CanonCallee)) {
548+
if (lifetimes::shouldTrackFirstArgument(CanonCallee)) {
609549
VisitGSLPointerArg(CanonCallee, Arg);
610550
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
611551
Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {

0 commit comments

Comments
 (0)