Skip to content

Commit 0041a69

Browse files
committed
Thread safety analysis: Support copy-elided production of scoped capabilities through arbitrary calls
When support for copy elision was initially added in e97654b, it was taking attributes from a constructor call, although that constructor call is actually not involved. It seems more natural to use attributes on the function returning the scoped capability, which is where it's actually coming from. This would also support a number of interesting use cases, like producing different scope kinds without the need for tag types, or producing scopes from a private mutex. Changing the behavior was surprisingly difficult: we were not handling CXXConstructorExpr calls like regular calls but instead handled them through the DeclStmt they're contained in. This was based on the assumption that constructors are basically only called in variable declarations (not true because of temporaries), and that variable declarations necessitate constructors (not true with C++17 anymore). Untangling this required separating construction from assigning a variable name. When a call produces an object, we use a placeholder til::LiteralPtr for `this`, and we collect the call expression and placeholder in a map. Later when going through a DeclStmt, we look up the call expression and set the placeholder to the new VarDecl. The change has a couple of nice side effects: * We don't miss constructor calls not contained in DeclStmts anymore, allowing patterns like MutexLock{&mu}, requiresMutex(); The scoped lock temporary will be destructed at the end of the full statement, so it protects the following call without the need for a scope, but with the ability to unlock in case of an exception. * We support lifetime extension of temporaries. While unusual, one can now write const MutexLock &scope = MutexLock(&mu); and have it behave as expected. * Destructors used to be handled in a weird way: since there is no expression in the AST for implicit destructor calls, we instead provided a made-up DeclRefExpr to the variable being destructed, and passed that instead of a CallExpr. Then later in translateAttrExpr there was special code that knew that destructor expressions worked a bit different. * We were producing dummy DeclRefExprs in a number of places, this has been eliminated. We now use til::SExprs instead. Technically this could break existing code, but the current handling seems unexpected enough to justify this change. Reviewed By: aaron.ballman Differential Revision: https://reviews.llvm.org/D129755
1 parent d8fa40d commit 0041a69

File tree

8 files changed

+220
-151
lines changed

8 files changed

+220
-151
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ Improvements to Clang's diagnostics
235235
function definition without a prototype which is preceded by a static
236236
declaration of the function with a prototype. Fixes
237237
`Issue 58181 <https://github.com/llvm/llvm-project/issues/58181>`_.
238+
- Copy-elided initialization of lock scopes is now handled differently in
239+
``-Wthread-safety-analysis``: annotations on the move constructor are no
240+
longer taken into account, in favor of annotations on the function returning
241+
the lock scope by value. This could result in new warnings if code depended
242+
on the previous undocumented behavior.
238243

239244
Non-comprehensive list of changes in this release
240245
-------------------------------------------------

clang/docs/ThreadSafetyAnalysis.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,8 @@ and destructor refer to the capability via different names; see the
408408
Scoped capabilities are treated as capabilities that are implicitly acquired
409409
on construction and released on destruction. They are associated with
410410
the set of (regular) capabilities named in thread safety attributes on the
411-
constructor. Acquire-type attributes on other member functions are treated as
411+
constructor or function returning them by value (using C++17 guaranteed copy
412+
elision). Acquire-type attributes on other member functions are treated as
412413
applying to that set of associated capabilities, while ``RELEASE`` implies that
413414
a function releases all associated capabilities in whatever mode they're held.
414415

@@ -930,6 +931,13 @@ implementation.
930931
// Assume mu is not held, implicitly acquire *this and associate it with mu.
931932
MutexLocker(Mutex *mu, defer_lock_t) EXCLUDES(mu) : mut(mu), locked(false) {}
932933
934+
// Same as constructors, but without tag types. (Requires C++17 copy elision.)
935+
static MutexLocker Lock(Mutex *mu) ACQUIRE(mu);
936+
static MutexLocker Adopt(Mutex *mu) REQUIRES(mu);
937+
static MutexLocker ReaderLock(Mutex *mu) ACQUIRE_SHARED(mu);
938+
static MutexLocker AdoptReaderLock(Mutex *mu) REQUIRES_SHARED(mu);
939+
static MutexLocker DeferLock(Mutex *mu) EXCLUDES(mu);
940+
933941
// Release *this and all associated mutexes, if they are still held.
934942
// There is no warning if the scope was already unlocked before.
935943
~MutexLocker() RELEASE() {

clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "clang/Basic/LLVM.h"
3232
#include "llvm/ADT/DenseMap.h"
3333
#include "llvm/ADT/PointerIntPair.h"
34+
#include "llvm/ADT/PointerUnion.h"
3435
#include "llvm/ADT/SmallVector.h"
3536
#include "llvm/Support/Casting.h"
3637
#include <sstream>
@@ -354,7 +355,7 @@ class SExprBuilder {
354355
const NamedDecl *AttrDecl;
355356

356357
// Implicit object argument -- e.g. 'this'
357-
const Expr *SelfArg = nullptr;
358+
llvm::PointerUnion<const Expr *, til::SExpr *> SelfArg = nullptr;
358359

359360
// Number of funArgs
360361
unsigned NumArgs = 0;
@@ -378,10 +379,18 @@ class SExprBuilder {
378379
// Translate a clang expression in an attribute to a til::SExpr.
379380
// Constructs the context from D, DeclExp, and SelfDecl.
380381
CapabilityExpr translateAttrExpr(const Expr *AttrExp, const NamedDecl *D,
381-
const Expr *DeclExp, VarDecl *SelfD=nullptr);
382+
const Expr *DeclExp,
383+
til::SExpr *Self = nullptr);
382384

383385
CapabilityExpr translateAttrExpr(const Expr *AttrExp, CallingContext *Ctx);
384386

387+
// Translate a variable reference.
388+
til::LiteralPtr *createVariable(const VarDecl *VD);
389+
390+
// Create placeholder for this: we don't know the VarDecl on construction yet.
391+
std::pair<til::LiteralPtr *, StringRef>
392+
createThisPlaceholder(const Expr *Exp);
393+
385394
// Translate a clang statement or expression to a TIL expression.
386395
// Also performs substitution of variables; Ctx provides the context.
387396
// Dispatches on the type of S.

clang/include/clang/Analysis/Analyses/ThreadSafetyTIL.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,15 +634,14 @@ typename V::R_SExpr Literal::traverse(V &Vs, typename V::R_Ctx Ctx) {
634634
/// At compile time, pointer literals are represented by symbolic names.
635635
class LiteralPtr : public SExpr {
636636
public:
637-
LiteralPtr(const ValueDecl *D) : SExpr(COP_LiteralPtr), Cvdecl(D) {
638-
assert(D && "ValueDecl must not be null");
639-
}
637+
LiteralPtr(const ValueDecl *D) : SExpr(COP_LiteralPtr), Cvdecl(D) {}
640638
LiteralPtr(const LiteralPtr &) = default;
641639

642640
static bool classof(const SExpr *E) { return E->opcode() == COP_LiteralPtr; }
643641

644642
// The clang declaration for the value that this pointer points to.
645643
const ValueDecl *clangDecl() const { return Cvdecl; }
644+
void setClangDecl(const ValueDecl *VD) { Cvdecl = VD; }
646645

647646
template <class V>
648647
typename V::R_SExpr traverse(V &Vs, typename V::R_Ctx Ctx) {
@@ -651,6 +650,8 @@ class LiteralPtr : public SExpr {
651650

652651
template <class C>
653652
typename C::CType compare(const LiteralPtr* E, C& Cmp) const {
653+
if (!Cvdecl || !E->Cvdecl)
654+
return Cmp.comparePointers(this, E);
654655
return Cmp.comparePointers(Cvdecl, E->Cvdecl);
655656
}
656657

clang/include/clang/Analysis/Analyses/ThreadSafetyTraverse.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,10 @@ class PrettyPrinter {
623623
}
624624

625625
void printLiteralPtr(const LiteralPtr *E, StreamType &SS) {
626-
SS << E->clangDecl()->getNameAsString();
626+
if (const NamedDecl *D = E->clangDecl())
627+
SS << D->getNameAsString();
628+
else
629+
SS << "<temporary>";
627630
}
628631

629632
void printVariable(const Variable *V, StreamType &SS, bool IsVarDecl=false) {

0 commit comments

Comments
 (0)