-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[LifetimeSafety] Add implicit tracking for STL functions #170005
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: users/usx95/11-17-lifetime-safety-multi-origin
Are you sure you want to change the base?
[LifetimeSafety] Add implicit tracking for STL functions #170005
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
363a78c to
f19ea1b
Compare
3e3ccdc to
67d76ba
Compare
67d76ba to
96bad25
Compare
f19ea1b to
6b70da3
Compare
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
a71af86 to
651cb0b
Compare
|
@llvm/pr-subscribers-clang-analysis @llvm/pr-subscribers-clang Author: Utkarsh Saxena (usx95) ChangesAdd support for tracking STL container methods and free functions in the lifetime safety analysis.
Full diff: https://github.com/llvm/llvm-project/pull/170005.diff 7 Files Affected:
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 5b5626020e772..d3ef72a8d12dd 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -48,6 +48,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE);
void VisitInitListExpr(const InitListExpr *ILE);
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
+ void VisitExprWithCleanups(const ExprWithCleanups *EC);
private:
OriginList *getOriginsList(const ValueDecl &D);
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 1a16fb82f9a84..8e26a4d41a957 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -38,6 +38,20 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD);
/// method or because it's a normal assignment operator.
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
+// Returns true if the implicit object argument (this) of a method call should
+// be tracked for GSL lifetime analysis. This applies to STL methods that return
+// pointers or references that depend on the lifetime of the object, such as
+// container iterators (begin, end), data accessors (c_str, data, get), or
+// element accessors (operator[], operator*, front, back, at).
+bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee);
+
+// Returns true if the first argument of a free function should be tracked for
+// GSL lifetime analysis. This applies to STL free functions that take a pointer
+// to a GSL Owner or Pointer and return a pointer or reference that depends on
+// the lifetime of the argument, such as std::begin, std::data, std::get, or
+// std::any_cast.
+bool shouldTrackFirstArgument(const FunctionDecl *FD);
+
// Tells whether the type is annotated with [[gsl::Pointer]].
bool isGslPointerType(QualType QT);
// Tells whether the type is annotated with [[gsl::Owner]].
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 460be3ad99347..62b42a8dfb1b8 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -14,6 +14,7 @@
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/Debug.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/TimeProfiler.h"
@@ -328,6 +329,12 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
}
}
+void FactsGenerator::VisitExprWithCleanups(const ExprWithCleanups *EC) {
+ if (hasOrigins(EC)) {
+ killAndFlowOrigin(*EC, *EC->getSubExpr());
+ }
+}
+
void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
/// TODO: Handle loans to temporaries.
const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl();
@@ -387,11 +394,14 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
Method && Method->isInstance()) {
if (I == 0)
// For the 'this' argument, the attribute is on the method itself.
- return implicitObjectParamIsLifetimeBound(Method);
+ return implicitObjectParamIsLifetimeBound(Method) ||
+ shouldTrackImplicitObjectArg(Method);
if ((I - 1) < Method->getNumParams())
// For explicit arguments, find the corresponding parameter
// declaration.
PVD = Method->getParamDecl(I - 1);
+ } else if (I == 0 && shouldTrackFirstArgument(FD)) {
+ return true;
} else if (I < FD->getNumParams()) {
// For free functions or static methods.
PVD = FD->getParamDecl(I);
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 54e343fc2ee5e..860aa5373a32c 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -71,6 +71,88 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
return isNormalAssignmentOperator(FD);
}
+// Decl::isInStdNamespace will return false for iterators in some STL
+// implementations due to them being defined in a namespace outside of the std
+// namespace.
+static bool isInStlNamespace(const Decl *D) {
+ const DeclContext *DC = D->getDeclContext();
+ if (!DC)
+ return false;
+ if (const auto *ND = dyn_cast<NamespaceDecl>(DC))
+ if (const IdentifierInfo *II = ND->getIdentifier()) {
+ StringRef Name = II->getName();
+ if (Name.size() >= 2 && Name.front() == '_' &&
+ (Name[1] == '_' || isUppercase(Name[1])))
+ return true;
+ }
+
+ return DC->isStdNamespace();
+}
+
+static bool isPointerLikeType(QualType QT) {
+ return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
+}
+
+bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
+ if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
+ if (isGslPointerType(Conv->getConversionType()) &&
+ Callee->getParent()->hasAttr<OwnerAttr>())
+ return true;
+ if (!isInStlNamespace(Callee->getParent()))
+ return false;
+ if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
+ !isGslOwnerType(Callee->getFunctionObjectParameterType()))
+ return false;
+ if (isPointerLikeType(Callee->getReturnType())) {
+ if (!Callee->getIdentifier())
+ return false;
+ return llvm::StringSwitch<bool>(Callee->getName())
+ .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
+ .Cases({"end", "rend", "cend", "crend"}, true)
+ .Cases({"c_str", "data", "get"}, true)
+ // Map and set types.
+ .Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true)
+ .Default(false);
+ }
+ if (Callee->getReturnType()->isReferenceType()) {
+ if (!Callee->getIdentifier()) {
+ auto OO = Callee->getOverloadedOperator();
+ if (!Callee->getParent()->hasAttr<OwnerAttr>())
+ return false;
+ return OO == OverloadedOperatorKind::OO_Subscript ||
+ OO == OverloadedOperatorKind::OO_Star;
+ }
+ return llvm::StringSwitch<bool>(Callee->getName())
+ .Cases({"front", "back", "at", "top", "value"}, true)
+ .Default(false);
+ }
+ return false;
+}
+
+bool shouldTrackFirstArgument(const FunctionDecl *FD) {
+ if (!FD->getIdentifier() || FD->getNumParams() != 1)
+ return false;
+ const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
+ if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
+ return false;
+ if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
+ return false;
+ if (FD->getReturnType()->isPointerType() ||
+ isGslPointerType(FD->getReturnType())) {
+ return llvm::StringSwitch<bool>(FD->getName())
+ .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
+ .Cases({"end", "rend", "cend", "crend"}, true)
+ .Case("data", true)
+ .Default(false);
+ }
+ if (FD->getReturnType()->isReferenceType()) {
+ return llvm::StringSwitch<bool>(FD->getName())
+ .Cases({"get", "any_cast"}, true)
+ .Default(false);
+ }
+ return false;
+}
+
template <typename T> static bool isRecordWithAttr(QualType Type) {
auto *RD = Type->getAsCXXRecordDecl();
if (!RD)
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index ac8d8041f600b..65fc0c21e11b8 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -11,6 +11,7 @@
#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/AST/TypeBase.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
@@ -79,6 +80,8 @@ OriginList *OriginManager::getOrCreateList(const ValueDecl *D) {
OriginList *OriginManager::getOrCreateList(const Expr *E, size_t Depth) {
if (auto *ParenIgnored = E->IgnoreParens(); ParenIgnored != E)
return getOrCreateList(ParenIgnored);
+ if (auto *EC = dyn_cast<ExprWithCleanups>(E))
+ return getOrCreateList(EC->getSubExpr(), Depth);
if (!hasOrigins(E))
return nullptr;
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index c91ca751984c9..26e4d75b1fa49 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -320,66 +320,6 @@ static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
return false;
}
-static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
- if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
- if (isGslPointerType(Conv->getConversionType()) &&
- Callee->getParent()->hasAttr<OwnerAttr>())
- return true;
- if (!isInStlNamespace(Callee->getParent()))
- return false;
- if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
- !isGslOwnerType(Callee->getFunctionObjectParameterType()))
- return false;
- if (isPointerLikeType(Callee->getReturnType())) {
- if (!Callee->getIdentifier())
- return false;
- return llvm::StringSwitch<bool>(Callee->getName())
- .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
- .Cases({"end", "rend", "cend", "crend"}, true)
- .Cases({"c_str", "data", "get"}, true)
- // Map and set types.
- .Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true)
- .Default(false);
- }
- if (Callee->getReturnType()->isReferenceType()) {
- if (!Callee->getIdentifier()) {
- auto OO = Callee->getOverloadedOperator();
- if (!Callee->getParent()->hasAttr<OwnerAttr>())
- return false;
- return OO == OverloadedOperatorKind::OO_Subscript ||
- OO == OverloadedOperatorKind::OO_Star;
- }
- return llvm::StringSwitch<bool>(Callee->getName())
- .Cases({"front", "back", "at", "top", "value"}, true)
- .Default(false);
- }
- return false;
-}
-
-static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
- if (!FD->getIdentifier() || FD->getNumParams() != 1)
- return false;
- const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
- if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
- return false;
- if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
- return false;
- if (FD->getReturnType()->isPointerType() ||
- isGslPointerType(FD->getReturnType())) {
- return llvm::StringSwitch<bool>(FD->getName())
- .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
- .Cases({"end", "rend", "cend", "crend"}, true)
- .Case("data", true)
- .Default(false);
- }
- if (FD->getReturnType()->isReferenceType()) {
- return llvm::StringSwitch<bool>(FD->getName())
- .Cases({"get", "any_cast"}, true)
- .Default(false);
- }
- return false;
-}
-
// Returns true if the given constructor is a copy-like constructor, such as
// `Ctor(Owner<U>&&)` or `Ctor(const Owner<U>&)`.
static bool isCopyLikeConstructor(const CXXConstructorDecl *Ctor) {
@@ -564,7 +504,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
VisitLifetimeBoundArg(Callee, ObjectArg);
else if (EnableGSLAnalysis) {
if (auto *CME = dyn_cast<CXXMethodDecl>(Callee);
- CME && shouldTrackImplicitObjectArg(CME))
+ CME && lifetimes::shouldTrackImplicitObjectArg(CME))
VisitGSLPointerArg(Callee, ObjectArg);
}
}
@@ -605,7 +545,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
else if (EnableGSLAnalysis && I == 0) {
// Perform GSL analysis for the first argument
- if (shouldTrackFirstArgument(CanonCallee)) {
+ if (lifetimes::shouldTrackFirstArgument(CanonCallee)) {
VisitGSLPointerArg(CanonCallee, Arg);
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index ddc9cb602fc26..553354f052875 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -1597,5 +1597,186 @@ TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAR) {
EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
}
+// ========================================================================= //
+// Tests for shouldTrackImplicitObjectArg
+// ========================================================================= //
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_STLBegin) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct vector {
+ struct iterator {};
+ iterator begin();
+ };
+ }
+
+ void target() {
+ std::vector<int> vec;
+ auto it = vec.begin();
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1"));
+}
+
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_OwnerDeref) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct optional {
+ T& operator*();
+ };
+ }
+
+ void target() {
+ std::optional<int> opt;
+ int& r = *opt;
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_Value) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct optional {
+ T& value();
+ };
+ }
+
+ void target() {
+ std::optional<int> opt;
+ int& r = opt.value();
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_UniquePtr_Get) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct unique_ptr {
+ T *get() const;
+ };
+ }
+
+ void target() {
+ std::unique_ptr<int> up;
+ int* r = up.get();
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("r"), HasLoansTo({"up"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_ConversionOperator) {
+ SetupTest(R"(
+ struct [[gsl::Pointer(int)]] IntPtr {
+ int& operator*();
+ };
+
+ struct [[gsl::Owner(int)]] OwnerWithConversion {
+ operator IntPtr();
+ };
+
+ void target() {
+ OwnerWithConversion owner;
+ IntPtr ptr = owner;
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("ptr"), HasLoansTo({"owner"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_MapFind) {
+ SetupTest(R"(
+ namespace std {
+ template<typename K, typename V>
+ struct map {
+ struct iterator {};
+ iterator find(const K&);
+ };
+ }
+
+ void target() {
+ std::map<int, int> m;
+ auto it = m.find(42);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("it"), HasLoansTo({"m"}, "p1"));
+}
+
+// ========================================================================= //
+// Tests for shouldTrackFirstArgument
+// ========================================================================= //
+
+TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdBegin) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct vector {
+ struct iterator {};
+ iterator begin();
+ };
+
+ template<typename C>
+ auto begin(C& c) -> decltype(c.begin());
+ }
+
+ void target() {
+ std::vector<int> vec;
+ auto it = std::begin(vec);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdData) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct vector {
+ const T* data() const;
+ };
+
+ template<typename C>
+ auto data(C& c) -> decltype(c.data());
+ }
+
+ void target() {
+ std::vector<int> vec;
+ const int* p = std::data(vec);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("p"), HasLoansTo({"vec"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdAnyCast) {
+ SetupTest(R"(
+ namespace std {
+ struct any {};
+
+ template<typename T>
+ T any_cast(const any& op);
+ }
+
+ void target() {
+ std::any a;
+ int& r = std::any_cast<int&>(a);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("r"), HasLoansTo({"a"}, "p1"));
+}
+
} // anonymous namespace
} // namespace clang::lifetimes::internal
|
|
@llvm/pr-subscribers-clang-temporal-safety Author: Utkarsh Saxena (usx95) ChangesAdd support for tracking STL container methods and free functions in the lifetime safety analysis.
Full diff: https://github.com/llvm/llvm-project/pull/170005.diff 7 Files Affected:
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 5b5626020e772..d3ef72a8d12dd 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -48,6 +48,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE);
void VisitInitListExpr(const InitListExpr *ILE);
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
+ void VisitExprWithCleanups(const ExprWithCleanups *EC);
private:
OriginList *getOriginsList(const ValueDecl &D);
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 1a16fb82f9a84..8e26a4d41a957 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -38,6 +38,20 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD);
/// method or because it's a normal assignment operator.
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
+// Returns true if the implicit object argument (this) of a method call should
+// be tracked for GSL lifetime analysis. This applies to STL methods that return
+// pointers or references that depend on the lifetime of the object, such as
+// container iterators (begin, end), data accessors (c_str, data, get), or
+// element accessors (operator[], operator*, front, back, at).
+bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee);
+
+// Returns true if the first argument of a free function should be tracked for
+// GSL lifetime analysis. This applies to STL free functions that take a pointer
+// to a GSL Owner or Pointer and return a pointer or reference that depends on
+// the lifetime of the argument, such as std::begin, std::data, std::get, or
+// std::any_cast.
+bool shouldTrackFirstArgument(const FunctionDecl *FD);
+
// Tells whether the type is annotated with [[gsl::Pointer]].
bool isGslPointerType(QualType QT);
// Tells whether the type is annotated with [[gsl::Owner]].
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 460be3ad99347..62b42a8dfb1b8 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -14,6 +14,7 @@
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/Debug.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/TimeProfiler.h"
@@ -328,6 +329,12 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
}
}
+void FactsGenerator::VisitExprWithCleanups(const ExprWithCleanups *EC) {
+ if (hasOrigins(EC)) {
+ killAndFlowOrigin(*EC, *EC->getSubExpr());
+ }
+}
+
void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
/// TODO: Handle loans to temporaries.
const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl();
@@ -387,11 +394,14 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
Method && Method->isInstance()) {
if (I == 0)
// For the 'this' argument, the attribute is on the method itself.
- return implicitObjectParamIsLifetimeBound(Method);
+ return implicitObjectParamIsLifetimeBound(Method) ||
+ shouldTrackImplicitObjectArg(Method);
if ((I - 1) < Method->getNumParams())
// For explicit arguments, find the corresponding parameter
// declaration.
PVD = Method->getParamDecl(I - 1);
+ } else if (I == 0 && shouldTrackFirstArgument(FD)) {
+ return true;
} else if (I < FD->getNumParams()) {
// For free functions or static methods.
PVD = FD->getParamDecl(I);
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 54e343fc2ee5e..860aa5373a32c 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -71,6 +71,88 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
return isNormalAssignmentOperator(FD);
}
+// Decl::isInStdNamespace will return false for iterators in some STL
+// implementations due to them being defined in a namespace outside of the std
+// namespace.
+static bool isInStlNamespace(const Decl *D) {
+ const DeclContext *DC = D->getDeclContext();
+ if (!DC)
+ return false;
+ if (const auto *ND = dyn_cast<NamespaceDecl>(DC))
+ if (const IdentifierInfo *II = ND->getIdentifier()) {
+ StringRef Name = II->getName();
+ if (Name.size() >= 2 && Name.front() == '_' &&
+ (Name[1] == '_' || isUppercase(Name[1])))
+ return true;
+ }
+
+ return DC->isStdNamespace();
+}
+
+static bool isPointerLikeType(QualType QT) {
+ return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
+}
+
+bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
+ if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
+ if (isGslPointerType(Conv->getConversionType()) &&
+ Callee->getParent()->hasAttr<OwnerAttr>())
+ return true;
+ if (!isInStlNamespace(Callee->getParent()))
+ return false;
+ if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
+ !isGslOwnerType(Callee->getFunctionObjectParameterType()))
+ return false;
+ if (isPointerLikeType(Callee->getReturnType())) {
+ if (!Callee->getIdentifier())
+ return false;
+ return llvm::StringSwitch<bool>(Callee->getName())
+ .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
+ .Cases({"end", "rend", "cend", "crend"}, true)
+ .Cases({"c_str", "data", "get"}, true)
+ // Map and set types.
+ .Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true)
+ .Default(false);
+ }
+ if (Callee->getReturnType()->isReferenceType()) {
+ if (!Callee->getIdentifier()) {
+ auto OO = Callee->getOverloadedOperator();
+ if (!Callee->getParent()->hasAttr<OwnerAttr>())
+ return false;
+ return OO == OverloadedOperatorKind::OO_Subscript ||
+ OO == OverloadedOperatorKind::OO_Star;
+ }
+ return llvm::StringSwitch<bool>(Callee->getName())
+ .Cases({"front", "back", "at", "top", "value"}, true)
+ .Default(false);
+ }
+ return false;
+}
+
+bool shouldTrackFirstArgument(const FunctionDecl *FD) {
+ if (!FD->getIdentifier() || FD->getNumParams() != 1)
+ return false;
+ const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
+ if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
+ return false;
+ if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
+ return false;
+ if (FD->getReturnType()->isPointerType() ||
+ isGslPointerType(FD->getReturnType())) {
+ return llvm::StringSwitch<bool>(FD->getName())
+ .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
+ .Cases({"end", "rend", "cend", "crend"}, true)
+ .Case("data", true)
+ .Default(false);
+ }
+ if (FD->getReturnType()->isReferenceType()) {
+ return llvm::StringSwitch<bool>(FD->getName())
+ .Cases({"get", "any_cast"}, true)
+ .Default(false);
+ }
+ return false;
+}
+
template <typename T> static bool isRecordWithAttr(QualType Type) {
auto *RD = Type->getAsCXXRecordDecl();
if (!RD)
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index ac8d8041f600b..65fc0c21e11b8 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -11,6 +11,7 @@
#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/AST/TypeBase.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
@@ -79,6 +80,8 @@ OriginList *OriginManager::getOrCreateList(const ValueDecl *D) {
OriginList *OriginManager::getOrCreateList(const Expr *E, size_t Depth) {
if (auto *ParenIgnored = E->IgnoreParens(); ParenIgnored != E)
return getOrCreateList(ParenIgnored);
+ if (auto *EC = dyn_cast<ExprWithCleanups>(E))
+ return getOrCreateList(EC->getSubExpr(), Depth);
if (!hasOrigins(E))
return nullptr;
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index c91ca751984c9..26e4d75b1fa49 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -320,66 +320,6 @@ static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
return false;
}
-static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
- if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
- if (isGslPointerType(Conv->getConversionType()) &&
- Callee->getParent()->hasAttr<OwnerAttr>())
- return true;
- if (!isInStlNamespace(Callee->getParent()))
- return false;
- if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
- !isGslOwnerType(Callee->getFunctionObjectParameterType()))
- return false;
- if (isPointerLikeType(Callee->getReturnType())) {
- if (!Callee->getIdentifier())
- return false;
- return llvm::StringSwitch<bool>(Callee->getName())
- .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
- .Cases({"end", "rend", "cend", "crend"}, true)
- .Cases({"c_str", "data", "get"}, true)
- // Map and set types.
- .Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true)
- .Default(false);
- }
- if (Callee->getReturnType()->isReferenceType()) {
- if (!Callee->getIdentifier()) {
- auto OO = Callee->getOverloadedOperator();
- if (!Callee->getParent()->hasAttr<OwnerAttr>())
- return false;
- return OO == OverloadedOperatorKind::OO_Subscript ||
- OO == OverloadedOperatorKind::OO_Star;
- }
- return llvm::StringSwitch<bool>(Callee->getName())
- .Cases({"front", "back", "at", "top", "value"}, true)
- .Default(false);
- }
- return false;
-}
-
-static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
- if (!FD->getIdentifier() || FD->getNumParams() != 1)
- return false;
- const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
- if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
- return false;
- if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
- return false;
- if (FD->getReturnType()->isPointerType() ||
- isGslPointerType(FD->getReturnType())) {
- return llvm::StringSwitch<bool>(FD->getName())
- .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
- .Cases({"end", "rend", "cend", "crend"}, true)
- .Case("data", true)
- .Default(false);
- }
- if (FD->getReturnType()->isReferenceType()) {
- return llvm::StringSwitch<bool>(FD->getName())
- .Cases({"get", "any_cast"}, true)
- .Default(false);
- }
- return false;
-}
-
// Returns true if the given constructor is a copy-like constructor, such as
// `Ctor(Owner<U>&&)` or `Ctor(const Owner<U>&)`.
static bool isCopyLikeConstructor(const CXXConstructorDecl *Ctor) {
@@ -564,7 +504,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
VisitLifetimeBoundArg(Callee, ObjectArg);
else if (EnableGSLAnalysis) {
if (auto *CME = dyn_cast<CXXMethodDecl>(Callee);
- CME && shouldTrackImplicitObjectArg(CME))
+ CME && lifetimes::shouldTrackImplicitObjectArg(CME))
VisitGSLPointerArg(Callee, ObjectArg);
}
}
@@ -605,7 +545,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
else if (EnableGSLAnalysis && I == 0) {
// Perform GSL analysis for the first argument
- if (shouldTrackFirstArgument(CanonCallee)) {
+ if (lifetimes::shouldTrackFirstArgument(CanonCallee)) {
VisitGSLPointerArg(CanonCallee, Arg);
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index ddc9cb602fc26..553354f052875 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -1597,5 +1597,186 @@ TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAR) {
EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
}
+// ========================================================================= //
+// Tests for shouldTrackImplicitObjectArg
+// ========================================================================= //
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_STLBegin) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct vector {
+ struct iterator {};
+ iterator begin();
+ };
+ }
+
+ void target() {
+ std::vector<int> vec;
+ auto it = vec.begin();
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1"));
+}
+
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_OwnerDeref) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct optional {
+ T& operator*();
+ };
+ }
+
+ void target() {
+ std::optional<int> opt;
+ int& r = *opt;
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_Value) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct optional {
+ T& value();
+ };
+ }
+
+ void target() {
+ std::optional<int> opt;
+ int& r = opt.value();
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_UniquePtr_Get) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct unique_ptr {
+ T *get() const;
+ };
+ }
+
+ void target() {
+ std::unique_ptr<int> up;
+ int* r = up.get();
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("r"), HasLoansTo({"up"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_ConversionOperator) {
+ SetupTest(R"(
+ struct [[gsl::Pointer(int)]] IntPtr {
+ int& operator*();
+ };
+
+ struct [[gsl::Owner(int)]] OwnerWithConversion {
+ operator IntPtr();
+ };
+
+ void target() {
+ OwnerWithConversion owner;
+ IntPtr ptr = owner;
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("ptr"), HasLoansTo({"owner"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_MapFind) {
+ SetupTest(R"(
+ namespace std {
+ template<typename K, typename V>
+ struct map {
+ struct iterator {};
+ iterator find(const K&);
+ };
+ }
+
+ void target() {
+ std::map<int, int> m;
+ auto it = m.find(42);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("it"), HasLoansTo({"m"}, "p1"));
+}
+
+// ========================================================================= //
+// Tests for shouldTrackFirstArgument
+// ========================================================================= //
+
+TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdBegin) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct vector {
+ struct iterator {};
+ iterator begin();
+ };
+
+ template<typename C>
+ auto begin(C& c) -> decltype(c.begin());
+ }
+
+ void target() {
+ std::vector<int> vec;
+ auto it = std::begin(vec);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdData) {
+ SetupTest(R"(
+ namespace std {
+ template<typename T>
+ struct vector {
+ const T* data() const;
+ };
+
+ template<typename C>
+ auto data(C& c) -> decltype(c.data());
+ }
+
+ void target() {
+ std::vector<int> vec;
+ const int* p = std::data(vec);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("p"), HasLoansTo({"vec"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdAnyCast) {
+ SetupTest(R"(
+ namespace std {
+ struct any {};
+
+ template<typename T>
+ T any_cast(const any& op);
+ }
+
+ void target() {
+ std::any a;
+ int& r = std::any_cast<int&>(a);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("r"), HasLoansTo({"a"}, "p1"));
+}
+
} // anonymous namespace
} // namespace clang::lifetimes::internal
|
651cb0b to
ddc91c3
Compare
ddc91c3 to
2ac7726
Compare

Add support for tracking STL container methods and free functions in the lifetime safety analysis.
VisitExprWithCleanupsto theFactsGeneratorto properly handle expressions with cleanup codeshouldTrackImplicitObjectArgandshouldTrackFirstArgumentfromCheckExprLifetime.cpptoLifetimeAnnotations.h/cppto make them available to the lifetime safety analysisbegin(),data(),c_str())std::begin,std::data, andstd::any_castthat return pointers or references dependent on their argumentsFixes #162622