]> granicus.if.org Git - clang/commitdiff
Thread-safety analysis: adding in a basic lockset tracking system. This
authorCaitlin Sadowski <supertri@google.com>
Tue, 23 Aug 2011 18:46:34 +0000 (18:46 +0000)
committerCaitlin Sadowski <supertri@google.com>
Tue, 23 Aug 2011 18:46:34 +0000 (18:46 +0000)
system flags an error when unlocking a lock which was not held, locking
the same lock twice, having a different lockset on each iteration of a
loop, or going out of scope while still holding a lock. In order to
successfully use the lockset, this patch also makes sure that attribute
arguments are attached correctly for later parsing.

This patch was also worked on by DeLesley Hutchins.

Note: This patch has been reviewed by Chandler Carruth and Jeffrey
Yasskin. Feel free to provide post-commit review comments for a
subsequent patch.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@138350 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Basic/Attr.td
include/clang/Basic/DiagnosticSemaKinds.td
include/clang/Sema/AnalysisBasedWarnings.h
lib/Sema/AnalysisBasedWarnings.cpp
lib/Sema/SemaDeclAttr.cpp
test/SemaCXX/warn-thread-safety-analysis.cpp [new file with mode: 0644]
test/SemaCXX/warn-thread-safety-parsing.cpp [moved from test/SemaCXX/warn-thread-safety.cpp with 99% similarity]

index 2b0ed3d6691254661b58e8b4cc8314dc439eda62..fb3ba0257552782df462a9522a1fbff1f911a1ab 100644 (file)
@@ -55,6 +55,7 @@ class FunctionArgument<string name> : Argument<name>;
 class TypeArgument<string name> : Argument<name>;
 class UnsignedArgument<string name> : Argument<name>;
 class VariadicUnsignedArgument<string name> : Argument<name>;
+class VariadicExprArgument<string name> : Argument<name>;
 
 // A version of the form major.minor[.subminor].
 class VersionArgument<string name> : Argument<name>;
@@ -564,52 +565,65 @@ def NoThreadSafetyAnalysis : InheritableAttr {
 
 def GuardedBy : InheritableAttr {
   let Spellings = ["guarded_by"];
+  let Args = [ExprArgument<"Arg">];
 }
 
 def PtGuardedBy : InheritableAttr {
   let Spellings = ["pt_guarded_by"];
+  let Args = [ExprArgument<"Arg">];
 }
 
 def AcquiredAfter : InheritableAttr {
   let Spellings = ["acquired_after"];
+  let Args = [VariadicExprArgument<"Args">];
 }
 
 def AcquiredBefore : InheritableAttr {
   let Spellings = ["acquired_before"];
+  let Args = [VariadicExprArgument<"Args">];
 }
 
 def ExclusiveLockFunction : InheritableAttr {
   let Spellings = ["exclusive_lock_function"];
+  let Args = [VariadicExprArgument<"Args">];
 }
 
 def SharedLockFunction : InheritableAttr {
   let Spellings = ["shared_lock_function"];
+  let Args = [VariadicExprArgument<"Args">];
 }
 
 def ExclusiveTrylockFunction : InheritableAttr {
   let Spellings = ["exclusive_trylock_function"];
+  let Args = [VariadicExprArgument<"Args">];
 }
 
 def SharedTrylockFunction : InheritableAttr {
   let Spellings = ["shared_trylock_function"];
+  let Args = [VariadicExprArgument<"Args">];
 }
 
 def UnlockFunction : InheritableAttr {
   let Spellings = ["unlock_function"];
+  let Args = [VariadicExprArgument<"Args">];
 }
 
 def LockReturned : InheritableAttr {
   let Spellings = ["lock_returned"];
+  let Args = [ExprArgument<"Arg">];
 }
 
 def LocksExcluded : InheritableAttr {
   let Spellings = ["locks_excluded"];
+  let Args = [VariadicExprArgument<"Args">];
 }
 
 def ExclusiveLocksRequired : InheritableAttr {
   let Spellings = ["exclusive_locks_required"];
+  let Args = [VariadicExprArgument<"Args">];
 }
 
 def SharedLocksRequired : InheritableAttr {
   let Spellings = ["shared_locks_required"];
+  let Args = [VariadicExprArgument<"Args">];
 }
index 89ffebbbab9accf7e8aea6eb16c2278fa75c5b4b..0ded2fb3e40251ec63d3ad793edbf0a8135339e2 100644 (file)
@@ -1383,7 +1383,24 @@ def err_attribute_argument_not_lockable : Error<
 def err_attribute_decl_not_lockable : Error<
   "%0 attribute can only be applied in a context annotated "
   "with 'lockable' attribute">;
-
+def warn_unlock_but_no_acquire : Warning<
+  "unlocking '%0' that was not acquired">,
+  InGroup<DiagGroup<"thread-safety">>, DefaultIgnore;
+def warn_double_lock : Warning<
+  "locking '%0' that is already acquired">,
+  InGroup<DiagGroup<"thread-safety">>, DefaultIgnore;
+def warn_function_requires_lock : Warning<
+  "calling function '%0' requires lock '%0'">,
+  InGroup<DiagGroup<"thread-safety">>, DefaultIgnore;
+def warn_locks_not_released : Warning<
+  "lock '%0' is not released at the end of function '%1'">,
+  InGroup<DiagGroup<"thread-safety">>, DefaultIgnore;
+def warn_lock_not_released_in_scope : Warning<
+  "lock '%0' is not released at the end of its scope">,
+  InGroup<DiagGroup<"thread-safety">>, DefaultIgnore;
+def warn_expecting_lock_held_on_loop : Warning<
+  "expecting lock '%0' to be held at start of each loop">,
+  InGroup<DiagGroup<"thread-safety">>, DefaultIgnore;
 
 def warn_impcast_vector_scalar : Warning<
   "implicit conversion turns vector to scalar: %0 to %1">,
index 8e781cd521c2f017585db40e3cdfd774c3df0d49..eeac97332dfbca0b6549a318a280deb59264416f 100644 (file)
@@ -37,6 +37,7 @@ public:
     // The warnings to run.
     unsigned enableCheckFallThrough : 1;
     unsigned enableCheckUnreachable : 1;
+    unsigned enableThreadSafetyAnalysis : 1;
   public:
     Policy();
     void disableCheckFallThrough() { enableCheckFallThrough = 0; }
index 9f5ab502660db812d973c7f922103a33de5bce44..99525bae9fef78693d33deb65a2149ff60440b78 100644 (file)
@@ -25,6 +25,7 @@
 #include "clang/AST/StmtObjC.h"
 #include "clang/AST/StmtCXX.h"
 #include "clang/AST/EvaluatedExprVisitor.h"
+#include "clang/AST/StmtVisitor.h"
 #include "clang/Analysis/AnalysisContext.h"
 #include "clang/Analysis/CFG.h"
 #include "clang/Analysis/Analyses/ReachableCode.h"
 #include "clang/Analysis/CFGStmtMap.h"
 #include "clang/Analysis/Analyses/UninitializedValues.h"
 #include "llvm/ADT/BitVector.h"
+#include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/ImmutableMap.h"
+#include "llvm/ADT/PostOrderIterator.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/Casting.h"
+#include <algorithm>
+#include <vector>
 
 using namespace clang;
 
@@ -585,6 +592,551 @@ public:
 };
 }
 
+
+//===----------------------------------------------------------------------===//
+// -Wthread-safety
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// \brief Implements a set of CFGBlocks using a BitVector.
+///
+/// This class contains a minimal interface, primarily dictated by the SetType
+/// template parameter of the llvm::po_iterator template, as used with external
+/// storage. We also use this set to keep track of which CFGBlocks we visit
+/// during the analysis.
+class CFGBlockSet {
+  llvm::BitVector VisitedBlockIDs;
+
+public:
+  // po_iterator requires this iterator, but the only interface needed is the
+  // value_type typedef.
+  struct iterator {
+    typedef const CFGBlock *value_type;
+  };
+
+  CFGBlockSet() {}
+  CFGBlockSet(const CFG *G) : VisitedBlockIDs(G->getNumBlockIDs(), false) {}
+
+  /// \brief Set the bit associated with a particular CFGBlock.
+  /// This is the important method for the SetType template parameter.
+  bool insert(const CFGBlock *Block) {
+    if (VisitedBlockIDs.test(Block->getBlockID()))
+      return false;
+    VisitedBlockIDs.set(Block->getBlockID());
+    return true;
+  }
+
+  /// \brief Check if the bit for a CFGBlock has been already set.
+  /// This mehtod is for tracking visited blocks in the main threadsafety loop.
+  bool alreadySet(const CFGBlock *Block) {
+    return VisitedBlockIDs.test(Block->getBlockID());
+  }
+};
+
+/// \brief We create a helper class which we use to iterate through CFGBlocks in
+/// the topological order.
+class TopologicallySortedCFG {
+  typedef llvm::po_iterator<const CFG*, CFGBlockSet, true>  po_iterator;
+
+  std::vector<const CFGBlock*> Blocks;
+
+public:
+  typedef std::vector<const CFGBlock*>::reverse_iterator iterator;
+
+  TopologicallySortedCFG(const CFG *CFGraph) {
+    Blocks.reserve(CFGraph->getNumBlockIDs());
+    CFGBlockSet BSet(CFGraph);
+
+    for (po_iterator I = po_iterator::begin(CFGraph, BSet),
+         E = po_iterator::end(CFGraph, BSet); I != E; ++I) {
+      Blocks.push_back(*I);
+    }
+  }
+
+  iterator begin() {
+    return Blocks.rbegin();
+  }
+
+  iterator end() {
+    return Blocks.rend();
+  }
+};
+
+/// \brief A Lock object uniquely identifies a particular lock acquired, and is
+/// built from an Expr* (i.e. calling a lock function).
+///
+/// Thread-safety analysis works by comparing lock expressions.  Within the
+/// body of a function, an expression such as "x->foo->bar.mu" will resolve to
+/// a particular lock object at run-time.  Subsequent occurrences of the same
+/// expression (where "same" means syntactic equality) will refer to the same
+/// run-time object if three conditions hold:
+/// (1) Local variables in the expression, such as "x" have not changed.
+/// (2) Values on the heap that affect the expression have not changed.
+/// (3) The expression involves only pure function calls.
+/// The current implementation assumes, but does not verify, that multiple uses
+/// of the same lock expression satisfies these criteria.
+///
+/// Clang introduces an additional wrinkle, which is that it is difficult to
+/// derive canonical expressions, or compare expressions directly for equality.
+/// Thus, we identify a lock not by an Expr, but by the set of named
+/// declarations that are referenced by the Expr.  In other words,
+/// x->foo->bar.mu will be a four element vector with the Decls for
+/// mu, bar, and foo, and x.  The vector will uniquely identify the expression
+/// for all practical purposes.
+///
+/// Note we will need to perform substitution on "this" and function parameter
+/// names when constructing a lock expression.
+///
+/// For example:
+/// class C { Mutex Mu;  void lock() EXCLUSIVE_LOCK_FUNCTION(this->Mu); };
+/// void myFunc(C *X) { ... X->lock() ... }
+/// The original expression for the lock acquired by myFunc is "this->Mu", but
+/// "X" is substituted for "this" so we get X->Mu();
+///
+/// For another example:
+/// foo(MyList *L) EXCLUSIVE_LOCKS_REQUIRED(L->Mu) { ... }
+/// MyList *MyL;
+/// foo(MyL);  // requires lock MyL->Mu to be held
+///
+/// FIXME: In C++0x Mutexes are the objects that control access to shared
+/// variables, while Locks are the objects that acquire and release Mutexes. We
+/// may want to switch to this new terminology soon, in which case we should
+/// rename this class "Mutex" and rename "LockId" to "MutexId", as well as
+/// making sure that the terms Lock and Mutex throughout this code are
+/// consistent with C++0x
+///
+/// FIXME: We should also pick one and canonicalize all usage of lock vs acquire
+/// and unlock vs release as verbs.
+class LockID {
+  SmallVector<NamedDecl*, 2> DeclSeq;
+  /// Build a Decl sequence representing the lock from the given expression.
+  /// Recursive function that bottoms out when the final DeclRefExpr is reached.
+  void buildLock(Expr *Exp) {
+    if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Exp)) {
+      NamedDecl *ND = cast<NamedDecl>(DRE->getDecl()->getCanonicalDecl());
+      DeclSeq.push_back(ND);
+    } else if (MemberExpr *ME = dyn_cast<MemberExpr>(Exp)) {
+      NamedDecl *ND = ME->getMemberDecl();
+      DeclSeq.push_back(ND);
+      buildLock(ME->getBase());
+    } else {
+      // FIXME: add diagnostic
+      llvm::report_fatal_error("Expected lock expression!");
+    }
+  }
+
+public:
+  LockID(Expr *LExpr) {
+    buildLock(LExpr);
+    assert(!DeclSeq.empty());
+  }
+
+  bool operator==(const LockID &other) const {
+    return DeclSeq == other.DeclSeq;
+  }
+
+  bool operator!=(const LockID &other) const {
+    return !(*this == other);
+  }
+
+  // SmallVector overloads Operator< to do lexicographic ordering. Note that
+  // we use pointer equality (and <) to compare NamedDecls. This means the order
+  // of locks in a lockset is nondeterministic. In order to output
+  // diagnostics in a deterministic ordering, we must order all diagnostics to
+  // output by SourceLocation when iterating through this lockset.
+  bool operator<(const LockID &other) const {
+    return DeclSeq < other.DeclSeq;
+  }
+
+  /// \brief Returns the name of the first Decl in the list for a given LockId;
+  /// e.g. the lock expression foo.bar() has name "bar".
+  /// The caret will point unambiguously to the lock expression, so using this
+  /// name in diagnostics is a way to get simple, and consistent, lock names.
+  /// We do not want to output the entire expression text for security reasons.
+  StringRef getName() const {
+    return DeclSeq.front()->getName();
+  }
+
+  void Profile(llvm::FoldingSetNodeID &ID) const {
+    for (SmallVectorImpl<NamedDecl*>::const_iterator I = DeclSeq.begin(),
+         E = DeclSeq.end(); I != E; ++I) {
+      ID.AddPointer(*I);
+    }
+  }
+};
+
+/// \brief This is a helper class that stores info about the most recent
+/// accquire of a Lock.
+///
+/// The main body of the analysis maps Locks to LockDatas.
+struct LockData {
+  SourceLocation AcquireLoc;
+
+  LockData(SourceLocation Loc) : AcquireLoc(Loc) {}
+
+  bool operator==(const LockData &other) const {
+    return AcquireLoc == other.AcquireLoc;
+  }
+
+  bool operator!=(const LockData &other) const {
+    return !(*this == other);
+  }
+
+  void Profile(llvm::FoldingSetNodeID &ID) const {
+    ID.AddInteger(AcquireLoc.getRawEncoding());
+  }
+};
+
+ /// A Lockset maps each lock (defined above) to information about how it has
+/// been locked.
+typedef llvm::ImmutableMap<LockID, LockData> Lockset;
+
+/// \brief We use this class to visit different types of expressions in
+/// CFGBlocks, and build up the lockset.
+/// An expression may cause us to add or remove locks from the lockset, or else
+/// output error messages related to missing locks.
+/// FIXME: In future, we may be able to not inherit from a visitor.
+class BuildLockset : public StmtVisitor<BuildLockset> {
+  Sema &S;
+  Lockset LSet;
+  Lockset::Factory &LocksetFactory;
+
+  // Helper functions
+  void RemoveLock(SourceLocation UnlockLoc, Expr *LockExp);
+  void AddLock(SourceLocation LockLoc, Expr *LockExp);
+
+public:
+  BuildLockset(Sema &S, Lockset LS, Lockset::Factory &F)
+    : StmtVisitor<BuildLockset>(), S(S), LSet(LS),
+      LocksetFactory(F) {}
+
+  Lockset getLockset() {
+    return LSet;
+  }
+
+  void VisitDeclRefExpr(DeclRefExpr *Exp);
+  void VisitCXXMemberCallExpr(CXXMemberCallExpr *Exp);
+};
+
+/// \brief Add a new lock to the lockset, warning if the lock is already there.
+/// \param LockExp The lock expression corresponding to the lock to be added
+/// \param LockLoc The source location of the acquire
+void BuildLockset::AddLock(SourceLocation LockLoc, Expr *LockExp) {
+  LockID Lock(LockExp);
+  LockData NewLockData(LockLoc);
+
+  if (LSet.contains(Lock))
+    S.Diag(LockLoc, diag::warn_double_lock) << Lock.getName();
+
+  LSet = LocksetFactory.add(LSet, Lock, NewLockData);
+}
+
+/// \brief Remove a lock from the lockset, warning if the lock is not there.
+/// \param LockExp The lock expression corresponding to the lock to be removed
+/// \param UnlockLoc The source location of the unlock (only used in error msg)
+void BuildLockset::RemoveLock(SourceLocation UnlockLoc, Expr *LockExp) {
+  LockID Lock(LockExp);
+
+  Lockset NewLSet = LocksetFactory.remove(LSet, Lock);
+  if(NewLSet == LSet)
+    S.Diag(UnlockLoc, diag::warn_unlock_but_no_acquire) << Lock.getName();
+
+  LSet = NewLSet;
+}
+
+void BuildLockset::VisitDeclRefExpr(DeclRefExpr *Exp) {
+  // FIXME: checking for guarded_by/var and pt_guarded_by/var
+}
+
+/// \brief When visiting CXXMemberCallExprs we need to examine the attributes on
+/// the method that is being called and add, remove or check locks in the
+/// lockset accordingly.
+void BuildLockset::VisitCXXMemberCallExpr(CXXMemberCallExpr *Exp) {
+  NamedDecl *D = dyn_cast<NamedDecl>(Exp->getCalleeDecl());
+  SourceLocation ExpLocation = Exp->getExprLoc();
+  Expr *Parent = Exp->getImplicitObjectArgument();
+
+  if(!D || !D->hasAttrs())
+    return;
+
+  AttrVec &ArgAttrs = D->getAttrs();
+  for(unsigned i = 0; i < ArgAttrs.size(); ++i) {
+    Attr *Attr = ArgAttrs[i];
+    switch (Attr->getKind()) {
+      // When we encounter an exclusive lock function, we need to add the lock
+      // to our lockset.
+      case attr::ExclusiveLockFunction: {
+        ExclusiveLockFunctionAttr *ELFAttr =
+          cast<ExclusiveLockFunctionAttr>(Attr);
+
+        if (ELFAttr->args_size() == 0) {// The lock held is the "this" object.
+          AddLock(ExpLocation, Parent);
+          break;
+        }
+
+        for (ExclusiveLockFunctionAttr::args_iterator I = ELFAttr->args_begin(),
+             E = ELFAttr->args_end(); I != E; ++I)
+          AddLock(ExpLocation, *I);
+        // FIXME: acquired_after/acquired_before annotations
+        break;
+      }
+
+      // When we encounter an unlock function, we need to remove unlocked locks
+      // from the lockset, and flag a warning if they are not there.
+      case attr::UnlockFunction: {
+        UnlockFunctionAttr *UFAttr = cast<UnlockFunctionAttr>(Attr);
+
+        if (UFAttr->args_size() == 0) { // The lock held is the "this" object.
+          RemoveLock(ExpLocation, Parent);
+          break;
+        }
+
+        for (UnlockFunctionAttr::args_iterator I = UFAttr->args_begin(),
+            E = UFAttr->args_end(); I != E; ++I)
+          RemoveLock(ExpLocation, *I);
+        break;
+      }
+
+      // Ignore other (non thread-safety) attributes
+      default:
+        break;
+    }
+  }
+}
+
+typedef std::pair<SourceLocation, PartialDiagnostic> DelayedDiag;
+typedef llvm::SmallVector<DelayedDiag, 4> DiagList;
+
+struct SortDiagBySourceLocation {
+  Sema &S;
+
+  SortDiagBySourceLocation(Sema &S) : S(S) {}
+
+  bool operator()(const DelayedDiag &left, const DelayedDiag &right) {
+    // Although this call will be slow, this is only called when outputting
+    // multiple warnings.
+    return S.getSourceManager().isBeforeInTranslationUnit(left.first,
+                                                          right.first);
+  }
+};
+} // end anonymous namespace
+
+/// \brief Emit all buffered diagnostics in order of sourcelocation.
+/// We need to output diagnostics produced while iterating through
+/// the lockset in deterministic order, so this function orders diagnostics
+/// and outputs them.
+static void EmitDiagnostics(Sema &S, DiagList &D) {
+  SortDiagBySourceLocation SortDiagBySL(S);
+  sort(D.begin(), D.end(), SortDiagBySL);
+  for (DiagList::iterator I = D.begin(), E = D.end(); I != E; ++I)
+    S.Diag(I->first, I->second);
+}
+
+/// \brief Compute the intersection of two locksets and issue warnings for any
+/// locks in the symmetric difference.
+///
+/// This function is used at a merge point in the CFG when comparing the lockset
+/// of each branch being merged. For example, given the following sequence:
+/// A; if () then B; else C; D; we need to check that the lockset after B and C
+/// are the same. In the event of a difference, we use the intersection of these
+/// two locksets at the start of D.
+static Lockset intersectAndWarn(Sema &S, Lockset LSet1, Lockset LSet2,
+                                Lockset::Factory &Fact) {
+  Lockset Intersection = LSet1;
+  DiagList Warnings;
+
+  for (Lockset::iterator I = LSet2.begin(), E = LSet2.end(); I != E; ++I) {
+    if (!LSet1.contains(I.getKey())) {
+      const LockID &MissingLock = I.getKey();
+      const LockData &MissingLockData = I.getData();
+      PartialDiagnostic Warning =
+        S.PDiag(diag::warn_lock_not_released_in_scope) << MissingLock.getName();
+      Warnings.push_back(DelayedDiag(MissingLockData.AcquireLoc, Warning));
+    }
+  }
+
+  for (Lockset::iterator I = LSet1.begin(), E = LSet1.end(); I != E; ++I) {
+    if (!LSet2.contains(I.getKey())) {
+      const LockID &MissingLock = I.getKey();
+      const LockData &MissingLockData = I.getData();
+      PartialDiagnostic Warning =
+        S.PDiag(diag::warn_lock_not_released_in_scope) << MissingLock.getName();
+      Warnings.push_back(DelayedDiag(MissingLockData.AcquireLoc, Warning));
+      Intersection = Fact.remove(Intersection, MissingLock);
+    }
+  }
+
+  EmitDiagnostics(S, Warnings);
+  return Intersection;
+}
+
+/// \brief Returns the location of the first Stmt in a Block.
+static SourceLocation getFirstStmtLocation(CFGBlock *Block) {
+  for (CFGBlock::const_iterator BI = Block->begin(), BE = Block->end();
+       BI != BE; ++BI) {
+    if (const CFGStmt *CfgStmt = dyn_cast<CFGStmt>(&(*BI)))
+      return CfgStmt->getStmt()->getLocStart();
+  }
+  return SourceLocation();
+}
+
+/// \brief Warn about different locksets along backedges of loops.
+/// This function is called when we encounter a back edge. At that point,
+/// we need to verify that the lockset before taking the backedge is the
+/// same as the lockset before entering the loop.
+///
+/// \param LoopEntrySet Locks held before starting the loop
+/// \param LoopReentrySet Locks held in the last CFG block of the loop
+static void warnBackEdgeUnequalLocksets(Sema &S, const Lockset LoopReentrySet,
+                                        const Lockset LoopEntrySet,
+                                        SourceLocation FirstLocInLoop) {
+  assert(FirstLocInLoop.isValid());
+  DiagList Warnings;
+
+  // Warn for locks held at the start of the loop, but not the end.
+  for (Lockset::iterator I = LoopEntrySet.begin(), E = LoopEntrySet.end();
+       I != E; ++I) {
+    if (!LoopReentrySet.contains(I.getKey())) {
+      const LockID &MissingLock = I.getKey();
+      // We report this error at the location of the first statement in a loop
+      PartialDiagnostic Warning =
+        S.PDiag(diag::warn_expecting_lock_held_on_loop)
+          << MissingLock.getName();
+      Warnings.push_back(DelayedDiag(FirstLocInLoop, Warning));
+    }
+  }
+
+  // Warn for locks held at the end of the loop, but not at the start.
+  for (Lockset::iterator I = LoopReentrySet.begin(), E = LoopReentrySet.end();
+       I != E; ++I) {
+    if (!LoopEntrySet.contains(I.getKey())) {
+      const LockID &MissingLock = I.getKey();
+      const LockData &MissingLockData = I.getData();
+      PartialDiagnostic Warning =
+        S.PDiag(diag::warn_lock_not_released_in_scope) << MissingLock.getName();
+      Warnings.push_back(DelayedDiag(MissingLockData.AcquireLoc, Warning));
+    }
+  }
+
+  EmitDiagnostics(S, Warnings);
+}
+
+/// \brief Check a function's CFG for thread-safety violations.
+///
+/// We traverse the blocks in the CFG, compute the set of locks that are held
+/// at the end of each block, and issue warnings for thread safety violations.
+/// Each block in the CFG is traversed exactly once.
+static void checkThreadSafety(Sema &S, AnalysisContext &AC) {
+  CFG *CFGraph = AC.getCFG();
+  if (!CFGraph) return;
+
+  StringRef FunName;
+  if (const NamedDecl *ContextDecl = dyn_cast<NamedDecl>(AC.getDecl()))
+    FunName = ContextDecl->getName();
+
+  Lockset::Factory LocksetFactory;
+
+  // FIXME: Swith to SmallVector? Otherwise improve performance impact?
+  std::vector<Lockset> EntryLocksets(CFGraph->getNumBlockIDs(),
+                                     LocksetFactory.getEmptyMap());
+  std::vector<Lockset> ExitLocksets(CFGraph->getNumBlockIDs(),
+                                    LocksetFactory.getEmptyMap());
+
+  // We need to explore the CFG via a "topological" ordering.
+  // That way, we will be guaranteed to have information about required
+  // predecessor locksets when exploring a new block.
+  TopologicallySortedCFG SortedGraph(CFGraph);
+  CFGBlockSet VisitedBlocks(CFGraph);
+
+  for (TopologicallySortedCFG::iterator I = SortedGraph.begin(),
+       E = SortedGraph.end(); I!= E; ++I) {
+    const CFGBlock *CurrBlock = *I;
+    int CurrBlockID = CurrBlock->getBlockID();
+
+    VisitedBlocks.insert(CurrBlock);
+
+    // Use the default initial lockset in case there are no predecessors.
+    Lockset &Entryset = EntryLocksets[CurrBlockID];
+    Lockset &Exitset = ExitLocksets[CurrBlockID];
+
+    // Iterate through the predecessor blocks and warn if the lockset for all
+    // predecessors is not the same. We take the entry lockset of the current
+    // block to be the intersection of all previous locksets.
+    // FIXME: By keeping the intersection, we may output more errors in future
+    // for a lock which is not in the intersection, but was in the union. We
+    // may want to also keep the union in future. As an example, let's say
+    // the intersection contains Lock L, and the union contains L and M.
+    // Later we unlock M. At this point, we would output an error because we
+    // never locked M; although the real error is probably that we forgot to
+    // lock M on all code paths. Conversely, let's say that later we lock M.
+    // In this case, we should compare against the intersection instead of the
+    // union because the real error is probably that we forgot to unlock M on
+    // all code paths.
+    bool LocksetInitialized = false;
+    for (CFGBlock::const_pred_iterator PI = CurrBlock->pred_begin(),
+         PE  = CurrBlock->pred_end(); PI != PE; ++PI) {
+
+      // if *PI -> CurrBlock is a back edge
+      if (!VisitedBlocks.alreadySet(*PI))
+        continue;
+
+      int PrevBlockID = (*PI)->getBlockID();
+      if (!LocksetInitialized) {
+        Entryset = ExitLocksets[PrevBlockID];
+        LocksetInitialized = true;
+      } else {
+        Entryset = intersectAndWarn(S, Entryset, ExitLocksets[PrevBlockID],
+                                LocksetFactory);
+      }
+    }
+
+    BuildLockset LocksetBuilder(S, Entryset, LocksetFactory);
+    for (CFGBlock::const_iterator BI = CurrBlock->begin(),
+         BE = CurrBlock->end(); BI != BE; ++BI) {
+      if (const CFGStmt *CfgStmt = dyn_cast<CFGStmt>(&*BI)) {
+        LocksetBuilder.Visit(CfgStmt->getStmt());
+      }
+    }
+    Exitset = LocksetBuilder.getLockset();
+
+    // For every back edge from CurrBlock (the end of the loop) to another block
+    // (FirstLoopBlock) we need to check that the Lockset of Block is equal to
+    // the one held at the beginning of FirstLoopBlock. We can look up the
+    // Lockset held at the beginning of FirstLoopBlock in the EntryLockSets map.
+    for (CFGBlock::const_succ_iterator SI = CurrBlock->succ_begin(),
+         SE  = CurrBlock->succ_end(); SI != SE; ++SI) {
+
+      // if CurrBlock -> *SI is *not* a back edge
+      if (!VisitedBlocks.alreadySet(*SI))
+        continue;
+
+      CFGBlock *FirstLoopBlock = *SI;
+      SourceLocation FirstLoopLocation = getFirstStmtLocation(FirstLoopBlock);
+
+      Lockset PreLoop = EntryLocksets[FirstLoopBlock->getBlockID()];
+      Lockset LoopEnd = ExitLocksets[CurrBlockID];
+      warnBackEdgeUnequalLocksets(S, LoopEnd, PreLoop, FirstLoopLocation);
+    }
+  }
+
+  Lockset FinalLockset = ExitLocksets[CFGraph->getExit().getBlockID()];
+  if (!FinalLockset.isEmpty()) {
+    DiagList Warnings;
+    for (Lockset::iterator I=FinalLockset.begin(), E=FinalLockset.end();
+         I != E; ++I) {
+      const LockID &MissingLock = I.getKey();
+      const LockData &MissingLockData = I.getData();
+      PartialDiagnostic Warning =
+        S.PDiag(diag::warn_locks_not_released)
+          << MissingLock.getName() << FunName;
+      Warnings.push_back(DelayedDiag(MissingLockData.AcquireLoc, Warning));
+    }
+    EmitDiagnostics(S, Warnings);
+  }
+}
+
+
 //===----------------------------------------------------------------------===//
 // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
 //  warnings on a function, method, or block.
@@ -593,6 +1145,7 @@ public:
 clang::sema::AnalysisBasedWarnings::Policy::Policy() {
   enableCheckFallThrough = 1;
   enableCheckUnreachable = 0;
+  enableThreadSafetyAnalysis = 0;
 }
 
 clang::sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
@@ -610,6 +1163,10 @@ clang::sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s)
   DefaultPolicy.enableCheckUnreachable = (unsigned)
     (D.getDiagnosticLevel(diag::warn_unreachable, SourceLocation()) !=
         Diagnostic::Ignored);
+  DefaultPolicy.enableThreadSafetyAnalysis = (unsigned)
+    (D.getDiagnosticLevel(diag::warn_double_lock, SourceLocation()) !=
+     Diagnostic::Ignored);
+
 }
 
 static void flushDiagnostics(Sema &S, sema::FunctionScopeInfo *fscope) {
@@ -735,6 +1292,10 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P,
   if (P.enableCheckUnreachable)
     CheckUnreachable(S, AC);
   
+  // Check for thread safety violations
+  if (P.enableThreadSafetyAnalysis)
+    checkThreadSafety(S, AC);
+
   if (Diags.getDiagnosticLevel(diag::warn_uninit_var, D->getLocStart())
       != Diagnostic::Ignored ||
       Diags.getDiagnosticLevel(diag::warn_maybe_uninit_var, D->getLocStart())
index 70e684b36ec24ab76aef15c315bfc6e6f7f4885c..d3595a2abf5c137794a7e940505f63962c65e9fa 100644 (file)
@@ -272,55 +272,69 @@ static const RecordType *getRecordType(QualType QT) {
   return 0;
 }
 
+/// \brief Thread Safety Analysis: Checks that the passed in RecordType
+/// resolves to a lockable object. May flag an error.
+bool checkForLockableRecord(Sema &S, Decl *D, const AttributeList &Attr,
+                            const RecordType *RT) {
+  // Flag error if could not get record type for this argument.
+  if(!RT) {
+    S.Diag(Attr.getLoc(), diag::err_attribute_argument_not_class)
+      << Attr.getName();
+    return false;
+  }
+  // Flag error if the type is not lockable.
+  if(!RT->getDecl()->getAttr<LockableAttr>()) {
+    S.Diag(Attr.getLoc(), diag::err_attribute_argument_not_lockable)
+      << Attr.getName();
+    return false;
+  }
+  return true;
+}
+
 /// \brief Thread Safety Analysis: Checks that all attribute arguments, starting
 /// from Sidx, resolve to a lockable object. May flag an error.
-static bool checkAttrArgsAreLockableObjs(Sema & S, Decl *D,
-                                         const AttributeList & Attr,
+/// \param Sidx The attribute argument index to start checking with.
+/// \param ParamIdxOk Whether an argument can be indexing into a function
+/// parameter list.
+static bool checkAttrArgsAreLockableObjs(Sema &S, Decl *D,
+                                         const AttributeList &Attr,
+                                         SmallVectorImpl<Expr*> &Args,
                                          int Sidx = 0,
                                          bool ParamIdxOk = false) {
-  for(unsigned int Idx = Sidx; Idx < Attr.getNumArgs(); ++Idx) {
+  for(unsigned Idx = Sidx; Idx < Attr.getNumArgs(); ++Idx) {
     Expr *ArgExp = Attr.getArg(Idx);
+
     if (ArgExp->isTypeDependent())
-      continue;
+     continue;
 
-    QualType Arg_QT = ArgExp->getType();
+    QualType ArgTy = ArgExp->getType();
 
-    // Get record type.
-    // first see if we can just cast to record type, or point to record type
-    const RecordType *RT = getRecordType(Arg_QT);
+    // First see if we can just cast to record type, or point to record type.
+    const RecordType *RT = getRecordType(ArgTy);
 
-    // now check if we idx into a record type function param
-    if (!RT && ParamIdxOk) {
-      FunctionDecl *FD = dyn_cast <FunctionDecl>(D);
+    // Now check if we index into a record type function param.
+    if(!RT && ParamIdxOk) {
+      FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
       IntegerLiteral *IL = dyn_cast<IntegerLiteral>(ArgExp);
       if(FD && IL) {
         unsigned int NumParams = FD->getNumParams();
         llvm::APInt ArgValue = IL->getValue();
-        uint64_t ParamIdx_from1 = ArgValue.getZExtValue();
-        uint64_t ParamIdx_from0 = ParamIdx_from1 - 1;
-        if(!ArgValue.isStrictlyPositive() || ParamIdx_from1 > NumParams) {
+        uint64_t ParamIdxFromOne = ArgValue.getZExtValue();
+        uint64_t ParamIdxFromZero = ParamIdxFromOne - 1;
+        if(!ArgValue.isStrictlyPositive() || ParamIdxFromOne > NumParams) {
           S.Diag(Attr.getLoc(), diag::err_attribute_argument_out_of_range)
             << Attr.getName() << Idx + 1 << NumParams;
           return false;
         }
-        Arg_QT = FD->getParamDecl(ParamIdx_from0)->getType();
-        RT = getRecordType(Arg_QT);
+        ArgTy = FD->getParamDecl(ParamIdxFromZero)->getType();
+        RT = getRecordType(ArgTy);
       }
     }
 
-    //  Flag error if could not get record type for this argument
-    if (!RT) {
-      S.Diag(Attr.getLoc(), diag::err_attribute_argument_not_class)
-        << Attr.getName();
+    if (!checkForLockableRecord(S, D, Attr, RT))
       return false;
-    }
 
-    // Flag error if the type is not lockable
-    if (!RT->getDecl()->getAttr<LockableAttr>()) {
-      S.Diag(Attr.getLoc(), diag::err_attribute_argument_not_lockable)
-        << Attr.getName();
-      return false;
-    }
+    Args.push_back(ArgExp);
   }
   return true;
 }
@@ -363,6 +377,8 @@ static void handleGuardedByAttr(Sema &S, Decl *D, const AttributeList &Attr,
   if (!checkAttributeNumArgs(S, Attr, 1))
     return;
 
+  Expr *Arg = Attr.getArg(0);
+
   // D must be either a member field or global (potentially shared) variable.
   if (!mayBeSharedVariable(D)) {
     S.Diag(Attr.getLoc(), diag::warn_attribute_wrong_decl_type)
@@ -373,14 +389,18 @@ static void handleGuardedByAttr(Sema &S, Decl *D, const AttributeList &Attr,
   if (pointer && !checkIsPointer(S, D, Attr))
     return;
 
-  // check that all arguments are lockable objects
-  if (!checkAttrArgsAreLockableObjs(S, D, Attr))
+  if (Arg->isTypeDependent())
+    return;
+
+  // check that the argument is lockable object
+  if (!checkForLockableRecord(S, D, Attr, getRecordType(Arg->getType())))
     return;
 
   if (pointer)
-    D->addAttr(::new (S.Context) PtGuardedByAttr(Attr.getLoc(), S.Context));
+    D->addAttr(::new (S.Context) PtGuardedByAttr(Attr.getLoc(),
+                                                 S.Context, Arg));
   else
-    D->addAttr(::new (S.Context) GuardedByAttr(Attr.getLoc(), S.Context));
+    D->addAttr(::new (S.Context) GuardedByAttr(Attr.getLoc(), S.Context, Arg));
 }
 
 
@@ -446,14 +466,21 @@ static void handleAcquireOrderAttr(Sema &S, Decl *D, const AttributeList &Attr,
     }
   }
 
+  SmallVector<Expr*, 1> Args;
   // check that all arguments are lockable objects
-  if (!checkAttrArgsAreLockableObjs(S, D, Attr))
+  if (!checkAttrArgsAreLockableObjs(S, D, Attr, Args))
     return;
 
+  unsigned Size = Args.size();
+  assert(Size == Attr.getNumArgs());
+  Expr **StartArg = Size == 0 ? 0 : &Args[0];
+
   if (before)
-    D->addAttr(::new (S.Context) AcquiredBeforeAttr(Attr.getLoc(), S.Context));
+    D->addAttr(::new (S.Context) AcquiredBeforeAttr(Attr.getLoc(), S.Context,
+                                                    StartArg, Size));
   else
-    D->addAttr(::new (S.Context) AcquiredAfterAttr(Attr.getLoc(), S.Context));
+    D->addAttr(::new (S.Context) AcquiredAfterAttr(Attr.getLoc(), S.Context,
+                                                   StartArg, Size));
 }
 
 static void handleLockFunAttr(Sema &S, Decl *D, const AttributeList &Attr,
@@ -470,15 +497,22 @@ static void handleLockFunAttr(Sema &S, Decl *D, const AttributeList &Attr,
   }
 
   // check that all arguments are lockable objects
-  if (!checkAttrArgsAreLockableObjs(S, D, Attr, 0, /*ParamIdxOk=*/true))
+  SmallVector<Expr*, 1> Args;
+  if (!checkAttrArgsAreLockableObjs(S, D, Attr, Args, 0, /*ParamIdxOk=*/true))
     return;
 
+  unsigned Size = Args.size();
+  assert(Size == Attr.getNumArgs());
+  Expr **StartArg = Size == 0 ? 0 : &Args[0];
+
   if (exclusive)
     D->addAttr(::new (S.Context) ExclusiveLockFunctionAttr(Attr.getLoc(),
-                                                           S.Context));
+                                                           S.Context, StartArg,
+                                                           Size));
   else
     D->addAttr(::new (S.Context) SharedLockFunctionAttr(Attr.getLoc(),
-                                                        S.Context));
+                                                        S.Context, StartArg,
+                                                        Size));
 }
 
 static void handleTrylockFunAttr(Sema &S, Decl *D, const AttributeList &Attr,
@@ -501,16 +535,24 @@ static void handleTrylockFunAttr(Sema &S, Decl *D, const AttributeList &Attr,
     return;
   }
 
+  SmallVector<Expr*, 2> Args;
+  Args.push_back(Attr.getArg(0)); //FIXME
   // check that all arguments are lockable objects
-  if (!checkAttrArgsAreLockableObjs(S, D, Attr, 1))
+  if (!checkAttrArgsAreLockableObjs(S, D, Attr, Args, 1))
     return;
 
+  unsigned Size = Args.size();
+  assert(Size == Attr.getNumArgs());
+  Expr **StartArg = Size == 0 ? 0 : &Args[0];
+
   if (exclusive)
     D->addAttr(::new (S.Context) ExclusiveTrylockFunctionAttr(Attr.getLoc(),
-                                                              S.Context));
+                                                              S.Context,
+                                                              StartArg, Size));
   else
     D->addAttr(::new (S.Context) SharedTrylockFunctionAttr(Attr.getLoc(),
-                                                           S.Context));
+                                                           S.Context, StartArg,
+                                                           Size));
 }
 
 static void handleLocksRequiredAttr(Sema &S, Decl *D, const AttributeList &Attr,
@@ -527,15 +569,22 @@ static void handleLocksRequiredAttr(Sema &S, Decl *D, const AttributeList &Attr,
   }
 
   // check that all arguments are lockable objects
-  if (!checkAttrArgsAreLockableObjs(S, D, Attr))
+  SmallVector<Expr*, 1> Args;
+  if (!checkAttrArgsAreLockableObjs(S, D, Attr, Args))
     return;
 
+  unsigned Size = Args.size();
+  assert(Size == Attr.getNumArgs());
+  Expr **StartArg = Size == 0 ? 0 : &Args[0];
+
   if (exclusive)
     D->addAttr(::new (S.Context) ExclusiveLocksRequiredAttr(Attr.getLoc(),
-                                                            S.Context));
+                                                            S.Context, StartArg,
+                                                            Size));
   else
     D->addAttr(::new (S.Context) SharedLocksRequiredAttr(Attr.getLoc(),
-                                                         S.Context));
+                                                         S.Context, StartArg,
+                                                         Size));
 }
 
 static void handleUnlockFunAttr(Sema &S, Decl *D,
@@ -551,10 +600,16 @@ static void handleUnlockFunAttr(Sema &S, Decl *D,
   }
 
   // check that all arguments are lockable objects
-  if (!checkAttrArgsAreLockableObjs(S, D, Attr, 0, /*ParamIdxOk=*/true))
+  SmallVector<Expr*, 1> Args;
+  if (!checkAttrArgsAreLockableObjs(S, D, Attr, Args, 0, /*ParamIdxOk=*/true))
     return;
 
-  D->addAttr(::new (S.Context) UnlockFunctionAttr(Attr.getLoc(), S.Context));
+  unsigned Size = Args.size();
+  assert(Size == Attr.getNumArgs());
+  Expr **StartArg = Size == 0 ? 0 : &Args[0];
+
+  D->addAttr(::new (S.Context) UnlockFunctionAttr(Attr.getLoc(), S.Context,
+                                                  StartArg, Size));
 }
 
 static void handleLockReturnedAttr(Sema &S, Decl *D,
@@ -563,6 +618,7 @@ static void handleLockReturnedAttr(Sema &S, Decl *D,
 
   if (!checkAttributeNumArgs(S, Attr, 1))
     return;
+  Expr *Arg = Attr.getArg(0);
 
   if (!isa<FunctionDecl>(D) && !isa<FunctionTemplateDecl>(D)) {
     S.Diag(Attr.getLoc(), diag::warn_attribute_wrong_decl_type)
@@ -570,11 +626,14 @@ static void handleLockReturnedAttr(Sema &S, Decl *D,
     return;
   }
 
-  // check that all arguments are lockable objects
-  if (!checkAttrArgsAreLockableObjs(S, D, Attr))
+  if (Arg->isTypeDependent())
+    return;
+
+  // check that the argument is lockable object
+  if (!checkForLockableRecord(S, D, Attr, getRecordType(Arg->getType())))
     return;
 
-  D->addAttr(::new (S.Context) LockReturnedAttr(Attr.getLoc(), S.Context));
+  D->addAttr(::new (S.Context) LockReturnedAttr(Attr.getLoc(), S.Context, Arg));
 }
 
 static void handleLocksExcludedAttr(Sema &S, Decl *D,
@@ -591,10 +650,16 @@ static void handleLocksExcludedAttr(Sema &S, Decl *D,
   }
 
   // check that all arguments are lockable objects
-  if (!checkAttrArgsAreLockableObjs(S, D, Attr))
+  SmallVector<Expr*, 1> Args;
+  if (!checkAttrArgsAreLockableObjs(S, D, Attr, Args))
     return;
 
-  D->addAttr(::new (S.Context) LocksExcludedAttr(Attr.getLoc(), S.Context));
+  unsigned Size = Args.size();
+  assert(Size == Attr.getNumArgs());
+  Expr **StartArg = Size == 0 ? 0 : &Args[0];
+
+  D->addAttr(::new (S.Context) LocksExcludedAttr(Attr.getLoc(), S.Context,
+                                                 StartArg, Size));
 }
 
 
diff --git a/test/SemaCXX/warn-thread-safety-analysis.cpp b/test/SemaCXX/warn-thread-safety-analysis.cpp
new file mode 100644 (file)
index 0000000..e8b7e9f
--- /dev/null
@@ -0,0 +1,267 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety %s
+
+
+//-----------------------------------------//
+//  Helper fields
+//-----------------------------------------//
+
+
+class  __attribute__((lockable)) Mutex {
+ public:
+  void Lock() __attribute__((exclusive_lock_function));
+  void ReaderLock() __attribute__((shared_lock_function));
+  void Unlock() __attribute__((unlock_function));
+  bool TryLock() __attribute__((exclusive_trylock_function(true)));
+  bool ReaderTryLock() __attribute__((shared_trylock_function(true)));
+  void LockWhen(const int &cond) __attribute__((exclusive_lock_function));
+};
+
+
+Mutex sls_mu;
+
+Mutex sls_mu2 __attribute__((acquired_after(sls_mu)));
+int sls_guard_var __attribute__((guarded_var)) = 0;
+int sls_guardby_var __attribute__((guarded_by(sls_mu))) = 0;
+
+bool getBool();
+
+class MutexWrapper {
+public:
+   Mutex mu;
+   // int x __attribute__((guarded_by(mu))); // FIXME: scoping error
+};
+
+MutexWrapper sls_mw;
+
+void sls_fun_0() {
+  sls_mw.mu.Lock();
+  // sls_mw.x = 5; // FIXME: turn mu into sls_mw.mu
+  sls_mw.mu.Unlock();
+}
+
+
+void sls_fun_2() {
+  sls_mu.Lock();
+  int x = sls_guard_var;
+  sls_mu.Unlock();
+}
+
+void sls_fun_3() {
+  sls_mu.Lock();
+  sls_guard_var = 2;
+  sls_mu.Unlock();
+}
+
+void sls_fun_4() {
+  sls_mu2.Lock();
+  sls_guard_var = 2;
+  sls_mu2.Unlock();
+}
+
+void sls_fun_5() {
+  sls_mu.Lock();
+  int x = sls_guardby_var;
+  sls_mu.Unlock();
+}
+
+void sls_fun_6() {
+  sls_mu.Lock();
+  sls_guardby_var = 2;
+  sls_mu.Unlock();
+}
+
+void sls_fun_7() {
+  sls_mu.Lock();
+  sls_mu2.Lock();
+  sls_mu2.Unlock();
+  sls_mu.Unlock();
+}
+
+void sls_fun_8() {
+  sls_mu.Lock();
+  if (getBool())
+    sls_mu.Unlock();
+  else
+    sls_mu.Unlock();
+}
+
+void sls_fun_9() {
+  if (getBool())
+    sls_mu.Lock();
+  else
+    sls_mu.Lock();
+  sls_mu.Unlock();
+}
+
+void sls_fun_good_6() {
+  if (getBool()) {
+    sls_mu.Lock();
+  } else {
+    if (getBool()) {
+      getBool(); // EMPTY
+    } else {
+      getBool(); // EMPTY
+    }
+    sls_mu.Lock();
+  }
+  sls_mu.Unlock();
+}
+
+void sls_fun_good_7() {
+  sls_mu.Lock();
+  while (getBool()) {
+    sls_mu.Unlock();
+    if (getBool()) {
+      if (getBool()) {
+        sls_mu.Lock();
+        continue;
+      }
+    }
+    sls_mu.Lock();
+  }
+  sls_mu.Unlock();
+}
+
+void sls_fun_bad_1() {
+  sls_mu.Unlock(); // \
+    expected-warning {{unlocking 'sls_mu' that was not acquired}}
+}
+
+void sls_fun_bad_2() {
+  sls_mu.Lock();
+  sls_mu.Lock(); // \
+    expected-warning {{locking 'sls_mu' that is already acquired}}
+  sls_mu.Unlock();
+}
+
+void sls_fun_bad_3() {
+  sls_mu.Lock(); // \
+    expected-warning {{lock 'sls_mu' is not released at the end of function 'sls_fun_bad_3'}}
+}
+
+void sls_fun_bad_4() {
+  if (getBool())
+    sls_mu.Lock(); // \
+      expected-warning {{lock 'sls_mu' is not released at the end of its scope}}
+  else
+    sls_mu2.Lock(); // \
+      expected-warning {{lock 'sls_mu2' is not released at the end of its scope}}
+}
+
+void sls_fun_bad_5() {
+  sls_mu.Lock(); // \
+    expected-warning {{lock 'sls_mu' is not released at the end of its scope}}
+  if (getBool())
+    sls_mu.Unlock();
+}
+
+void sls_fun_bad_6() {
+  if (getBool()) {
+    sls_mu.Lock(); // \
+      expected-warning {{lock 'sls_mu' is not released at the end of its scope}}
+  } else {
+    if (getBool()) {
+      getBool(); // EMPTY
+    } else {
+      getBool(); // EMPTY
+    }
+  }
+  sls_mu.Unlock(); // \
+    expected-warning {{unlocking 'sls_mu' that was not acquired}}
+}
+
+void sls_fun_bad_7() {
+  sls_mu.Lock();
+  while (getBool()) { // \
+      expected-warning {{expecting lock 'sls_mu' to be held at start of each loop}}
+    sls_mu.Unlock();
+    if (getBool()) {
+      if (getBool()) {
+        continue;
+      }
+    }
+    sls_mu.Lock(); // \
+      expected-warning {{lock 'sls_mu' is not released at the end of its scope}}
+  }
+  sls_mu.Unlock();
+}
+
+void sls_fun_bad_8() {
+  sls_mu.Lock();
+  do {
+    sls_mu.Unlock();  // \
+      expected-warning {{expecting lock 'sls_mu' to be held at start of each loop}}
+  } while (getBool());
+}
+
+void sls_fun_bad_9() {
+  do {
+    sls_mu.Lock(); // \
+      expected-warning {{lock 'sls_mu' is not released at the end of its scope}}
+  } while (getBool());
+  sls_mu.Unlock();
+}
+
+void sls_fun_bad_10() {
+  sls_mu.Lock(); // \
+    expected-warning {{lock 'sls_mu' is not released at the end of function 'sls_fun_bad_10'}}
+  while(getBool()) { // \
+      expected-warning {{expecting lock 'sls_mu' to be held at start of each loop}}
+    sls_mu.Unlock();
+  }
+}
+
+void sls_fun_bad_11() {
+  while (getBool()) {
+    sls_mu.Lock(); // \
+      expected-warning {{lock 'sls_mu' is not released at the end of its scope}}
+  }
+  sls_mu.Unlock(); // \
+    expected-warning {{unlocking 'sls_mu' that was not acquired}}
+}
+
+
+//-----------------------------------------//
+// Handling lock expressions in attribute args
+// -------------------------------------------//
+
+Mutex aa_mu;
+
+class GlobalLocker {
+public:
+  void globalLock() __attribute__((exclusive_lock_function(aa_mu)));
+  void globalUnlock() __attribute__((unlock_function(aa_mu)));
+};
+
+GlobalLocker glock;
+
+void aa_elr_fun() __attribute__((exclusive_locks_required(aa_mu)));
+void aa_elr_fun() { }
+
+void aa_fun_1() {
+  glock.globalLock();
+  glock.globalUnlock();
+}
+
+void aa_fun_2() {
+  aa_mu.Lock();
+  aa_elr_fun();
+  aa_mu.Unlock();
+}
+
+void aa_fun_bad_1() {
+  glock.globalUnlock(); // \
+    expected-warning {{unlocking 'aa_mu' that was not acquired}}
+}
+
+void aa_fun_bad_2() {
+  glock.globalLock();
+  glock.globalLock(); // \
+    expected-warning {{locking 'aa_mu' that is already acquired}}
+  glock.globalUnlock();
+}
+
+void aa_fun_bad_3() {
+  glock.globalLock(); // \
+    expected-warning {{lock 'aa_mu' is not released at the end of function 'aa_fun_bad_3'}}
+}
similarity index 99%
rename from test/SemaCXX/warn-thread-safety.cpp
rename to test/SemaCXX/warn-thread-safety-parsing.cpp
index c5523e2a1315fc50ca04c2dcb672002a2adadc99..04680c519b7e8f8f6f08cb55b61d05f47dcad7bf 100644 (file)
@@ -51,9 +51,9 @@ Mu& muRef = mu1;
 #error "Should support no_thread_safety_analysis attribute"
 #endif
 
-void noanal_function() __attribute__((no_thread_safety_analysis));
+void noanal_fun() __attribute__((no_thread_safety_analysis));
 
-void noanal_function() __attribute__((no_thread_safety_analysis(1))); // \
+void noanal_fun_args() __attribute__((no_thread_safety_analysis(1))); // \
   expected-error {{attribute takes no arguments}}
 
 int noanal_testfn(int y) __attribute__((no_thread_safety_analysis));