Skip to content

Commit e97654b

Browse files
committed
Handle scoped_lockable objects being returned by value in C++17.
In C++17, guaranteed copy elision means that there isn't necessarily a constructor call when a local variable is initialized by a function call that returns a scoped_lockable by value. In order to model the effects of initializing a local variable with a function call returning a scoped_lockable, pretend that the move constructor was invoked within the caller at the point of return. llvm-svn: 322316
1 parent c43b7e6 commit e97654b

File tree

2 files changed

+77
-5
lines changed

2 files changed

+77
-5
lines changed

clang/lib/Analysis/ThreadSafety.cpp

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,7 +1689,7 @@ void BuildLockset::handleCall(Expr *Exp, const NamedDecl *D, VarDecl *VD) {
16891689
CapExprSet ScopedExclusiveReqs, ScopedSharedReqs;
16901690
StringRef CapDiagKind = "mutex";
16911691

1692-
// Figure out if we're calling the constructor of scoped lockable class
1692+
// Figure out if we're constructing an object of scoped lockable class
16931693
bool isScopedVar = false;
16941694
if (VD) {
16951695
if (const CXXConstructorDecl *CD = dyn_cast<const CXXConstructorDecl>(D)) {
@@ -1991,22 +1991,68 @@ void BuildLockset::VisitCXXConstructExpr(CXXConstructExpr *Exp) {
19911991
// FIXME -- only handles constructors in DeclStmt below.
19921992
}
19931993

1994+
static CXXConstructorDecl *
1995+
findConstructorForByValueReturn(const CXXRecordDecl *RD) {
1996+
// Prefer a move constructor over a copy constructor. If there's more than
1997+
// one copy constructor or more than one move constructor, we arbitrarily
1998+
// pick the first declared such constructor rather than trying to guess which
1999+
// one is more appropriate.
2000+
CXXConstructorDecl *CopyCtor = nullptr;
2001+
for (CXXConstructorDecl *Ctor : RD->ctors()) {
2002+
if (Ctor->isDeleted())
2003+
continue;
2004+
if (Ctor->isMoveConstructor())
2005+
return Ctor;
2006+
if (!CopyCtor && Ctor->isCopyConstructor())
2007+
CopyCtor = Ctor;
2008+
}
2009+
return CopyCtor;
2010+
}
2011+
2012+
static Expr *buildFakeCtorCall(CXXConstructorDecl *CD, ArrayRef<Expr *> Args,
2013+
SourceLocation Loc) {
2014+
ASTContext &Ctx = CD->getASTContext();
2015+
return CXXConstructExpr::Create(Ctx, Ctx.getRecordType(CD->getParent()), Loc,
2016+
CD, true, Args, false, false, false, false,
2017+
CXXConstructExpr::CK_Complete,
2018+
SourceRange(Loc, Loc));
2019+
}
2020+
19942021
void BuildLockset::VisitDeclStmt(DeclStmt *S) {
19952022
// adjust the context
19962023
LVarCtx = Analyzer->LocalVarMap.getNextContext(CtxIndex, S, LVarCtx);
19972024

19982025
for (auto *D : S->getDeclGroup()) {
19992026
if (VarDecl *VD = dyn_cast_or_null<VarDecl>(D)) {
20002027
Expr *E = VD->getInit();
2028+
if (!E)
2029+
continue;
2030+
E = E->IgnoreParens();
2031+
20012032
// handle constructors that involve temporaries
2002-
if (ExprWithCleanups *EWC = dyn_cast_or_null<ExprWithCleanups>(E))
2033+
if (auto *EWC = dyn_cast<ExprWithCleanups>(E))
20032034
E = EWC->getSubExpr();
2035+
if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
2036+
E = BTE->getSubExpr();
20042037

2005-
if (CXXConstructExpr *CE = dyn_cast_or_null<CXXConstructExpr>(E)) {
2038+
if (CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(E)) {
20062039
NamedDecl *CtorD = dyn_cast_or_null<NamedDecl>(CE->getConstructor());
20072040
if (!CtorD || !CtorD->hasAttrs())
2008-
return;
2009-
handleCall(CE, CtorD, VD);
2041+
continue;
2042+
handleCall(E, CtorD, VD);
2043+
} else if (isa<CallExpr>(E) && E->isRValue()) {
2044+
// If the object is initialized by a function call that returns a
2045+
// scoped lockable by value, use the attributes on the copy or move
2046+
// constructor to figure out what effect that should have on the
2047+
// lockset.
2048+
// FIXME: Is this really the best way to handle this situation?
2049+
auto *RD = E->getType()->getAsCXXRecordDecl();
2050+
if (!RD || !RD->hasAttr<ScopedLockableAttr>())
2051+
continue;
2052+
CXXConstructorDecl *CtorD = findConstructorForByValueReturn(RD);
2053+
if (!CtorD || !CtorD->hasAttrs())
2054+
continue;
2055+
handleCall(buildFakeCtorCall(CtorD, {E}, E->getLocStart()), CtorD, VD);
20102056
}
20112057
}
20122058
}

clang/test/SemaCXX/warn-thread-safety-analysis.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_ASSERT_CAPABILITY=0 %s
22
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_ASSERT_CAPABILITY=1 %s
3+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_ASSERT_CAPABILITY=0 %s
34

45
// FIXME: should also run %clang_cc1 -fsyntax-only -verify -Wthread-safety -std=c++11 -Wc++98-compat %s
56
// FIXME: should also run %clang_cc1 -fsyntax-only -verify -Wthread-safety %s
@@ -5248,3 +5249,28 @@ struct C {
52485249
C c;
52495250
void f() { c[A()]->g(); }
52505251
} // namespace PR34800
5252+
5253+
namespace ReturnScopedLockable {
5254+
template<typename Object> class SCOPED_LOCKABLE ReadLockedPtr {
5255+
public:
5256+
ReadLockedPtr(Object *ptr) SHARED_LOCK_FUNCTION((*this)->mutex);
5257+
ReadLockedPtr(ReadLockedPtr &&) SHARED_LOCK_FUNCTION((*this)->mutex);
5258+
~ReadLockedPtr() UNLOCK_FUNCTION();
5259+
5260+
Object *operator->() const { return object; }
5261+
5262+
private:
5263+
Object *object;
5264+
};
5265+
5266+
struct Object {
5267+
int f() SHARED_LOCKS_REQUIRED(mutex);
5268+
Mutex mutex;
5269+
};
5270+
5271+
ReadLockedPtr<Object> get();
5272+
int use() {
5273+
auto ptr = get();
5274+
return ptr->f();
5275+
}
5276+
}

0 commit comments

Comments
 (0)